@mstar-harness/dsh 2.2.0 → 2.3.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.
Files changed (62) hide show
  1. package/README.i18n.yaml +2 -3
  2. package/README.md +118 -3
  3. package/README.zh.md +118 -3
  4. package/bundle/README.md +11 -1
  5. package/dist/client/panel/graph/event-log.d.ts +18 -3
  6. package/dist/client/panel/graph/project-graph.d.ts +40 -14
  7. package/dist/client/panel/locale.d.ts +1 -1
  8. package/dist/client.js +131 -17
  9. package/dist/gates/_shared.d.ts +104 -5
  10. package/dist/gates/adapter.d.ts +34 -1
  11. package/dist/gates/agent-flow.d.ts +186 -4
  12. package/dist/gates/agent-personas.d.ts +44 -0
  13. package/dist/gates/dispatch.d.ts +64 -2
  14. package/dist/gates/fallbacks-advisory.d.ts +77 -0
  15. package/dist/gates/fallbacks-decoration.d.ts +108 -0
  16. package/dist/gates/fallbacks-probe.d.ts +63 -0
  17. package/dist/gates/fallbacks-seeds.d.ts +98 -0
  18. package/dist/gates/goal-bridge.d.ts +151 -0
  19. package/dist/gates/plan-mode-bridge.d.ts +70 -0
  20. package/dist/gates/skill-lint.d.ts +4 -3
  21. package/dist/gates/system-prompt.d.ts +121 -0
  22. package/dist/gates/workflow-ledger.d.ts +149 -0
  23. package/dist/gates/workflow-policy.d.ts +203 -0
  24. package/dist/index.d.ts +10 -4
  25. package/dist/index.js +1833 -60
  26. package/dist/types.d.ts +30 -5
  27. package/harness-agents/architect.md +35 -0
  28. package/harness-agents/code-reviewer.md +98 -0
  29. package/harness-agents/frontend-dev.md +33 -0
  30. package/harness-agents/fullstack-dev-2.md +33 -0
  31. package/harness-agents/fullstack-dev.md +33 -0
  32. package/harness-agents/ops-engineer.md +33 -0
  33. package/harness-agents/product-manager.md +35 -0
  34. package/harness-agents/project-manager.md +32 -0
  35. package/harness-agents/prompt-engineer.md +33 -0
  36. package/harness-agents/qa-engineer.md +35 -0
  37. package/harness-agents/qc-specialist-2.md +62 -0
  38. package/harness-agents/qc-specialist-3.md +62 -0
  39. package/harness-agents/qc-specialist.md +62 -0
  40. package/harness-agents/writing-specialist.md +35 -0
  41. package/harness-commands/codebase-audit.md +2 -2
  42. package/harness-skills/mstar-audit/SKILL.md +20 -0
  43. package/harness-skills/mstar-audit/references/audit-playbook.md +27 -0
  44. package/harness-skills/mstar-branch-worktree/SKILL.md +7 -0
  45. package/harness-skills/mstar-coding-behavior/SKILL.md +5 -0
  46. package/harness-skills/mstar-compound/references/compound-workflow.md +16 -0
  47. package/harness-skills/mstar-compound-refresh/SKILL.md +5 -0
  48. package/harness-skills/mstar-iteration/references/phase-4-5-pr-delivery.md +1 -0
  49. package/harness-skills/mstar-plan-artifacts/SKILL.md +1 -0
  50. package/harness-skills/mstar-plan-artifacts/references/status-and-residuals.md +8 -0
  51. package/harness-skills/mstar-review-qc/SKILL.md +6 -0
  52. package/harness-skills/mstar-roles/references/project-manager/qc-and-residuals.md +2 -1
  53. package/harness-skills/mstar-roles/references/qc-specialist/deep-review-lenses.md +16 -7
  54. package/harness-skills/mstar-roles/references/qc-specialist/report-template.md +17 -1
  55. package/harness-skills/mstar-roles/references/qc-specialist-shared.md +3 -1
  56. package/harness-skills/mstar-roles/references/writing-specialist.md +33 -0
  57. package/harness-skills/mstar-sdd/SKILL.md +5 -1
  58. package/harness-skills/mstar-sdd/references/file-handoffs.md +2 -0
  59. package/harness-skills/mstar-skill-authoring/SKILL.md +4 -2
  60. package/harness-skills/mstar-skill-authoring/references/regression-fixation.md +46 -0
  61. package/harness-skills/mstar-skill-authoring/references/skillsbench-authoring.md +37 -0
  62. package/package.json +7 -4
@@ -89,28 +89,32 @@ export interface GraphViolation {
89
89
  code: string;
90
90
  message: string;
91
91
  }
92
- /** Event status coloring: dispatch → dispatched|advisory|denied; settle → ok|error|denied. */
93
- export type FlowEventStatus = 'dispatched' | 'advisory' | 'denied' | 'ok' | 'error';
92
+ /** Event status coloring: dispatch → dispatched|advisory|denied; settle → ok|error|denied; workflow/unknown rows → unknown (no gate status — honest). */
93
+ export type FlowEventStatus = 'dispatched' | 'advisory' | 'denied' | 'ok' | 'error' | 'unknown';
94
94
  /**
95
95
  * One projected agent-flow event (spec agent-flow-catalog-graph §2.4 —
96
- * unchanged by the zone refactor). Every field degrades individually via
97
- * `guards.ts` a missing/illegal value becomes `null`/''/`0`/a base status,
98
- * never a throw and never a guessed value. Rows whose `kind` is not
99
- * dispatch/settle cannot be classified and are skipped entirely
100
- * (belt-and-suspenders: the T1 ledger reader already normalizes kind).
96
+ * unchanged by the zone refactor; plan `20260815-dsh-workflow-ledger` Task 4
97
+ * adds the workflow kinds + the unknown-kind degradation path). Every field
98
+ * degrades individually via `guards.ts` a missing/illegal value becomes
99
+ * `null`/''/`0`/a base status, never a throw and never a guessed value.
100
+ * Dispatch/settle rows keep their gate semantics; the three WORKFLOW kinds
101
+ * project with their run identity (no verdict/outcome → status `unknown`);
102
+ * any UNKNOWN kind string renders as a GENERIC row — never dropped, never
103
+ * guessed. Only a row WITHOUT a kind is unclassifiable and skipped.
101
104
  */
