@quintinshaw/pi-dynamic-workflows 1.9.2 → 1.9.3

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.
@@ -70,5 +70,7 @@ export declare function buildForcedWorkflowPrompt(text: string): string;
70
70
  * Install the workflows-mode editor and the submit-time forcing hook.
71
71
  * Call once with the UI context (e.g. in `session_start`).
72
72
  */
73
+ /** The exact name of the workflow tool that workflows mode forces. */
74
+ export declare const WORKFLOW_TOOL_NAME = "workflow";
73
75
  export declare function installWorkflowEditor(pi: ExtensionAPI, ui: ExtensionUIContext): WorkflowModeState;
74
76
  export {};
@@ -205,24 +205,61 @@ export function buildForcedWorkflowPrompt(text) {
205
205
  return [
206
206
  text,
207
207
  "",
208
- "[workflows mode] The user armed workflows mode for this message. Handle it by",
209
- "running the `workflow` tool: decompose the request and orchestrate subagents",
210
- "with a workflow script, rather than answering directly.",
208
+ "---",
209
+ "[workflows mode is ON for this message]",
210
+ "You MUST handle this request by calling the tool named exactly `workflow` (Pi's",
211
+ "deterministic JavaScript workflow-orchestration tool from pi-dynamic-workflows).",
212
+ "Write a workflow script that fans the task out across subagents via",
213
+ "agent()/parallel()/pipeline().",
214
+ "",
215
+ "The ONLY acceptable action is a `workflow` tool call. Do NOT instead:",
216
+ "- answer directly or in prose,",
217
+ "- call the `subagent` tool yourself,",
218
+ "- use any skill or command (e.g. pi-subagents, /code-review, deep-research),",
219
+ '- or interpret the word "workflow/workflows" loosely as some other parallel/audit approach.',
220
+ "Even for a small task, wrap it in a minimal `workflow` call with at least one agent().",
211
221
  ].join("\n");
212
222
  }
213
223
  /**
214
224
  * Install the workflows-mode editor and the submit-time forcing hook.
215
225
  * Call once with the UI context (e.g. in `session_start`).
216
226
  */
227
+ /** The exact name of the workflow tool that workflows mode forces. */
228
+ export const WORKFLOW_TOOL_NAME = "workflow";
217
229
  export function installWorkflowEditor(pi, ui) {
218
230
  const state = { active: false };
219
231
  ui.setEditorComponent((tui, theme, keybindings) => new WorkflowEditor(tui, theme, keybindings, state));
220
- // When armed at submit time, rewrite the user's message to force a workflow.
232
+ // Active tools saved while a turn is restricted to `workflow`; restored on turn_end.
233
+ let savedTools;
234
+ // When armed at submit time, rewrite the user's message to force a workflow AND
235
+ // restrict this turn's tools to just `workflow`, so the model can't fall back to
236
+ // the subagent tool, a skill, or a direct answer. Restored at turn_end.
221
237
  pi.on("input", (event) => {
222
238
  if (event.source !== "interactive" || !state.active || !event.text)
223
239
  return { action: "continue" };
224
240
  state.active = false; // consume the arm for this submission
241
+ try {
242
+ if (savedTools === undefined)
243
+ savedTools = pi.getActiveTools?.();
244
+ pi.setActiveTools?.([WORKFLOW_TOOL_NAME]);
245
+ }
246
+ catch {
247
+ // Tool restriction is best-effort; the directive still forces the workflow.
248
+ }
225
249
  return { action: "transform", text: buildForcedWorkflowPrompt(event.text) };
226
250
  });
251
+ // Restore the user's full tool set once the forced turn completes.
252
+ pi.on("turn_end", () => {
253
+ if (savedTools === undefined)
254
+ return;
255
+ const restore = savedTools;
256
+ savedTools = undefined;
257
+ try {
258
+ pi.setActiveTools?.(restore);
259
+ }
260
+ catch {
261
+ // ignore — nothing we can do if the host rejects the restore
262
+ }
263
+ });
227
264
  return state;
228
265
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quintinshaw/pi-dynamic-workflows",
3
- "version": "1.9.2",
3
+ "version": "1.9.3",
4
4
  "description": "Claude-Code-style dynamic workflows for Pi — fan a task out across 100s of subagents with real model routing, token/cost accounting, resume, git-worktree isolation, an interactive /workflows TUI, and a real /deep-research.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -226,9 +226,19 @@ export function buildForcedWorkflowPrompt(text: string): string {
226
226
  return [
227
227
  text,
228
228
  "",
229
- "[workflows mode] The user armed workflows mode for this message. Handle it by",
230
- "running the `workflow` tool: decompose the request and orchestrate subagents",
231
- "with a workflow script, rather than answering directly.",
229
+ "---",
230
+ "[workflows mode is ON for this message]",
231
+ "You MUST handle this request by calling the tool named exactly `workflow` (Pi's",
232
+ "deterministic JavaScript workflow-orchestration tool from pi-dynamic-workflows).",
233
+ "Write a workflow script that fans the task out across subagents via",
234
+ "agent()/parallel()/pipeline().",
235
+ "",
236
+ "The ONLY acceptable action is a `workflow` tool call. Do NOT instead:",
237
+ "- answer directly or in prose,",
238
+ "- call the `subagent` tool yourself,",
239
+ "- use any skill or command (e.g. pi-subagents, /code-review, deep-research),",
240
+ '- or interpret the word "workflow/workflows" loosely as some other parallel/audit approach.',
241
+ "Even for a small task, wrap it in a minimal `workflow` call with at least one agent().",
232
242
  ].join("\n");
233
243
  }
