@pi-unipi/workflow 2.4.1 → 2.6.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.
- package/README.md +8 -0
- package/commands.ts +16 -26
- package/index.ts +165 -85
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -128,6 +128,14 @@ Skills define:
|
|
|
128
128
|
|
|
129
129
|
The agent reads the skill, executes the steps, and produces artifacts in the `.unipi/` directory.
|
|
130
130
|
|
|
131
|
+
## Workflow Sandbox
|
|
132
|
+
|
|
133
|
+
Each workflow activates a command-specific sandbox for the duration of its agent run. The sandbox blocks disallowed tool calls by name (for example, `write`, `edit`, or `bash` in read-only workflows) without changing Pi's active tool list. Keeping the provider tool schemas and their order unchanged makes the workflow prefix cache-stable.
|
|
134
|
+
|
|
135
|
+
Sandbox instructions are stored as hidden, append-only `unipi-workflow-sandbox-snapshot` messages rather than being injected into the system prompt. An active snapshot explicitly supersedes older snapshots. After the workflow completes at `agent_end`, the next agent start appends an inactive snapshot when needed so stale restrictions no longer apply. Sessions that have never activated a workflow do not receive a marker.
|
|
136
|
+
|
|
137
|
+
The sandbox preserves the existing command-level semantics; skill instructions still define narrower write locations and setup-only shell usage where applicable.
|
|
138
|
+
|
|
131
139
|
## Directory Structure
|
|
132
140
|
|
|
133
141
|
```
|
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,
|
|
11
|
+
import { UNIPI_PREFIX, WORKFLOW_COMMANDS, type UnipiWorkflowEvent } from "@pi-unipi/core";
|
|
12
12
|
|
|
13
13
|
type CompletionItem = { value: string; label: string; description: string };
|
|
14
14
|
|
|
@@ -16,14 +16,10 @@ type CompletionItem = { value: string; label: string; description: string };
|
|
|
16
16
|
export interface WorkflowCommandOptions {
|
|
17
17
|
/** Check if ralph module is detected */
|
|
18
18
|
isRalphDetected: () => boolean;
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
/** Save tools for later restore */
|
|
24
|
-
saveTools: (tools: string[]) => void;
|
|
25
|
-
/** Begin one workflow lifecycle; false means another workflow is active. */
|
|
26
|
-
startWorkflow: (event: UnipiWorkflowEvent) => boolean;
|
|
19
|
+
/** Begin one workflow lifecycle and activate its sandbox. */
|
|
20
|
+
activateSandbox: (event: UnipiWorkflowEvent) => boolean;
|
|
21
|
+
/** Roll back lifecycle and sandbox state after dispatch failure. */
|
|
22
|
+
abortWorkflow: () => void;
|
|
27
23
|
}
|
|
28
24
|
|
|
29
25
|
/** Command definition */
|
|
@@ -409,19 +405,12 @@ export function registerWorkflowCommands(
|
|
|
409
405
|
fullCommand: `/${fullCommand}`,
|
|
410
406
|
args: args?.trim() ?? "",
|
|
411
407
|
};
|
|
412
|
-
if (!options.
|
|
408
|
+
if (!options.activateSandbox(workflowEvent)) {
|
|
413
409
|
if (ctx.hasUI) ctx.ui.notify("Another UniPi workflow is still active", "warning");
|
|
414
410
|
return;
|
|
415
411
|
}
|
|
416
412
|
|
|
417
|
-
//
|
|
418
|
-
const currentTools = options.getActiveTools();
|
|
419
|
-
options.saveTools(currentTools);
|
|
420
|
-
const sandboxTools = getToolsForCommand(cmd.name, currentTools);
|
|
421
|
-
const sandboxLevel = getSandboxLevel(cmd.name);
|
|
422
|
-
options.setActiveTools([...sandboxTools], sandboxLevel);
|
|
423
|
-
|
|
424
|
-
// Load skill content from SKILL.md
|
|
413
|
+
// Load skill content from SKILL.md.
|
|
425
414
|
let skillContent = "";
|
|
426
415
|
try {
|
|
427
416
|
const skillPath = join(
|
|
@@ -431,33 +420,34 @@ export function registerWorkflowCommands(
|
|
|
431
420
|
);
|
|
432
421
|
skillContent = readFileSync(skillPath, "utf-8");
|
|
433
422
|
} catch {
|
|
434
|
-
// Skill file not found — continue without it
|
|
423
|
+
// Skill file not found — continue without it.
|
|
435
424
|
}
|
|
436
425
|
|
|
437
|
-
// Build skill invocation message
|
|
438
426
|
let message = `Execute the ${cmd.skillName} workflow.`;
|
|
439
427
|
|
|
440
|
-
// Add args if provided
|
|
441
428
|
if (args?.trim()) {
|
|
442
429
|
message += `\n\nArguments: ${args.trim()}`;
|
|
443
430
|
}
|
|
444
431
|
|
|
445
|
-
// Add ralph hint if applicable
|
|
446
432
|
if (cmd.ralphHint && options.isRalphDetected()) {
|
|
447
433
|
message += `\n\n💡 ${cmd.ralphHint}`;
|
|
448
434
|
}
|
|
449
435
|
|
|
450
|
-
// Inject skill content as context
|
|
451
436
|
if (skillContent) {
|
|
452
437
|
message += `\n\n<skill_content>\n${skillContent}\n</skill_content>`;
|
|
453
438
|
}
|
|
454
439
|
|
|
455
|
-
//
|
|
456
|
-
|
|
440
|
+
// Any synchronous dispatch failure must not leave a phantom workflow
|
|
441
|
+
// lifecycle or sandbox behind.
|
|
442
|
+
try {
|
|
443
|
+
pi.sendUserMessage(message, { deliverAs: "followUp" });
|
|
444
|
+
} catch (error) {
|
|
445
|
+
options.abortWorkflow();
|
|
446
|
+
throw error;
|
|
447
|
+
}
|
|
457
448
|
|
|
458
449
|
if (ctx.hasUI) {
|
|
459
450
|
ctx.ui.notify(`Running /${fullCommand}`, "info");
|
|
460
|
-
// Update extension status with active command name
|
|
461
451
|
const ralphStatus = options.isRalphDetected() ? "✓ rl" : "○ rl";
|
|
462
452
|
ctx.ui.setStatus("unipi-workflow", `⚡ wf:${cmd.name} ${ralphStatus}`);
|
|
463
453
|
}
|
package/index.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @unipi/workflow — Structured development workflow commands
|
|
3
3
|
*
|
|
4
|
-
* Registers
|
|
5
|
-
* Emits MODULE_READY
|
|
6
|
-
*
|
|
7
|
-
* Applies sandbox (tool filtering) per command.
|
|
4
|
+
* Registers workflow commands that dispatch to skills for LLM instruction.
|
|
5
|
+
* Emits MODULE_READY for inter-module discovery and detects @unipi/ralph.
|
|
6
|
+
* Enforces workflow sandboxes without changing Pi's active tool schemas.
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
import { dirname } from "node:path";
|
|
11
10
|
import { fileURLToPath } from "node:url";
|
|
12
|
-
import
|
|
11
|
+
import {
|
|
12
|
+
buildSessionContext,
|
|
13
|
+
type ExtensionAPI,
|
|
14
|
+
type SessionEntry,
|
|
15
|
+
} from "@earendil-works/pi-coding-agent";
|
|
13
16
|
import {
|
|
14
17
|
UNIPI_EVENTS,
|
|
15
18
|
MODULES,
|
|
@@ -17,8 +20,10 @@ import {
|
|
|
17
20
|
emitEvent,
|
|
18
21
|
getPackageVersion,
|
|
19
22
|
initUnipiDirs,
|
|
20
|
-
type SandboxLevel,
|
|
21
23
|
getBlockedToolsForLevel,
|
|
24
|
+
getSandboxLevel,
|
|
25
|
+
isToolAllowed,
|
|
26
|
+
type SandboxLevel,
|
|
22
27
|
} from "@pi-unipi/core";
|
|
23
28
|
import { registerWorkflowCommands } from "./commands.js";
|
|
24
29
|
import { WorkflowLifecycle } from "./lifecycle.js";
|
|
@@ -26,115 +31,198 @@ import { WorkflowLifecycle } from "./lifecycle.js";
|
|
|
26
31
|
/** Package version (read from package.json at load time) */
|
|
27
32
|
const VERSION = getPackageVersion(dirname(fileURLToPath(import.meta.url)));
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
export const WORKFLOW_SANDBOX_SNAPSHOT_TYPE = "unipi-workflow-sandbox-snapshot";
|
|
35
|
+
|
|
36
|
+
interface WorkflowSandboxSnapshotDetails {
|
|
37
|
+
active: boolean;
|
|
38
|
+
command?: string;
|
|
39
|
+
level?: SandboxLevel;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface EffectiveSandboxSnapshot {
|
|
43
|
+
content: unknown;
|
|
44
|
+
details?: WorkflowSandboxSnapshotDetails;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function sandboxRestrictions(level: SandboxLevel): string[] {
|
|
48
|
+
const common = [
|
|
49
|
+
"Do not attempt to call tools blocked by name.",
|
|
50
|
+
"If the user requests an action that requires a blocked tool, explain that the workflow sandbox does not allow it.",
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
if (level === "brainstorm") {
|
|
54
|
+
return [
|
|
55
|
+
"The write tool is restricted to .unipi/docs/specs/ only.",
|
|
56
|
+
"Use bash only for specific setup operations such as git init or mkdir; use grep, find, and ls for discovery instead of bash.",
|
|
57
|
+
...common,
|
|
58
|
+
];
|
|
59
|
+
}
|
|
31
60
|
|
|
32
|
-
|
|
33
|
-
|
|
61
|
+
if (level === "write_unipi") {
|
|
62
|
+
return [
|
|
63
|
+
"The write tool is restricted to .unipi/docs/ only (specs and plans).",
|
|
64
|
+
"Use grep, find, and ls for file discovery instead of guessing filenames.",
|
|
65
|
+
"Bash is blocked; use read, write, edit, grep, find, and ls only.",
|
|
66
|
+
...common,
|
|
67
|
+
];
|
|
68
|
+
}
|
|
34
69
|
|
|
35
|
-
|
|
36
|
-
|
|
70
|
+
return common;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Build a persistent snapshot that explicitly invalidates earlier sandbox state. */
|
|
74
|
+
export function formatActiveSandboxSnapshot(command: string, level: SandboxLevel): string {
|
|
75
|
+
const blocked = getBlockedToolsForLevel(level);
|
|
76
|
+
const blockedLine = blocked.length > 0
|
|
77
|
+
? blocked.join(", ")
|
|
78
|
+
: "none";
|
|
79
|
+
|
|
80
|
+
return [
|
|
81
|
+
"# UniPi Workflow Sandbox Snapshot",
|
|
82
|
+
"This snapshot supersedes all prior UniPi workflow sandbox snapshots; use only this snapshot for workflow sandbox status and restrictions.",
|
|
83
|
+
"Status: active",
|
|
84
|
+
`Workflow: /unipi:${command}`,
|
|
85
|
+
`Sandbox level: ${level}`,
|
|
86
|
+
`Blocked tool names: ${blockedLine}`,
|
|
87
|
+
"Pi's provider tool schemas and tool order remain unchanged. Calls to blocked tool names are rejected by the workflow sandbox.",
|
|
88
|
+
["Restrictions:", ...sandboxRestrictions(level).map((restriction) => `- ${restriction}`)].join("\n"),
|
|
89
|
+
].join("\n\n");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function formatInactiveSandboxSnapshot(): string {
|
|
93
|
+
return [
|
|
94
|
+
"# UniPi Workflow Sandbox Snapshot",
|
|
95
|
+
"This snapshot supersedes all prior UniPi workflow sandbox snapshots; use only this snapshot for workflow sandbox status and restrictions.",
|
|
96
|
+
"Status: inactive",
|
|
97
|
+
"No UniPi workflow sandbox is active. Prior workflow sandbox restrictions no longer apply.",
|
|
98
|
+
].join("\n\n");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function latestEffectiveSandboxSnapshot(
|
|
102
|
+
branch: SessionEntry[],
|
|
103
|
+
): EffectiveSandboxSnapshot | undefined {
|
|
104
|
+
const messages = buildSessionContext(branch).messages;
|
|
105
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
106
|
+
const message = messages[index];
|
|
107
|
+
if (message.role === "custom" && message.customType === WORKFLOW_SANDBOX_SNAPSHOT_TYPE) {
|
|
108
|
+
return {
|
|
109
|
+
content: message.content,
|
|
110
|
+
details: message.details as WorkflowSandboxSnapshotDetails | undefined,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
37
116
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
117
|
+
/** Find state that may survive only as prose inside a compaction summary. */
|
|
118
|
+
function latestHistoricalSandboxSnapshot(
|
|
119
|
+
branch: SessionEntry[],
|
|
120
|
+
): EffectiveSandboxSnapshot | undefined {
|
|
121
|
+
for (let index = branch.length - 1; index >= 0; index--) {
|
|
122
|
+
const entry = branch[index];
|
|
123
|
+
if (entry.type === "custom_message" && entry.customType === WORKFLOW_SANDBOX_SNAPSHOT_TYPE) {
|
|
124
|
+
return {
|
|
125
|
+
content: entry.content,
|
|
126
|
+
details: entry.details as WorkflowSandboxSnapshotDetails | undefined,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
40
132
|
|
|
41
|
-
|
|
42
|
-
|
|
133
|
+
function isActiveSnapshot(snapshot: EffectiveSandboxSnapshot): boolean {
|
|
134
|
+
if (typeof snapshot.details?.active === "boolean") return snapshot.details.active;
|
|
135
|
+
return typeof snapshot.content === "string" && snapshot.content.includes("Status: active");
|
|
136
|
+
}
|
|
43
137
|
|
|
44
138
|
export default function (pi: ExtensionAPI) {
|
|
45
139
|
const workflowLifecycle = new WorkflowLifecycle();
|
|
140
|
+
let ralphDetected = false;
|
|
141
|
+
let sandboxCommand: string | null = null;
|
|
46
142
|
|
|
47
|
-
// Register all workflow commands
|
|
48
143
|
registerWorkflowCommands(pi, {
|
|
49
144
|
isRalphDetected: () => ralphDetected,
|
|
50
|
-
|
|
51
|
-
setActiveTools: (tools: string[], level: SandboxLevel) => {
|
|
52
|
-
pi.setActiveTools(tools);
|
|
53
|
-
sandboxActive = true;
|
|
54
|
-
currentSandboxLevel = level;
|
|
55
|
-
currentSandboxTools = tools;
|
|
56
|
-
},
|
|
57
|
-
saveTools: (tools: string[]) => {
|
|
58
|
-
savedTools = tools;
|
|
59
|
-
},
|
|
60
|
-
startWorkflow: (event) => {
|
|
145
|
+
activateSandbox: (event) => {
|
|
61
146
|
if (!workflowLifecycle.start(event)) return false;
|
|
147
|
+
sandboxCommand = event.command;
|
|
62
148
|
emitEvent(pi, UNIPI_EVENTS.WORKFLOW_START, event);
|
|
63
149
|
return true;
|
|
64
150
|
},
|
|
151
|
+
abortWorkflow: () => {
|
|
152
|
+
sandboxCommand = null;
|
|
153
|
+
workflowLifecycle.reset();
|
|
154
|
+
},
|
|
65
155
|
});
|
|
66
156
|
|
|
67
|
-
//
|
|
157
|
+
// Keep tool schemas/order stable and enforce only the existing blocked names.
|
|
68
158
|
pi.on("tool_call", async (event, _ctx) => {
|
|
69
|
-
if (!
|
|
159
|
+
if (!sandboxCommand) return;
|
|
70
160
|
|
|
71
|
-
const
|
|
72
|
-
if (!
|
|
161
|
+
const level = getSandboxLevel(sandboxCommand);
|
|
162
|
+
if (!isToolAllowed(level, event.toolName)) {
|
|
163
|
+
const blocked = getBlockedToolsForLevel(level);
|
|
73
164
|
return {
|
|
74
165
|
block: true,
|
|
75
|
-
reason: `Tool "${event.toolName}" is not allowed in ${
|
|
166
|
+
reason: `Tool "${event.toolName}" is not allowed in ${level} sandbox. Blocked: ${blocked.join(", ")}`,
|
|
76
167
|
};
|
|
77
168
|
}
|
|
78
169
|
});
|
|
79
170
|
|
|
80
|
-
//
|
|
81
|
-
pi.on("before_agent_start", async (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const blocked = getBlockedToolsForLevel(currentSandboxLevel);
|
|
171
|
+
// Persist hidden append-only sandbox state without mutating the system prompt.
|
|
172
|
+
pi.on("before_agent_start", async (_event, ctx) => {
|
|
173
|
+
const branch = ctx.sessionManager.getBranch();
|
|
174
|
+
const latest = latestEffectiveSandboxSnapshot(branch);
|
|
175
|
+
const historical = latestHistoricalSandboxSnapshot(branch);
|
|
86
176
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
177
|
+
if (sandboxCommand) {
|
|
178
|
+
const level = getSandboxLevel(sandboxCommand);
|
|
179
|
+
const content = formatActiveSandboxSnapshot(sandboxCommand, level);
|
|
180
|
+
if (latest?.content === content) return undefined;
|
|
91
181
|
|
|
92
|
-
if (currentSandboxLevel === "brainstorm") {
|
|
93
182
|
return {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
183
|
+
message: {
|
|
184
|
+
customType: WORKFLOW_SANDBOX_SNAPSHOT_TYPE,
|
|
185
|
+
content,
|
|
186
|
+
display: false,
|
|
187
|
+
details: {
|
|
188
|
+
active: true,
|
|
189
|
+
command: sandboxCommand,
|
|
190
|
+
level,
|
|
191
|
+
} satisfies WorkflowSandboxSnapshotDetails,
|
|
192
|
+
},
|
|
98
193
|
};
|
|
99
194
|
}
|
|
100
195
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
base +
|
|
106
|
-
`\nWrite tool is restricted to .unipi/docs/ only (specs and plans).\nUse grep, find, ls for file discovery — do NOT guess filenames.\nbash is blocked — use read, write, edit, grep, find, ls only.\nDo NOT attempt to call blocked tools. Do NOT output tool call XML for them.\nIf the user requests an action that requires a blocked tool, respond that you do not have access.\n</sandbox>`,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
196
|
+
// A clean session gets no marker. Only an effective active snapshot needs
|
|
197
|
+
// an append-only inactive successor after agent_end completes the workflow.
|
|
198
|
+
const prior = latest ?? historical;
|
|
199
|
+
if (!prior || !isActiveSnapshot(prior)) return undefined;
|
|
109
200
|
|
|
110
201
|
return {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
202
|
+
message: {
|
|
203
|
+
customType: WORKFLOW_SANDBOX_SNAPSHOT_TYPE,
|
|
204
|
+
content: formatInactiveSandboxSnapshot(),
|
|
205
|
+
display: false,
|
|
206
|
+
details: {
|
|
207
|
+
active: false,
|
|
208
|
+
} satisfies WorkflowSandboxSnapshotDetails,
|
|
209
|
+
},
|
|
115
210
|
};
|
|
116
211
|
});
|
|
117
212
|
|
|
118
|
-
//
|
|
213
|
+
// Pi 0.80.2 compatibility: agent_end remains the workflow completion boundary.
|
|
119
214
|
pi.on("agent_end", async (event, _ctx) => {
|
|
120
|
-
if (sandboxActive && savedTools) {
|
|
121
|
-
pi.setActiveTools(savedTools);
|
|
122
|
-
savedTools = null;
|
|
123
|
-
sandboxActive = false;
|
|
124
|
-
currentSandboxLevel = null;
|
|
125
|
-
currentSandboxTools = null;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
215
|
const completedWorkflow = workflowLifecycle.complete(event.messages);
|
|
129
|
-
if (completedWorkflow)
|
|
216
|
+
if (!completedWorkflow) return;
|
|
217
|
+
|
|
218
|
+
sandboxCommand = null;
|
|
219
|
+
emitEvent(pi, UNIPI_EVENTS.WORKFLOW_END, completedWorkflow);
|
|
130
220
|
});
|
|
131
221
|
|
|
132
|
-
// Announce module presence on session start
|
|
222
|
+
// Announce module presence on session start.
|
|
133
223
|
pi.on("session_start", async (_event, ctx) => {
|
|
134
|
-
// Initialize .unipi directory structure
|
|
135
224
|
initUnipiDirs();
|
|
136
225
|
|
|
137
|
-
// Emit MODULE_READY
|
|
138
226
|
emitEvent(pi, UNIPI_EVENTS.MODULE_READY, {
|
|
139
227
|
name: MODULES.WORKFLOW,
|
|
140
228
|
version: VERSION,
|
|
@@ -142,37 +230,29 @@ export default function (pi: ExtensionAPI) {
|
|
|
142
230
|
tools: [],
|
|
143
231
|
});
|
|
144
232
|
|
|
145
|
-
// Listen for ralph module
|
|
146
233
|
if (!ralphDetected) {
|
|
147
234
|
try {
|
|
148
|
-
// Check if ralph tools exist (indicates @unipi/ralph is loaded)
|
|
149
235
|
const allTools = pi.getAllTools();
|
|
150
|
-
ralphDetected = allTools.some((
|
|
236
|
+
ralphDetected = allTools.some((tool) => tool.name === "ralph_start");
|
|
151
237
|
} catch {
|
|
152
|
-
// Ignore — ralph not present
|
|
238
|
+
// Ignore — ralph not present.
|
|
153
239
|
}
|
|
154
240
|
}
|
|
155
241
|
|
|
156
|
-
// Show workflow status in UI
|
|
157
242
|
if (ctx.hasUI) {
|
|
158
243
|
const ralphStatus = ralphDetected ? "✓ rl" : "○ rl";
|
|
159
244
|
ctx.ui.setStatus("unipi-workflow", `⚡ wf ${ralphStatus}`);
|
|
160
245
|
}
|
|
161
246
|
});
|
|
162
247
|
|
|
163
|
-
// Listen for ralph module ready event
|
|
164
248
|
pi.events.on(UNIPI_EVENTS.MODULE_READY, (data) => {
|
|
165
249
|
const event = data as { name?: string };
|
|
166
|
-
if (event?.name === MODULES.RALPH)
|
|
167
|
-
ralphDetected = true;
|
|
168
|
-
}
|
|
250
|
+
if (event?.name === MODULES.RALPH) ralphDetected = true;
|
|
169
251
|
});
|
|
170
252
|
|
|
171
|
-
// Clean up on shutdown
|
|
172
253
|
pi.on("session_shutdown", async () => {
|
|
173
254
|
ralphDetected = false;
|
|
174
|
-
|
|
175
|
-
sandboxActive = false;
|
|
255
|
+
sandboxCommand = null;
|
|
176
256
|
workflowLifecycle.reset();
|
|
177
257
|
});
|
|
178
258
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/workflow",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Structured development workflow commands for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"author": "Neuron Mr White",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "npx bun test lifecycle.test.ts tests"
|
|
11
|
+
},
|
|
9
12
|
"repository": {
|
|
10
13
|
"type": "git",
|
|
11
14
|
"url": "git+https://github.com/Neuron-Mr-White/unipi.git",
|
|
@@ -27,7 +30,7 @@
|
|
|
27
30
|
"access": "public"
|
|
28
31
|
},
|
|
29
32
|
"dependencies": {
|
|
30
|
-
"@pi-unipi/core": "2.
|
|
33
|
+
"@pi-unipi/core": "2.6.0"
|
|
31
34
|
},
|
|
32
35
|
"peerDependencies": {
|
|
33
36
|
"@earendil-works/pi-ai": "^0.80.0",
|