102
105
  export interface FlowEventView {
103
106
  /** `${ts}-${kind}-${index}` — stable id (index = position in the projected window). */
104
107
  id: string;
105
108
  ts: number;
106
- kind: 'dispatch' | 'settle';
107
- /** `Execute as`; '' for settle rows without a paired identity. */
109
+ /** The ledger kind verbatim: dispatch | settle | the three workflow kinds | any unknown kind string (generic row). */
110
+ kind: 'dispatch' | 'settle' | 'workflow-run' | 'workflow-agent' | 'workflow-run-end' | (string & {});
111
+ /** `Execute as`; '' for settle rows without a paired identity and for workflow/unknown rows. */
108
112
  role: string;
109
113
  planId: string | null;
110
114
  taskId: string | null;
111
115
  taskCategory: string | null;
112
116
  agent: string | null;
113
- /** dispatch → dispatched|advisory|denied; settle → ok|error|denied (spec §2.4). */
117
+ /** dispatch → dispatched|advisory|denied; settle → ok|error|denied; workflow/unknown → unknown (spec §2.4). */
114
118
  status: FlowEventStatus;
115
119
  /** `role` ∈ the EXPECTED_ROLE_FLOW role union (spec §2.3 exact-string match). */
116
120
  expected: boolean;
@@ -124,6 +128,22 @@ export interface FlowEventView {
124
128
  /** Settle rows only (plan `20260811-panel-f4-timeliness` Task 1): the settle carries the PAIRED dispatch's identity (exact pairing). */
125
129
  paired?: boolean;
126
130
  durationMs: number | null;
131
+ /** Workflow run identity (workflow-* + unknown rows, when carried; absent otherwise). */
132
+ readonly runId?: string;
133
+ /** Workflow run display name (workflow-run rows only). */
134
+ readonly name?: string;
135
+ /** The run's member count in the window (workflow-run rows only — the window's `workflow-agent` rows for the runId; absent when 0/truncated, never a 0 guess). */
136
+ readonly memberCount?: number;
137
+ /** Run-member 1-based sequence (workflow-agent rows only). */
138
+ readonly seq?: number;
139
+ /** Run-member display label (workflow-agent rows only). */
140
+ readonly label?: string;
141
+ /** Run-member phase (workflow-agent rows only, when carried). */
142
+ readonly phase?: string;
143
+ /** The published member's child session identity (workflow-agent rows only). */
144
+ readonly childId?: string;
145
+ /** Terminal workflow run reason (workflow-run-end rows only). */
146
+ readonly stopReason?: string;
127
147
  }
128
148
  /**
129
149
  * One iteration step (spec §3 + plan 20260812-panel-f5-iteration-zone-fix
@@ -456,9 +476,11 @@ export declare function projectGraph(source: MstarEngineStatusSource | null): Zo
456
476
  export declare function projectAgents(source: MstarEngineStatusSource | null, currentStep: number | null): AgentZoneView;
457
477
  /** One row of the pairing walk — the identity a PAIRED settle carries (plan
458
478
  * `20260811-panel-f4-timeliness` Task 1, spec R1: exact identity pairing,
459
- * never owner+time guessing). */
479
+ * never owner+time guessing). The kind is the FULL projected kind union:
480
+ * workflow/unknown rows are walk no-ops (they carry no paired marker) —
481
+ * widened so the classify output feeds the walk unchanged. */
460
482
  interface PairingRow {
461
- kind: 'dispatch' | 'settle';
483
+ kind: FlowEventView['kind'];
462
484
  agent: string | null;
463
485
  role?: string;
464
486
  planId?: string | null;
@@ -479,11 +501,15 @@ export declare function pairSettleIndexes(rows: readonly PairingRow[]): Readonly
479
501
  * `flow.stages` merged into the plan-3 agents projection). Total function:
480
502
  * NEVER throws, NEVER fabricates values.
481
503
  *
482
- * - events: the actual events, latest first, ≤50 (dispatch + settle rows);
504
+ * - events: the actual events, latest first, ≤50 dispatch, settle, the
505
+ * three workflow kinds (run identity: name / member count / stopReason,
506
+ * plan `20260815-dsh-workflow-ledger` Task 4) and ANY unknown kind string
507
+ * as a generic row (degradation — never dropped, never guessed);
483
508
  * - unexpected: DISPATCH events whose role is not in the expected role union
484
509
  * (e.g. `general` / `explore` / `scout`). Settle rows are completion
485
510
  * records — they carry no role at all, so they never flag as unexpected
486
- * even though their `expected` field is false ('' ∉ union);
511
+ * even though their `expected` field is false ('' ∉ union). Workflow /
512
+ * unknown rows are not dispatches → never unexpected either;
487
513
  * - settled: EXACT identity pairing via `pairSettleIndexes` (the same walk the
488
514
  * agent-entity status derivation uses; an unpaired settle stays unpaired —
489
515
  * honest). The panel never depends on the pairing's correctness;
