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.
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/logging/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/dist/runtime-api.d.ts +61 -6
- package/packages/runtime/dist/runtime-api.d.ts.map +1 -1
- package/packages/runtime/dist/runtime-api.js +83 -30
- package/packages/runtime/dist/runtime-api.js.map +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/runtime/src/runtime-api.ts +168 -52
- package/packages/runtime/test/runtime-api.test.ts +169 -0
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/tooling-preference/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/two-phase-review/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/typecheck-packages.mjs +1 -1
- package/harness/task-graph.d.ts +0 -51
- package/packages/runtime/command-executor.ts +0 -523
- package/packages/runtime/index.ts +0 -50
- package/packages/runtime/local-runtime-agent.ts +0 -480
- package/packages/runtime/policy-engine.ts +0 -813
- package/packages/runtime/runtime-api.ts +0 -665
- package/packages/runtime/workspace-manager.ts +0 -591
- package/packages/types/src/ai-providers.js +0 -66
- 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.
|
|
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",
|
package/harness/task-graph.d.ts
DELETED
|
@@ -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
|
-
}
|