@zq-silk/yui 0.6.8 → 0.6.10

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.
Files changed (58) hide show
  1. package/README.md +10 -3
  2. package/dist/cli/commandCatalog.js +1 -1
  3. package/dist/commands/taskCommands.js +63 -28
  4. package/dist/commands/taskContextCommand.js +27 -1
  5. package/dist/commands/taskRoleRuntimeStatus.js +17 -6
  6. package/dist/controller/agentRuntimeObserver.js +6 -3
  7. package/dist/controller/controller.js +29 -47
  8. package/dist/controller/fileSchedulerStoreAdapter.js +572 -258
  9. package/dist/controller/runtime.js +8 -1
  10. package/dist/controller/runtimeEventInbox.js +16 -5
  11. package/dist/controller/runtimeHookRunFence.js +51 -5
  12. package/dist/controller/runtimeObservationHook.js +8 -2
  13. package/dist/coordination/workMailbox.js +408 -28
  14. package/dist/coordination/workMailboxQueue.js +12 -10
  15. package/dist/executor/agentExecutor.js +101 -94
  16. package/dist/executor/executorRegistry.js +47 -2
  17. package/dist/executor/fileRoleLaunchPlanner.js +4 -2
  18. package/dist/lifecycle/exactRunTerminalization.js +1 -7
  19. package/dist/repository/taskWorkspaceCoordinator.js +9 -4
  20. package/dist/runtime/agentDriver.js +83 -4
  21. package/dist/runtime/agentDriverObservation.js +25 -10
  22. package/dist/runtime/builtinAgentDrivers.js +168 -18
  23. package/dist/runtime/codexAppServerRuntime.js +355 -0
  24. package/dist/runtime/continuationManager.js +117 -0
  25. package/dist/runtime/index.js +2 -0
  26. package/dist/runtime/lifecycleReservation.js +4 -3
  27. package/dist/runtime/promptEnvelope.js +14 -3
  28. package/dist/runtime/providerContinuation.js +225 -0
  29. package/dist/runtime/providerContinuationReconciliationService.js +172 -0
  30. package/dist/runtime/providerRuntimeIdentity.js +232 -0
  31. package/dist/runtime/providerRuntimeReconciler.js +166 -0
  32. package/dist/runtime/runtimeContinuationProjection.js +34 -0
  33. package/dist/runtime/runtimeObservation.js +217 -6
  34. package/dist/runtime/runtimeProjection.js +172 -11
  35. package/dist/scheduler/activeRoleRunDelivery.js +314 -1
  36. package/dist/scheduler/leaderWakeupProcessor.js +2 -1
  37. package/dist/scheduler/operatorInputNotificationProcessor.js +3 -2
  38. package/dist/scheduler/roleRunLiveness.js +8 -7
  39. package/dist/scheduler/roleRunStall.js +4 -2
  40. package/dist/scheduler/taskExecutionProjection.js +2 -2
  41. package/dist/storage/migration/productionRegistry.js +474 -1
  42. package/dist/storage/sqliteSchema.js +102 -21
  43. package/dist/storage/sqliteStore.js +52 -110
  44. package/dist/storage/storageVersions.js +1 -1
  45. package/dist/storage/storeRpc.js +0 -1
  46. package/dist/storage/taskStore.js +40 -53
  47. package/dist/storage/upgrade/sqliteStateMigration.js +0 -21
  48. package/dist/task/nextAction.js +1 -1
  49. package/dist/web/assets/client/app.js +1 -1
  50. package/dist/web/assets/client/components.js +233 -4
  51. package/dist/web/assets/client/i18n.js +166 -2
  52. package/dist/web/assets/client/view.js +30 -13
  53. package/dist/web/assets/styles/cards.js +62 -0
  54. package/dist/web/assets/styles/widgets.js +1 -0
  55. package/dist/web/webSnapshot.js +11 -2
  56. package/package.json +1 -1
  57. package/skills/yui-leader/SKILL.md +33 -24
  58. package/skills/yui-operator/SKILL.md +7 -5
@@ -88,6 +88,159 @@ export function anchorSection(id, head, body) {
88
88
  return section;
89
89
  }
90
90
 
