@mediadatafusion/pi-workflow-suite 0.0.19 → 0.0.21

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.
@@ -0,0 +1,114 @@
1
+ import type { KeyId } from "@earendil-works/pi-tui";
2
+
3
+ export type WorkflowShortcutPlatform = "darwin" | "win32" | "linux";
4
+ export type WorkflowShortcutActionId =
5
+ | "workflow.widget.top.toggle"
6
+ | "workflow.widget.bottom.toggle"
7
+ | "workflow.presets.cycle"
8
+ | "workflow.standard.toggle"
9
+ | "workflow.plan.toggle"
10
+ | "workflow.mission.toggle";
11
+
12
+ export interface WorkflowShortcutDefinition {
13
+ id: WorkflowShortcutActionId;
14
+ description: string;
15
+ fallbackCommand: string;
16
+ keys: Record<WorkflowShortcutPlatform, KeyId>;
17
+ }
18
+
19
+ export const WORKFLOW_SHORTCUTS: WorkflowShortcutDefinition[] = [
20
+ {
21
+ id: "workflow.widget.top.toggle",
22
+ description: "Toggle active Plan/Mission/Standard top workflow widget",
23
+ fallbackCommand: "/workflow widgets toggle top",
24
+ keys: { darwin: "ctrl+shift+t", win32: "f2", linux: "f2" },
25
+ },
26
+ {
27
+ id: "workflow.widget.bottom.toggle",
28
+ description: "Toggle active Plan/Mission/Standard bottom workflow widget",
29
+ fallbackCommand: "/workflow widgets toggle bottom",
30
+ keys: { darwin: "ctrl+shift+b", win32: "f3", linux: "f3" },
31
+ },
32
+ {
33
+ id: "workflow.presets.cycle",
34
+ description: "Cycle workflow presets during Plan/Mission/Standard Mode",
35
+ fallbackCommand: "/workflow presets next",
36
+ keys: { darwin: "ctrl+shift+u", win32: "f4", linux: "f4" },
37
+ },
38
+ {
39
+ id: "workflow.standard.toggle",
40
+ description: "Toggle Standard Mode",
41
+ fallbackCommand: "/standard",
42
+ keys: { darwin: "ctrl+shift+s", win32: "f6", linux: "f6" },
43
+ },
44
+ {
45
+ id: "workflow.plan.toggle",
46
+ description: "Enter Plan Mode",
47
+ fallbackCommand: "/plan",
48
+ keys: { darwin: "ctrl+shift+l", win32: "f7", linux: "f7" },
49
+ },
50
+ {
51
+ id: "workflow.mission.toggle",
52
+ description: "Toggle Mission Mode",
53
+ fallbackCommand: "/mission",
54
+ keys: { darwin: "ctrl+shift+m", win32: "f8", linux: "f8" },
55
+ },
56
+ ];
57
+
58
+ const SHORTCUT_BY_ID = new Map(WORKFLOW_SHORTCUTS.map((shortcut) => [shortcut.id, shortcut]));
59
+
60
+ export function workflowShortcutPlatform(platform = process.platform): WorkflowShortcutPlatform {
61
+ if (platform === "darwin" || platform === "win32" || platform === "linux") return platform;
62
+ return "linux";
63
+ }
64
+
65
+ export function workflowShortcutKey(id: WorkflowShortcutActionId, platform = process.platform): KeyId {
66
+ return workflowShortcutDefinition(id).keys[workflowShortcutPlatform(platform)];
67
+ }
68
+
69
+ export function workflowShortcutDefinition(id: WorkflowShortcutActionId): WorkflowShortcutDefinition {
70
+ const shortcut = SHORTCUT_BY_ID.get(id);
71
+ if (!shortcut) throw new Error(`Unknown Workflow Suite shortcut: ${id}`);
72
+ return shortcut;
73
+ }
74
+
75
+ export function workflowShortcutLabel(id: WorkflowShortcutActionId, platform = process.platform): string {
76
+ return workflowShortcutKeyLabel(workflowShortcutKey(id, platform));
77
+ }
78
+
79
+ export function workflowShortcutKeyLabel(key: KeyId): string {
80
+ return key.split("+").map((part) => {
81
+ if (/^f\d+$/i.test(part)) return part.toUpperCase();
82
+ return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
83
+ }).join("+");
84
+ }
85
+
86
+ export function workflowEntryShortcutLabel(mode: "standard" | "plan" | "mission", platform = process.platform): string {
87
+ if (mode === "standard") return `Standard:${workflowShortcutLabel("workflow.standard.toggle", platform)}`;
88
+ if (mode === "plan") return `Plan:${workflowShortcutLabel("workflow.plan.toggle", platform)}`;
89
+ return `Mission:${workflowShortcutLabel("workflow.mission.toggle", platform)}`;
90
+ }
91
+
92
+ export function workflowWidgetShortcutLabel(includeBottom: boolean, platform = process.platform): string {
93
+ const prefix = includeBottom ? "Widgets" : "Widget";
94
+ if (!includeBottom) return `${prefix}:${workflowShortcutLabel("workflow.widget.top.toggle", platform)}`;
95
+ const activePlatform = workflowShortcutPlatform(platform);
96
+ if (activePlatform === "darwin") return `${prefix}:Ctrl+Shift+T/B`;
97
+ return `${prefix}:${workflowShortcutLabel("workflow.widget.top.toggle", platform)}/${workflowShortcutLabel("workflow.widget.bottom.toggle", platform)}`;
98
+ }
99
+
100
+ export function workflowPresetCycleShortcutLabel(platform = process.platform): string {
101
+ return workflowShortcutLabel("workflow.presets.cycle", platform);
102
+ }
103
+
104
+ export function workflowSettingsShortcutLines(platform = process.platform): string[] {
105
+ return [
106
+ `Standard Shortcut: ${workflowShortcutLabel("workflow.standard.toggle", platform)} toggles Standard Mode`,
107
+ `Plan Shortcut: ${workflowShortcutLabel("workflow.plan.toggle", platform)} enters Plan Mode`,
108
+ `Mission Shortcut: ${workflowShortcutLabel("workflow.mission.toggle", platform)} enters Mission Mode`,
109
+ `Widget Shortcuts: ${workflowWidgetShortcutLabel(true, platform).replace(/^Widgets:/, "")} while Plan/Mission/Standard Mode is active`,
110
+ `Preset Cycle Shortcut: ${workflowPresetCycleShortcutLabel(platform)} while Plan/Mission/Standard Mode is active`,
111
+ ];
112
+ }
113
+
114
+ export default function workflowShortcutsNoopExtension(): void {}
@@ -141,6 +141,25 @@ export interface StandardTodoState {
141
141
 
142
142
  export type StandardClarificationStage = "drafting" | "awaiting_answer" | "answered";
143
143
  export type WorkflowSubagentPhase = "Planning" | "Execution" | "Repair" | "Review" | "Validation";
144
+ export type WorkflowSubagentPolicyDecisionOutcome =
145
+ | "required"
146
+ | "exempt_trivial"
147
+ | "auto_delegate"
148
+ | "auto_skip_trivial"
149
+ | "auto_skip_no_useful_parallel_work"
150
+ | "unavailable";
151
+
152
+ export interface WorkflowSubagentPolicyDecision {
153
+ phase: WorkflowSubagentPhase;
154
+ policy: "off" | "auto" | "deep" | "maximum" | "forced";
155
+ outcome: WorkflowSubagentPolicyDecisionOutcome;
156
+ reason: string;
157
+ task?: string;
158
+ required?: number;
159
+ observed?: number;
160
+ background?: boolean;
161
+ createdAt: string;
162
+ }
144
163
 
145
164
  export interface StandardSubagentPreflightRecord {
146
165
  task?: string;
@@ -310,6 +329,7 @@ export interface WorkflowState {
310
329
  standardClarifyingQuestions?: ClarificationQuestion[];
311
330
  standardClarifyingAnswers?: ClarificationAnswer[];
312
331
  standardSubagentPreflight?: Partial<Record<WorkflowSubagentPhase, StandardSubagentPreflightRecord>>;
332
+ subagentPolicyDecisions?: Partial<Record<WorkflowSubagentPhase, WorkflowSubagentPolicyDecision>>;
313
333
  standardLastTodoDecision?: string;
314
334
  standardLastTodoReason?: string;
315
335
  lastCompletedPlanSummary?: CompletedPlanSummary;
@@ -14,6 +14,28 @@ import { getAgentDir } from "@earendil-works/pi-coding-agent";
14
14
 
15
15
  export type SubagentPhase = "Planning" | "Execution" | "Repair" | "Review" | "Validation";
16
16
  export type SubagentPolicyValue = "off" | "auto" | "deep" | "maximum" | "forced";
17
+ export type SubagentPolicyDecisionOutcome =
18
+ | "required"
19
+ | "exempt_trivial"
20
+ | "allow_probe"
21
+ | "allow_mechanical"
22
+ | "allow_user_explicit_finalization"
23
+ | "auto_delegate"
24
+ | "auto_skip_trivial"
25
+ | "auto_skip_no_useful_parallel_work"
26
+ | "unavailable";
27
+
28
+ export interface SubagentPolicyDecision {
29
+ phase: SubagentPhase;
30
+ policy: SubagentPolicyValue;
31
+ outcome: SubagentPolicyDecisionOutcome;
32
+ reason: string;
33
+ task?: string;
34
+ required?: number;
35
+ observed?: number;
36
+ background?: boolean;
37
+ createdAt: string;
38
+ }
17
39
 
18
40
  export interface SubagentToolProfile {
19
41
  name: string;
@@ -106,10 +128,218 @@ export function workerTargetForPolicy(policy: SubagentPolicyValue | undefined, w
106
128
  return 0;
107
129
  }
108
130
 
131
+ export function subagentPolicyRequiresRequiredEvidence(policy: SubagentPolicyValue | undefined): boolean {
132
+ return policy === "forced";
133
+ }
134
+
135
+ export function subagentPolicyNeedsInternalDecision(policy: SubagentPolicyValue | undefined): boolean {
136
+ return policy === "auto" || policy === "deep" || policy === "maximum";
137
+ }
138
+
139
+ export function formatSubagentPolicyDecision(decision: SubagentPolicyDecision): string {
140
+ return [
141
+ `Policy decision: ${decision.outcome}`,
142
+ `Phase: ${decision.phase}`,
143
+ `Policy: ${decision.policy}`,
144
+ decision.required !== undefined ? `Required workers: ${decision.required}` : undefined,
145
+ decision.observed !== undefined ? `Observed workers: ${decision.observed}` : undefined,
146
+ decision.background !== undefined ? `Background: ${decision.background ? "yes" : "no"}` : undefined,
147
+ `Reason: ${decision.reason}`,
148
+ ].filter((line): line is string => Boolean(line)).join("\n");
149
+ }
150
+
151
+ export type ForcedSubagentActionTool =
152
+ | "read"
153
+ | "grep"
154
+ | "find"
155
+ | "ls"
156
+ | "bash"
157
+ | "edit"
158
+ | "write"
159
+ | "subagent"
160
+ | "workflow_progress"
161
+ | "workflow_execution_result"
162
+ | "workflow_validation_result"
163
+ | "workflow_repair_result"
164
+ | "workflow_review_result"
165
+ | "mission_milestone_result"
166
+ | "standard_handoff_result"
167
+ | "standard_todo"
168
+ | string;
169
+
170
+ export interface ForcedSubagentActionInput {
171
+ phase: SubagentPhase;
172
+ policy: SubagentPolicyValue;
173
+ task?: string;
174
+ kind?: string;
175
+ toolName?: ForcedSubagentActionTool;
176
+ command?: string;
177
+ }
178
+
179
+ export interface ForcedSubagentActionDecision {
180
+ outcome: Extract<SubagentPolicyDecisionOutcome, "required" | "exempt_trivial" | "allow_probe" | "allow_mechanical" | "allow_user_explicit_finalization">;
181
+ reason: string;
182
+ allowBeforeEvidence: boolean;
183
+ }
184
+
185
+ export interface AdvisorySubagentActionDecision {
186
+ outcome: SubagentPolicyDecisionOutcome;
187
+ reason: string;
188
+ allowBeforeEvidence: boolean;
189
+ }
190
+
191
+ const WORK_MUTATION_RE = /\b(?:edit|write|modify|change|implement|add|remove|delete|refactor|migrate|apply|create files?|update files?|repair|fix)\b/i;
192
+ const EVIDENCE_WORK_RE = /\b(?:validate|validation|verify|test|lint|build|typecheck|quality review|review)\b/i;
193
+ const FINALIZATION_WORK_RE = /\b(?:commit|push|sync(?:\s+to)?\s+live|install(?:\s+to)?\s+live)\b/i;
194
+ const TRIVIAL_REQUEST_RE = /^(?:hi|hello|hey|thanks|thank you|ok|okay|yes|no|help|status)$/i;
195
+ const SMALL_LOOKUP_RE = /\b(?:what|where|when|who|which|explain|summari[sz]e)\b/i;
196
+ const SIMPLE_READ_RE = /\b(?:status|list|show|summari[sz]e|inspect|scan|read.?only|docs? only|no code|do not edit|without editing)\b/i;
197
+ const COMPLEX_CONTEXT_RE = /\b(?:codebase|architecture|multi-file|implementation|workflow|release|runtime|regression)\b/i;
198
+ const SAFE_PROBE_COMMAND_RE = /^(?:pwd|date|whoami|id|hostname|uname\b.*|git\s+(?:status|diff|log|show|branch|rev-parse|ls-files|describe|remote)\b.*|(?:node|npm|pnpm|yarn|bun|python3?|pip3?|cargo|go|rustc|tsc)\s+(?:--version|-v|-V)\b.*)$/i;
199
+ const EXACT_USER_FINALIZATION_COMMAND_RE = /^(?:git\s+add\b.+|git\s+commit\b.+|git\s+push(?:\s+\S+){0,2}|scripts\/install-to-live\.sh|scripts\/verify-live\.sh)$/i;
200
+
201
+ function normalizedWords(text: string | undefined): string[] {
202
+ return (text ?? "").trim().toLowerCase().split(/\s+/).filter(Boolean);
203
+ }
204
+
205
+ function forcedTrivialTaskReason(phase: SubagentPhase, task: string | undefined, kind?: string): string | undefined {
206
+ const text = task?.trim() ?? "";
207
+ if (!text) return "empty/no-op request";
208
+ const normalized = text.toLowerCase();
209
+ const words = normalizedWords(text);
210
+ if (TRIVIAL_REQUEST_RE.test(normalized)) return "trivial conversational/status request";
211
+ if (WORK_MUTATION_RE.test(normalized) || EVIDENCE_WORK_RE.test(normalized) || FINALIZATION_WORK_RE.test(normalized)) return undefined;
212
+ if (kind && kind !== "read_only") return undefined;
213
+ if (SIMPLE_READ_RE.test(normalized) && words.length <= 14) return `trivial ${phase.toLowerCase()} read-only/status request`;
214
+ if (normalized.length <= 80 && SMALL_LOOKUP_RE.test(normalized) && !COMPLEX_CONTEXT_RE.test(normalized)) return `small ${phase.toLowerCase()} lookup/explanation request`;
215
+ return undefined;
216
+ }
217
+
218
+ function commandWithoutTimeout(command: string | undefined): string {
219
+ return (command ?? "").trim().replace(/^timeout\s+\d+[smhd]?\s+/, "").trim();
220
+ }
221
+
222
+ export function forcedSubagentActionDecision(input: ForcedSubagentActionInput): ForcedSubagentActionDecision {
223
+ const toolName = input.toolName;
224
+ if (!subagentPolicyRequiresRequiredEvidence(input.policy)) {
225
+ return { outcome: "allow_mechanical", reason: "sub-agent policy is not forced", allowBeforeEvidence: true };
226
+ }
227
+
228
+ if (!toolName) {
229
+ const reason = forcedTrivialTaskReason(input.phase, input.task, input.kind);
230
+ if (reason) return { outcome: "exempt_trivial", reason, allowBeforeEvidence: true };
231
+ return { outcome: "required", reason: "non-trivial phase requires forced sub-agent evidence", allowBeforeEvidence: false };
232
+ }
233
+
234
+ if (toolName === "subagent") {
235
+ return { outcome: "allow_mechanical", reason: "visible sub-agent call is the required evidence path", allowBeforeEvidence: true };
236
+ }
237
+
238
+ if (toolName === "workflow_progress") {
239
+ return { outcome: "allow_mechanical", reason: "workflow_progress only marks the current execution step active", allowBeforeEvidence: true };
240
+ }
241
+
242
+ if (toolName === "standard_todo") {
243
+ return { outcome: "allow_mechanical", reason: "standard_todo only initializes or updates required Standard Mode task tracking", allowBeforeEvidence: true };
244
+ }
245
+
246
+ if (toolName === "read" || toolName === "grep" || toolName === "find" || toolName === "ls") {
247
+ return { outcome: "allow_probe", reason: "local read/search/list probe can run before forced worker evidence", allowBeforeEvidence: true };
248
+ }
249
+
250
+ if (
251
+ toolName === "workflow_execution_result"
252
+ || toolName === "workflow_validation_result"
253
+ || toolName === "workflow_repair_result"
254
+ || toolName === "workflow_review_result"
255
+ || toolName === "mission_milestone_result"
256
+ ) {
257
+ return { outcome: "required", reason: "typed phase handoff requires forced sub-agent evidence", allowBeforeEvidence: false };
258
+ }
259
+
260
+ if (toolName === "edit" || toolName === "write") {
261
+ return { outcome: "required", reason: "file mutation requires forced sub-agent evidence", allowBeforeEvidence: false };
262
+ }
263
+
264
+ if (toolName === "bash") {
265
+ const command = commandWithoutTimeout(input.command);
266
+ if (SAFE_PROBE_COMMAND_RE.test(command)) {
267
+ return { outcome: "allow_probe", reason: "safe shell probe can run before forced worker evidence", allowBeforeEvidence: true };
268
+ }
269
+ const task = input.task?.toLowerCase() ?? "";
270
+ if (FINALIZATION_WORK_RE.test(task) && EXACT_USER_FINALIZATION_COMMAND_RE.test(command)) {
271
+ return { outcome: "allow_user_explicit_finalization", reason: "exact user-requested finalization command is not useful worker fanout", allowBeforeEvidence: true };
272
+ }
273
+ return { outcome: "required", reason: "shell command is meaningful work and requires forced sub-agent evidence", allowBeforeEvidence: false };
274
+ }
275
+
276
+ const phaseReason = forcedTrivialTaskReason(input.phase, input.task, input.kind);
277
+ if (phaseReason) return { outcome: "exempt_trivial", reason: phaseReason, allowBeforeEvidence: true };
278
+ return { outcome: "required", reason: "tool use requires forced sub-agent evidence", allowBeforeEvidence: false };
279
+ }
280
+
281
+ export function advisorySubagentPolicyDecision(phase: SubagentPhase, policy: SubagentPolicyValue, task?: string, kind?: string): { outcome: Extract<SubagentPolicyDecisionOutcome, "auto_delegate" | "auto_skip_trivial" | "auto_skip_no_useful_parallel_work">; reason: string } {
282
+ const trivialReason = forcedTrivialTaskReason(phase, task, kind);
283
+ if (trivialReason) return { outcome: "auto_skip_trivial", reason: trivialReason };
284
+ if (policy === "auto") return { outcome: "auto_delegate", reason: `auto policy must actively consider worker delegation for non-trivial ${phase.toLowerCase()} work` };
285
+ return { outcome: "auto_delegate", reason: `${policy} policy expects worker delegation for non-trivial ${phase.toLowerCase()} work` };
286
+ }
287
+
288
+ export function advisorySubagentActionDecision(input: ForcedSubagentActionInput): AdvisorySubagentActionDecision {
289
+ const toolName = input.toolName;
290
+ if (!subagentPolicyNeedsInternalDecision(input.policy)) {
291
+ return { outcome: "allow_mechanical", reason: "sub-agent policy does not require advisory consideration", allowBeforeEvidence: true };
292
+ }
293
+
294
+ if (!toolName) {
295
+ const reason = forcedTrivialTaskReason(input.phase, input.task, input.kind);
296
+ if (reason) return { outcome: "auto_skip_trivial", reason, allowBeforeEvidence: true };
297
+ return { outcome: "auto_delegate", reason: `non-trivial ${input.phase.toLowerCase()} work must actively consider sub-agent delegation`, allowBeforeEvidence: false };
298
+ }
299
+
300
+ if (toolName === "subagent") {
301
+ return { outcome: "auto_delegate", reason: "visible parent sub-agent call satisfies advisory consideration", allowBeforeEvidence: true };
302
+ }
303
+
304
+ if (toolName === "workflow_progress") {
305
+ return { outcome: "allow_mechanical", reason: "workflow_progress only marks workflow bookkeeping", allowBeforeEvidence: true };
306
+ }
307
+
308
+ if (toolName === "standard_todo") {
309
+ return { outcome: "allow_mechanical", reason: "standard_todo only initializes or updates required Standard Mode task tracking", allowBeforeEvidence: true };
310
+ }
311
+
312
+ if (
313
+ toolName === "workflow_execution_result"
314
+ || toolName === "workflow_validation_result"
315
+ || toolName === "workflow_repair_result"
316
+ || toolName === "workflow_review_result"
317
+ || toolName === "mission_milestone_result"
318
+ || toolName === "standard_handoff_result"
319
+ ) {
320
+ return { outcome: "allow_user_explicit_finalization", reason: "typed phase handoff is finalization; advisory sub-agent consideration must happen before substantive work", allowBeforeEvidence: true };
321
+ }
322
+
323
+ if (toolName === "bash") {
324
+ const command = commandWithoutTimeout(input.command);
325
+ if (SAFE_PROBE_COMMAND_RE.test(command)) {
326
+ return { outcome: "allow_probe", reason: "safe shell probe can run before advisory worker evidence", allowBeforeEvidence: true };
327
+ }
328
+ const task = input.task?.toLowerCase() ?? "";
329
+ if (FINALIZATION_WORK_RE.test(task) && EXACT_USER_FINALIZATION_COMMAND_RE.test(command)) {
330
+ return { outcome: "allow_user_explicit_finalization", reason: "exact user-requested finalization command is not useful worker fanout", allowBeforeEvidence: true };
331
+ }
332
+ }
333
+
334
+ const trivialReason = forcedTrivialTaskReason(input.phase, input.task, input.kind);
335
+ if (trivialReason) return { outcome: "auto_skip_trivial", reason: trivialReason, allowBeforeEvidence: true };
336
+ return { outcome: "auto_delegate", reason: `non-trivial ${input.phase.toLowerCase()} ${toolName} use must actively consider sub-agent delegation`, allowBeforeEvidence: false };
337
+ }
338
+
109
339
  export function activeWorkerTargetLabel(policy: SubagentPolicyValue | undefined, workers: { deep: number; maximum: number }): string {
110
340
  const effectivePolicy = policy ?? "auto";
111
341
  if (effectivePolicy === "off") return "off (sub-agents disabled for this phase)";
112
- if (effectivePolicy === "auto") return "strongly encouraged (model must give a skip reason if no worker is useful)";
342
+ if (effectivePolicy === "auto") return `actively considered; prefer up to ${Math.max(1, workers.deep)} target worker${Math.max(1, workers.deep) === 1 ? "" : "s"} for non-trivial work`;
113
343
  if (effectivePolicy === "deep") return `${workers.deep} target worker${workers.deep === 1 ? "" : "s"} (deep policy; expected for non-trivial work)`;
114
344
  if (effectivePolicy === "maximum") return `${workers.maximum} target worker${workers.maximum === 1 ? "" : "s"} (maximum policy; skip only if trivial/unavailable)`;
115
345
  return `${Math.max(1, workers.maximum)} required worker${Math.max(1, workers.maximum) === 1 ? "" : "s"} (forced policy; hard requirement)`;
@@ -136,14 +366,14 @@ export function repairPolicySource(settings: WorkflowSettings): "configured/defa
136
366
  }
137
367
 
138
368
  export function forcedSubagentUnavailableReason(settings: WorkflowSettings, phase: SubagentPhase, cwd: string, policy = phasePolicy(settings, phase), workers = workerCount(settings, phase)): string | undefined {
139
- if (policy !== "forced") return undefined;
369
+ if (!subagentPolicyRequiresRequiredEvidence(policy)) return undefined;
140
370
  if (settings.subagents.enabled === false) return "subagents.enabled=false";
141
371
  if (!phaseAutoUseAllowed(settings, phase)) return `subagents.autoUseDuring${phase}=false`;
142
372
  if (!phaseParallelAllowed(settings, phase)) return `subagents.allowParallel${phase}=false`;
143
373
  if (phase !== "Execution" && settings.subagents.allowParallelReadOnly === false) return "subagents.allowParallelReadOnly=false";
144
374
  const subagentInstalled = existsSync(USER_SUBAGENT_EXTENSION_FILE) || existsSync(PACKAGE_SUBAGENT_EXTENSION_FILE);
145
375
  if (!subagentInstalled) return `the subagent extension is not installed at ${USER_SUBAGENT_EXTENSION_FILE} or bundled at ${PACKAGE_SUBAGENT_EXTENSION_FILE}`;
146
- const target = workerTargetForPolicy("forced", workers);
376
+ const target = workerTargetForPolicy(policy, workers);
147
377
  if (target > 8) return `required worker target ${target} exceeds subagent maximum of 8`;
148
378
  return undefined;
149
379
  }
@@ -164,6 +394,9 @@ export function hasRequiredSubagentPreflight(preflightBlock?: string): boolean {
164
394
 
165
395
  export function requiredSubagentPreflightSection(preflightBlock?: string): string {
166
396
  if (!preflightBlock?.trim()) return "";
397
+ if (/^Policy decision:\s*exempt_trivial\b/im.test(preflightBlock)) {
398
+ return `\n\n## Required Sub-Agent Policy Decision\n${preflightBlock.trim()}\n\nWorkflow Suite internally classified this forced-policy request as deterministic trivial/read-only or no-op work. Do not call the visible subagent tool solely for policy compliance, and do not print policy deliberation to the user.`;
399
+ }
167
400
  return `\n\n## Required Sub-Agent Preflight\n${preflightBlock.trim()}\n\nThe workflow already ran the required forced-policy sub-agents for this phase. Use these findings as input. Do not rerun required workers just to satisfy policy; call more sub-agents only if additional targeted work is genuinely useful.`;
168
401
  }
169
402
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mediadatafusion/pi-workflow-suite",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "Multi-agent workflow suite for Pi with Idle, Standard, Plan, Mission, approval gates, reviewer/validator roles, sub-agents, model routing, web search/fetch, browser checks, diagrams, compaction, presets, settings, themes, widgets, and Repo Lock.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -68,6 +68,7 @@ warn_unexpected_loadable_extensions() {
68
68
  extensions/subagent/index.ts
69
69
  extensions/workflow-model-router.ts
70
70
  extensions/workflow-modes.ts
71
+ extensions/workflow-shortcuts.ts
71
72
  extensions/workflow-parsers.ts
72
73
  extensions/workflow-settings-capabilities.ts
73
74
  extensions/workflow-state.ts
@@ -96,6 +97,7 @@ require_file "extensions/workflow-state.ts"
96
97
  require_file "extensions/workflow-summary.ts"
97
98
  require_file "extensions/workflow-tool-guard.ts"
98
99
  require_file "extensions/workflow-model-router.ts"
100
+ require_file "extensions/workflow-shortcuts.ts"
99
101
  require_file "extensions/subagent/index.ts"
100
102
  require_file "extensions/subagent/agents.ts"
101
103
  require_file "package.json"