@@ -122,7 +122,7 @@ import type { LocaleDictOf } from '@deepseek-ai/dsh-client-ui-slots';
122
122
  /** Locale namespace id for the workflow-viz panel. */
123
123
  export declare const NS = "mstar-panel";
124
124
  /** Panel dictionary keys (union of every translatable string the panel renders). */
125
- export type PanelKey = 'view.mstar-workflow' | 'tab.tasks' | 'tab.agents' | 'tab.events' | 'page.iteration.not-started' | 'page.iteration.expand' | 'page.iteration.collapse' | 'event-log.section.events' | 'event-log.section.violations' | 'event-log.empty' | 'event-log.empty.events' | 'event-log.empty.violations' | 'event-log.field.role' | 'event-log.field.agent' | 'event-log.field.stage' | 'event-log.field.plan' | 'event-log.field.task' | 'event-log.field.category' | 'event-log.field.time' | 'event-log.field.kind' | 'event-log.field.status' | 'event-log.field.expected' | 'event-log.field.settled' | 'event-log.field.duration' | 'event-log.field.severity' | 'event-log.field.code' | 'event-log.field.message' | 'event-log.kind.dispatch' | 'event-log.kind.settle' | 'event-log.yes' | 'event-log.no' | 'empty.waiting' | 'empty.no-harness' | 'empty.no-harness-hint' | 'watermark.version' | 'watermark.harness' | 'watermark.none' | 'panel.unknown' | 'graph.pass' | 'graph.fail' | 'zone.legend.title' | 'zone.legend.agent-running' | 'zone.legend.agent-settled' | 'zone.legend.agent-idle' | 'zone.iteration.step-label' | 'zone.iteration.step-badge' | 'zone.iteration.step.current' | 'zone.iteration.step.next' | 'zone.iteration.step.done' | 'zone.iteration.step.idle' | 'zone.phase.iteration-start' | 'zone.phase.autonomous-execute' | 'zone.phase.iteration-close' | 'zone.phase.pr-delivery' | 'zone.phase.merge-ready' | 'zone.branches.title' | 'zone.branches.iteration-base' | 'zone.branches.target' | 'zone.branches.spec-integration' | 'zone.tasks.title' | 'zone.tasks.total' | 'zone.tasks.no-plans' | 'zone.tasks.more' | 'zone.tasks.collapse' | 'zone.state.Todo' | 'zone.state.InProgress' | 'zone.state.InReview' | 'zone.state.Done' | 'zone.state.blocked-unknown' | 'zone.agents.title' | 'zone.agents.summary' | 'zone.agents.on-demand' | 'zone.agents.unknown-sub' | 'zone.agents.bucket.implementor' | 'zone.agents.bucket.reviewer' | 'zone.agents.group.phase-1' | 'zone.agents.group.phase-2' | 'zone.agents.group.phase-n' | 'zone.agents.group.plan' | 'zone.agents.group.no-plan' | 'zone.agents.group.plan-more' | 'flow.empty' | 'flow.settle-only' | 'flow.degraded' | 'flow.unexpected' | 'flow.in-flight' | 'flow.settled-ok' | 'flow.error' | 'flow.advisory' | 'flow.denied' | 'state.title' | 'state.plans' | 'state.residuals' | 'state.policy' | 'state.leases' | 'state.knowledge' | 'state.direction' | 'state.none' | 'state.enforcement' | 'state.enforcement.hard' | 'state.enforcement.soft' | 'state.plans.more' | 'state.residual.more' | 'state.policy.push' | 'state.policy.worktree' | 'state.policy.control-worktree' | 'state.knowledge.docs' | 'freshness.last-updated' | 'freshness.refresh-note';
125
+ export type PanelKey = 'view.mstar-workflow' | 'tab.tasks' | 'tab.agents' | 'tab.events' | 'page.iteration.not-started' | 'page.iteration.expand' | 'page.iteration.collapse' | 'event-log.section.events' | 'event-log.section.violations' | 'event-log.empty' | 'event-log.empty.events' | 'event-log.empty.violations' | 'event-log.field.role' | 'event-log.field.agent' | 'event-log.field.stage' | 'event-log.field.plan' | 'event-log.field.task' | 'event-log.field.category' | 'event-log.field.time' | 'event-log.field.kind' | 'event-log.field.status' | 'event-log.field.expected' | 'event-log.field.settled' | 'event-log.field.duration' | 'event-log.field.run-id' | 'event-log.field.name' | 'event-log.field.members' | 'event-log.field.stop-reason' | 'event-log.field.severity' | 'event-log.field.code' | 'event-log.field.message' | 'event-log.kind.dispatch' | 'event-log.kind.settle' | 'event-log.yes' | 'event-log.no' | 'empty.waiting' | 'empty.no-harness' | 'empty.no-harness-hint' | 'watermark.version' | 'watermark.harness' | 'watermark.none' | 'panel.unknown' | 'graph.pass' | 'graph.fail' | 'zone.legend.title' | 'zone.legend.agent-running' | 'zone.legend.agent-settled' | 'zone.legend.agent-idle' | 'zone.iteration.step-label' | 'zone.iteration.step-badge' | 'zone.iteration.step.current' | 'zone.iteration.step.next' | 'zone.iteration.step.done' | 'zone.iteration.step.idle' | 'zone.phase.iteration-start' | 'zone.phase.autonomous-execute' | 'zone.phase.iteration-close' | 'zone.phase.pr-delivery' | 'zone.phase.merge-ready' | 'zone.branches.title' | 'zone.branches.iteration-base' | 'zone.branches.target' | 'zone.branches.spec-integration' | 'zone.tasks.title' | 'zone.tasks.total' | 'zone.tasks.no-plans' | 'zone.tasks.more' | 'zone.tasks.collapse' | 'zone.state.Todo' | 'zone.state.InProgress' | 'zone.state.InReview' | 'zone.state.Done' | 'zone.state.blocked-unknown' | 'zone.agents.title' | 'zone.agents.summary' | 'zone.agents.on-demand' | 'zone.agents.unknown-sub' | 'zone.agents.bucket.implementor' | 'zone.agents.bucket.reviewer' | 'zone.agents.group.phase-1' | 'zone.agents.group.phase-2' | 'zone.agents.group.phase-n' | 'zone.agents.group.plan' | 'zone.agents.group.no-plan' | 'zone.agents.group.plan-more' | 'flow.empty' | 'flow.settle-only' | 'flow.degraded' | 'flow.unexpected' | 'flow.in-flight' | 'flow.settled-ok' | 'flow.error' | 'flow.advisory' | 'flow.denied' | 'state.title' | 'state.plans' | 'state.residuals' | 'state.policy' | 'state.leases' | 'state.knowledge' | 'state.direction' | 'state.none' | 'state.enforcement' | 'state.enforcement.hard' | 'state.enforcement.soft' | 'state.plans.more' | 'state.residual.more' | 'state.policy.push' | 'state.policy.worktree' | 'state.policy.control-worktree' | 'state.knowledge.docs' | 'freshness.last-updated' | 'freshness.refresh-note';
126
126
  declare module '@deepseek-ai/dsh-client-ui-slots' {
127
127
  interface LocaleNamespaceMap {
128
128
  'mstar-panel': PanelKey;
package/dist/client.js CHANGED
@@ -76,6 +76,10 @@ var zh = {
76
76
  "event-log.field.expected": "预期角色",
77
77
  "event-log.field.settled": "已结算",
78
78
  "event-log.field.duration": "耗时",
79
+ "event-log.field.run-id": "运行 ID",
80
+ "event-log.field.name": "运行名称",
81
+ "event-log.field.members": "成员数",
82
+ "event-log.field.stop-reason": "停止原因",
79
83
  "event-log.field.severity": "严重度",
80
84
  "event-log.field.code": "代码",
81
85
  "event-log.field.message": "信息",
@@ -187,6 +191,10 @@ var en = {
187
191
  "event-log.field.expected": "Expected role",
188
192
  "event-log.field.settled": "Settled",
189
193
  "event-log.field.duration": "Duration",
194
+ "event-log.field.run-id": "Run ID",
195
+ "event-log.field.name": "Run name",
196
+ "event-log.field.members": "Members",
197
+ "event-log.field.stop-reason": "Stop reason",
190
198
  "event-log.field.severity": "Severity",
191
199
  "event-log.field.code": "Code",
192
200
  "event-log.field.message": "Message",
@@ -1524,27 +1532,77 @@ function settleStatus(outcome) {
1524
1532
  }
1525
1533
  function flowEventOf(raw, index, stageIndex) {
1526
1534
  const row = raw;
1527
- const kind = row?.kind;
1528
- if (kind !== "dispatch" && kind !== "settle")
1535
+ const kind = str(row?.kind);
1536
+ if (kind === null)
1529
1537
  return null;
1530
1538
  const role = str(row?.role) ?? "";
1531
1539
  const matched = stageIndex.get(role);
1532
1540
  const ts = count(row?.ts) ?? 0;
1541
+ if (kind === "dispatch") {
1542
+ return {
1543
+ id: `${ts}-${kind}-${index}`,
1544
+ ts,
1545
+ kind,
1546
+ role,
1547
+ planId: str(row?.planId),
1548
+ taskId: str(row?.taskId),
1549
+ taskCategory: str(row?.taskCategory),
1550
+ agent: str(row?.agent),
1551
+ status: dispatchStatus(row?.verdict),
1552
+ expected: matched !== undefined,
1553
+ stage: matched ?? null,
1554
+ settled: false,
1555
+ durationMs: count(row?.durationMs)
1556
+ };
1557
+ }
1558
+ if (kind === "settle") {
1559
+ return {
1560
+ id: `${ts}-${kind}-${index}`,
1561
+ ts,
1562
+ kind,
1563
+ role,
1564
+ planId: str(row?.planId),
1565
+ taskId: str(row?.taskId),
1566
+ taskCategory: str(row?.taskCategory),
1567
+ agent: str(row?.agent),
1568
+ status: settleStatus(row?.outcome),
1569
+ expected: matched !== undefined,
1570
+ stage: matched ?? null,
1571
+ settled: false,
1572
+ ...row?.paired === true ? { paired: true } : {},
1573
+ durationMs: count(row?.durationMs)
1574
+ };
1575
+ }
1576
+ const runId = str(row?.runId);
1577
+ const name = str(row?.name);
1578
+ const seq = count(row?.seq);
1579
+ const label = str(row?.label);
1580
+ const phase = str(row?.phase);
1581
+ const childId = str(row?.childId);
1582
+ const stopReason = str(row?.stopReason);
1533
1583
  return {
1534
1584
  id: `${ts}-${kind}-${index}`,
1535
1585
  ts,
1536
1586
  kind,
1537
- role,
1538
- planId: str(row?.planId),
1539
- taskId: str(row?.taskId),
1540
- taskCategory: str(row?.taskCategory),
1587
+ role: "",
1588
+ planId: null,
1589
+ taskId: null,
1590
+ taskCategory: null,
1541
1591
  agent: str(row?.agent),
1542
- status: kind === "dispatch" ? dispatchStatus(row?.verdict) : settleStatus(row?.outcome),
1543
- expected: matched !== undefined,
1544
- stage: matched ?? null,
1592
+ status: "unknown",
1593
+ expected: false,
1594
+ stage: null,
1545
1595
  settled: false,
1546
- ...kind === "settle" && row?.paired === true ? { paired: true } : {},
1547
- durationMs: count(row?.durationMs)
1596
+ durationMs: null,
1597
+ ...runId !== null ? { runId } : {},
1598
+ ...kind === "workflow-run" && name !== null ? { name } : {},
1599
+ ...kind === "workflow-agent" ? {
1600
+ ...seq !== null ? { seq } : {},
1601
+ ...label !== null ? { label } : {},
1602
+ ...phase !== null ? { phase } : {},
1603
+ ...childId !== null ? { childId } : {}
1604
+ } : {},
1605
+ ...kind === "workflow-run-end" && stopReason !== null ? { stopReason } : {}
1548
1606
  };
1549
1607
  }
1550
1608
  function pairingKeyOf(row) {
@@ -1572,6 +1630,15 @@ function pairSettleIndexes(rows) {
1572
1630
  function classifyFlowRows(rawEvents) {
1573
1631
  const stageIndex = roleStageIndex();
1574
1632
  const windowed = rawEvents.slice(0, FLOW_EVENT_WINDOW);
1633
+ const memberCounts = new Map;
1634
+ for (const raw of windowed) {
1635
+ const row = raw;
1636
+ if (row?.kind !== "workflow-agent")
1637
+ continue;
1638
+ const runId = str(row.runId);
1639
+ if (runId !== null)
1640
+ memberCounts.set(runId, (memberCounts.get(runId) ?? 0) + 1);
1641
+ }
1575
1642
  const entries = [];
1576
1643
  windowed.forEach((raw, i) => {
1577
1644
  const view = flowEventOf(raw, i, stageIndex);
@@ -1579,6 +1646,14 @@ function classifyFlowRows(rawEvents) {
1579
1646
  return;
1580
1647
  entries.push({ view });
1581
1648
  });
1649
+ for (const entry of entries) {
1650
+ const v = entry.view;
1651
+ if (v.kind !== "workflow-run" || v.runId === undefined)
1652
+ continue;
1653
+ const n = memberCounts.get(v.runId);
1654
+ if (n !== undefined && n > 0)
1655
+ entry.view = { ...v, memberCount: n };
1656
+ }
1582
1657
  return entries;
1583
1658
  }
1584
1659
  function projectFlowEvents(source) {
@@ -3464,6 +3539,9 @@ function AgentCanvasPage({ view, iteration, t, initialPan }) {
3464
3539
  }
3465
3540
 
3466
3541
  // src/client/panel/graph/event-log.ts
3542
+ function isWorkflowKind(kind) {
3543
+ return kind === "workflow-run" || kind === "workflow-agent" || kind === "workflow-run-end";
3544
+ }
3467
3545
  function taskTag(event) {
3468
3546
  if (event.planId !== null && event.taskId !== null)
3469
3547
  return `${event.planId}#${event.taskId}`;
@@ -3473,7 +3551,7 @@ function taskTag(event) {
3473
3551
  return `#${event.taskId}`;
3474
3552
  return "";
3475
3553
  }
3476
- function eventEntryOf(event) {
3554
+ function eventEntryOf(event, runNames) {
3477
3555
  return {
3478
3556
  kind: "event",
3479
3557
  id: event.id,
@@ -3486,11 +3564,19 @@ function eventEntryOf(event) {
3486
3564
  status: event.status,
3487
3565
  settled: event.settled,
3488
3566
  durationMs: event.durationMs,
3489
- expected: event.expected
3567
+ expected: event.expected,
3568
+ runId: event.runId ?? "",
3569
+ name: event.kind === "workflow-run" && event.name !== undefined ? event.name : event.runId !== undefined ? runNames.get(event.runId) ?? "" : ""
3490
3570
  };
3491
3571
  }
3492
3572
  function eventLogEntries(view) {
3493
- const entries = view.events.map(eventEntryOf);
3573
+ const runNames = new Map;
3574
+ for (const event of view.events) {
3575
+ if (event.kind === "workflow-run" && event.name !== undefined && event.name !== "") {
3576
+ runNames.set(event.runId ?? "", event.name);
3577
+ }
3578
+ }
3579
+ const entries = view.events.map((event) => eventEntryOf(event, runNames));
3494
3580
  view.violations.forEach((violation, i) => {
3495
3581
  entries.push({
3496
3582
  kind: "violation",
@@ -3892,7 +3978,7 @@ function EventDetailsBody({
3892
3978
  /* @__PURE__ */ jsx_runtime8.jsx(DetailField, {
3893
3979
  field: "kind",
3894
3980
  label: t("event-log.field.kind"),
3895
- value: entry.eventKind === "dispatch" ? t("event-log.kind.dispatch") : t("event-log.kind.settle")
3981
+ value: entry.eventKind === "dispatch" ? t("event-log.kind.dispatch") : entry.eventKind === "settle" ? t("event-log.kind.settle") : entry.eventKind
3896
3982
  }),
3897
3983
  /* @__PURE__ */ jsx_runtime8.jsx(DetailField, {
3898
3984
  field: "status",
@@ -3902,17 +3988,41 @@ function EventDetailsBody({
3902
3988
  /* @__PURE__ */ jsx_runtime8.jsx(DetailField, {
3903
3989
  field: "expected",
3904
3990
  label: t("event-log.field.expected"),
3905
- value: entry.eventKind === "settle" ? "" : entry.expected ? t("event-log.yes") : t("event-log.no")
3991
+ value: entry.eventKind === "settle" || isWorkflowKind(entry.eventKind) ? "" : entry.expected ? t("event-log.yes") : t("event-log.no")
3906
3992
  }),
3907
3993
  /* @__PURE__ */ jsx_runtime8.jsx(DetailField, {
3908
3994
  field: "settled",
3909
3995
  label: t("event-log.field.settled"),
3910
- value: entry.eventKind === "settle" ? "" : entry.settled ? t("event-log.yes") : t("event-log.no")
3996
+ value: entry.eventKind === "settle" || isWorkflowKind(entry.eventKind) ? "" : entry.settled ? t("event-log.yes") : t("event-log.no")
3911
3997
  }),
3912
3998
  /* @__PURE__ */ jsx_runtime8.jsx(DetailField, {
3913
3999
  field: "duration",
3914
4000
  label: t("event-log.field.duration"),
3915
4001
  value: entry.durationMs === null ? "" : `${entry.durationMs}ms`
4002
+ }),
4003
+ isWorkflowKind(entry.eventKind) && /* @__PURE__ */ jsx_runtime8.jsxs(jsx_runtime8.Fragment, {
4004
+ children: [
4005
+ /* @__PURE__ */ jsx_runtime8.jsx(DetailField, {
4006
+ field: "run-id",
4007
+ label: t("event-log.field.run-id"),
4008
+ value: source?.runId ?? ""
4009
+ }),
4010
+ /* @__PURE__ */ jsx_runtime8.jsx(DetailField, {
4011
+ field: "name",
4012
+ label: t("event-log.field.name"),
4013
+ value: entry.name
4014
+ }),
4015
+ /* @__PURE__ */ jsx_runtime8.jsx(DetailField, {
4016
+ field: "members",
4017
+ label: t("event-log.field.members"),
4018
+ value: source?.memberCount !== undefined ? String(source.memberCount) : ""
4019
+ }),
4020
+ /* @__PURE__ */ jsx_runtime8.jsx(DetailField, {
4021
+ field: "stop-reason",
4022
+ label: t("event-log.field.stop-reason"),
4023
+ value: source?.stopReason ?? ""
4024
+ })
4025
+ ]
3916
4026
  })
3917
4027
  ]
3918
4028
  });
@@ -3923,6 +4033,7 @@ function EventLogEventRow({
3923
4033
  t
3924
4034
  }) {
3925
4035
  const time = formatEventTime(entry.ts);
4036
+ const workflowIdentity = isWorkflowKind(entry.eventKind) ? entry.name !== "" ? entry.name : entry.runId !== "" ? entry.runId : "" : "";
3926
4037
  return /* @__PURE__ */ jsx_runtime8.jsx("li", {
3927
4038
  className: event_log_module_default.eventRow,
3928
4039
  "data-event-log-row-kind": "event",
@@ -3942,6 +4053,9 @@ function EventLogEventRow({
3942
4053
  className: event_log_module_default.settleGlyph,
3943
4054
  "aria-hidden": "true",
3944
4055
  children: "✓"
4056
+ }) : workflowIdentity !== "" ? /* @__PURE__ */ jsx_runtime8.jsx("span", {
4057
+ "data-event-log-name": workflowIdentity,
4058
+ children: workflowIdentity
3945
4059
  }) : entry.role !== "" ? entry.role : t("panel.unknown")
3946
4060
  }),
3947
4061
  entry.stage !== "" && /* @__PURE__ */ jsx_runtime8.jsx("span", {
@@ -24,10 +24,11 @@ export interface Config {
24
24
  enforcement?: 'hard' | 'soft';
25
25
  /**
26
26
  * Model-facing delegation tool name(s) the dispatch gate matches. The dsh
27
- * subagent tool registers as `subagent` by default, but its `toolName`
28
- * config may rename instances (tool-subagent README: each instance needs a
29
- * distinct name), so the match list is deployment-settable. Defaults to
30
- * `['subagent']`.
27
+ * subagent tool registers as `subagent` by default (its fork sibling
28
+ * `subagent_fork` carries the same Assignment-shaped args and is gated
29
+ * too), but the `toolName` config may rename instances (tool-subagent
30
+ * README: each instance needs a distinct name), so the match list is
31
+ * deployment-settable. Defaults to `['subagent', 'subagent_fork']`.
31
32
  */
32
33
  dispatchTools?: string[];
33
34
  /**
@@ -68,8 +69,93 @@ export interface Config {
68
69
  * per interval). Absent → 60000.
69
70
  */
70
71
  catalogTtlMs?: number;
72
+ /**
73
+ * Taxonomy bridge: mstar role id → fallbacks role id (the
74
+ * `dsh-llm-fallbacks` role taxonomy). Logging + future rule-driven
75
+ * interop only — NOT required for persona injection (`rolePersonas` is
76
+ * the decoration's only payload source). Absent → no bridge mapping.
77
+ */
78
+ roleMap?: Record<string, string>;
79
+ /**
80
+ * mstar role id → persona text — the subagent decoration's only payload
81
+ * source (plan `20260814-dsh-fallbacks-integration` Task 2). A
82
+ * role-matched `subagent/start` registers the persona as the child's
83
+ * `mstar:role-persona` system-prompt section (agent-scoped on
84
+ * `Agent.ctx`, unwinds on disposal). Lookup is DIRECT — never gated on
85
+ * `roleMap` or on the fallbacks mounted state (unmounted → same
86
+ * injection from Config + one debug log). Absent → no decoration.
87
+ *
88
+ * INTERPOLATION CONSTRAINT: dsh system-prompt renders section text with
89
+ * STRICT `{{variable}}` interpolation and throws on any `{{` paired with a
90
+ * later `}}` (unknown/malformed/undefined reference), so persona values
91
+ * MUST NOT contain that pattern — a violating persona would break child
92
+ * prompt assembly at the child's first render, for EVERY role-matched
93
+ * dispatch. The Config schema rejects such values at plugin mount with a
94
+ * clear error (see {@link PERSONA_INTERPOLATION_HAZARD}); a lone `{{` with
95
+ * no later `}}` is literal prose (safe), and the escape rule is single
96
+ * braces or rewording. Keep persona text concise; bound its length at
97
+ * deployment.
98
+ */
99
+ rolePersonas?: Record<string, string>;
100
+ /**
101
+ * Workflow/ralph gate mode (plan `20260815-dsh-workflow-gate` W-B3, AC-7):
102
+ * `off` disables the gate entirely (workflow/ralph calls pass through
103
+ * untouched, NO verdict row); `warn` (default) logs an advisory for
104
+ * policy-unknown fan-out without ever blocking; `ask` routes first-seen
105
+ * workflow names through the dsh approval channel (`{kind:'ask'}` —
106
+ * fail-closed upstream, this gate invents no answerer); `hard` vetoes a
107
+ * policy-violating fan-out call before any child starts. Absent → 'warn'.
108
+ */
109
+ workflowGate?: 'off' | 'warn' | 'ask' | 'hard';
110
+ /**
111
+ * Workflow name allowlist (P-a): `meta.name` values treated as KNOWN by
112
+ * the gate. Empty or absent ⇒ every name is unknown (documented — the
113
+ * gate is NOT "allow all" by omission). Ralph calls carry no `meta.name`,
114
+ * so P-a never applies to them.
115
+ */
116
+ workflowNames?: string[];
117
+ /**
118
+ * Goal-bridge round cap (plan `20260816-dsh-nb2-goal-bridge` Task 2): the
119
+ * flat `maxGoalRounds` the goal bridge passes to the goals service when it
120
+ * mirrors the active iteration objective (bounds autonomous Phase 2
121
+ * loops — the service itself throws on resume past the cap). Absent →
122
+ * 256 (aligned with the GoalService default and ralph `maxRounds` —
123
+ * architect decision; see the plan).
124
+ */
125
+ maxGoalRounds?: number;
71
126
  }
72
- /** Schemastery configuration schema for the plugin consumer. Object keys are optional by default (`.optional()` is a vendored-fork addition not present in npm schemastery); omitted ARRAY keys would materialize as `[]` (schemastery empty-value default — the tool-subagent `toolFilter` pitfall), so both dispatch keys preserve omission via `.default(undefined)`. */
127
+ /**
128
+ * dsh system-prompt strict `{{variable}}` interpolation hazard: the renderer
129
+ * (`renderPrompt` → `interpolate` in `@deepseek-ai/dsh-system-prompt`) scans
130
+ * section text for `{{` and THROWS on any `{{` paired with a later `}}` in
131
+ * the same text (unknown variable, malformed group, or undefined value).
132
+ * Persona text lands in the `mstar:role-persona` section verbatim, so a
133
+ * persona containing this pattern breaks child prompt assembly at the
134
+ * child's first render — every role-matched dispatch. The Config schema
135
+ * rejects such values at validation (plugin mount) with a clear error. The
136
+ * mirror-default extraction (agent-personas.ts) reuses this constant: a
137
+ * violating default is WARNED + skipped at extraction, never a boot throw. A
138
+ * lone `{{` with no later `}}` is literal prose (safe); the escape rule is
139
+ * single braces or rewording.
140
+ */
141
+ export declare const PERSONA_INTERPOLATION_HAZARD: RegExp;
142
+ /**
143
+ * Screen ONE dynamic string before it is embedded into dsh system-prompt
144
+ * section/context text (plan QC fix wave W-1): break every COMPLETE
145
+ * `{{...}}` group (the renderer's STRICT `interpolate` throws on any `{{`
146
+ * paired with a later `}}` — unknown variable, malformed group, undefined
147
+ * value), while leaving a LONE `{{` without a later `}}` verbatim (upstream
148
+ * renders it as literal prose — already safe, do not mangle it).
149
+ *
150
+ * A complete group is rewritten to `{ {…} }` (a space inside each pair), so
151
+ * no adjacent `{{`/`}}` survives the renderer scan; the rewrite is re-scanned
152
+ * because a nested/adjacent brace at either boundary can expose a fresh pair.
153
+ * The output is plain text the renderer treats as literal — operator-chosen
154
+ * values (paths, plan/iteration ids, lease fields, compass direction prose)
155
+ * can never break prompt assembly.
156
+ */
157
+ export declare function stripInterpolationHazard(text: string): string;
158
+ /** Schemastery configuration schema for the plugin consumer. Object keys are optional by default (`.optional()` is a vendored-fork addition not present in npm schemastery); omitted ARRAY keys would materialize as `[]` (schemastery empty-value default — the tool-subagent `toolFilter` pitfall) and omitted DICT keys would materialize as `{}`, so the dispatch keys and the decoration keys all preserve omission via `.default(undefined)`. */
73
159
  export declare const Config: z<Config>;
74
160
  /** One violation line for logs and the typed veto message. */
75
161
  export declare function formatViolation(violation: ValidationResult): string;
@@ -117,6 +203,19 @@ export declare function resolveSeamHard(harnessDir: string | null, config: Confi
117
203
  */
118
204
  export declare function resolvePackagedSkillsDir(fileUrl: string): string | undefined;
119
205
  export declare function packagedSkillsDir(): string | undefined;
206
+ /**
207
+ * Resolve the plugin package's own `harness-agents/` mirror (synced from the
208
+ * repo root by `bundle-assets` at build/postinstall; gitignored) — the
209
+ * zero-config role-persona default source (plan
210
+ * `20260815-dsh-fallbacks-personas` Task 3). Same dual-depth probe semantics
211
+ * as {@link resolvePackagedSkillsDir}: `'../harness-agents'` (dist layout
212
+ * candidate) then `'../../harness-agents'` (source-layout candidate);
213
+ * `src/harness-agents` is non-canonical and skipped shallow-first.
214
+ * @param fileUrl - the module's `import.meta.url` string (or any file URL at
215
+ * the depth whose layout should be probed).
216
+ */
217
+ export declare function resolvePackagedAgentsDir(fileUrl: string): string | undefined;
218
+ export declare function packagedAgentsDir(): string | undefined;
120
219
  /**
121
220
  * Per-workspace `{HARNESS_DIR}` resolution for the plugin.
122
221
  *
@@ -2,7 +2,8 @@ import { Service, type Context } from '@deepseek-ai/cordis';
2
2
  import type { AssignmentFields, GateResult, HostAdapter, IntegrationMergeLease, ValidationResult } from '@mstar-harness/engine';
3
3
  import type { ToolExecution } from '@deepseek-ai/dsh-tools';
4
4
  import type { HarnessResolver, Config } from './_shared.ts';
5
- import type { AgentFlowPairing } from './agent-flow.ts';
5
+ import type { AgentFlowPairing, WorkflowVerdictInput } from './agent-flow.ts';
6
+ import { WorkflowAskCache } from './workflow-policy.ts';
6
7
  /** Options for {@link DshHostAdapter}. */
7
8
  export interface DshHostAdapterOptions {
8
9
  /**
@@ -22,6 +23,16 @@ export interface DshHostAdapterOptions {
22
23
  * construction) → no pairing registration (record-only).
23
24
  */
24
25
  readonly pairing?: AgentFlowPairing;
26
+ /**
27
+ * The P-c first-seen ask cache (plan `20260815-dsh-workflow-gate`
28
+ * Task 2): workflow name → resolved decision, apply-scoped. Absent (the
29
+ * entry relies on this default — `index.ts` constructs the adapter
30
+ * without it) → the constructor builds ONE cache per adapter per apply,
31
+ * shared by the gate and any answerer integration via the readonly
32
+ * property. An explicit instance may be passed to share a specific cache
33
+ * (host-adapter tests / direct construction).
34
+ */
35
+ readonly workflowAskCache?: WorkflowAskCache;
25
36
  /**
26
37
  * Log sink for `HostAdapter.log`. Defaults to the dsh ctx logger scoped
27
38
  * `mstar/host-adapter` (dsh logger naming: `<scope>/<subject>`).
@@ -64,6 +75,14 @@ export declare class DshHostAdapter extends Service implements HostAdapter {
64
75
  private readonly resolver;
65
76
  private readonly config;
66
77
  private readonly pairing;
78
+ /**
79
+ * The P-c first-seen ask cache (plan `20260815-dsh-workflow-gate`
80
+ * Task 2) — apply-scoped with the adapter: the dispatch gate reads it
81
+ * through `gateDispatch`, and tests/answerer integrations reach the same
82
+ * instance via `ctx.dshHostAdapter.workflowAskCache`. Dies with the
83
+ * fiber (no module-level reference — an HMR reload starts a fresh cache).
84
+ */
85
+ readonly workflowAskCache: WorkflowAskCache;
67
86
  private readonly logSink;
68
87
  constructor(ctx: Context, options: DshHostAdapterOptions);
69
88
  /**
@@ -98,6 +117,20 @@ export declare class DshHostAdapter extends Service implements HostAdapter {
98
117
  * when omitted (external callers) the adapter resolves it itself.
99
118
  */
100
119
  dispatchGate(prompt: string, exec?: ToolExecution, hard?: boolean): GateResult;
120
+ /**
121
+ * Record one workflow/ralph gate verdict row (plan
122
+ * `20260815-dsh-workflow-gate` Task 4 — the durable ledger row for every
123
+ * gated workflow/ralph call: verdict + metaName/objective + mode, via the
124
+ * ledger plan's record path). `gateWorkflow` routes the record through
125
+ * this adapter method so dispatch.ts stays free of a runtime agent-flow
126
+ * import (the ledger imports dispatch helpers — the adapter is the
127
+ * acyclic junction, the same role it plays for `dispatchGate`). The
128
+ * underlying record is fully try/catch-contained (a failing ledger write
129
+ * logs and never reaches the gate) — this method never throws.
130
+ * @param input - the gate's decision identity (harness dir + tool +
131
+ * workflow/objective + mode + verdict + code).
132
+ */
133
+ recordWorkflowVerdict(input: WorkflowVerdictInput): void;
101
134
  /**
102
135
  * `HostAdapter.beforeStatusWrite` — see the class doc for the doc-first /
103
136
  * on-disk-fallback semantics. Never throws; a failing gate maps to its