oh-my-claude-sisyphus 3.2.5 → 3.3.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/README.md +37 -2
- package/agents/scientist-high.md +1003 -0
- package/agents/scientist-low.md +232 -0
- package/agents/scientist.md +1180 -0
- package/bridge/__pycache__/gyoshu_bridge.cpython-310.pyc +0 -0
- package/bridge/gyoshu_bridge.py +846 -0
- package/commands/research.md +511 -0
- package/dist/agents/definitions.d.ts +9 -0
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +25 -0
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/index.d.ts +2 -1
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +2 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/scientist.d.ts +16 -0
- package/dist/agents/scientist.d.ts.map +1 -0
- package/dist/agents/scientist.js +370 -0
- package/dist/agents/scientist.js.map +1 -0
- package/dist/lib/atomic-write.d.ts +29 -0
- package/dist/lib/atomic-write.d.ts.map +1 -0
- package/dist/lib/atomic-write.js +111 -0
- package/dist/lib/atomic-write.js.map +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/python-repl/bridge-manager.d.ts +65 -0
- package/dist/tools/python-repl/bridge-manager.d.ts.map +1 -0
- package/dist/tools/python-repl/bridge-manager.js +478 -0
- package/dist/tools/python-repl/bridge-manager.js.map +1 -0
- package/dist/tools/python-repl/index.d.ts +40 -0
- package/dist/tools/python-repl/index.d.ts.map +1 -0
- package/dist/tools/python-repl/index.js +36 -0
- package/dist/tools/python-repl/index.js.map +1 -0
- package/dist/tools/python-repl/paths.d.ts +84 -0
- package/dist/tools/python-repl/paths.d.ts.map +1 -0
- package/dist/tools/python-repl/paths.js +213 -0
- package/dist/tools/python-repl/paths.js.map +1 -0
- package/dist/tools/python-repl/session-lock.d.ts +111 -0
- package/dist/tools/python-repl/session-lock.d.ts.map +1 -0
- package/dist/tools/python-repl/session-lock.js +510 -0
- package/dist/tools/python-repl/session-lock.js.map +1 -0
- package/dist/tools/python-repl/socket-client.d.ts +42 -0
- package/dist/tools/python-repl/socket-client.d.ts.map +1 -0
- package/dist/tools/python-repl/socket-client.js +157 -0
- package/dist/tools/python-repl/socket-client.js.map +1 -0
- package/dist/tools/python-repl/tool.d.ts +100 -0
- package/dist/tools/python-repl/tool.d.ts.map +1 -0
- package/dist/tools/python-repl/tool.js +575 -0
- package/dist/tools/python-repl/tool.js.map +1 -0
- package/dist/tools/python-repl/types.d.ts +95 -0
- package/dist/tools/python-repl/types.d.ts.map +1 -0
- package/dist/tools/python-repl/types.js +2 -0
- package/dist/tools/python-repl/types.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge Manager - Python process lifecycle management
|
|
3
|
+
*
|
|
4
|
+
* Manages the gyoshu_bridge.py process:
|
|
5
|
+
* - Spawning with proper environment detection
|
|
6
|
+
* - Ensuring single bridge per session with security validations
|
|
7
|
+
* - Graceful shutdown with signal escalation
|
|
8
|
+
* - PID reuse detection via process identity verification
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from 'child_process';
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as fsPromises from 'fs/promises';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
import { execFile } from 'child_process';
|
|
16
|
+
import { promisify } from 'util';
|
|
17
|
+
import { getSessionDir, getBridgeSocketPath, getBridgeMetaPath } from './paths.js';
|
|
18
|
+
import { atomicWriteJson, safeReadJson, ensureDirSync } from '../../lib/atomic-write.js';
|
|
19
|
+
const execFileAsync = promisify(execFile);
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// CONSTANTS
|
|
22
|
+
// =============================================================================
|
|
23
|
+
const BRIDGE_SPAWN_TIMEOUT_MS = 30000; // 30 seconds to wait for socket
|
|
24
|
+
const DEFAULT_GRACE_PERIOD_MS = 5000; // 5 seconds for SIGINT
|
|
25
|
+
const SIGTERM_GRACE_MS = 2500; // 2.5 seconds for SIGTERM
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// BRIDGE PATH RESOLUTION
|
|
28
|
+
// =============================================================================
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the path to gyoshu_bridge.py relative to this module.
|
|
31
|
+
* The bridge script is at: <package-root>/bridge/gyoshu_bridge.py
|
|
32
|
+
*/
|
|
33
|
+
function getBridgeScriptPath() {
|
|
34
|
+
// In ESM, __dirname equivalent is:
|
|
35
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
36
|
+
const __dirname = path.dirname(__filename);
|
|
37
|
+
// From src/tools/python-repl/ -> ../../.. -> package root -> bridge/
|
|
38
|
+
const packageRoot = path.resolve(__dirname, '..', '..', '..');
|
|
39
|
+
return path.join(packageRoot, 'bridge', 'gyoshu_bridge.py');
|
|
40
|
+
}
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// PYTHON ENVIRONMENT DETECTION
|
|
43
|
+
// =============================================================================
|
|
44
|
+
/**
|
|
45
|
+
* Detect an existing Python virtual environment in the project directory.
|
|
46
|
+
* Returns null if no .venv is found.
|
|
47
|
+
*/
|
|
48
|
+
function detectExistingPythonEnv(projectRoot) {
|
|
49
|
+
const isWindows = process.platform === 'win32';
|
|
50
|
+
const binDir = isWindows ? 'Scripts' : 'bin';
|
|
51
|
+
const pythonExe = isWindows ? 'python.exe' : 'python';
|
|
52
|
+
const venvPython = path.join(projectRoot, '.venv', binDir, pythonExe);
|
|
53
|
+
if (fs.existsSync(venvPython)) {
|
|
54
|
+
return { pythonPath: venvPython, type: 'venv' };
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Ensure a Python environment is available for the project.
|
|
60
|
+
* Currently requires an existing .venv - does not auto-create.
|
|
61
|
+
*/
|
|
62
|
+
async function ensurePythonEnvironment(projectRoot) {
|
|
63
|
+
const existing = detectExistingPythonEnv(projectRoot);
|
|
64
|
+
if (existing) {
|
|
65
|
+
return existing;
|
|
66
|
+
}
|
|
67
|
+
// Fallback: try system python3
|
|
68
|
+
try {
|
|
69
|
+
await execFileAsync('python3', ['--version']);
|
|
70
|
+
return { pythonPath: 'python3', type: 'venv' };
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// python3 not available
|
|
74
|
+
}
|
|
75
|
+
throw new Error('No Python environment found. Create a virtual environment first:\n' +
|
|
76
|
+
' python -m venv .venv\n' +
|
|
77
|
+
' .venv/bin/pip install pandas numpy matplotlib');
|
|
78
|
+
}
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// PROCESS IDENTITY VERIFICATION
|
|
81
|
+
// =============================================================================
|
|
82
|
+
/**
|
|
83
|
+
* Get process start time on Linux via /proc/{pid}/stat field 22.
|
|
84
|
+
*/
|
|
85
|
+
async function getProcessStartTimeLinux(pid) {
|
|
86
|
+
try {
|
|
87
|
+
const stat = await fsPromises.readFile(`/proc/${pid}/stat`, 'utf8');
|
|
88
|
+
const closeParen = stat.lastIndexOf(')');
|
|
89
|
+
if (closeParen === -1)
|
|
90
|
+
return undefined;
|
|
91
|
+
const fieldsAfterComm = stat.substring(closeParen + 2).split(' ');
|
|
92
|
+
// starttime is field 22 in /proc/pid/stat, index 19 after removing pid and comm
|
|
93
|
+
const startTimeField = fieldsAfterComm[19];
|
|
94
|
+
if (!startTimeField)
|
|
95
|
+
return undefined;
|
|
96
|
+
const startTime = parseInt(startTimeField, 10);
|
|
97
|
+
return isNaN(startTime) ? undefined : startTime;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get process start time on macOS via `ps -p {pid} -o lstart=`.
|
|
105
|
+
*/
|
|
106
|
+
async function getProcessStartTimeMacOS(pid) {
|
|
107
|
+
if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0)
|
|
108
|
+
return undefined;
|
|
109
|
+
try {
|
|
110
|
+
const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-o', 'lstart='], {
|
|
111
|
+
env: { ...process.env, LC_ALL: 'C' },
|
|
112
|
+
});
|
|
113
|
+
const lstart = stdout.trim();
|
|
114
|
+
if (!lstart)
|
|
115
|
+
return undefined;
|
|
116
|
+
const date = new Date(lstart);
|
|
117
|
+
if (isNaN(date.getTime()))
|
|
118
|
+
return undefined;
|
|
119
|
+
return date.getTime();
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get process start time for a given PID (cross-platform).
|
|
127
|
+
*/
|
|
128
|
+
async function getProcessStartTime(pid) {
|
|
129
|
+
if (process.platform === 'linux') {
|
|
130
|
+
return getProcessStartTimeLinux(pid);
|
|
131
|
+
}
|
|
132
|
+
else if (process.platform === 'darwin') {
|
|
133
|
+
return getProcessStartTimeMacOS(pid);
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check if a process is alive (basic check, no start time verification).
|
|
139
|
+
*/
|
|
140
|
+
function isProcessAliveBasic(pid) {
|
|
141
|
+
try {
|
|
142
|
+
process.kill(pid, 0);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Verify that a bridge process is still running and is the same process
|
|
151
|
+
* that was originally spawned (guards against PID reuse).
|
|
152
|
+
*
|
|
153
|
+
* Returns false if:
|
|
154
|
+
* - Process is not alive
|
|
155
|
+
* - Start time was recorded but doesn't match (PID reused)
|
|
156
|
+
* - Start time was recorded but cannot be retrieved (fail-closed)
|
|
157
|
+
*/
|
|
158
|
+
export async function verifyProcessIdentity(meta) {
|
|
159
|
+
// Basic alive check first
|
|
160
|
+
if (!isProcessAliveBasic(meta.pid)) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
// If we have a recorded start time, verify it matches
|
|
164
|
+
if (meta.processStartTime !== undefined) {
|
|
165
|
+
const currentStartTime = await getProcessStartTime(meta.pid);
|
|
166
|
+
// Fail-closed: if we can't get current start time but we have a recorded one,
|
|
167
|
+
// assume PID reuse has occurred (safer than assuming same process)
|
|
168
|
+
if (currentStartTime === undefined) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
if (currentStartTime !== meta.processStartTime) {
|
|
172
|
+
return false; // PID reuse detected
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
// =============================================================================
|
|
178
|
+
// SOCKET UTILITIES
|
|
179
|
+
// =============================================================================
|
|
180
|
+
/**
|
|
181
|
+
* Check if a path points to a Unix socket.
|
|
182
|
+
*/
|
|
183
|
+
function isSocket(socketPath) {
|
|
184
|
+
try {
|
|
185
|
+
const stat = fs.lstatSync(socketPath);
|
|
186
|
+
return stat.isSocket();
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Safely unlink a socket file if it exists within the expected directory.
|
|
194
|
+
*/
|
|
195
|
+
function safeUnlinkSocket(socketPath) {
|
|
196
|
+
try {
|
|
197
|
+
if (fs.existsSync(socketPath)) {
|
|
198
|
+
fs.unlinkSync(socketPath);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// Ignore errors
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// =============================================================================
|
|
206
|
+
// BRIDGE METADATA VALIDATION
|
|
207
|
+
// =============================================================================
|
|
208
|
+
/**
|
|
209
|
+
* Validate that parsed JSON matches BridgeMeta schema.
|
|
210
|
+
*/
|
|
211
|
+
function isValidBridgeMeta(data) {
|
|
212
|
+
if (typeof data !== 'object' || data === null)
|
|
213
|
+
return false;
|
|
214
|
+
const obj = data;
|
|
215
|
+
return (typeof obj.pid === 'number' &&
|
|
216
|
+
Number.isInteger(obj.pid) &&
|
|
217
|
+
obj.pid > 0 &&
|
|
218
|
+
typeof obj.socketPath === 'string' &&
|
|
219
|
+
typeof obj.startedAt === 'string' &&
|
|
220
|
+
typeof obj.sessionId === 'string' &&
|
|
221
|
+
typeof obj.pythonEnv === 'object' &&
|
|
222
|
+
obj.pythonEnv !== null &&
|
|
223
|
+
typeof obj.pythonEnv.pythonPath === 'string' &&
|
|
224
|
+
(obj.processStartTime === undefined || typeof obj.processStartTime === 'number'));
|
|
225
|
+
}
|
|
226
|
+
// =============================================================================
|
|
227
|
+
// PROCESS GROUP MANAGEMENT
|
|
228
|
+
// =============================================================================
|
|
229
|
+
/**
|
|
230
|
+
* Kill a process group (process + children).
|
|
231
|
+
* Uses negative PID to target the process group.
|
|
232
|
+
*/
|
|
233
|
+
function killProcessGroup(pid, signal) {
|
|
234
|
+
try {
|
|
235
|
+
// Negative PID sends signal to process group
|
|
236
|
+
process.kill(-pid, signal);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// Process might already be dead
|
|
241
|
+
try {
|
|
242
|
+
// Fallback: kill just the process
|
|
243
|
+
process.kill(pid, signal);
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// =============================================================================
|
|
252
|
+
// SPAWN BRIDGE SERVER
|
|
253
|
+
// =============================================================================
|
|
254
|
+
/**
|
|
255
|
+
* Spawn a new bridge server process for the given session.
|
|
256
|
+
*
|
|
257
|
+
* @param sessionId - Unique session identifier
|
|
258
|
+
* @param projectDir - Optional project directory (defaults to cwd)
|
|
259
|
+
* @returns BridgeMeta containing process information
|
|
260
|
+
*/
|
|
261
|
+
export async function spawnBridgeServer(sessionId, projectDir) {
|
|
262
|
+
const sessionDir = getSessionDir(sessionId);
|
|
263
|
+
ensureDirSync(sessionDir);
|
|
264
|
+
const socketPath = getBridgeSocketPath(sessionId);
|
|
265
|
+
const bridgePath = getBridgeScriptPath();
|
|
266
|
+
// Verify bridge script exists
|
|
267
|
+
if (!fs.existsSync(bridgePath)) {
|
|
268
|
+
throw new Error(`Bridge script not found: ${bridgePath}`);
|
|
269
|
+
}
|
|
270
|
+
// Clean up any stale socket
|
|
271
|
+
safeUnlinkSocket(socketPath);
|
|
272
|
+
const effectiveProjectDir = projectDir || process.cwd();
|
|
273
|
+
const pythonEnv = await ensurePythonEnvironment(effectiveProjectDir);
|
|
274
|
+
// Pass socket path as positional argument (matches gyoshu_bridge.py argparse)
|
|
275
|
+
const bridgeArgs = [bridgePath, socketPath];
|
|
276
|
+
const proc = spawn(pythonEnv.pythonPath, bridgeArgs, {
|
|
277
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
278
|
+
cwd: effectiveProjectDir,
|
|
279
|
+
env: { ...process.env, PYTHONUNBUFFERED: '1' },
|
|
280
|
+
detached: true,
|
|
281
|
+
});
|
|
282
|
+
proc.unref();
|
|
283
|
+
// Capture stderr for error reporting (capped at 64KB)
|
|
284
|
+
const MAX_STDERR_CHARS = 64 * 1024;
|
|
285
|
+
let stderrBuffer = '';
|
|
286
|
+
let stderrTruncated = false;
|
|
287
|
+
proc.stderr?.on('data', (chunk) => {
|
|
288
|
+
if (stderrTruncated)
|
|
289
|
+
return;
|
|
290
|
+
const text = chunk.toString();
|
|
291
|
+
if (stderrBuffer.length + text.length > MAX_STDERR_CHARS) {
|
|
292
|
+
stderrBuffer = stderrBuffer.slice(0, MAX_STDERR_CHARS - 20) + '\n...[truncated]';
|
|
293
|
+
stderrTruncated = true;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
stderrBuffer += text;
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
// Wait for socket to appear
|
|
300
|
+
const startTime = Date.now();
|
|
301
|
+
while (!isSocket(socketPath)) {
|
|
302
|
+
if (Date.now() - startTime > BRIDGE_SPAWN_TIMEOUT_MS) {
|
|
303
|
+
// Kill the process on timeout
|
|
304
|
+
if (proc.pid) {
|
|
305
|
+
killProcessGroup(proc.pid, 'SIGKILL');
|
|
306
|
+
}
|
|
307
|
+
// Clean up any non-socket file that might exist (poisoning attempt)
|
|
308
|
+
if (fs.existsSync(socketPath) && !isSocket(socketPath)) {
|
|
309
|
+
safeUnlinkSocket(socketPath);
|
|
310
|
+
}
|
|
311
|
+
throw new Error(`Bridge failed to create socket in ${BRIDGE_SPAWN_TIMEOUT_MS}ms. ` +
|
|
312
|
+
`Stderr: ${stderrBuffer || '(empty)'}`);
|
|
313
|
+
}
|
|
314
|
+
await sleep(100);
|
|
315
|
+
}
|
|
316
|
+
// Get process start time for PID reuse detection
|
|
317
|
+
const processStartTime = proc.pid ? await getProcessStartTime(proc.pid) : undefined;
|
|
318
|
+
const meta = {
|
|
319
|
+
pid: proc.pid,
|
|
320
|
+
socketPath,
|
|
321
|
+
startedAt: new Date().toISOString(),
|
|
322
|
+
sessionId,
|
|
323
|
+
pythonEnv,
|
|
324
|
+
processStartTime,
|
|
325
|
+
};
|
|
326
|
+
// Persist metadata
|
|
327
|
+
const metaPath = getBridgeMetaPath(sessionId);
|
|
328
|
+
await atomicWriteJson(metaPath, meta);
|
|
329
|
+
return meta;
|
|
330
|
+
}
|
|
331
|
+
// =============================================================================
|
|
332
|
+
// ENSURE BRIDGE
|
|
333
|
+
// =============================================================================
|
|
334
|
+
/**
|
|
335
|
+
* Get or spawn a bridge server for the session.
|
|
336
|
+
*
|
|
337
|
+
* Implements security validations:
|
|
338
|
+
* - Anti-poisoning: Verifies sessionId in metadata matches expected
|
|
339
|
+
* - Anti-hijack: Verifies socketPath is the expected canonical path
|
|
340
|
+
* - Socket type: Verifies the socket path is actually a socket
|
|
341
|
+
* - Process identity: Verifies PID + start time match
|
|
342
|
+
*
|
|
343
|
+
* @param sessionId - Unique session identifier
|
|
344
|
+
* @param projectDir - Optional project directory (defaults to cwd)
|
|
345
|
+
* @returns BridgeMeta for the active bridge
|
|
346
|
+
*/
|
|
347
|
+
export async function ensureBridge(sessionId, projectDir) {
|
|
348
|
+
const metaPath = getBridgeMetaPath(sessionId);
|
|
349
|
+
const expectedSocketPath = getBridgeSocketPath(sessionId);
|
|
350
|
+
const meta = await safeReadJson(metaPath);
|
|
351
|
+
if (meta && isValidBridgeMeta(meta)) {
|
|
352
|
+
// Security validation 1: Anti-poisoning - verify sessionId matches
|
|
353
|
+
if (meta.sessionId !== sessionId) {
|
|
354
|
+
await deleteBridgeMeta(sessionId);
|
|
355
|
+
return spawnBridgeServer(sessionId, projectDir);
|
|
356
|
+
}
|
|
357
|
+
// Security validation 2: Anti-hijack - verify socket path is expected
|
|
358
|
+
if (meta.socketPath !== expectedSocketPath) {
|
|
359
|
+
await deleteBridgeMeta(sessionId);
|
|
360
|
+
return spawnBridgeServer(sessionId, projectDir);
|
|
361
|
+
}
|
|
362
|
+
// Security validation 3: Process identity - verify PID is still our process
|
|
363
|
+
const stillOurs = await verifyProcessIdentity(meta);
|
|
364
|
+
if (stillOurs) {
|
|
365
|
+
// Security validation 4: Socket type - verify it's actually a socket
|
|
366
|
+
if (isSocket(meta.socketPath)) {
|
|
367
|
+
return meta;
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
// Socket missing or wrong type - kill the orphan process
|
|
371
|
+
try {
|
|
372
|
+
process.kill(meta.pid, 'SIGKILL');
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// Process might already be dead
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
await deleteBridgeMeta(sessionId);
|
|
380
|
+
}
|
|
381
|
+
return spawnBridgeServer(sessionId, projectDir);
|
|
382
|
+
}
|
|
383
|
+
// =============================================================================
|
|
384
|
+
// KILL BRIDGE WITH ESCALATION
|
|
385
|
+
// =============================================================================
|
|
386
|
+
/**
|
|
387
|
+
* Terminate a bridge process with signal escalation.
|
|
388
|
+
*
|
|
389
|
+
* Escalation order:
|
|
390
|
+
* 1. SIGINT - wait gracePeriodMs (default 5000ms)
|
|
391
|
+
* 2. SIGTERM - wait 2500ms
|
|
392
|
+
* 3. SIGKILL - immediate termination
|
|
393
|
+
*
|
|
394
|
+
* Uses process group kill (-pid) to also terminate child processes.
|
|
395
|
+
*
|
|
396
|
+
* @param sessionId - Session whose bridge to kill
|
|
397
|
+
* @param options - Optional configuration
|
|
398
|
+
* @returns EscalationResult with termination details
|
|
399
|
+
*/
|
|
400
|
+
export async function killBridgeWithEscalation(sessionId, options) {
|
|
401
|
+
const gracePeriod = options?.gracePeriodMs ?? DEFAULT_GRACE_PERIOD_MS;
|
|
402
|
+
const startTime = Date.now();
|
|
403
|
+
const metaPath = getBridgeMetaPath(sessionId);
|
|
404
|
+
const meta = await safeReadJson(metaPath);
|
|
405
|
+
if (!meta || !isValidBridgeMeta(meta)) {
|
|
406
|
+
return { terminated: true }; // Already dead or no metadata
|
|
407
|
+
}
|
|
408
|
+
// Anti-poisoning check
|
|
409
|
+
if (meta.sessionId !== sessionId) {
|
|
410
|
+
await deleteBridgeMeta(sessionId);
|
|
411
|
+
return { terminated: true };
|
|
412
|
+
}
|
|
413
|
+
// Verify we're killing the right process
|
|
414
|
+
if (!(await verifyProcessIdentity(meta))) {
|
|
415
|
+
await deleteBridgeMeta(sessionId);
|
|
416
|
+
return { terminated: true }; // Process already dead or PID reused
|
|
417
|
+
}
|
|
418
|
+
// Helper to wait for process exit with identity verification
|
|
419
|
+
const waitForExit = async (timeoutMs) => {
|
|
420
|
+
const checkStart = Date.now();
|
|
421
|
+
while (Date.now() - checkStart < timeoutMs) {
|
|
422
|
+
const stillOurs = await verifyProcessIdentity(meta);
|
|
423
|
+
if (!stillOurs) {
|
|
424
|
+
return true; // Process is gone or PID reused
|
|
425
|
+
}
|
|
426
|
+
await sleep(100);
|
|
427
|
+
}
|
|
428
|
+
return false;
|
|
429
|
+
};
|
|
430
|
+
let terminatedBy = 'SIGINT';
|
|
431
|
+
// Stage 1: SIGINT
|
|
432
|
+
killProcessGroup(meta.pid, 'SIGINT');
|
|
433
|
+
if (!(await waitForExit(gracePeriod))) {
|
|
434
|
+
// Stage 2: SIGTERM
|
|
435
|
+
terminatedBy = 'SIGTERM';
|
|
436
|
+
killProcessGroup(meta.pid, 'SIGTERM');
|
|
437
|
+
if (!(await waitForExit(SIGTERM_GRACE_MS))) {
|
|
438
|
+
// Stage 3: SIGKILL
|
|
439
|
+
terminatedBy = 'SIGKILL';
|
|
440
|
+
killProcessGroup(meta.pid, 'SIGKILL');
|
|
441
|
+
await waitForExit(1000); // Brief wait for SIGKILL
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// Cleanup
|
|
445
|
+
await deleteBridgeMeta(sessionId);
|
|
446
|
+
const sessionDir = getSessionDir(sessionId);
|
|
447
|
+
const socketPath = meta.socketPath;
|
|
448
|
+
if (socketPath.startsWith(sessionDir)) {
|
|
449
|
+
safeUnlinkSocket(socketPath);
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
terminated: true,
|
|
453
|
+
terminatedBy,
|
|
454
|
+
terminationTimeMs: Date.now() - startTime,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
// =============================================================================
|
|
458
|
+
// HELPER FUNCTIONS
|
|
459
|
+
// =============================================================================
|
|
460
|
+
/**
|
|
461
|
+
* Delete bridge metadata file.
|
|
462
|
+
*/
|
|
463
|
+
async function deleteBridgeMeta(sessionId) {
|
|
464
|
+
const metaPath = getBridgeMetaPath(sessionId);
|
|
465
|
+
try {
|
|
466
|
+
await fsPromises.unlink(metaPath);
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
// Ignore errors (file might not exist)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Sleep for specified milliseconds.
|
|
474
|
+
*/
|
|
475
|
+
function sleep(ms) {
|
|
476
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
477
|
+
}
|
|
478
|
+
//# sourceMappingURL=bridge-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-manager.js","sourceRoot":"","sources":["../../../src/tools/python-repl/bridge-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python REPL Tool - Persistent Python execution environment
|
|
3
|
+
*
|
|
4
|
+
* Provides a persistent Python REPL with variable persistence across
|
|
5
|
+
* tool invocations, session locking, and structured output markers.
|
|
6
|
+
*/
|
|
7
|
+
import { pythonReplHandler } from './tool.js';
|
|
8
|
+
export declare const pythonReplTool: {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
schema: import("zod").ZodObject<{
|
|
12
|
+
action: import("zod").ZodEnum<["execute", "interrupt", "reset", "get_state"]>;
|
|
13
|
+
researchSessionID: import("zod").ZodString;
|
|
14
|
+
code: import("zod").ZodOptional<import("zod").ZodString>;
|
|
15
|
+
executionLabel: import("zod").ZodOptional<import("zod").ZodString>;
|
|
16
|
+
executionTimeout: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
17
|
+
queueTimeout: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
18
|
+
projectDir: import("zod").ZodOptional<import("zod").ZodString>;
|
|
19
|
+
}, "strip", import("zod").ZodTypeAny, {
|
|
20
|
+
action: "execute" | "interrupt" | "reset" | "get_state";
|
|
21
|
+
researchSessionID: string;
|
|
22
|
+
executionTimeout: number;
|
|
23
|
+
queueTimeout: number;
|
|
24
|
+
code?: string | undefined;
|
|
25
|
+
executionLabel?: string | undefined;
|
|
26
|
+
projectDir?: string | undefined;
|
|
27
|
+
}, {
|
|
28
|
+
action: "execute" | "interrupt" | "reset" | "get_state";
|
|
29
|
+
researchSessionID: string;
|
|
30
|
+
code?: string | undefined;
|
|
31
|
+
executionLabel?: string | undefined;
|
|
32
|
+
executionTimeout?: number | undefined;
|
|
33
|
+
queueTimeout?: number | undefined;
|
|
34
|
+
projectDir?: string | undefined;
|
|
35
|
+
}>;
|
|
36
|
+
handler: typeof pythonReplHandler;
|
|
37
|
+
};
|
|
38
|
+
export * from './types.js';
|
|
39
|
+
export { pythonReplSchema, pythonReplHandler } from './tool.js';
|
|
40
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/python-repl/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAoB,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEhE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyB1B,CAAC;AAGF,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python REPL Tool - Persistent Python execution environment
|
|
3
|
+
*
|
|
4
|
+
* Provides a persistent Python REPL with variable persistence across
|
|
5
|
+
* tool invocations, session locking, and structured output markers.
|
|
6
|
+
*/
|
|
7
|
+
import { pythonReplSchema, pythonReplHandler } from './tool.js';
|
|
8
|
+
export const pythonReplTool = {
|
|
9
|
+
name: 'python_repl',
|
|
10
|
+
description: `Execute Python code in a persistent REPL environment with variable persistence across invocations.
|
|
11
|
+
|
|
12
|
+
Actions:
|
|
13
|
+
- execute: Run Python code (variables persist between calls)
|
|
14
|
+
- reset: Clear namespace and reset environment
|
|
15
|
+
- get_state: Get memory usage and list of defined variables
|
|
16
|
+
- interrupt: Stop long-running execution
|
|
17
|
+
|
|
18
|
+
Features:
|
|
19
|
+
- Variables persist across tool calls within the same session
|
|
20
|
+
- Structured output markers: [OBJECTIVE], [DATA], [FINDING], [STAT:*], [LIMITATION]
|
|
21
|
+
- Memory tracking (RSS/VMS)
|
|
22
|
+
- Automatic timeout handling (default 5 minutes)
|
|
23
|
+
- Session locking for safe concurrent access
|
|
24
|
+
|
|
25
|
+
Use this instead of Bash heredocs when you need:
|
|
26
|
+
- Multi-step analysis with state persistence
|
|
27
|
+
- Large datasets that shouldn't be reloaded
|
|
28
|
+
- Iterative ML model training
|
|
29
|
+
- Any workflow benefiting from Python state persistence`,
|
|
30
|
+
schema: pythonReplSchema,
|
|
31
|
+
handler: pythonReplHandler
|
|
32
|
+
};
|
|
33
|
+
// Re-export types for convenience
|
|
34
|
+
export * from './types.js';
|
|
35
|
+
export { pythonReplSchema, pythonReplHandler } from './tool.js';
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/python-repl/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEhE,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,aAAa;IACnB,WAAW,EAAE;;;;;;;;;;;;;;;;;;;wDAmByC;IAEtD,MAAM,EAAE,gBAAgB;IACxB,OAAO,EAAE,iBAAiB;CAC3B,CAAC;AAEF,kCAAkC;AAClC,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path utilities for Python REPL tool
|
|
3
|
+
*
|
|
4
|
+
* Provides secure path resolution for session directories, sockets, and metadata.
|
|
5
|
+
* Uses OS-appropriate runtime directories outside the project root.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Get the path to the runtime directory.
|
|
9
|
+
* Contains ephemeral session data like locks and sockets.
|
|
10
|
+
* Uses OS-appropriate temp directories.
|
|
11
|
+
*
|
|
12
|
+
* Priority:
|
|
13
|
+
* 1. XDG_RUNTIME_DIR/omc (Linux standard, usually /run/user/{uid})
|
|
14
|
+
* 2. Platform-specific user cache directory
|
|
15
|
+
* 3. os.tmpdir() fallback
|
|
16
|
+
*
|
|
17
|
+
* @returns Path to runtime directory
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* getRuntimeDir();
|
|
21
|
+
* // Linux with XDG: '/run/user/1000/omc'
|
|
22
|
+
* // macOS: '~/Library/Caches/omc/runtime'
|
|
23
|
+
* // Fallback: '/tmp/omc/runtime'
|
|
24
|
+
*/
|
|
25
|
+
export declare function getRuntimeDir(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Shorten a session ID to fit within Unix socket path constraints.
|
|
28
|
+
* Uses SHA256 hash truncated to 12 hex chars (48 bits).
|
|
29
|
+
*
|
|
30
|
+
* Unix sockets have path length limits (UNIX_PATH_MAX):
|
|
31
|
+
* - Linux: 108 bytes
|
|
32
|
+
* - macOS: 104 bytes
|
|
33
|
+
*
|
|
34
|
+
* SECURITY: Always hashes the input, even for short IDs.
|
|
35
|
+
* This prevents path traversal attacks via malicious short IDs like ".." or "../x".
|
|
36
|
+
*
|
|
37
|
+
* @param sessionId - Original session identifier (can be any length)
|
|
38
|
+
* @returns Short identifier (12 hex chars) suitable for socket paths
|
|
39
|
+
*/
|
|
40
|
+
export declare function shortenSessionId(sessionId: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Get the path to a specific session's runtime directory.
|
|
43
|
+
* Uses shortened session ID to ensure socket paths stay within limits.
|
|
44
|
+
*
|
|
45
|
+
* @param sessionId - Unique identifier for the session
|
|
46
|
+
* @returns Path to runtime/{shortId}/ in OS temp directory
|
|
47
|
+
*/
|
|
48
|
+
export declare function getSessionDir(sessionId: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* Get the path to a session's bridge socket.
|
|
51
|
+
* Path is kept short to respect Unix socket path limits (~108 bytes).
|
|
52
|
+
*
|
|
53
|
+
* @param sessionId - Unique identifier for the session
|
|
54
|
+
* @returns Path to bridge.sock in session's runtime directory
|
|
55
|
+
*/
|
|
56
|
+
export declare function getBridgeSocketPath(sessionId: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Get the path to a session's bridge metadata file.
|
|
59
|
+
*
|
|
60
|
+
* @param sessionId - Unique identifier for the session
|
|
61
|
+
* @returns Path to bridge_meta.json in session's runtime directory
|
|
62
|
+
*/
|
|
63
|
+
export declare function getBridgeMetaPath(sessionId: string): string;
|
|
64
|
+
/**
|
|
65
|
+
* Get the path to a session's lock file.
|
|
66
|
+
*
|
|
67
|
+
* @param sessionId - Unique identifier for the session
|
|
68
|
+
* @returns Path to session.lock in session's runtime directory
|
|
69
|
+
*/
|
|
70
|
+
export declare function getSessionLockPath(sessionId: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* Validates that a path segment is safe to use in file paths.
|
|
73
|
+
* Prevents directory traversal and path injection attacks.
|
|
74
|
+
*
|
|
75
|
+
* @param segment - The path segment to validate (e.g., session ID, file name)
|
|
76
|
+
* @param name - Name of the parameter for error messages (e.g., "sessionId", "filename")
|
|
77
|
+
* @throws Error if segment is invalid
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* validatePathSegment("my-session-123", "sessionId"); // OK
|
|
81
|
+
* validatePathSegment("../evil", "sessionId"); // throws Error
|
|
82
|
+
*/
|
|
83
|
+
export declare function validatePathSegment(segment: string, name: string): void;
|
|
84
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../../src/tools/python-repl/paths.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA2DH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAkBtC;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAQ1D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5D;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAyCvE"}
|