diff --git a/mateclaw-ui/src/components/agents/PlanBoard.vue b/mateclaw-ui/src/components/agents/PlanBoard.vue
index 3a64e4ec..f14be5ba 100644
--- a/mateclaw-ui/src/components/agents/PlanBoard.vue
+++ b/mateclaw-ui/src/components/agents/PlanBoard.vue
@@ -73,28 +73,29 @@
v-for="group in visibleGroups(lane, col.key)"
:key="group.key"
class="pb-card"
+ :class="{ 'is-spill': isSpill(group.latest, col.key) }"
+ :title="isSpill(group.latest, col.key) ? t('plans.queuedSpillHint') : undefined"
@click="openDetail(group.latest)"
>
{{ group.goal }}
-
-
-
- {{ stepDist(group.latest).running }} {{ t('plans.col.running') }}
-
-
- {{ stepDist(group.latest).pending }} {{ t('plans.col.pending') }}
-
-
- {{ stepDist(group.latest).completed }} {{ t('plans.col.completed') }}
-
+
+
+ {{ chip.n }} {{ chip.label }}
@@ -243,7 +244,7 @@ function cleanGoal(goal?: string): string {
function groupsIn(lane: Lane, status: PlanStatus): PlanGroup[] {
const map = new Map
()
for (const p of lane.plans) {
- if (p.status !== status) continue
+ if (!belongsToColumn(p, status)) continue
const key = cleanGoal(p.goal) || String(p.id)
const arr = map.get(key)
if (arr) arr.push(p)
@@ -294,10 +295,44 @@ function stepDist(plan: Plan): { pending: number; running: number; completed: nu
return { pending, running, completed }
}
-// Only worth showing for multi-step plans that are still active; a finished or
-// single-step plan is already fully described by the progress bar.
-function showDist(plan: Plan): boolean {
- return (plan.totalSteps ?? 0) > 1 && (plan.status === 'running' || plan.status === 'pending')
+// A multi-step plan spans columns: its one running step belongs in 执行中 while
+// its queued steps belong in 待执行. So a running plan that still has queued
+// steps also surfaces in the pending column — otherwise that column sits empty
+// next to a card whose badge claims "N pending", which reads as a bug.
+function belongsToColumn(plan: Plan, status: PlanStatus): boolean {
+ if (status === 'pending') {
+ return plan.status === 'pending' || (plan.status === 'running' && stepDist(plan).pending > 0)
+ }
+ return plan.status === status
+}
+
+// True when this card is the queued-steps mirror of an in-progress plan shown in
+// the pending column (vs. the plan's primary card in 执行中).
+function isSpill(plan: Plan, colKey: PlanStatus): boolean {
+ return colKey === 'pending' && plan.status === 'running'
+}
+
+// The progress bar follows the plan's real status, except the queued mirror in
+// the pending column uses the muted pending tone so it doesn't look "running"
+// while sitting in 待执行.
+function barStatus(plan: Plan, colKey: PlanStatus): PlanStatus {
+ return isSpill(plan, colKey) ? 'pending' : (plan.status as PlanStatus)
+}
+
+// Distribution chips scoped to the column the card is rendered in: the pending
+// column shows only the queued count, the running column shows the active step
+// (and any completed steps). Only multi-step, still-active plans get chips.
+function columnChips(plan: Plan, colKey: PlanStatus): { cls: string; n: number; label: string }[] {
+ if ((plan.totalSteps ?? 0) <= 1) return []
+ if (plan.status !== 'running' && plan.status !== 'pending') return []
+ const d = stepDist(plan)
+ if (colKey === 'pending') {
+ return d.pending ? [{ cls: 'is-pending', n: d.pending, label: t('plans.col.pending') }] : []
+ }
+ const chips: { cls: string; n: number; label: string }[] = []
+ if (d.running) chips.push({ cls: 'is-running', n: d.running, label: t('plans.col.running') })
+ if (d.completed) chips.push({ cls: 'is-completed', n: d.completed, label: t('plans.col.completed') })
+ return chips
}
function laneLetter(name: string): string {
@@ -554,6 +589,13 @@ onMounted(reload)
border-color: var(--mc-border-strong);
box-shadow: var(--mc-shadow-soft);
}
+/* Queued-steps mirror of an in-progress plan (shown in the pending column):
+ dashed + sunken so it reads as a secondary view of a card that also lives in
+ 执行中, not a duplicate. */
+.pb-card.is-spill {
+ border-style: dashed;
+ background: var(--mc-bg-sunken);
+}
.pb-card__goal {
font-size: 12.5px;
line-height: 1.45;
diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts
index 8d8e578e..dfd74580 100644
--- a/mateclaw-ui/src/i18n/locales/en-US.ts
+++ b/mateclaw-ui/src/i18n/locales/en-US.ts
@@ -459,6 +459,7 @@ export default {
steps: 'steps',
viewResult: 'Click to view result',
delegatedTo: 'Delegated to',
+ queuedSpillHint: 'Queued steps of an in-progress plan — the same plan also shows in In Progress',
col: {
pending: 'To Do',
running: 'In Progress',
diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts
index a37da88b..3dc8fba1 100644
--- a/mateclaw-ui/src/i18n/locales/zh-CN.ts
+++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts
@@ -459,6 +459,7 @@ export default {
steps: '步骤',
viewResult: '点击查看结果',
delegatedTo: '委派给',
+ queuedSpillHint: '进行中计划的待执行步骤——同一计划也显示在「执行中」列',
col: {
pending: '待执行',
running: '执行中',