@pi-unipi/workflow 2.3.0 → 2.4.1

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.
package/commands.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
9
  import { readFileSync, readdirSync, existsSync, statSync } from "fs";
10
10
  import { join, basename } from "path";
11
- import { UNIPI_PREFIX, WORKFLOW_COMMANDS, getToolsForCommand, getSandboxLevel, type SandboxLevel } from "@pi-unipi/core";
11
+ import { UNIPI_PREFIX, WORKFLOW_COMMANDS, getToolsForCommand, getSandboxLevel, type SandboxLevel, type UnipiWorkflowEvent } from "@pi-unipi/core";
12
12
 
13
13
  type CompletionItem = { value: string; label: string; description: string };
14
14
 
@@ -22,6 +22,8 @@ export interface WorkflowCommandOptions {
22
22
  setActiveTools: (tools: string[], level: SandboxLevel) => void;
23
23
  /** Save tools for later restore */
24
24
  saveTools: (tools: string[]) => void;
25
+ /** Begin one workflow lifecycle; false means another workflow is active. */
26
+ startWorkflow: (event: UnipiWorkflowEvent) => boolean;
25
27
  }
26
28
 
27
29
  /** Command definition */
@@ -402,6 +404,16 @@ export function registerWorkflowCommands(
402
404
  return null;
403
405
  },
404
406
  handler: async (args, ctx) => {
407
+ const workflowEvent: UnipiWorkflowEvent = {
408
+ command: cmd.name,
409
+ fullCommand: `/${fullCommand}`,
410
+ args: args?.trim() ?? "",
411
+ };
412
+ if (!options.startWorkflow(workflowEvent)) {
413
+ if (ctx.hasUI) ctx.ui.notify("Another UniPi workflow is still active", "warning");
414
+ return;
415
+ }
416
+
405
417
  // Apply sandbox — save current tools, set command's tools
406
418
  const currentTools = options.getActiveTools();
407
419
  options.saveTools(currentTools);
package/index.ts CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  getBlockedToolsForLevel,
22
22
  } from "@pi-unipi/core";
23
23
  import { registerWorkflowCommands } from "./commands.js";
24
+ import { WorkflowLifecycle } from "./lifecycle.js";
24
25
 
25
26
  /** Package version (read from package.json at load time) */
26
27
  const VERSION = getPackageVersion(dirname(fileURLToPath(import.meta.url)));
@@ -41,6 +42,7 @@ let currentSandboxLevel: SandboxLevel | null = null;
41
42
  let currentSandboxTools: string[] | null = null;
42
43
 
43
44
  export default function (pi: ExtensionAPI) {
45
+ const workflowLifecycle = new WorkflowLifecycle();
44
46
 
45
47
  // Register all workflow commands
46
48
  registerWorkflowCommands(pi, {
@@ -55,6 +57,11 @@ export default function (pi: ExtensionAPI) {
55
57
  saveTools: (tools: string[]) => {
56
58
  savedTools = tools;
57
59
  },
60
+ startWorkflow: (event) => {
61
+ if (!workflowLifecycle.start(event)) return false;
62
+ emitEvent(pi, UNIPI_EVENTS.WORKFLOW_START, event);
63
+ return true;
64
+ },
58
65
  });
59
66
 
60
67
  // Block tool calls that violate sandbox
@@ -109,7 +116,7 @@ export default function (pi: ExtensionAPI) {
109
116
  });
110
117
 
111
118
  // Restore tools when agent finishes
112
- pi.on("agent_end", async (_event, _ctx) => {
119
+ pi.on("agent_end", async (event, _ctx) => {
113
120
  if (sandboxActive && savedTools) {
114
121
  pi.setActiveTools(savedTools);
115
122
  savedTools = null;
@@ -117,6 +124,9 @@ export default function (pi: ExtensionAPI) {
117
124
  currentSandboxLevel = null;
118
125
  currentSandboxTools = null;
119
126
  }
127
+
128
+ const completedWorkflow = workflowLifecycle.complete(event.messages);
129
+ if (completedWorkflow) emitEvent(pi, UNIPI_EVENTS.WORKFLOW_END, completedWorkflow);
120
130
  });
121
131
 
122
132
  // Announce module presence on session start
@@ -163,5 +173,6 @@ export default function (pi: ExtensionAPI) {
163
173
  ralphDetected = false;
164
174
  savedTools = null;
165
175
  sandboxActive = false;
176
+ workflowLifecycle.reset();
166
177
  });
167
178
  }
package/lifecycle.ts ADDED
@@ -0,0 +1,43 @@
1
+ import type { UnipiWorkflowEvent } from "@pi-unipi/core";
2
+
3
+ interface WorkflowMessage {
4
+ role: string;
5
+ stopReason?: string;
6
+ }
7
+
8
+ export interface CompletedWorkflowEvent extends UnipiWorkflowEvent {
9
+ success: boolean;
10
+ durationMs: number;
11
+ }
12
+
13
+ /** Single-active workflow lifecycle state shared by slash handlers and agent_end. */
14
+ export class WorkflowLifecycle {
15
+ private active: (UnipiWorkflowEvent & { startedAt: number }) | null = null;
16
+
17
+ constructor(private readonly now: () => number = Date.now) {}
18
+
19
+ start(event: UnipiWorkflowEvent): boolean {
20
+ if (this.active) return false;
21
+ this.active = { ...event, startedAt: this.now() };
22
+ return true;
23
+ }
24
+
25
+ complete(messages: WorkflowMessage[]): CompletedWorkflowEvent | undefined {
26
+ if (!this.active) return undefined;
27
+ const workflow = this.active;
28
+ this.active = null;
29
+ const finalAssistant = [...messages].reverse().find((message) => message.role === "assistant");
30
+ const success = finalAssistant?.stopReason !== "error" && finalAssistant?.stopReason !== "aborted";
31
+ return {
32
+ command: workflow.command,
33
+ fullCommand: workflow.fullCommand,
34
+ args: workflow.args,
35
+ success,
36
+ durationMs: this.now() - workflow.startedAt,
37
+ };
38
+ }
39
+
40
+ reset(): void {
41
+ this.active = null;
42
+ }
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/workflow",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "description": "Structured development workflow commands for Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -19,6 +19,7 @@
19
19
  ],
20
20
  "files": [
21
21
  "*.ts",
22
+ "!*.test.ts",
22
23
  "skills/**/*",
23
24
  "README.md"
24
25
  ],
@@ -26,7 +27,7 @@
26
27
  "access": "public"
27
28
  },
28
29
  "dependencies": {
29
- "@pi-unipi/core": "2.3.0"
30
+ "@pi-unipi/core": "2.4.1"
30
31
  },
31
32
  "peerDependencies": {
32
33
  "@earendil-works/pi-ai": "^0.80.0",