234
244
 
@@ -236,17 +246,43 @@ export function buildForcedWorkflowPrompt(text: string): string {
236
246
  * Install the workflows-mode editor and the submit-time forcing hook.
237
247
  * Call once with the UI context (e.g. in `session_start`).
238
248
  */
249
+ /** The exact name of the workflow tool that workflows mode forces. */
250
+ export const WORKFLOW_TOOL_NAME = "workflow";
251
+
239
252
  export function installWorkflowEditor(pi: ExtensionAPI, ui: ExtensionUIContext): WorkflowModeState {
240
253
  const state: WorkflowModeState = { active: false };
241
254
 
242
255
  ui.setEditorComponent((tui, theme, keybindings) => new WorkflowEditor(tui, theme, keybindings, state));
243
256
 
244
- // When armed at submit time, rewrite the user's message to force a workflow.
257
+ // Active tools saved while a turn is restricted to `workflow`; restored on turn_end.
258
+ let savedTools: string[] | undefined;
259
+
260
+ // When armed at submit time, rewrite the user's message to force a workflow AND
261
+ // restrict this turn's tools to just `workflow`, so the model can't fall back to
262
+ // the subagent tool, a skill, or a direct answer. Restored at turn_end.
245
263
  pi.on("input", (event: { source?: string; text?: string }) => {
246
264
  if (event.source !== "interactive" || !state.active || !event.text) return { action: "continue" } as const;
247
265
  state.active = false; // consume the arm for this submission
266
+ try {
267
+ if (savedTools === undefined) savedTools = pi.getActiveTools?.();
268
+ pi.setActiveTools?.([WORKFLOW_TOOL_NAME]);
269
+ } catch {
270
+ // Tool restriction is best-effort; the directive still forces the workflow.
271
+ }
248
272
  return { action: "transform", text: buildForcedWorkflowPrompt(event.text) } as const;
249
273
  });
250
274
 
275
+ // Restore the user's full tool set once the forced turn completes.
276
+ pi.on("turn_end", () => {
277
+ if (savedTools === undefined) return;
278
+ const restore = savedTools;
279
+ savedTools = undefined;
280
+ try {
281
+ pi.setActiveTools?.(restore);
282
+ } catch {
283
+ // ignore — nothing we can do if the host rejects the restore
284
+ }
285
+ });
286
+
251
287
  return state;
252
288
  }