pi-crew 0.7.4 → 0.7.6

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 (56) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +11 -11
  3. package/docs/commands-reference.md +14 -10
  4. package/docs/troubleshooting.md +131 -0
  5. package/docs/usage.md +9 -4
  6. package/package.json +1 -1
  7. package/src/config/config.ts +11 -4
  8. package/src/config/types.ts +2 -0
  9. package/src/errors.ts +66 -0
  10. package/src/extension/action-suggestions.ts +71 -0
  11. package/src/extension/context-status-injection.ts +174 -0
  12. package/src/extension/knowledge-injection.ts +29 -1
  13. package/src/extension/register.ts +81 -65
  14. package/src/extension/team-tool/api.ts +3 -2
  15. package/src/extension/team-tool/cancel.ts +5 -4
  16. package/src/extension/team-tool/explain.ts +2 -1
  17. package/src/extension/team-tool/failure-patterns.ts +124 -0
  18. package/src/extension/team-tool/inspect.ts +10 -6
  19. package/src/extension/team-tool/lifecycle-actions.ts +5 -4
  20. package/src/extension/team-tool/respond.ts +4 -3
  21. package/src/extension/team-tool/run-not-found.ts +54 -0
  22. package/src/extension/team-tool/run.ts +26 -4
  23. package/src/extension/team-tool/status.ts +58 -4
  24. package/src/extension/team-tool.ts +5 -3
  25. package/src/runtime/async-runner.ts +7 -0
  26. package/src/runtime/background-runner.ts +7 -1
  27. package/src/runtime/chain-parser.ts +13 -5
  28. package/src/runtime/checkpoint.ts +13 -1
  29. package/src/runtime/child-pi.ts +9 -1
  30. package/src/runtime/live-session-runtime.ts +15 -1
  31. package/src/runtime/parent-guard.ts +2 -2
  32. package/src/runtime/pipeline-runner.ts +3 -1
  33. package/src/runtime/stale-reconciler.ts +28 -4
  34. package/src/runtime/task-runner.ts +50 -20
  35. package/src/runtime/team-runner.ts +19 -2
  36. package/src/runtime/verification-gates.ts +21 -1
  37. package/src/runtime/workspace-tree.ts +28 -2
  38. package/src/schema/team-tool-schema.ts +9 -0
  39. package/src/state/blob-store.ts +12 -10
  40. package/src/state/event-log-rotation.ts +114 -93
  41. package/src/state/event-log.ts +83 -23
  42. package/src/state/health-store.ts +6 -1
  43. package/src/state/locks.ts +66 -16
  44. package/src/state/state-store.ts +46 -2
  45. package/src/ui/card-colors.ts +7 -3
  46. package/src/ui/dashboard-panes/agents-pane.ts +15 -2
  47. package/src/ui/live-duration.ts +58 -0
  48. package/src/ui/tool-render.ts +7 -11
  49. package/src/ui/tool-renderers/index.ts +6 -3
  50. package/src/ui/widget/widget-formatters.ts +2 -13
  51. package/src/utils/fs-watch.ts +11 -60
  52. package/src/utils/run-watcher-registry.ts +164 -0
  53. package/src/workflows/discover-workflows.ts +2 -1
  54. package/src/workflows/workflow-config.ts +5 -0
  55. package/src/runtime/dynamic-script-runner.ts +0 -497
  56. package/src/runtime/sandbox.ts +0 -335