91
+ // --- Task-first execution status -------------------------------------------
92
+ // The projection is the current read-model vocabulary: one derived status,
93
+ // owner, next action, and the attention/blocker facts behind it.
94
+ const EXEC_STATUS_TONE = {
95
+ "needs-leader-action": "is-accent",
96
+ "waiting-on-agents": "is-active",
97
+ "waiting-user": "is-warning",
98
+ "recovering": "is-warning",
99
+ "attention": "is-danger",
100
+ "progressing-with-attention": "is-warning",
101
+ "blocked": "is-danger",
102
+ "working": "is-active",
103
+ "completed": "is-muted",
104
+ "retired": "is-muted",
105
+ "archived": "is-muted"
106
+ };
107
+
108
+ export function executionBand(projection, t, locale) {
109
+ if (!projection) return null;
110
+ const tone = EXEC_STATUS_TONE[projection.status] || "";
111
+ const band = node("div", "exec-band " + tone);
112
+
113
+ const head = node("div", "exec-band-head");
114
+ head.append(pill(t, "exec.status", projection.status));
115
+ head.append(node("span", "exec-band-owner",
116
+ t("exec.owner." + projection.owner) + " · " + t("exec.action." + projection.action)));
117
+ if (projection.activeExecutorCount > 0) {
118
+ head.append(node("span", "exec-band-executors",
119
+ projection.activeExecutorCount + " " + t("exec.executors")));
120
+ }
121
+ if (projection.monitoring === "stopped") {
122
+ head.append(node("span", "exec-band-stopped", t("exec.monitoring.stopped")));
123
+ }
124
+ if (projection.failClosed) {
125
+ head.append(node("span", "exec-band-failclosed", t("exec.failClosed")));
126
+ }
127
+ band.append(head);
128
+
129
+ if (projection.summary) {
130
+ band.append(node("p", "exec-band-summary", projection.summary));
131
+ }
132
+
133
+ if (projection.attention && projection.attention.length) {
134
+ const list = node("div", "exec-signal-list");
135
+ projection.attention.forEach(function (item) {
136
+ const row = node("div", "exec-signal is-attention");
137
+ row.append(node("span", "exec-signal-kind", t("exec.attention." + item.kind)));
138
+ row.append(node("span", "exec-signal-text", item.summary));
139
+ list.append(row);
140
+ });
141
+ band.append(list);
142
+ }
143
+
144
+ if (projection.blockers && projection.blockers.length) {
145
+ const list = node("div", "exec-signal-list");
146
+ projection.blockers.forEach(function (blocker) {
147
+ const row = node("div", "exec-signal is-blocker");
148
+ row.append(node("span", "exec-signal-kind",
149
+ t("exec.blocker." + blocker.kind) + " · " + t("exec.owner." + blocker.owner)));
150
+ row.append(node("span", "exec-signal-text", blocker.summary));
151
+ list.append(row);
152
+ });
153
+ band.append(list);
154
+ }
155
+
156
+ return band;
157
+ }
158
+
159
+ // --- Unified execution Group (Lanes + resolution) ---------------------------
160
+ export function executionGroupCard(summary, t, locale) {
161
+ const card = node("article", "record-card exec-group");
162
+ const head = node("div", "record-head");
163
+ const titleRow = node("div", "record-title-row");
164
+ titleRow.append(node("strong", "record-title", summary.groupId));
165
+ head.append(titleRow);
166
+ const pills = node("div", "record-pills");
167
+ pills.append(pill(t, "exec.purpose", summary.purpose));
168
+ const strategy = summary.strategy.mode === "fixed"
169
+ ? t("exec.strategy.fixed").replace("{count}", String(summary.strategy.count))
170
+ : t("exec.strategy.adaptive").replace("{max}", String(summary.strategy.max));
171
+ pills.append(chip(strategy));
172
+ if (summary.failedLaneCount > 0) {
173
+ pills.append(chip(summary.failedLaneCount + " " + t("exec.lanesFailed"), "is-danger"));
174
+ }
175
+ head.append(pills);
176
+ card.append(head);
177
+
178
+ const lanes = node("div", "lane-list");
179
+ summary.laneSummaries.forEach(function (lane) {
180
+ const row = node("div", "lane-row");
181
+ row.append(statusDot(lane.status));
182
+ row.append(node("span", "lane-role", lane.roleName));
183
+ row.append(node("span", "lane-status", t("lane." + lane.status)));
184
+ if (lane.summary) {
185
+ row.append(node("span", "lane-summary", lane.summary));
186
+ }
187
+ if (lane.decision) {
188
+ row.append(chip(t("resolution." + lane.decision), "is-active"));
189
+ }
190
+ lanes.append(row);
191
+ });
192
+ card.append(lanes);
193
+
194
+ if (summary.resolution) {
195
+ const res = node("div", "exec-resolution");
196
+ res.append(node("span", "exec-resolution-decision",
197
+ t("resolution." + summary.resolution.decision)));
198
+ res.append(node("span", "exec-resolution-summary", summary.resolution.summary));
199
+ res.append(node("time", "", formatDateTime(summary.resolution.decidedAt, locale)));
200
+ card.append(res);
201
+ }
202
+ return card;
203
+ }
204
+
205
+ // --- WorkItem Candidates -----------------------------------------------------
206
+ export function candidateList(candidates, t, locale) {
207
+ if (!candidates || !candidates.length) return null;
208
+ const block = node("div", "record-block");
209
+ block.append(node("small", "", t("detail.candidates")));
210
+ const list = node("div", "candidate-list");
211
+ candidates.slice().sort(function (a, b) { return b.sequence - a.sequence; })
212
+ .forEach(function (candidate) {
213
+ const row = node("div", "candidate-row");
214
+ row.append(node("span", "candidate-seq", "#" + candidate.sequence));
215
+ row.append(node("span", "candidate-summary", candidate.summary));
216
+ const source = candidate.source.type === "direct"
217
+ ? t("candidate.source.direct")
218
+ : t("candidate.source.run") + " " + candidate.source.runId;
219
+ row.append(node("span", "candidate-source", source));
220
+ row.append(node("time", "", formatDateTime(candidate.createdAt, locale)));
221
+ list.append(row);
222
+ });
223
+ block.append(list);
224
+ return block;
225
+ }
226
+
227
+ // --- Execution findings (checks / review findings) ---------------------------
228
+ export function findingsBlock(label, findings, t) {
229
+ if (!findings || !findings.length) return null;
230
+ const block = node("div", "record-block");
231
+ block.append(node("small", "", label));
232
+ const list = node("div", "finding-list");
233
+ findings.forEach(function (finding) {
234
+ const row = node("div", "finding-row is-" + finding.severity);
235
+ row.append(chip(t("finding.severity." + finding.severity), "is-" + finding.severity));
236
+ row.append(node("span", "finding-summary", finding.summary));
237
+ row.append(chip(t("finding.status." + finding.status)));
238
+ list.append(row);
239
+ });
240
+ block.append(list);
241
+ return block;
242
+ }
243
+
91
244
  // --- Progressive disclosure --------------------------------------------------
