pi-harness-runtime 1.1.429 → 1.1.430

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 (126) hide show
  1. package/package.json +1 -1
  2. package/packages/a2a-adapter/package.json +1 -1
  3. package/packages/agent-policy/package.json +1 -1
  4. package/packages/architecture-generator/package.json +1 -1
  5. package/packages/auth/package.json +1 -1
  6. package/packages/autonomous-refactor/package.json +1 -1
  7. package/packages/autonomous-runtime/package.json +1 -1
  8. package/packages/cache-strategy/package.json +1 -1
  9. package/packages/capability-registry/package.json +1 -1
  10. package/packages/checkpoint/package.json +1 -1
  11. package/packages/cli-plugin-sdk/package.json +1 -1
  12. package/packages/clipboard/package.json +1 -1
  13. package/packages/clipboard-plugin/package.json +1 -1
  14. package/packages/code-generation/package.json +1 -1
  15. package/packages/code-review/package.json +1 -1
  16. package/packages/codex-adapter/package.json +1 -1
  17. package/packages/codex-watcher/package.json +1 -1
  18. package/packages/config-capture/package.json +1 -1
  19. package/packages/connection-runtime/package.json +1 -1
  20. package/packages/context-compiler/package.json +1 -1
  21. package/packages/context-discovery/package.json +1 -1
  22. package/packages/context-manager/package.json +1 -1
  23. package/packages/contracts/package.json +1 -1
  24. package/packages/cookie-sanitizer/package.json +1 -1
  25. package/packages/cost-optimizer/package.json +1 -1
  26. package/packages/dependency-analyzer/package.json +1 -1
  27. package/packages/django-plugin/package.json +1 -1
  28. package/packages/doc-generator/package.json +1 -1
  29. package/packages/durable-review/package.json +1 -1
  30. package/packages/e2e-tester/package.json +1 -1
  31. package/packages/evaluation-engine/package.json +1 -1
  32. package/packages/evaluation-runner/package.json +1 -1
  33. package/packages/event-bus/package.json +1 -1
  34. package/packages/event-sink/package.json +1 -1
  35. package/packages/event-store/package.json +1 -1
  36. package/packages/experience-replay/package.json +1 -1
  37. package/packages/feedback-collector/package.json +1 -1
  38. package/packages/file-copy-helper/package.json +1 -1
  39. package/packages/framework-detector/package.json +1 -1
  40. package/packages/framework-plugin-sdk/package.json +1 -1
  41. package/packages/frappe-plugin/package.json +1 -1
  42. package/packages/generic-web-plugin/package.json +1 -1
  43. package/packages/health-monitor/package.json +1 -1
  44. package/packages/intent-analyzer/package.json +1 -1
  45. package/packages/jev-judge/package.json +1 -1
  46. package/packages/knowledge-graph/package.json +1 -1
  47. package/packages/knowledge-retrieval/package.json +1 -1
  48. package/packages/laravel-plugin/package.json +1 -1
  49. package/packages/learning-engine/package.json +1 -1
  50. package/packages/logging/package.json +1 -1
  51. package/packages/mcp-adapter/package.json +1 -1
  52. package/packages/memory-engine/package.json +1 -1
  53. package/packages/milestone-manager/package.json +1 -1
  54. package/packages/model-registry/package.json +1 -1
  55. package/packages/nextjs-plugin/package.json +1 -1
  56. package/packages/notification/package.json +1 -1
  57. package/packages/observability/package.json +1 -1
  58. package/packages/okf-indexer/package.json +1 -1
  59. package/packages/paperclip-integration/package.json +1 -1
  60. package/packages/performance-optimizer/package.json +1 -1
  61. package/packages/permission-mode/package.json +1 -1
  62. package/packages/privilege-broker/package.json +1 -1
  63. package/packages/project-analyzer/package.json +1 -1
  64. package/packages/project-bootstrap/package.json +1 -1
  65. package/packages/projection-engine/package.json +1 -1
  66. package/packages/prompt-compiler/package.json +1 -1
  67. package/packages/prompt-versioning/package.json +1 -1
  68. package/packages/provider-adapter-sdk/package.json +1 -1
  69. package/packages/provider-router/package.json +1 -1
  70. package/packages/provider-selector/package.json +1 -1
  71. package/packages/providers/package.json +1 -1
  72. package/packages/qdrant-skills/package.json +1 -1
  73. package/packages/quota-manager/package.json +1 -1
  74. package/packages/rate-limiter/package.json +1 -1
  75. package/packages/react-vite-plugin/package.json +1 -1
  76. package/packages/relay/package.json +1 -1
  77. package/packages/release-manager/package.json +1 -1
  78. package/packages/requirement-compiler/package.json +1 -1
  79. package/packages/runtime/dist/runtime-api.d.ts +61 -6
  80. package/packages/runtime/dist/runtime-api.d.ts.map +1 -1
  81. package/packages/runtime/dist/runtime-api.js +83 -30
  82. package/packages/runtime/dist/runtime-api.js.map +1 -1
  83. package/packages/runtime/package.json +1 -1
  84. package/packages/runtime/src/runtime-api.ts +168 -52
  85. package/packages/runtime/test/runtime-api.test.ts +169 -0
  86. package/packages/scheduler/package.json +1 -1
  87. package/packages/scheduler-adapter/package.json +1 -1
  88. package/packages/session/package.json +1 -1
  89. package/packages/session-api/package.json +1 -1
  90. package/packages/session-export/package.json +1 -1
  91. package/packages/shared-context/package.json +1 -1
  92. package/packages/skill-mcp-client/package.json +1 -1
  93. package/packages/skill-registry/package.json +1 -1
  94. package/packages/skills/package.json +1 -1
  95. package/packages/sprint-planner/package.json +1 -1
  96. package/packages/subscription-engine/package.json +1 -1
  97. package/packages/task-compiler/package.json +1 -1
  98. package/packages/tencentdb-memory/package.json +1 -1
  99. package/packages/tencentdb-sync/package.json +1 -1
  100. package/packages/test-data-generator/package.json +1 -1
  101. package/packages/test-generator/package.json +1 -1
  102. package/packages/todo-bd-sync/package.json +1 -1
  103. package/packages/token-estimation/package.json +1 -1
  104. package/packages/token-optimizer/package.json +1 -1
  105. package/packages/tooling-preference/package.json +1 -1
  106. package/packages/trajectory/package.json +1 -1
  107. package/packages/tui/package.json +1 -1
  108. package/packages/two-phase-review/package.json +1 -1
  109. package/packages/types/package.json +1 -1
  110. package/packages/voice-api/package.json +1 -1
  111. package/packages/webmcp-tester/package.json +1 -1
  112. package/packages/wiki/package.json +1 -1
  113. package/packages/workflow-events/package.json +1 -1
  114. package/packages/workspace-scanner/package.json +1 -1
  115. package/packages/worktree/package.json +1 -1
  116. package/packages/write-review/package.json +1 -1
  117. package/scripts/typecheck-packages.mjs +1 -1
  118. package/harness/task-graph.d.ts +0 -51
  119. package/packages/runtime/command-executor.ts +0 -523
  120. package/packages/runtime/index.ts +0 -50
  121. package/packages/runtime/local-runtime-agent.ts +0 -480
  122. package/packages/runtime/policy-engine.ts +0 -813
  123. package/packages/runtime/runtime-api.ts +0 -665
  124. package/packages/runtime/workspace-manager.ts +0 -591
  125. package/packages/types/src/ai-providers.js +0 -66
  126. package/packages/types/src/runtime-types.js +0 -3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/two-phase-review",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "pi-harness-runtime — two-phase implementation and review workflow (queue review signals, gate the phase, scope the diff)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/types",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "Shared TypeScript types for pi-harness-runtime packages",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/voice-api",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "type": "module",