@@ -1,335 +0,0 @@
1
- import * as vm from "node:vm";
2
-
3
- import { sanitizeEnvSecrets } from "../utils/env-filter.ts";
4
-
5
- /**
6
- * Forbidden patterns for sandbox security (C4).
7
- * These are checked during script compilation/validation.
8
- */
9
- const FORBIDDEN_PATTERNS = [
10
- // ESM patterns
11
- /import\s*\(/, // Dynamic import()
12
- /import\s+.*from\s+/, // Static import
13
- /export\s+(default\s+)?/, // Export statements
14
- /import\.meta/, // import.meta
15
- // Module patterns
16
- /require\s*\(/, // CommonJS require
17
- /module\./, // module.exports, module.id, etc.
18
- /__dirname/, // __dirname reference
19
- /__filename/, // __filename reference
20
- /\bdefine\s*\(/, // AMD define
21
- // Global escape vectors
22
- /\bglobalThis\b/, // globalThis reference
23
- /\bglobal\b/, // global reference (Node.js)
24
- // Block constructor chain escape vectors:
25
- // - `.constructor` followed by `(`, `.`, `;`, or end-of-line/string
26
- // - `[constructor]` or `["constructor"]` bracket access on any value
27
- // The bare `constructor` keyword in a class body is safe and allowed.
28
- /\.constructor\s*\(/, // Block obj.constructor() chain calls
29
- /\.constructor\s*(?:\.|$|;|,|\s)/, // Block obj.constructor at end, semicolon, comma, or whitespace
30
- /\[\s*['"]?constructor['"]?\s*\]/, // Block ["constructor"] or ['constructor'] or [constructor]
31
- ] as const;
32
-
33
- Object.freeze(FORBIDDEN_PATTERNS);
34
-
35
- /**
36
- * SECURITY (HIGH #3 fix): Normalize source code before forbidden-pattern checks
37
- * to prevent unicode-escape bypasses.
38
- *
39
- * Attackers can write `import\u0028"fs"\u0029` which compiles as
40
- * `import("fs")` but does not match the regex `/import\s*\(/`.
41
- *
42
- * This function:
43
- * 1. Strips null bytes (used to split keywords across boundaries)
44
- * 2. Decodes \uXXXX escape sequences so regexes see the actual characters
45
- */
46
- export function normalizeCodeForValidation(code: string): string {
47
- // Strip null bytes
48
- let normalized = code.replace(/\0/g, "");
49
- // Decode common unicode escapes: \u0028 → (
50
- normalized = normalized.replace(
51
- /\\u([0-9a-fA-F]{4})/g,
52
- (_, hex) => String.fromCharCode(Number.parseInt(hex, 16)),
53
- );
54
- return normalized;
55
- }
56
-
57
- export interface SandboxOptions {
58
- timeout?: number;
59
- globals?: Record<string, unknown>;
60
- onLog?: (message: string) => void;
61
- onError?: (message: string) => void;
62
- onWarn?: (message: string) => void;
63
- }
64
-
65
- /**
66
- * WorkflowSandbox provides a safe execution context for dynamic JavaScript
67
- * in pi-crew workflows. It creates a VM context with restricted globals
68
- * and provides safe console and process objects.
69
- */
70
- export class WorkflowSandbox {
71
- private context: vm.Context;
72
- private timeout: number;
73
-
74
- constructor(options: SandboxOptions = {}) {
75
- this.timeout = options.timeout ?? 30000;
76
- this.context = this.createSafeContext(options.globals ?? {}, options);
77
- }
78
-
79
- private createSafeContext(globals: Record<string, unknown>, options: SandboxOptions): vm.Context {
80
- // C4: Frozen process object - limited access to process internals.
81
- // FIX (Round 14, C1+C3): Sanitize env to a small allow-list so secrets
82
- // like ANTHROPIC_API_KEY, AWS_SECRET_ACCESS_KEY, etc. never reach
83
- // sandboxed code. Then deep-freeze the env so callers cannot inject
84
- // new keys (Object.freeze on the wrapper alone would not prevent
85
- // `frozenProcess.env.newKey = "..."`).
86
- const safeEnv = Object.freeze(sanitizeEnvSecrets(process.env, {
87
- allowList: [
88
- "NODE_ENV",
89
- // Note: PI_CREW_* globs are not used here because isDangerousGlob
90
- // flags them as potentially matching secret env vars (PI_CREW_token,
91
- // PI_CREW_api_key, etc.). Instead, list the specific PI_CREW env vars
92
- // that sandboxed code legitimately needs.
93
- "PI_CREW_DEPTH",
94
- "PI_CREW_INHERIT_PROJECT_CONTEXT",
95
- "PI_CREW_INHERIT_SKILLS",
96
- "PI_CREW_MOCK_LIVE_SESSION",
97
- "PI_CREW_SKIP_HOME_CHECK",
98
- "PI_CREW_WARM_POOL_SIZE",
99
- "PATH",
100
- "PATH_SEPARATOR",
101
- "USERPROFILE",
102
- "USER",
103
- "SHELL",
104
- "LANG",
105
- "LC_ALL",
106
- "LC_CTYPE",
107
- "TERM",
108
- "TZ",
109
- "TMPDIR",
110
- "TMP",
111
- "TEMP",
112
- ],
113
- }));
114
- const frozenProcess = Object.freeze({
115
- cwd: () => process.cwd(),
116
- platform: process.platform,
117
- arch: process.arch,
118
- version: process.version,
119
- env: safeEnv,
120
- // Explicitly excluded: exit, kill, hrtime, memoryUsage, cpuUsage, binding, dlopen, _tickCallback
121
- });
122
-
123
- // Safe console implementation
124
- const safeConsole = {
125
- log: (...args: unknown[]) => (options.onLog ?? console.log)(args.map(formatArg).join(" ")),
126
- error: (...args: unknown[]) => (options.onError ?? console.error)(args.map(formatArg).join(" ")),
127
- warn: (...args: unknown[]) => (options.onWarn ?? console.warn)(args.map(formatArg).join(" ")),
128
- info: (...args: unknown[]) => (options.onLog ?? console.log)(args.map(formatArg).join(" ")),
129
- debug: (...args: unknown[]) => (options.onLog ?? console.log)(args.map(formatArg).join(" ")),
130
- table: (data: unknown) => (options.onLog ?? console.log)(JSON.stringify(data, null, 2)),
131
- dir: (data: unknown) => (options.onLog ?? console.log)(JSON.stringify(data, null, 2)),
132
- };
133
-
134
- // C4: Ensure globals don't include process, global, or globalThis references
135
- const safeGlobals: Record<string, unknown> = {};
136
- for (const [key, value] of Object.entries(globals)) {
137
- // Filter out dangerous global references
138
- if (key === "process" || key === "global" || key === "globalThis" || key === "GLOBAL") {
139
- continue; // Skip - these are handled by frozenProcess or intentionally omitted
140
- }
141
- safeGlobals[key] = value;
142
- }
143
-
144
- // Context isolation - explicitly list allowed globals
145
- const contextGlobals: Record<string, unknown> = {
146
- ...safeGlobals,
147
- process: frozenProcess,
148
- console: safeConsole,
149
- // Safe Math (static methods only)
150
- Math: Math,
151
- // Safe JSON
152
- JSON: JSON,
153
- // Safe Number
154
- Number: Number,
155
- // Safe String
156
- String: String,
157
- // Safe Boolean
158
- Boolean: Boolean,
159
- // Safe Array
160
- Array: Array,
161
- // Safe Object
162
- Object: Object,
163
- // Safe RegExp
164
- RegExp: RegExp,
165
- // Safe Error
166
- Error: Error,
167
- // Safe Map
168
- Map: Map,
169
- // Safe Set
170
- Set: Set,
171
- // Safe Promise
172
- Promise: Promise,
173
- // Safe Symbol
174
- Symbol: Symbol,
175
- // Safe parseInt/parseFloat
176
- parseInt: parseInt,
177
- parseFloat: parseFloat,
178
- isNaN: isNaN,
179
- isFinite: isFinite,
180
- // Safe encodeURI/decodeURI
181
- encodeURI: encodeURI,
182
- decodeURI: decodeURI,
183
- encodeURIComponent: encodeURIComponent,
184
- decodeURIComponent: decodeURIComponent,
185
- // Safe typed arrays (read-only buffer views)
186
- ArrayBuffer: ArrayBuffer,
187
- Uint8Array: Uint8Array,
188
- };
189
-
190
- // Freeze the context object itself to prevent sandbox code from
191
- // adding/removing globals.
192
- Object.freeze(contextGlobals);
193
-
194
- const ctx = vm.createContext(contextGlobals);
195
-
196
- // Freeze prototypes INSIDE the VM context to prevent sandboxed code
197
- // from polluting Object.prototype or Array.prototype.
198
- //
199
- // SECURITY TRADE-OFF: vm.createContext shares host prototypes, so
200
- // freezing inside the context also freezes them for the host process.
201
- // This is acceptable because:
202
- // 1. Pi-crew extensions should not modify built-in prototypes
203
- // 2. The freeze is idempotent (safe to call multiple times)
204
- // 3. In test environments, we skip this to allow test frameworks
205
- // that extend prototypes (e.g., Sinon, should.js)
206
- if (process.env.NODE_ENV !== "test") {
207
- try {
208
- vm.runInContext(
209
- "Object.freeze(Object.prototype); Object.freeze(Array.prototype);",
210
- ctx,
211
- { filename: "sandbox-init.js", timeout: 1000 },
212
- );
213
- } catch {
214
- // Already frozen — idempotent, safe to ignore
215
- }
216
- }
217
-
218
- return ctx;
219
- }
220
-
221
- /**
222
- * C4: Validate code before execution - check for forbidden patterns and
223
- * ensure compilation is safe.
224
- */
225
- private validateScript(code: string): void {
226
- // SECURITY (HIGH #3 fix): Normalize unicode escapes before pattern matching
227
- const normalized = normalizeCodeForValidation(code);
228
- // Check for ESM/module patterns
229
- for (const pattern of FORBIDDEN_PATTERNS) {
230
- if (pattern.test(normalized)) {
231
- throw new Error(`Forbidden pattern detected: ${pattern.source}`);
232
- }
233
- }
234
-
235
- // Check for import.meta specifically (C4)
236
- if (/import\.meta/.test(normalized)) {
237
- throw new Error("import.meta is not allowed in sandboxed code");
238
- }
239
-
240
- // Verify compilation succeeds (C4)
241
- const wrappedCode = `(function(){ ${code} })()`;
242
- new vm.Script(wrappedCode, {
243
- filename: "sandbox-validate.js",
244
- });
245
- }
246
-
247
- /**
248
- * Execute JavaScript code in the sandboxed context.
249
- * @param code - The JavaScript code to execute
250
- * @param timeout - Optional timeout override in milliseconds
251
- * @returns The result of the script execution
252
- * @throws Error if code contains forbidden patterns or fails compilation
253
- */
254
- execute(code: string, timeout?: number): unknown {
255
- // C4: Validate script before execution
256
- this.validateScript(code);
257
-
258
- const effectiveTimeout = timeout ?? this.timeout;
259
- // Wrap code in an IIFE to allow return statements
260
- const wrappedCode = `(function(){ ${code} })()`;
261
- const script = new vm.Script(wrappedCode, {
262
- filename: "workflow.js",
263
- });
264
-
265
- return script.runInContext(this.context, {
266
- timeout: effectiveTimeout,
267
- displayErrors: true,
268
- });
269
- }
270
-
271
- /**
272
- * Execute an async function in the sandboxed context.
273
- * @param fn - Async function to execute
274
- * @param timeout - Optional timeout override in milliseconds
275
- * @returns Promise resolving to the function result
276
- */
277
- async executeAsync<T>(fn: () => Promise<T>, timeout?: number): Promise<T> {
278
- const effectiveTimeout = timeout ?? this.timeout;
279
- // FIX (Round 14, C2): Run the same validation chain as `execute()` so
280
- // forbidden patterns (require/import/__dirname/etc.) cannot slip through
281
- // by hiding inside an arrow function. Previously the function body was
282
- // stringified and executed with no checks.
283
- const fnSource = fn.toString();
284
- this.validateScript(fnSource);
285
- const script = new vm.Script(`(${fnSource})()`, {
286
- filename: "workflow.js",
287
- });
288
-
289
- const result = script.runInContext(this.context, {
290
- timeout: effectiveTimeout,
291
- displayErrors: true,
292
- });
293
-
294
- return result as Promise<T>;
295
- }
296
-
297
- /**
298
- * Create a new sandbox with additional globals merged in.
299
- */
300
- extend(additionalGlobals: Record<string, unknown>): WorkflowSandbox {
301
- const newSandbox = new WorkflowSandbox({
302
- timeout: this.timeout,
303
- globals: { ...additionalGlobals },
304
- });
305
- return newSandbox;
306
- }
307
-
308
- /**
309
- * Get the VM context for advanced use cases.
310
- */
311
- getContext(): vm.Context {
312
- return this.context;
313
- }
314
- }
315
-
316
- function formatArg(arg: unknown): string {
317
- if (typeof arg === "string") return arg;
318
- if (arg === null) return "null";
319
- if (arg === undefined) return "undefined";
320
- if (typeof arg === "object") {
321
- try {
322
- return JSON.stringify(arg);
323
- } catch {
324
- return String(arg);
325
- }
326
- }
327
- return String(arg);
328
- }
329
-
330
- /**
331
- * Create a pre-configured sandbox for workflow execution.
332
- */
333
- export function createWorkflowSandbox(options?: SandboxOptions): WorkflowSandbox {
334
- return new WorkflowSandbox(options);
335
- }