92
245
  // Re-render is cheap and happens whenever the underlying data changes, so all
93
246
  // disclosure state (collapsed blocks, visible pages) is deliberately
@@ -242,6 +395,10 @@ export function inputCard(input, _options, t, locale, actions) {
242
395
  const blocked = input.blockedRefs.map(function (ref) { return ref.type + "·" + ref.id; }).join(" ");
243
396
  top.append(node("span", "input-blocked", blocked));
244
397
  }
398
+ if (input.requester) {
399
+ top.append(node("span", "input-requester",
400
+ t("detail.requester") + " · " + input.requester.roleName + " / " + input.requester.runId));
401
+ }
245
402
  card.append(top);
246
403
  card.append(answerActions(input, actions, t));
247
404
  return card;
@@ -286,6 +443,9 @@ export function workItemCard(item, titles, t, locale, actions, taskId) {
286
443
  if (item.assignee) meta.append(node("span", "meta-name", item.assignee));
287
444
  meta.append(node("span", "mono", item.id));
288
445
  meta.append(node("time", "", formatDateTime(item.updatedAt, locale)));
446
+ if (item.endedAt) {
447
+ meta.append(node("span", "", t("detail.endedAt") + " · " + formatDateTime(item.endedAt, locale)));
448
+ }
289
449
  body.append(meta);
290
450
 
291
451
  if (item.objective && item.objective !== item.title) {
@@ -307,6 +467,28 @@ export function workItemCard(item, titles, t, locale, actions, taskId) {
307
467
  }
308
468
  if (chipCols.childNodes.length) body.append(chipCols);
309
469
 
470
+ // Current execution iteration: the unified Group with its Lanes and the
471
+ // Leader's resolution when the iteration is settled.
472
+ if (item.currentExecution) {
473
+ body.append(executionGroupCard(item.currentExecution, t, locale));
474
+ }
475
+
476
+ // Review candidates submitted for this WorkItem, newest first.
477
+ const candidates = candidateList(item.candidates, t, locale);
478
+ if (candidates) body.append(candidates);
479
+
480
+ // Explicit Leader retirement disposition.
481
+ if (item.disposition) {
482
+ const disposition = node("div", "record-block disposition-block");
483
+ disposition.append(node("small", "", t("detail.disposition")));
484
+ const text = item.disposition.summary
485
+ + (item.disposition.replacementWorkItemId
486
+ ? " · " + t("detail.replacementWorkItem") + " " + item.disposition.replacementWorkItemId
487
+ : "");
488
+ disposition.append(node("p", "muted", text));
489
+ body.append(disposition);
490
+ }
491
+
310
492
  if (item.outcome) {
311
493
  body.append(richText(t("detail.outcome"), item.outcome, t, { extraClass: "outcome-callout", muted: true }));
312
494
  }
@@ -323,7 +505,8 @@ export function runCard(run, t, locale) {
323
505
  idRow.append(node("span", "role", run.roleName));
324
506
  idRow.append(node("span", "", run.id));
325
507
  if (run.workItemId) idRow.append(node("span", "", t("detail.workItem") + " · " + run.workItemId));
326
- idRow.append(node("time", "", formatDateTime(run.updatedAt, locale)));
508
+ if (run.purpose) idRow.append(chip(t("run.purpose." + run.purpose)));
509
+ idRow.append(node("time", "", formatDateTime(run.endedAt || run.updatedAt, locale)));
327
510
  card.append(idRow);
328
511
 
329
512
  card.append(richText(t("detail.instruction"), run.input, t, { className: "execute-io", threshold: 320 }));
@@ -334,6 +517,10 @@ export function runCard(run, t, locale) {
334
517
  const foot = node("div", "execute-foot");
335
518
  const tags = node("div", "execute-tags");
336
519
  tags.append(chip(t("mode." + run.mode)));
520
+ if (run.executionGroupId) {
521
+ tags.append(chip(t("detail.lineage") + " · " + run.executionGroupId
522
+ + (run.executionLaneId ? "/" + run.executionLaneId : "")));
523
+ }
337
524
  const deliveryKey = run.deliveredAt
338
525
  ? "delivery.delivered"
339
526
  : run.pushedAt
@@ -354,6 +541,26 @@ export function runCard(run, t, locale) {
354
541
  card.append(eff);
355
542
  }
356
543
 
544
+ // Leader Run disposition: the machine-derived outcome of a terminal Leader
545
+ // Run, plus any structured wait reference.
546
+ if (run.disposition) {
547
+ const disposition = node("div", "record-meta");
548
+ disposition.append(node("span", "", t("run.disposition") + " · " + t("run.disposition." + run.disposition)));
549
+ if (run.waitReason) {
550
+ disposition.append(node("span", "", t("run.waitReason") + " · " + run.waitReason.kind
551
+ + (run.waitReason.ref ? " (" + run.waitReason.ref + ")" : "")));
552
+ }
553
+ card.append(disposition);
554
+ }
555
+
556
+ // Idempotent yield receipt on a yielded Run.
557
+ if (run.yieldReceipt) {
558
+ const receipt = node("div", "record-meta");
559
+ receipt.append(node("span", "mono", t("run.yieldReceipt") + " · " + run.yieldReceipt.receiptId));
560
+ receipt.append(node("time", "", formatDateTime(run.yieldReceipt.committedAt, locale)));
561
+ card.append(receipt);
562
+ }
563
+
357
564
  return card;
358
565
  }
359
566
 
@@ -363,7 +570,10 @@ export function reviewCard(round, t, locale) {
363
570
  const titleRow = node("div", "record-title-row");
364
571
  titleRow.append(node("strong", "record-title", round.id));
365
572
  head.append(titleRow);
366
- head.append(pill(t, "review", round.status));
573
+ const headPills = node("div", "record-pills");
574
+ if (round.scope) headPills.append(pill(t, "review.scope", round.scope));
575
+ headPills.append(pill(t, "review", round.status));
576
+ head.append(headPills);
367
577
  card.append(head);
368
578
  const meta = node("div", "record-meta");
369
579
  meta.append(node("span", "meta-name", round.reviewerRoleName));
@@ -376,13 +586,20 @@ export function reviewCard(round, t, locale) {
376
586
  if (round.evidenceCommit) {
377
587
  meta.append(node("span", "", t("detail.evidence") + " · " + round.evidenceCommit));
378
588
  }
589
+ if (round.workspaceDisposition) {
590
+ meta.append(node("span", "", t("detail.workspaceDisposition") + " · "
591
+ + t("disposition." + round.workspaceDisposition.kind)));
592
+ }
379
593
  card.append(meta);
380
594
  if (round.checks && round.checks.length) {
381
595
  card.append(richText(t("detail.checks"), round.checks.map(function (check) {
382
596
  return check.name + "=" + check.outcome;
383
597
  }).join(", "), t));
384
598
  }
599
+ const findings = findingsBlock(t("detail.findings"), round.findings, t);
600
+ if (findings) card.append(findings);
385
601
  if (round.summary) card.append(richText(null, round.summary, t));
602
+ if (round.report) card.append(richText(t("detail.report"), round.report, t, { muted: true }));
386
603
  return card;
387
604
  }
388
605
 
@@ -562,14 +779,18 @@ export function attentionRow(item, t, locale, onSelect) {
562
779
  return row;
563
780
  }
564
781
 
565
- export function overviewRow(task, t, locale, onSelect) {
782
+ export function overviewRow(task, label, variant, t, locale, onSelect) {
566
783
  const hasInputs = task.openInputCount > 0;
567
- const row = node("button", "overview-row" + (hasInputs ? " has-inputs" : ""));
784
+ const row = node("button", "overview-row"
785
+ + (hasInputs || variant === "has-inputs" ? " has-inputs" : ""));
568
786
  row.type = "button";
569
787
  row.append(statusDot(task.status));
570
788
  const main = node("span", "overview-row-title", task.title);
571
789
  main.title = task.title;
572
790
  row.append(main);
791
+ if (label) {
792
+ row.append(node("span", "overview-row-label", label));
793
+ }
573
794
  row.append(node("span", "overview-row-time", relativeTime(task.updatedAt, locale, t)));
574
795
  row.addEventListener("click", function () { onSelect(task.id); });
575
796
  return row;
@@ -588,6 +809,14 @@ export function taskCard(task, hasAttention, state, t, locale, onSelect) {
588
809
  main.append(node("span", "task-meta", relativeTime(task.updatedAt, locale, t)));
589
810
  button.append(main);
590
811
 
812
+ // Derived Task-first execution status for active tasks (blocked, waiting on
813
+ // agents, recovering, etc.) — a richer signal than the raw lifecycle status.
814
+ if (task.executionStatus && task.status === "active") {
815
+ const exec = node("span", "task-exec is-" + task.executionStatus,
816
+ t("exec.status." + task.executionStatus));
817
+ button.append(exec);
818
+ }
819
+
591
820
  // One signal only: an open input (needs the user) outranks a running count.
592
821
  if (hasAttention) {
593
822
  const badge = node("span", "task-signal is-input", String(task.openInputCount || 1));
@@ -117,6 +117,7 @@ const messages = {
117
117
  "group.attention": "Needs input",
118
118
  "group.draft": "Draft",
119
119
  "group.finished": "Completed",
120
+ "group.retired": "Retired",
120
121
  "input.answered": "Input answered.",
121
122
  "input.blocking": "Blocking",
122
123
  "input.freeText": "Type your answer",
@@ -214,7 +215,88 @@ const messages = {
214
215
  "work.superseded": "Superseded",
215
216
  "stats.running": "running",
216
217
  "stats.inputs": "inputs",
217
- "stats.stalledRuns": "stalled Runs"
218
+ "stats.stalledRuns": "stalled Runs",
219
+ "exec.status.needs-leader-action": "Needs leader action",
220
+ "exec.status.waiting-on-agents": "Waiting on agents",
221
+ "exec.status.waiting-user": "Waiting on you",
222
+ "exec.status.recovering": "Recovering",
223
+ "exec.status.attention": "Needs attention",
224
+ "exec.status.progressing-with-attention": "Progressing with attention",
225
+ "exec.status.blocked": "Blocked",
226
+ "exec.status.working": "Working",
227
+ "exec.status.completed": "Completed",
228
+ "exec.status.retired": "Retired",
229
+ "exec.status.archived": "Archived",
230
+ "exec.owner.leader": "Leader",
231
+ "exec.owner.worker": "Worker",
232
+ "exec.owner.reviewer": "Reviewer",
233
+ "exec.owner.tester": "Tester",
234
+ "exec.owner.operator": "Operator",
235
+ "exec.owner.user": "You",
236
+ "exec.owner.none": "—",
237
+ "exec.action.advance-task": "Advance task",
238
+ "exec.action.wait-for-agents": "Wait for agents",
239
+ "exec.action.answer-input": "Answer input",
240
+ "exec.action.inspect-attention": "Inspect attention",
241
+ "exec.action.recover-leader": "Recover leader",
242
+ "exec.action.resolve-blocker": "Resolve blocker",
243
+ "exec.action.complete-task": "Complete task",
244
+ "exec.action.none": "—",
245
+ "exec.attention.leader-recovery": "Leader recovery",
246
+ "exec.attention.leader-stalled": "Leader stalled",
247
+ "exec.attention.checkpoint-overdue": "Checkpoint overdue",
248
+ "exec.attention.identity-mismatch": "Identity mismatch",
249
+ "exec.attention.delivery-uncertain": "Delivery uncertain",
250
+ "exec.blocker.work": "Work",
251
+ "exec.blocker.review": "Review",
252
+ "exec.blocker.integration": "Integration",
253
+ "exec.blocker.input": "Input",
254
+ "exec.blocker.identity": "Identity",
255
+ "exec.monitoring.stopped": "Monitoring stopped",
256
+ "exec.failClosed": "Fail-closed",
257
+ "exec.executors": "active",
258
+ "exec.purpose.execution": "Execution",
259
+ "exec.purpose.review": "Review",
260
+ "exec.strategy.fixed": "Fixed · {count} lanes",
261
+ "exec.strategy.adaptive": "Adaptive · up to {max}",
262
+ "exec.lanesFailed": "failed",
263
+ "lane.pending": "Pending",
264
+ "lane.running": "Running",
265
+ "lane.yielded": "Yielded",
266
+ "lane.completed": "Completed",
267
+ "lane.failed": "Failed",
268
+ "resolution.accept": "Accepted",
269
+ "resolution.reject": "Rejected",
270
+ "resolution.retry": "Retry",
271
+ "resolution.blocked": "Blocked",
272
+ "detail.candidates": "Candidates",
273
+ "detail.disposition": "Retirement",
274
+ "detail.replacementWorkItem": "Replaced by",
275
+ "detail.endedAt": "Ended",
276
+ "detail.findings": "Findings",
277
+ "detail.report": "Report",
278
+ "detail.workspaceDisposition": "Workspace",
279
+ "detail.lineage": "Execution",
280
+ "detail.requester": "Requested by",
281
+ "candidate.source.direct": "Direct",
282
+ "candidate.source.run": "Run",
283
+ "finding.severity.low": "Low",
284
+ "finding.severity.medium": "Medium",
285
+ "finding.severity.high": "High",
286
+ "finding.severity.critical": "Critical",
287
+ "finding.status.open": "Open",
288
+ "finding.status.resolved": "Resolved",
289
+ "run.purpose.execution": "Execution",
290
+ "run.purpose.review": "Review",
291
+ "run.disposition": "Disposition",
292
+ "run.disposition.progress": "Progressing",
293
+ "run.disposition.waiting": "Waiting",
294
+ "run.disposition.blocked": "Blocked",
295
+ "run.disposition.completed": "Completed",
296
+ "run.waitReason": "Waiting on",
297
+ "run.yieldReceipt": "Yield receipt",
298
+ "review.scope.work-item": "Work item",
299
+ "review.scope.task": "Task"
218
300
  },
219
301
  "zh-CN": {
220
302
  "a11y.skip": "跳到主面板",
@@ -333,6 +415,7 @@ const messages = {
333
415
  "group.attention": "需输入",
334
416
  "group.draft": "草稿",
335
417
  "group.finished": "已完成",
418
+ "group.retired": "已退役",
336
419
  "input.answered": "输入已回答。",
337
420
  "input.blocking": "阻塞",
338
421
  "input.freeText": "输入回答",
@@ -430,7 +513,88 @@ const messages = {
430
513
  "work.superseded": "已替代",
431
514
  "stats.running": "运行中",
432
515
  "stats.inputs": "项输入",
433
- "stats.stalledRuns": "停滞运行"
516
+ "stats.stalledRuns": "停滞运行",
517
+ "exec.status.needs-leader-action": "待负责人处理",
518
+ "exec.status.waiting-on-agents": "等待智能体",
519
+ "exec.status.waiting-user": "等待你回答",
520
+ "exec.status.recovering": "恢复中",
521
+ "exec.status.attention": "需要关注",
522
+ "exec.status.progressing-with-attention": "推进中(有注意项)",
523
+ "exec.status.blocked": "已阻塞",
524
+ "exec.status.working": "执行中",
525
+ "exec.status.completed": "已完成",
526
+ "exec.status.retired": "已退役",
527
+ "exec.status.archived": "已归档",
528
+ "exec.owner.leader": "负责人",
529
+ "exec.owner.worker": "执行者",
530
+ "exec.owner.reviewer": "评审者",
531
+ "exec.owner.tester": "测试者",
532
+ "exec.owner.operator": "Operator",
533
+ "exec.owner.user": "你",
534
+ "exec.owner.none": "—",
535
+ "exec.action.advance-task": "推进任务",
536
+ "exec.action.wait-for-agents": "等待智能体",
537
+ "exec.action.answer-input": "回答输入",
538
+ "exec.action.inspect-attention": "检查注意项",
539
+ "exec.action.recover-leader": "恢复负责人",
540
+ "exec.action.resolve-blocker": "解决阻塞",
541
+ "exec.action.complete-task": "完成任务",
542
+ "exec.action.none": "—",
543
+ "exec.attention.leader-recovery": "负责人恢复",
544
+ "exec.attention.leader-stalled": "负责人停滞",
545
+ "exec.attention.checkpoint-overdue": "检查点超时",
546
+ "exec.attention.identity-mismatch": "身份不匹配",
547
+ "exec.attention.delivery-uncertain": "送达不确定",
548
+ "exec.blocker.work": "工作",
549
+ "exec.blocker.review": "评审",
550
+ "exec.blocker.integration": "集成",
551
+ "exec.blocker.input": "输入",
552
+ "exec.blocker.identity": "身份",
553
+ "exec.monitoring.stopped": "监控已停止",
554
+ "exec.failClosed": "失败关闭",
555
+ "exec.executors": "活跃",
556
+ "exec.purpose.execution": "执行",
557
+ "exec.purpose.review": "评审",
558
+ "exec.strategy.fixed": "固定 · {count} 条泳道",
559
+ "exec.strategy.adaptive": "自适应 · 最多 {max}",
560
+ "exec.lanesFailed": "失败",
561
+ "lane.pending": "待处理",
562
+ "lane.running": "运行中",
563
+ "lane.yielded": "已交付",
564
+ "lane.completed": "已完成",
565
+ "lane.failed": "失败",
566
+ "resolution.accept": "已接受",
567
+ "resolution.reject": "已拒绝",
568
+ "resolution.retry": "已重试",
569
+ "resolution.blocked": "已阻塞",
570
+ "detail.candidates": "候选方案",
571
+ "detail.disposition": "退役处置",
572
+ "detail.replacementWorkItem": "替代项",
573
+ "detail.endedAt": "结束时间",
574
+ "detail.findings": "发现",
575
+ "detail.report": "报告",
576
+ "detail.workspaceDisposition": "工作区",
577
+ "detail.lineage": "执行谱系",
578
+ "detail.requester": "请求方",
579
+ "candidate.source.direct": "直接",
580
+ "candidate.source.run": "运行",
581
+ "finding.severity.low": "低",
582
+ "finding.severity.medium": "中",
583
+ "finding.severity.high": "高",
584
+ "finding.severity.critical": "严重",
585
+ "finding.status.open": "未解决",
586
+ "finding.status.resolved": "已解决",
587
+ "run.purpose.execution": "执行",
588
+ "run.purpose.review": "评审",
589
+ "run.disposition": "处置",
590
+ "run.disposition.progress": "推进中",
591
+ "run.disposition.waiting": "等待中",
592
+ "run.disposition.blocked": "已阻塞",
593
+ "run.disposition.completed": "已完成",
594
+ "run.waitReason": "等待",
595
+ "run.yieldReceipt": "交付回执",
596
+ "review.scope.work-item": "工作项",
597
+ "review.scope.task": "任务"
434
598
  }
435
599
  };
436
600
 
@@ -8,6 +8,7 @@ import {
8
8
  attentionRow,
9
9
  conclusionMeta,
10
10
  emptyRow,
11
+ executionBand,
11
12
  historyEventRow,
12
13
  inputCard,
13
14
  messageCard,
@@ -27,7 +28,7 @@ import {
27
28
  workItemCard
28
29
  } from "/assets/js/components.js";
29
30
 
30
- const statuses = ["all", "active", "draft", "completed", "archived"];
31
+ const statuses = ["all", "active", "draft", "completed", "retired", "archived"];
31
32
 
32
33
  export function renderFilters(container, state, t, onFilter) {
33
34
  const counts = state.counts || {};
@@ -49,7 +50,7 @@ export function renderFilters(container, state, t, onFilter) {
49
50
  dot.classList.add(status);
50
51
  btn.append(dot);
51
52
  }
52
- btn.append(document.createTextNode(translatedStatus(t, "status", status)));
53
+ btn.append(node("span", "filter-label", translatedStatus(t, "status", status)));
53
54
  const badge = node("span", "filter-count");
54
55
  btn.append(badge);
55
56
  btn.addEventListener("click", function () { onFilter(status); });
@@ -61,6 +62,8 @@ export function renderFilters(container, state, t, onFilter) {
61
62
  const btn = row.querySelector('.filter-chip[data-status="' + status + '"]');
62
63
  if (!btn) return;
63
64
  btn.classList.toggle("is-active", state.filter === status);
65
+ const label = btn.querySelector(".filter-label");
66
+ if (label) label.textContent = translatedStatus(t, "status", status);
64
67
  const count = status === "all" ? counts.total : counts[status];
65
68
  const badge = btn.querySelector(".filter-count");
66
69
  if (badge) {
@@ -78,6 +81,7 @@ function taskGroupOf(task, attentionIds) {
78
81
  if (attentionIds.has(task.id)) return "attention";
79
82
  if (task.status === "active") return "active";
80
83
  if (task.status === "draft") return "draft";
84
+ if (task.status === "retired") return "retired";
81
85
  if (task.status === "archived") return "archived";
82
86
  return "finished";
83
87
  }
@@ -96,7 +100,7 @@ export function renderTasks(container, state, t, locale, onSelect) {
96
100
  clear(container);
97
101
  const query = state.query.trim().toLocaleLowerCase(locale);
98
102
  const attentionIds = new Set((state.attention || []).map(function (item) { return item.taskId; }));
99
- const groups = { attention: [], active: [], draft: [], finished: [], archived: [] };
103
+ const groups = { attention: [], active: [], draft: [], finished: [], retired: [], archived: [] };
100
104
  (state.tasks || []).forEach(function (task) {
101
105
  if (state.filter !== "all" && task.status !== state.filter) return;
102
106
  if (!taskMatchesQuery(task, query, locale)) return;
@@ -108,6 +112,7 @@ export function renderTasks(container, state, t, locale, onSelect) {
108
112
  ["active", t("group.active")],
109
113
  ["draft", t("group.draft")],
110
114
  ["finished", t("group.finished")],
115
+ ["retired", t("group.retired")],
111
116
  ["archived", t("group.archived")]
112
117
  ];
113
118
  const firstGroup = order.find(function (entry) { return groups[entry[0]].length > 0; });
@@ -134,7 +139,7 @@ function overviewBlock(head, tasks, t, locale, onSelect) {
134
139
  block.append(sectionHead(head, { count: tasks.length }));
135
140
  const list = node("div", "overview-list");
136
141
  tasks.forEach(function (task) {
137
- list.append(overviewRow(task, t, locale, onSelect));
142
+ list.append(overviewRow(task, null, null, t, locale, onSelect));
138
143
  });
139
144
  block.append(list);
140
145
  return block;
@@ -174,23 +179,30 @@ export function renderOverview(detail, state, t, locale, onSelect) {
174
179
  }
175
180
  wrap.append(inbox);
176
181
 
177
- const stalledTasks = (state.tasks || []).filter(function (task) {
178
- return (task.needsAttentionCount || 0) > 0;
182
+ // Tasks whose Task-first projection says they need attention: blocked,
183
+ // recovering, or in an attention state. This replaces the raw stalled-run
184
+ // count with the derived execution status.
185
+ const attentionTasks = (state.tasks || []).filter(function (task) {
186
+ const status = task.executionStatus;
187
+ return status === "blocked"
188
+ || status === "attention"
189
+ || status === "recovering"
190
+ || status === "progressing-with-attention";
179
191
  });
180
- if (stalledTasks.length) {
181
- const stalledBlock = node("section", "overview-block");
182
- stalledBlock.append(node("h3", "", t("overview.attention") + " · " + stalledTasks.length));
192
+ if (attentionTasks.length) {
193
+ const attentionBlock = node("section", "overview-block");
194
+ attentionBlock.append(node("h3", "", t("overview.attention") + " · " + attentionTasks.length));
183
195
  const list = node("div", "overview-list");
184
- stalledTasks.forEach(function (task) {
196
+ attentionTasks.forEach(function (task) {
185
197
  list.append(overviewRow(
186
198
  task,
187
- task.needsAttentionCount + " " + t("stats.stalledRuns"),
199
+ t("exec.status." + task.executionStatus),
188
200
  "has-inputs",
189
201
  onSelect
190
202
  ));
191
203
  });
192
- stalledBlock.append(list);
193
- wrap.append(stalledBlock);
204
+ attentionBlock.append(list);
205
+ wrap.append(attentionBlock);
194
206
  }
195
207
 
196
208
  // Active work and the freshest updates sit side by side on wide screens, so
@@ -274,6 +286,11 @@ export function renderTaskDetail(detail, data, t, locale, actions) {
274
286
  headBlock.append(meta);
275
287
  summaryBody.append(headBlock);
276
288
 
289
+ // Task-first execution status: the derived status, owner, next action, and
290
+ // the attention/blocker facts behind it.
291
+ const band = executionBand(data.execution, t, locale);
292
+ if (band) summaryBody.append(band);
293
+
277
294
  if (task.completionSummary) {
278
295
  const conclusion = node("div", "conclusion");
279
296
  conclusion.append(node("h3", "", t("detail.conclusion")));