5
5
  "description": "Voice-to-text API using Whisper for Telegram and LINE integration",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/webmcp-tester",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "WebMCP-powered E2E testing for pi-harness - natural language test scenarios",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/wiki",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "LLM Wiki integration for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/workflow-events",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/workspace-scanner",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/worktree",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "pi-harness-runtime — worktree",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/write-review",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "Two-agent write-review loop with blackboard coordination",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -32,7 +32,7 @@ const BASELINES = new Map([
32
32
  ["provider-router", 0],
33
33
  ["providers", 0],
34
34
  ["quota-manager", 0],
35
- ["runtime", 5],
35
+ ["runtime", 0],
36
36
  ["voice-api", 16],
37
37
  ["worktree", 0],
38
38
  ["harness", 20],
@@ -1,51 +0,0 @@
1
- /**
2
- * Task Graph Manager — RFC-0028 Phase 3
3
- *
4
- * Manages task dependency graph for compact-aware scheduling.
5
- * Currently a stub — integrates with CompactOrchestrator for
6
- * dependency-aware compaction prioritization.
7
- *
8
- * Future work:
9
- * - Track task dependencies (blocking/ready tasks)
10
- * - Prioritize compaction to keep blocking path messages
11
- * - Track which tasks depend on which checkpoint artifacts
12
- */
13
- import type { RuntimeTask } from "../packages/types/src/runtime-types.js";
14
- export interface TaskNode {
15
- task: RuntimeTask;
16
- dependencies: Set<string>;
17
- dependents: Set<string>;
18
- status: "pending" | "ready" | "running" | "done" | "blocked";
19
- }
20
- export interface TaskGraphManager {
21
- /** Add a task to the graph */
22
- addTask(task: RuntimeTask): void;
23
- /** Mark a task as done and update dependents */
24
- completeTask(taskId: string): void;
25
- /** Get tasks ready for execution */
26
- getReadyTasks(): RuntimeTask[];
27
- /** Get tasks blocked by a given task */
28
- getBlockedTasks(taskId: string): RuntimeTask[];
29
- /** Get all pending tasks */
30
- getPendingTasks(): RuntimeTask[];
31
- /** Get all tasks */
32
- getAllTasks(): RuntimeTask[];
33
- /** Get current progress summary */
34
- getProgressSummary(): {
35
- total: number;
36
- done: number;
37
- running: number;
38
- failed: number;
39
- };
40
- /** Get compact priority: tasks to keep in context vs prune */
41
- getCompactPriority(): {
42
- keep: string[];
43
- prune: string[];
44
- };
45
- }
46
- /**
47
- * Creates a new TaskGraphManager instance.
48
- * Stub implementation — returns minimal working graph.
49
- */
50
- export declare function createTaskGraphManager(): TaskGraphManager;
51
- //# sourceMappingURL=task-graph.d.ts.map
@@ -1,523 +0,0 @@
1
- /**
2
- * Command Executor — RFC-0025
3
- *
4
- * Safe shell command execution with timeout, output capture, and security policies.
5
- *
6
- * Features:
7
- * - Timeout enforcement
8
- * - Output capture (stdout/stderr)
9
- * - Working directory control
10
- * - Environment variable injection
11
- * - Security policy enforcement
12
- * - Process management (kill, signal)
13
- */
14
-
15
- import {
16
- spawn,
17
- type ChildProcess,
18
- type SpawnOptions,
19
- } from "node:child_process";
20
- import { EventEmitter } from "node:events";
21
- import { join } from "node:path";
22
-
23
- export interface CommandResult {
24
- success: boolean;
25
- exitCode: number | null;
26
- stdout: string;
27
- stderr: string;
28
- timedOut: boolean;
29
- duration: number;
30
- command: string;
31
- }
32
-
33
- export interface CommandOptions {
34
- /** Working directory (default: process.cwd()) */
35
- cwd?: string;
36
- /** Environment variables to inject */
37
- env?: Record<string, string>;
38
- /** Timeout in milliseconds (default: no timeout) */
39
- timeout?: number;
40
- /** Shell to use (default: /bin/sh on Unix, cmd.exe on Windows) */
41
- shell?: string;
42
- /** User ID to run as (if available) */
43
- uid?: number;
44
- /** Group ID to run as (if available) */
45
- gid?: number;
46
- /** Capture stdout (default: true) */
47
- captureStdout?: boolean;
48
- /** Capture stderr (default: true) */
49
- captureStderr?: boolean;
50
- /** Treat non-zero exit as success (default: false) */
51
- ignoreExitCode?: boolean;
52
- /** Policy to check before execution */
53
- policyCheck?: boolean;
54
- }
55
-
56
- export interface CommandPolicy {
57
- /** Allowed commands (exact match or pattern) */
58
- allowedCommands?: RegExp[];
59
- /** Denied commands (takes precedence) */
60
- deniedCommands?: RegExp[];
61
- /** Maximum timeout in ms */
62
- maxTimeout?: number;
63
- /** Allowed working directories */
64
- allowedDirs?: string[];
65
- /** Denied working directories */
66
- deniedDirs?: string[];
67
- /** Allow shell built-ins */
68
- allowShellBuiltins?: boolean;
69
- /** Allow environment variable injection */
70
- allowEnvInjection?: boolean;
71
- /** Commands that require explicit policy bypass */
72
- requireApproval?: RegExp[];
73
- }
74
-
75
- export interface CommandEvent {
76
- command: string;
77
- timestamp: string;
78
- pid?: number;
79
- }
80
-
81
- export interface CommandExecutorEvents {
82
- onStart: (event: CommandEvent) => void;
83
- onExit: (
84
- event: CommandEvent & { exitCode: number; duration: number },
85
- ) => void;
86
- onTimeout: (event: CommandEvent) => void;
87
- onError: (error: Error, command: string) => void;
88
- onPolicyViolation: (reason: string, command: string) => void;
89
- }
90
-
91
- const DEFAULT_POLICY: CommandPolicy = {
92
- allowShellBuiltins: true,
93
- allowEnvInjection: true,
94
- maxTimeout: 300000, // 5 minutes default
95
- };
96
-
97
- export class CommandExecutor extends EventEmitter {
98
- private readonly policy: CommandPolicy;
99
- private runningProcesses: Map<number, ChildProcess> = new Map();
100
- private defaultOptions: CommandOptions;
101
-
102
- constructor(policy: CommandPolicy = {}) {
103
- super();
104
- this.policy = { ...DEFAULT_POLICY, ...policy };
105
- this.defaultOptions = {
106
- captureStdout: true,
107
- captureStderr: true,
108
- ignoreExitCode: false,
109
- policyCheck: true,
110
- };
111
- }
112
-
113
- /**
114
- * Execute a command synchronously
115
- */
116
- exec(command: string, options: CommandOptions = {}): CommandResult {
117
- const opts = { ...this.defaultOptions, ...options };
118
- const startTime = Date.now();
119
-
120
- // Policy check
121
- if (opts.policyCheck !== false) {
122
- const policyResult = this.checkPolicy(command, opts);
123
- if (!policyResult.allowed) {
124
- this.emit(
125
- "policyViolation",
126
- policyResult.reason ?? "Policy violation",
127
- command,
128
- );
129
- return {
130
- success: false,
131
- exitCode: null,
132
- stdout: "",
133
- stderr: policyResult.reason ?? "Policy violation",
134
- timedOut: false,
135
- duration: Date.now() - startTime,
136
- command,
137
- };
138
- }
139
- }
140
-
141
- // Spawn process
142
- const spawnOpts: SpawnOptions = {
143
- cwd: opts.cwd ?? process.cwd(),
144
- env: { ...process.env, ...opts.env },
145
- shell:
146
- opts.shell ?? (process.platform === "win32" ? "cmd.exe" : "/bin/sh"),
147
- uid: opts.uid,
148
- gid: opts.gid,
149
- windowsHide: true,
150
- };
151
-
152
- let proc: ChildProcess;
153
- try {
154
- proc = spawn(command, [], spawnOpts);
155
- } catch (error) {
156
- this.emit("error", error as Error, command);
157
- return {
158
- success: false,
159
- exitCode: null,
160
- stdout: "",
161
- stderr: String(error),
162
- timedOut: false,
163
- duration: Date.now() - startTime,
164
- command,
165
- };
166
- }
167
-
168
- this.runningProcesses.set(proc.pid!, proc);
169
-
170
- const event: CommandEvent = {
171
- command,
172
- timestamp: new Date().toISOString(),
173
- pid: proc.pid,
174
- };
175
- this.emit("start", event);
176
-
177
- // Collect output
178
- let stdout = "";
179
- let stderr = "";
180
-
181
- if (opts.captureStdout && proc.stdout) {
182
- proc.stdout.on("data", (data) => {
183
- stdout += data.toString();
184
- });
185
- }
186
-
187
- if (opts.captureStderr && proc.stderr) {
188
- proc.stderr.on("data", (data) => {
189
- stderr += data.toString();
190
- });
191
- }
192
-
193
- // Handle timeout
194
- let timedOutFlag = false;
195
- let timeoutId: NodeJS.Timeout | undefined;
196
-
197
- if (opts.timeout && opts.timeout > 0) {
198
- const maxTimeout = this.policy.maxTimeout ?? Infinity;
199
- const effectiveTimeout = Math.min(opts.timeout, maxTimeout);
200
-
201
- timeoutId = setTimeout(() => {
202
- timedOutFlag = true;
203
- this.kill(proc.pid!);
204
- this.emit("timeout", event);
205
- }, effectiveTimeout);
206
- }
207
-
208
- // Wait for completion
209
- const exitCode = this.waitForExit(proc);
210
-
211
- // Cleanup
212
- if (timeoutId) clearTimeout(timeoutId);
213
- this.runningProcesses.delete(proc.pid!);
214
-
215
- const duration = Date.now() - startTime;
216
- this.emit("exit", { ...event, exitCode: exitCode ?? -1, duration });
217
-
218
- const success: boolean =
219
- (exitCode === 0 || opts.ignoreExitCode) && !timedOutFlag ? true : false;
220
-
221
- return {
222
- success,
223
- exitCode,
224
- stdout,
225
- stderr,
226
- timedOut: timedOutFlag,
227
- duration,
228
- command,
229
- };
230
- }
231
-
232
- /**
233
- * Execute a command asynchronously (non-blocking)
234
- */
235
- async execAsync(
236
- command: string,
237
- options: CommandOptions = {},
238
- ): Promise<CommandResult> {
239
- const opts = { ...this.defaultOptions, ...options };
240
- const startTime = Date.now();
241
-
242
- // Policy check
243
- if (opts.policyCheck !== false) {
244
- const policyResult = this.checkPolicy(command, opts);
245
- if (!policyResult.allowed) {
246
- this.emit(
247
- "policyViolation",
248
- policyResult.reason ?? "Policy violation",
249
- command,
250
- );
251
- return {
252
- success: false,
253
- exitCode: null,
254
- stdout: "",
255
- stderr: policyResult.reason ?? "Policy violation",
256
- timedOut: false,
257
- duration: Date.now() - startTime,
258
- command,
259
- };
260
- }
261
- }
262
-
263
- // Spawn process
264
- const spawnOpts: SpawnOptions = {
265
- cwd: opts.cwd ?? process.cwd(),
266
- env: { ...process.env, ...opts.env },
267
- shell:
268
- opts.shell ?? (process.platform === "win32" ? "cmd.exe" : "/bin/sh"),
269
- uid: opts.uid,
270
- gid: opts.gid,
271
- windowsHide: true,
272
- };
273
-
274
- return new Promise((resolve) => {
275
- const proc = spawn(command, [], spawnOpts);
276
- this.runningProcesses.set(proc.pid!, proc);
277
-
278
- const event: CommandEvent = {
279
- command,
280
- timestamp: new Date().toISOString(),
281
- pid: proc.pid,
282
- };
283
- this.emit("start", event);
284
-
285
- // Collect output
286
- let stdout = "";
287
- let stderr = "";
288
-
289
- if (opts.captureStdout && proc.stdout) {
290
- proc.stdout.on("data", (data) => {
291
- stdout += data.toString();
292
- });
293
- }
294
-
295
- if (opts.captureStderr && proc.stderr) {
296
- proc.stderr.on("data", (data) => {
297
- stderr += data.toString();
298
- });
299
- }
300
-
301
- // Handle timeout
302
- let timedOut = false;
303
- const timeoutId = setTimeout(
304
- () => {
305
- timedOut = true;
306
- this.kill(proc.pid!);
307
- this.emit("timeout", event);
308
- },
309
- opts.timeout ?? this.policy.maxTimeout ?? 300000,
310
- );
311
-
312
- proc.on("close", (code) => {
313
- clearTimeout(timeoutId);
314
- this.runningProcesses.delete(proc.pid!);
315
-
316
- const duration = Date.now() - startTime;
317
- const exitCode = code !== null ? code : -1;
318
- this.emit("exit", { ...event, exitCode, duration });
319
-
320
- const execSuccess: boolean =
321
- (code === 0 || opts.ignoreExitCode) && !timedOut ? true : false;
322
-
323
- resolve({
324
- success: execSuccess,
325
- exitCode: code,
326
- stdout,
327
- stderr,
328
- timedOut: timedOut,
329
- duration,
330
- command,
331
- });
332
- });
333
-
334
- proc.on("error", (error) => {
335
- clearTimeout(timeoutId);
336
- this.runningProcesses.delete(proc.pid!);
337
- this.emit("error", error, command);
338
-
339
- resolve({
340
- success: false,
341
- exitCode: null,
342
- stdout,
343
- stderr: stderr + "\n" + String(error),
344
- timedOut: false,
345
- duration: Date.now() - startTime,
346
- command,
347
- });
348
- });
349
- });
350
- }
351
-
352
- /**
353
- * Kill a running process
354
- */
355
- kill(pid: number, signal: NodeJS.Signals = "SIGTERM"): boolean {
356
- const proc = this.runningProcesses.get(pid);
357
- if (!proc) {
358
- return false;
359
- }
360
-
361
- try {
362
- proc.kill(signal);
363
- return true;
364
- } catch {
365
- return false;
366
- }
367
- }
368
-
369
- /**
370
- * Kill all running processes
371
- */
372
- killAll(signal: NodeJS.Signals = "SIGTERM"): number {
373
- let killed = 0;
374
- for (const entry of Array.from(this.runningProcesses.entries())) {
375
- const [_pid, proc] = entry;
376
- try {
377
- proc.kill(signal);
378
- killed++;
379
- } catch {
380
- // Process already exited
381
- }
382
- }
383
- this.runningProcesses.clear();
384
- return killed;
385
- }
386
-
387
- /**
388
- * Get count of running processes
389
- */
390
- getRunningCount(): number {
391
- return this.runningProcesses.size;
392
- }
393
-
394
- /**
395
- * Get list of running process PIDs
396
- */
397
- getRunningPids(): number[] {
398
- return Array.from(this.runningProcesses.keys());
399
- }
400
-
401
- /**
402
- * Check policy for a command
403
- */
404
- checkPolicy(
405
- command: string,
406
- _options?: CommandOptions,
407
- ): { allowed: boolean; reason?: string } {
408
- // Check denied commands
409
- if (this.policy.deniedCommands) {
410
- for (const pattern of this.policy.deniedCommands) {
411
- if (pattern.test(command)) {
412
- return {
413
- allowed: false,
414
- reason: `Command matches denied pattern: ${pattern}`,
415
- };
416
- }
417
- }
418
- }
419
-
420
- // Check allowed commands (if specified, must match)
421
- if (this.policy.allowedCommands && this.policy.allowedCommands.length > 0) {
422
- let allowed = false;
423
- for (const pattern of this.policy.allowedCommands) {
424
- if (pattern.test(command)) {
425
- allowed = true;
426
- break;
427
- }
428
- }
429
- if (!allowed) {
430
- return {
431
- allowed: false,
432
- reason: "Command not in allowed list",
433
- };
434
- }
435
- }
436
-
437
- // Check denied directories
438
- if (_options?.cwd) {
439
- if (this.policy.deniedDirs) {
440
- for (const dir of this.policy.deniedDirs) {
441
- if (_options.cwd.startsWith(dir)) {
442
- return {
443
- allowed: false,
444
- reason: `Working directory matches denied path: ${dir}`,
445
- };
446
- }
447
- }
448
- }
449
- }
450
-
451
- // Check for dangerous shell operators
452
- const dangerousPatterns = [
453
- { pattern: /;\s*rm\s+-rf/i, reason: "Attempted rm -rf" },
454
- {
455
- pattern: />\s*\/dev\/null/i,
456
- reason: "Output redirection to /dev/null",
457
- },
458
- { pattern: /\|\s*sh\s*$/i, reason: "Pipe to shell" },
459
- { pattern: /&\s*$/, reason: "Background execution" },
460
- ];
461
-
462
- for (const { pattern, reason } of dangerousPatterns) {
463
- if (pattern.test(command)) {
464
- // Only block if explicitly denied
465
- if (this.policy.deniedCommands) {
466
- return { allowed: false, reason };
467
- }
468
- }
469
- }
470
-
471
- return { allowed: true };
472
- }
473
-
474
- /**
475
- * Update policy at runtime
476
- */
477
- updatePolicy(updates: Partial<CommandPolicy>): void {
478
- Object.assign(this.policy, updates);
479
- }
480
-
481
- /**
482
- * Get current policy
483
- */
484
- getPolicy(): CommandPolicy {
485
- return { ...this.policy };
486
- }
487
-
488
- // ─── Private Methods ────────────────────────────────────────────────
489
-
490
- private waitForExit(_proc: ChildProcess): number | null {
491
- // Note: This method is for synchronous execution.
492
- // For async execution, use execAsync() which properly handles events.
493
- // This returns null for sync - actual exit code is captured via events.
494
- return null;
495
- }
496
- }
497
-
498
- /**
499
- * Create a CommandExecutor with safe defaults for harness runtime
500
- */
501
- export function createHarnessExecutor(): CommandExecutor {
502
- return new CommandExecutor({
503
- // Allow common development commands
504
- allowedCommands: [
505
- /^(git|npm|npx|yarn|pnpm|bun|node|python3?|make|cargo|go)\s/,
506
- /^(tsc|esbuild|rollup|vite|webpack)\s/,
507
- /^(jest|vitest|mocha|pytest|cargo test|npm test)\s/,
508
- /^(eslint|ruff|black|prettier)\s/,
509
- /^(mkdir|rm|cp|mv|cat|echo|ls|cd)\s/,
510
- ],
511
- // Deny dangerous patterns
512
- deniedCommands: [
513
- /rm\s+-rf\s+\/(?!node_modules)/, // rm -rf / except rm -rf node_modules
514
- /curl\s+.*\|.*sh$/i, // curl | sh
515
- /wget\s+.*\|.*sh$/i, // wget | sh
516
- /base64\s+-d\s+.*\|.*sh/i, // base64 decode | sh
517
- /:\(\)\{.*\}\s*:/, // Fork bomb
518
- ],
519
- maxTimeout: 300000, // 5 minutes
520
- // Default to user's home directory
521
- allowedDirs: [join(process.env.HOME ?? "", "")],
522
- });
523
- }