forkit-connect 0.1.5 → 0.1.7

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.
@@ -0,0 +1,867 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectActiveRuntimeTooling = detectActiveRuntimeTooling;
7
+ exports.extractObservedFolderLabels = extractObservedFolderLabels;
8
+ exports.extractObservedModelLabels = extractObservedModelLabels;
9
+ exports.buildRuntimeObservationSteps = buildRuntimeObservationSteps;
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_os_1 = __importDefault(require("node:os"));
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const node_child_process_1 = require("node:child_process");
14
+ const process_scout_1 = require("./process-scout");
15
+ const TOOLING_DEFINITIONS = [
16
+ { key: 'antigravity', label: 'Antigravity', binaries: ['antigravity'] },
17
+ { key: 'cursor', label: 'Cursor', binaries: ['cursor'] },
18
+ { key: 'vscode', label: 'VS Code', binaries: ['code', 'codium'] },
19
+ { key: 'claude', label: 'Claude', binaries: ['claude'] },
20
+ { key: 'codex', label: 'Codex', binaries: ['codex'] },
21
+ { key: 'anaconda', label: 'Anaconda', binaries: ['conda'] },
22
+ { key: 'jupyter', label: 'Jupyter', binaries: ['jupyter', 'jupyter-lab', 'jupyter-notebook'] },
23
+ ];
24
+ const AGENT_SIGNATURE_TO_TOOL_KEY = {
25
+ cursor: 'cursor',
26
+ 'vscode-agent': 'vscode',
27
+ claude: 'claude',
28
+ 'claude-code': 'claude',
29
+ codex: 'codex',
30
+ };
31
+ function trimOptional(value) {
32
+ const normalized = String(value ?? '').trim();
33
+ return normalized.length ? normalized : null;
34
+ }
35
+ function roundMetric(value) {
36
+ if (value === null || !Number.isFinite(value))
37
+ return null;
38
+ return Math.round(value * 100) / 100;
39
+ }
40
+ function bytesToMb(value) {
41
+ const numeric = Number(value);
42
+ if (!Number.isFinite(numeric) || numeric <= 0)
43
+ return null;
44
+ return roundMetric(numeric / (1024 * 1024));
45
+ }
46
+ function cpuValue(value) {
47
+ const numeric = Number(value);
48
+ if (!Number.isFinite(numeric) || numeric < 0)
49
+ return null;
50
+ return roundMetric(numeric);
51
+ }
52
+ function escapeRegExp(value) {
53
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
54
+ }
55
+ function runtimeRoot(repo) {
56
+ return node_path_1.default.resolve(repo.rootPath || repo.cwd);
57
+ }
58
+ function runtimeRelativeEntrypoint(repo) {
59
+ const relative = trimOptional(repo.relativeCwd);
60
+ return relative || '.';
61
+ }
62
+ function normalizeProcessText(value) {
63
+ return String(value || '').trim().toLowerCase();
64
+ }
65
+ function processTokens(command) {
66
+ return command
67
+ .split(/\s+/)
68
+ .map((token) => token.trim().toLowerCase())
69
+ .filter(Boolean);
70
+ }
71
+ function processBasename(token) {
72
+ const normalized = token.replace(/\\/g, '/');
73
+ const base = normalized.split('/').filter(Boolean).pop() || normalized;
74
+ return base.replace(/\.app$/i, '').replace(/\.exe$/i, '').toLowerCase();
75
+ }
76
+ function realpathSafe(value) {
77
+ const resolved = node_path_1.default.resolve(value);
78
+ try {
79
+ const nativeRealpath = node_fs_1.default.realpathSync.native;
80
+ return typeof nativeRealpath === 'function' ? nativeRealpath(resolved) : node_fs_1.default.realpathSync(resolved);
81
+ }
82
+ catch {
83
+ return resolved;
84
+ }
85
+ }
86
+ function normalizeRelativeToRuntime(repo, value) {
87
+ const raw = trimOptional(typeof value === 'string' ? value : null);
88
+ if (!raw)
89
+ return null;
90
+ if (raw === '.')
91
+ return '.';
92
+ const root = runtimeRoot(repo);
93
+ if (node_path_1.default.isAbsolute(raw)) {
94
+ if (!isPathWithinRuntimeRoot(repo, raw))
95
+ return null;
96
+ const relative = node_path_1.default.relative(root, raw).replace(/[\\/]+/g, '/');
97
+ return relative === '' ? '.' : relative;
98
+ }
99
+ const normalized = raw.replace(/[\\/]+/g, '/').replace(/^\.\/+/, '');
100
+ return normalized || '.';
101
+ }
102
+ function uniqueSortedStrings(values) {
103
+ return [...new Set(values.map((value) => String(value || '').trim()).filter(Boolean))].sort((left, right) => left.localeCompare(right));
104
+ }
105
+ function uniqueSortedNumbers(values) {
106
+ return [...new Set(values.filter((value) => Number.isFinite(value) && Number(value) > 0).map((value) => Number(value)))]
107
+ .sort((left, right) => left - right);
108
+ }
109
+ function summarizeObservationList(values, label, maxItems = 3) {
110
+ const normalized = values
111
+ .map((value) => String(value ?? '').trim())
112
+ .filter(Boolean);
113
+ if (normalized.length === 0)
114
+ return null;
115
+ const preview = normalized.slice(0, maxItems).join(', ');
116
+ const remainder = normalized.length > maxItems ? ` +${normalized.length - maxItems} more` : '';
117
+ return `${label}: ${preview}${remainder}`;
118
+ }
119
+ function matchesProcessBinary(processName, command, binary) {
120
+ const normalizedBinary = normalizeProcessText(binary);
121
+ const normalizedName = normalizeProcessText(processName);
122
+ const normalizedCommand = normalizeProcessText(command);
123
+ const boundaryPattern = new RegExp(`(^|[^a-z0-9])${escapeRegExp(normalizedBinary)}(?:\\.app|\\.exe)?($|[^a-z0-9])`, 'i');
124
+ if (normalizedName === normalizedBinary || boundaryPattern.test(normalizedName)) {
125
+ return true;
126
+ }
127
+ if (normalizedBinary.length > 4 && boundaryPattern.test(normalizedCommand)) {
128
+ return true;
129
+ }
130
+ return processTokens(normalizedCommand).some((token) => processBasename(token) === normalizedBinary);
131
+ }
132
+ function matchesVsCodeProcess(processName, command) {
133
+ const normalizedName = normalizeProcessText(processName);
134
+ const normalizedCommand = normalizeProcessText(command);
135
+ if (matchesProcessBinary(processName, command, 'code') || matchesProcessBinary(processName, command, 'codium')) {
136
+ return true;
137
+ }
138
+ return ((normalizedName.includes('code helper') || normalizedName === 'code')
139
+ && normalizedCommand.includes('/visual studio code.app/'));
140
+ }
141
+ function matchesCursorProcess(processName, command) {
142
+ const normalizedName = normalizeProcessText(processName);
143
+ const normalizedCommand = normalizeProcessText(command);
144
+ if (matchesProcessBinary(processName, command, 'cursor')) {
145
+ return true;
146
+ }
147
+ return ((normalizedName.includes('cursor helper') || normalizedName === 'cursor')
148
+ && normalizedCommand.includes('/cursor.app/'));
149
+ }
150
+ function matchesClaudeProcess(processName, command) {
151
+ const normalizedName = normalizeProcessText(processName);
152
+ const normalizedCommand = normalizeProcessText(command);
153
+ if (matchesProcessBinary(processName, command, 'claude')) {
154
+ return true;
155
+ }
156
+ return (normalizedCommand.includes('/claude.app/')
157
+ || normalizedCommand.includes('claude-mcp-browser-bridge')
158
+ || (normalizedName.includes('chrome-native-host') && normalizedCommand.includes('/applications/claude.app/')));
159
+ }
160
+ function matchesToolProcess(definition, processName, command) {
161
+ if (definition.key === 'claude')
162
+ return matchesClaudeProcess(processName, command);
163
+ if (definition.key === 'vscode')
164
+ return matchesVsCodeProcess(processName, command);
165
+ if (definition.key === 'cursor')
166
+ return matchesCursorProcess(processName, command);
167
+ return (definition.binaries ?? []).some((binary) => matchesProcessBinary(processName, command, binary));
168
+ }
169
+ function encodeWorkspacePathMarker(targetPath) {
170
+ return `file_${targetPath.replace(/\\/g, '/').replace(/^\/+/, '').replace(/[^a-z0-9]+/gi, '_').replace(/^_+|_+$/g, '').toLowerCase()}`;
171
+ }
172
+ function hasRepoScopeEvidence(repo, command) {
173
+ const normalizedCommand = normalizeProcessText(command);
174
+ const root = runtimeRoot(repo);
175
+ const cwd = node_path_1.default.resolve(repo.cwd);
176
+ const repoRootMarker = realpathSafe(root).replace(/\\/g, '/').toLowerCase();
177
+ const cwdMarker = realpathSafe(cwd).replace(/\\/g, '/').toLowerCase();
178
+ const encodedRepoRoot = encodeWorkspacePathMarker(root);
179
+ const encodedCwd = encodeWorkspacePathMarker(cwd);
180
+ return [repoRootMarker, cwdMarker, encodedRepoRoot, encodedCwd]
181
+ .some((marker) => marker.length > 0 && normalizedCommand.includes(marker));
182
+ }
183
+ function isLowSignalHelperProcess(processName, command) {
184
+ const haystack = `${normalizeProcessText(processName)} ${normalizeProcessText(command)}`;
185
+ return [
186
+ 'crashpad_handler',
187
+ '--type=gpu-process',
188
+ '--type=renderer',
189
+ '--utility-sub-type=network.',
190
+ '--utility-sub-type=audio.',
191
+ '--utility-sub-type=video_capture.',
192
+ 'typingsinstaller.js',
193
+ ].some((pattern) => haystack.includes(pattern));
194
+ }
195
+ function extractCommandFlagValue(command, flag) {
196
+ const escapedFlag = escapeRegExp(flag);
197
+ const match = command.match(new RegExp(`${escapedFlag}(?:=|\\s+)([^\\s]+)`, 'i'));
198
+ return trimOptional(match?.[1] || null);
199
+ }
200
+ function extractCommandFlagNumber(command, flag) {
201
+ const raw = extractCommandFlagValue(command, flag);
202
+ const numeric = Number(raw || '');
203
+ return Number.isInteger(numeric) ? numeric : null;
204
+ }
205
+ function normalizeExtensionFolderToId(folderName) {
206
+ const normalized = trimOptional(folderName);
207
+ if (!normalized)
208
+ return null;
209
+ return trimOptional(normalized.replace(/-\d[\w.-]*$/i, ''));
210
+ }
211
+ function extractVsCodeExtensionId(command) {
212
+ const normalized = String(command || '').replace(/\\/g, '/');
213
+ const match = normalized.match(/\/\.vscode\/extensions\/([^/\s]+)/i);
214
+ return normalizeExtensionFolderToId(match?.[1] || null);
215
+ }
216
+ function extractChromeExtensionId(command) {
217
+ const match = String(command || '').match(/chrome-extension:\/\/([a-z]{32})\b/i);
218
+ return trimOptional(match?.[1] || null);
219
+ }
220
+ function readJsonFile(targetPath) {
221
+ try {
222
+ return JSON.parse(node_fs_1.default.readFileSync(targetPath, 'utf8'));
223
+ }
224
+ catch {
225
+ return null;
226
+ }
227
+ }
228
+ function collectClaudeTrackedWorktreePaths(value) {
229
+ if (!value || typeof value !== 'object' || Array.isArray(value))
230
+ return [];
231
+ const collected = new Set();
232
+ const root = value;
233
+ for (const [key, entry] of Object.entries(root)) {
234
+ if (typeof key === 'string' && key.startsWith('/')) {
235
+ collected.add(realpathSafe(key));
236
+ }
237
+ if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
238
+ const record = entry;
239
+ for (const candidate of [record.path, record.worktreePath, record.rootPath]) {
240
+ if (typeof candidate === 'string' && candidate.startsWith('/')) {
241
+ collected.add(realpathSafe(candidate));
242
+ }
243
+ }
244
+ }
245
+ }
246
+ return [...collected].sort((left, right) => left.localeCompare(right));
247
+ }
248
+ function readClaudeLocalState() {
249
+ const appSupportDir = node_path_1.default.join(node_os_1.default.homedir(), 'Library', 'Application Support', 'Claude');
250
+ const desktopConfig = readJsonFile(node_path_1.default.join(appSupportDir, 'claude_desktop_config.json'));
251
+ const gitWorktrees = readJsonFile(node_path_1.default.join(appSupportDir, 'git-worktrees.json'));
252
+ const preferences = desktopConfig && typeof desktopConfig === 'object'
253
+ ? desktopConfig.preferences
254
+ : null;
255
+ const trustedFolders = preferences && typeof preferences === 'object'
256
+ ? preferences.localAgentModeTrustedFolders
257
+ : null;
258
+ const trackedWorktreePaths = gitWorktrees && typeof gitWorktrees === 'object'
259
+ ? collectClaudeTrackedWorktreePaths(gitWorktrees.worktrees && typeof gitWorktrees.worktrees === 'object'
260
+ ? gitWorktrees.worktrees
261
+ : gitWorktrees)
262
+ : [];
263
+ return {
264
+ trustedFolders: Array.isArray(trustedFolders)
265
+ ? uniqueSortedStrings(trustedFolders.map((value) => typeof value === 'string' && value.startsWith('/') ? realpathSafe(value) : null))
266
+ : [],
267
+ trackedWorktreePaths,
268
+ };
269
+ }
270
+ function readProcessCwd(pid) {
271
+ if (!Number.isInteger(pid) || pid <= 0)
272
+ return null;
273
+ const result = (0, node_child_process_1.spawnSync)('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'], {
274
+ encoding: 'utf8',
275
+ timeout: 1000,
276
+ });
277
+ if (result.error || result.status !== 0) {
278
+ return null;
279
+ }
280
+ const pathLine = String(result.stdout || '')
281
+ .split(/\r?\n/)
282
+ .map((line) => line.trim())
283
+ .find((line) => line.startsWith('n'));
284
+ return trimOptional(pathLine ? pathLine.slice(1) : null);
285
+ }
286
+ function isPathWithinRuntimeRoot(repo, candidatePath) {
287
+ const normalizedCandidate = trimOptional(candidatePath);
288
+ if (!normalizedCandidate)
289
+ return false;
290
+ const repoRoot = realpathSafe(runtimeRoot(repo)).replace(/\\/g, '/').toLowerCase();
291
+ const candidate = realpathSafe(normalizedCandidate).replace(/\\/g, '/').toLowerCase();
292
+ return candidate === repoRoot || candidate.startsWith(`${repoRoot}/`);
293
+ }
294
+ function isPathInsideFolder(candidatePath, parentPath) {
295
+ const normalizedCandidate = trimOptional(candidatePath);
296
+ const normalizedParent = trimOptional(parentPath);
297
+ if (!normalizedCandidate || !normalizedParent)
298
+ return false;
299
+ const candidate = realpathSafe(normalizedCandidate).replace(/\\/g, '/').toLowerCase();
300
+ const parent = realpathSafe(normalizedParent).replace(/\\/g, '/').toLowerCase();
301
+ return candidate === parent || candidate.startsWith(`${parent}/`);
302
+ }
303
+ function classifyAntigravityRole(processName, command) {
304
+ const haystack = `${normalizeProcessText(processName)} ${normalizeProcessText(command)}`;
305
+ if (haystack.includes('language_server_macos_'))
306
+ return 'language_server';
307
+ if (haystack.includes('tsserver.js'))
308
+ return 'typescript_server';
309
+ if (haystack.includes('jsonservermain'))
310
+ return 'json_language_server';
311
+ if (haystack.includes('shipit'))
312
+ return 'updater';
313
+ if (haystack.includes('--type=gpu-process'))
314
+ return 'gpu_helper';
315
+ if (haystack.includes('--type=renderer'))
316
+ return 'renderer';
317
+ if (haystack.includes('--utility-sub-type=node.mojom.nodeservice'))
318
+ return 'node_helper';
319
+ if (haystack.includes('/electron'))
320
+ return 'desktop_shell';
321
+ return 'helper';
322
+ }
323
+ function extractAntigravityProcessDetails(processName, command) {
324
+ const role = classifyAntigravityRole(processName, command);
325
+ const details = {
326
+ adapter: 'antigravity_v1',
327
+ role,
328
+ };
329
+ const workspaceMarker = extractCommandFlagValue(command, '--workspace_id');
330
+ const extensionServerPort = extractCommandFlagNumber(command, '--extension_server_port');
331
+ const httpsServerPort = extractCommandFlagNumber(command, '--https_server_port');
332
+ const lspPort = extractCommandFlagNumber(command, '--lsp_port');
333
+ const cloudCodeEndpoint = extractCommandFlagValue(command, '--cloud_code_endpoint');
334
+ const appDataDir = extractCommandFlagValue(command, '--app_data_dir');
335
+ if (workspaceMarker)
336
+ details.workspaceMarker = workspaceMarker;
337
+ if (extensionServerPort !== null)
338
+ details.extensionServerPort = extensionServerPort;
339
+ if (httpsServerPort !== null)
340
+ details.httpsServerPort = httpsServerPort;
341
+ if (lspPort !== null)
342
+ details.lspPort = lspPort;
343
+ if (cloudCodeEndpoint)
344
+ details.cloudCodeEndpoint = cloudCodeEndpoint;
345
+ if (appDataDir)
346
+ details.appDataDir = appDataDir;
347
+ if (normalizeProcessText(command).includes('--enable_lsp')) {
348
+ details.enableLsp = true;
349
+ }
350
+ return details;
351
+ }
352
+ function isAntigravitySupportRole(role) {
353
+ return ['updater', 'gpu_helper', 'renderer', 'node_helper', 'helper']
354
+ .includes(String(role || '').trim().toLowerCase());
355
+ }
356
+ function classifyClaudeRole(processName, command) {
357
+ const haystack = `${normalizeProcessText(processName)} ${normalizeProcessText(command)}`;
358
+ if (haystack.includes('chrome-native-host') && haystack.includes('/applications/claude.app/'))
359
+ return 'browser_bridge';
360
+ if (haystack.includes('/applications/claude.app/contents/helpers/disclaimer'))
361
+ return 'sandbox_disclaimer';
362
+ if (normalizeProcessText(processName) === 'claude'
363
+ || haystack.includes(' claude ')
364
+ || haystack.includes('/claude ')
365
+ || haystack.endsWith('/claude'))
366
+ return 'cli_agent';
367
+ return 'helper';
368
+ }
369
+ function extractClaudeProcessDetails(processName, command, cwdPath, repo, claudeState) {
370
+ const role = classifyClaudeRole(processName, command);
371
+ const host = role === 'browser_bridge'
372
+ ? 'desktop_app'
373
+ : (role === 'cli_agent' ? 'cli' : null);
374
+ const extensionId = extractChromeExtensionId(command);
375
+ const trustedRuntimeRoot = claudeState
376
+ ? claudeState.trustedFolders.some((folder) => isPathInsideFolder(runtimeRoot(repo), folder))
377
+ : false;
378
+ const worktreeTracked = claudeState
379
+ ? claudeState.trackedWorktreePaths.some((trackedPath) => isPathInsideFolder(runtimeRoot(repo), trackedPath))
380
+ : false;
381
+ return {
382
+ adapter: 'claude_v1',
383
+ role,
384
+ ...(host ? { host } : {}),
385
+ ...(extensionId ? { extensionId } : {}),
386
+ ...(cwdPath ? { cwdPath } : {}),
387
+ ...(trustedRuntimeRoot ? { trustedRuntimeRoot: true } : {}),
388
+ ...(worktreeTracked ? { worktreeTracked: true } : {}),
389
+ };
390
+ }
391
+ function isClaudeSupportRole(role) {
392
+ return ['sandbox_disclaimer', 'helper'].includes(String(role || '').trim().toLowerCase());
393
+ }
394
+ function classifyCodexRole(processName, command) {
395
+ const haystack = `${normalizeProcessText(processName)} ${normalizeProcessText(command)}`;
396
+ if (haystack.includes('/.vscode/extensions/') && haystack.includes('codex app-server'))
397
+ return 'vscode_extension_server';
398
+ if (haystack.includes('--working-dir') || haystack.includes('/applications/codex.app/contents/resources/node'))
399
+ return 'desktop_kernel';
400
+ if (haystack.includes('/applications/codex.app/contents/macos/codex'))
401
+ return 'desktop_shell';
402
+ if (haystack.includes('--type=gpu-process'))
403
+ return 'gpu_helper';
404
+ if (haystack.includes('--type=renderer'))
405
+ return 'renderer';
406
+ if (haystack.includes('crashpad_handler'))
407
+ return 'crashpad_helper';
408
+ return 'helper';
409
+ }
410
+ function extractCodexProcessDetails(processName, command, cwdPath) {
411
+ const role = classifyCodexRole(processName, command);
412
+ const extensionId = extractVsCodeExtensionId(command);
413
+ const sessionId = extractCommandFlagValue(command, '--session-id');
414
+ const workingDir = extractCommandFlagValue(command, '--working-dir');
415
+ const host = extensionId
416
+ ? 'vscode_extension'
417
+ : (normalizeProcessText(command).includes('/applications/codex.app/')
418
+ ? 'desktop_app'
419
+ : null);
420
+ return {
421
+ adapter: 'codex_v1',
422
+ role,
423
+ ...(host ? { host } : {}),
424
+ ...(extensionId ? { extensionId } : {}),
425
+ ...(sessionId ? { sessionId } : {}),
426
+ ...(workingDir ? { workingDir } : {}),
427
+ ...(cwdPath ? { cwdPath } : {}),
428
+ };
429
+ }
430
+ function isCodexSupportRole(role) {
431
+ return ['gpu_helper', 'renderer', 'crashpad_helper', 'helper']
432
+ .includes(String(role || '').trim().toLowerCase());
433
+ }
434
+ function classifyVsCodeRole(processName, command) {
435
+ const haystack = `${normalizeProcessText(processName)} ${normalizeProcessText(command)}`;
436
+ if (haystack.includes('jsonservermain'))
437
+ return 'json_language_server';
438
+ if (haystack.includes('markdown-language-features') || haystack.includes('serverworkermain'))
439
+ return 'markdown_language_server';
440
+ if (haystack.includes('--type=gpu-process'))
441
+ return 'gpu_helper';
442
+ if (haystack.includes('--type=renderer'))
443
+ return 'renderer';
444
+ if (haystack.includes('--utility-sub-type=network.'))
445
+ return 'network_helper';
446
+ if (haystack.includes('--utility-sub-type=node.mojom.nodeservice'))
447
+ return 'plugin_host';
448
+ if (haystack.includes('/contents/macos/code'))
449
+ return 'editor_shell';
450
+ return 'helper';
451
+ }
452
+ function extractVsCodeProcessDetails(processName, command, cwdPath) {
453
+ const role = classifyVsCodeRole(processName, command);
454
+ return {
455
+ adapter: 'vscode_v1',
456
+ role,
457
+ ...(cwdPath ? { cwdPath } : {}),
458
+ };
459
+ }
460
+ function isVsCodeSupportRole(role) {
461
+ return ['gpu_helper', 'renderer', 'network_helper', 'helper']
462
+ .includes(String(role || '').trim().toLowerCase());
463
+ }
464
+ function classifyCursorRole(processName, command) {
465
+ const haystack = `${normalizeProcessText(processName)} ${normalizeProcessText(command)}`;
466
+ if (haystack.includes('cursoruiviewservice'))
467
+ return 'ui_service';
468
+ if (haystack.includes('--type=gpu-process'))
469
+ return 'gpu_helper';
470
+ if (haystack.includes('--type=renderer'))
471
+ return 'renderer';
472
+ if (haystack.includes('/contents/macos/cursor'))
473
+ return 'editor_shell';
474
+ return 'helper';
475
+ }
476
+ function extractCursorProcessDetails(processName, command, cwdPath) {
477
+ const role = classifyCursorRole(processName, command);
478
+ return {
479
+ adapter: 'cursor_v1',
480
+ role,
481
+ ...(cwdPath ? { cwdPath } : {}),
482
+ };
483
+ }
484
+ function isCursorSupportRole(role) {
485
+ return ['ui_service', 'gpu_helper', 'renderer', 'helper']
486
+ .includes(String(role || '').trim().toLowerCase());
487
+ }
488
+ function detectActiveRuntimeTooling(repo, processEntries, options = {}) {
489
+ const toolingByKey = new Map(TOOLING_DEFINITIONS.map((definition) => [definition.key, definition]));
490
+ const accumulators = new Map();
491
+ const cwdCache = new Map();
492
+ let claudeStateCache = options.claudeState;
493
+ let claudeStateLoaded = options.claudeState !== undefined;
494
+ for (const entry of processEntries) {
495
+ const processName = String(entry.name || '').trim();
496
+ const command = String(entry.cmd || '').trim();
497
+ if (!processName && !command)
498
+ continue;
499
+ const matchedKeys = new Map();
500
+ for (const definition of TOOLING_DEFINITIONS) {
501
+ if (matchesToolProcess(definition, processName, command)) {
502
+ matchedKeys.set(definition.key, new Set(['binary']));
503
+ }
504
+ }
505
+ const agentProcess = (0, process_scout_1.classifyAgentProcessEntry)(entry);
506
+ const mappedAgentKey = agentProcess ? AGENT_SIGNATURE_TO_TOOL_KEY[agentProcess.signature] : null;
507
+ if (mappedAgentKey) {
508
+ const detectedBy = matchedKeys.get(mappedAgentKey) ?? new Set();
509
+ detectedBy.add('agent_signature');
510
+ matchedKeys.set(mappedAgentKey, detectedBy);
511
+ }
512
+ if (matchedKeys.size === 0) {
513
+ continue;
514
+ }
515
+ const commandScoped = hasRepoScopeEvidence(repo, command);
516
+ const pid = Number(entry.pid || 0);
517
+ const cwdPath = cwdCache.has(pid)
518
+ ? cwdCache.get(pid) ?? null
519
+ : (() => {
520
+ const resolved = options.processCwdLookup ? options.processCwdLookup(pid) : readProcessCwd(pid);
521
+ cwdCache.set(pid, resolved);
522
+ return resolved;
523
+ })();
524
+ const cwdScoped = isPathWithinRuntimeRoot(repo, cwdPath);
525
+ const scoped = commandScoped || cwdScoped;
526
+ const genericHelperProcess = isLowSignalHelperProcess(processName, command);
527
+ const processExample = processName || command.split(/\s+/)[0] || 'unknown';
528
+ for (const [toolKey, detectedBy] of matchedKeys.entries()) {
529
+ const definition = toolingByKey.get(toolKey);
530
+ if (!definition)
531
+ continue;
532
+ const antigravityDetails = toolKey === 'antigravity'
533
+ ? extractAntigravityProcessDetails(processName, command)
534
+ : null;
535
+ if (toolKey === 'claude' && !claudeStateLoaded) {
536
+ claudeStateCache = readClaudeLocalState();
537
+ claudeStateLoaded = true;
538
+ }
539
+ const claudeDetails = toolKey === 'claude'
540
+ ? extractClaudeProcessDetails(processName, command, cwdPath, repo, claudeStateCache ?? null)
541
+ : null;
542
+ const codexDetails = toolKey === 'codex'
543
+ ? extractCodexProcessDetails(processName, command, cwdPath)
544
+ : null;
545
+ const vscodeDetails = toolKey === 'vscode'
546
+ ? extractVsCodeProcessDetails(processName, command, cwdPath)
547
+ : null;
548
+ const cursorDetails = toolKey === 'cursor'
549
+ ? extractCursorProcessDetails(processName, command, cwdPath)
550
+ : null;
551
+ const helperProcess = genericHelperProcess
552
+ || (toolKey === 'antigravity'
553
+ && isAntigravitySupportRole(typeof antigravityDetails?.role === 'string' ? antigravityDetails.role : null))
554
+ || (toolKey === 'claude'
555
+ && isClaudeSupportRole(typeof claudeDetails?.role === 'string' ? claudeDetails.role : null))
556
+ || (toolKey === 'codex'
557
+ && isCodexSupportRole(typeof codexDetails?.role === 'string' ? codexDetails.role : null))
558
+ || (toolKey === 'vscode'
559
+ && isVsCodeSupportRole(typeof vscodeDetails?.role === 'string' ? vscodeDetails.role : null))
560
+ || (toolKey === 'cursor'
561
+ && isCursorSupportRole(typeof cursorDetails?.role === 'string' ? cursorDetails.role : null));
562
+ if (scoped) {
563
+ if (commandScoped)
564
+ detectedBy.add('repo_scope');
565
+ if (cwdScoped)
566
+ detectedBy.add('process_cwd');
567
+ }
568
+ const next = accumulators.get(toolKey) ?? {
569
+ label: definition.label,
570
+ processIds: new Set(),
571
+ activeIds: new Set(),
572
+ helperIds: new Set(),
573
+ scopedIds: new Set(),
574
+ ambientIds: new Set(),
575
+ scopedCpuPercentTotal: 0,
576
+ scopedCpuSamples: 0,
577
+ scopedMemoryMbTotal: 0,
578
+ scopedMemorySamples: 0,
579
+ detectedBy: new Set(),
580
+ exampleProcesses: [],
581
+ detailRecords: [],
582
+ };
583
+ next.processIds.add(pid);
584
+ if (helperProcess) {
585
+ next.helperIds.add(pid);
586
+ }
587
+ else {
588
+ next.activeIds.add(pid);
589
+ }
590
+ if (scoped) {
591
+ next.scopedIds.add(pid);
592
+ const scopedCpuPercent = cpuValue(entry.cpu);
593
+ if (scopedCpuPercent !== null) {
594
+ next.scopedCpuPercentTotal += scopedCpuPercent;
595
+ next.scopedCpuSamples += 1;
596
+ }
597
+ const scopedMemoryMb = bytesToMb(entry.memory);
598
+ if (scopedMemoryMb !== null) {
599
+ next.scopedMemoryMbTotal += scopedMemoryMb;
600
+ next.scopedMemorySamples += 1;
601
+ }
602
+ }
603
+ else {
604
+ next.ambientIds.add(pid);
605
+ }
606
+ for (const source of detectedBy) {
607
+ next.detectedBy.add(source);
608
+ }
609
+ if (!next.exampleProcesses.includes(processExample)) {
610
+ if (!helperProcess) {
611
+ next.exampleProcesses.unshift(processExample);
612
+ }
613
+ else {
614
+ next.exampleProcesses.push(processExample);
615
+ }
616
+ next.exampleProcesses = next.exampleProcesses.slice(0, 6);
617
+ }
618
+ if (toolKey === 'antigravity' && antigravityDetails)
619
+ next.detailRecords.push(antigravityDetails);
620
+ if (toolKey === 'claude' && claudeDetails)
621
+ next.detailRecords.push(claudeDetails);
622
+ if (toolKey === 'codex' && codexDetails)
623
+ next.detailRecords.push(codexDetails);
624
+ if (toolKey === 'vscode' && vscodeDetails)
625
+ next.detailRecords.push(vscodeDetails);
626
+ if (toolKey === 'cursor' && cursorDetails)
627
+ next.detailRecords.push(cursorDetails);
628
+ accumulators.set(toolKey, next);
629
+ }
630
+ }
631
+ return [...accumulators.entries()]
632
+ .map(([key, value]) => {
633
+ let details;
634
+ if (key === 'antigravity' && value.detailRecords.length > 0) {
635
+ const roles = uniqueSortedStrings(value.detailRecords.map((record) => String(record.role || '')));
636
+ const workspaceMarkers = uniqueSortedStrings(value.detailRecords.map((record) => typeof record.workspaceMarker === 'string' ? record.workspaceMarker : null));
637
+ const extensionServerPorts = uniqueSortedNumbers(value.detailRecords.map((record) => typeof record.extensionServerPort === 'number' ? record.extensionServerPort : null));
638
+ const httpsServerPorts = uniqueSortedNumbers(value.detailRecords.map((record) => typeof record.httpsServerPort === 'number' ? record.httpsServerPort : null));
639
+ const lspPorts = uniqueSortedNumbers(value.detailRecords.map((record) => typeof record.lspPort === 'number' ? record.lspPort : null));
640
+ const cloudCodeEndpoints = uniqueSortedStrings(value.detailRecords.map((record) => typeof record.cloudCodeEndpoint === 'string' ? record.cloudCodeEndpoint : null));
641
+ const appDataDirs = uniqueSortedStrings(value.detailRecords.map((record) => typeof record.appDataDir === 'string' ? record.appDataDir : null));
642
+ details = {
643
+ adapter: 'antigravity_v1',
644
+ roles,
645
+ ...(roles[0] ? { primaryRole: roles.includes('language_server') ? 'language_server' : roles[0] } : {}),
646
+ ...(workspaceMarkers.length > 0 ? { workspaceMarkers } : {}),
647
+ ...(extensionServerPorts.length > 0 ? { extensionServerPorts } : {}),
648
+ ...(httpsServerPorts.length > 0 ? { httpsServerPorts } : {}),
649
+ ...(lspPorts.length > 0 ? { lspPorts } : {}),
650
+ ...(cloudCodeEndpoints.length > 0 ? { cloudCodeEndpoints } : {}),
651
+ ...(appDataDirs.length > 0 ? { appDataDirs } : {}),
652
+ enableLsp: value.detailRecords.some((record) => record.enableLsp === true),
653
+ };
654
+ }
655
+ if (key === 'claude' && value.detailRecords.length > 0) {
656
+ const roles = uniqueSortedStrings(value.detailRecords.map((record) => String(record.role || '')));
657
+ const hosts = uniqueSortedStrings(value.detailRecords.map((record) => typeof record.host === 'string' ? record.host : null));
658
+ const extensionIds = uniqueSortedStrings(value.detailRecords.map((record) => typeof record.extensionId === 'string' ? record.extensionId : null));
659
+ const cwdPaths = uniqueSortedStrings(value.detailRecords.map((record) => typeof record.cwdPath === 'string' ? record.cwdPath : null));
660
+ const primaryRole = ['cli_agent', 'browser_bridge'].find((role) => roles.includes(role)) ?? roles[0] ?? null;
661
+ details = {
662
+ adapter: 'claude_v1',
663
+ roles,
664
+ ...(primaryRole ? { primaryRole } : {}),
665
+ ...(hosts.length > 0 ? { hosts } : {}),
666
+ ...(extensionIds.length > 0 ? { extensionIds } : {}),
667
+ ...(cwdPaths.length > 0 ? { cwdPaths } : {}),
668
+ trustedRuntimeRoot: value.detailRecords.some((record) => record.trustedRuntimeRoot === true),
669
+ worktreeTracked: value.detailRecords.some((record) => record.worktreeTracked === true),
670
+ };
671
+ }
672
+ if (key === 'codex' && value.detailRecords.length > 0) {
673
+ const roles = uniqueSortedStrings(value.detailRecords.map((record) => String(record.role || '')));
674
+ const hosts = uniqueSortedStrings(value.detailRecords.map((record) => typeof record.host === 'string' ? record.host : null));
675
+ const extensionIds = uniqueSortedStrings(value.detailRecords.map((record) => typeof record.extensionId === 'string' ? record.extensionId : null));
676
+ const sessionIds = uniqueSortedStrings(value.detailRecords.map((record) => typeof record.sessionId === 'string' ? record.sessionId : null));
677
+ const workingDirs = uniqueSortedStrings(value.detailRecords.map((record) => typeof record.workingDir === 'string' ? record.workingDir : null));
678
+ const cwdPaths = uniqueSortedStrings(value.detailRecords.map((record) => typeof record.cwdPath === 'string' ? record.cwdPath : null));
679
+ const primaryRole = ['desktop_kernel', 'vscode_extension_server', 'desktop_shell'].find((role) => roles.includes(role)) ?? roles[0] ?? null;
680
+ details = {
681
+ adapter: 'codex_v1',
682
+ roles,
683
+ ...(primaryRole ? { primaryRole } : {}),
684
+ ...(hosts.length > 0 ? { hosts } : {}),
685
+ ...(extensionIds.length > 0 ? { extensionIds } : {}),
686
+ ...(sessionIds.length > 0 ? { sessionIds } : {}),
687
+ ...(workingDirs.length > 0 ? { workingDirs } : {}),
688
+ ...(cwdPaths.length > 0 ? { cwdPaths } : {}),
689
+ };
690
+ }
691
+ if ((key === 'vscode' || key === 'cursor') && value.detailRecords.length > 0) {
692
+ const roles = uniqueSortedStrings(value.detailRecords.map((record) => String(record.role || '')));
693
+ const cwdPaths = uniqueSortedStrings(value.detailRecords.map((record) => typeof record.cwdPath === 'string' ? record.cwdPath : null));
694
+ const adapter = key === 'vscode' ? 'vscode_v1' : 'cursor_v1';
695
+ const preferredRoles = key === 'vscode'
696
+ ? ['json_language_server', 'markdown_language_server', 'plugin_host', 'editor_shell']
697
+ : ['editor_shell', 'ui_service'];
698
+ const primaryRole = preferredRoles.find((role) => roles.includes(role)) ?? roles[0] ?? null;
699
+ details = {
700
+ adapter,
701
+ roles,
702
+ ...(primaryRole ? { primaryRole } : {}),
703
+ ...(cwdPaths.length > 0 ? { cwdPaths } : {}),
704
+ };
705
+ }
706
+ return {
707
+ key,
708
+ label: value.label,
709
+ processCount: value.processIds.size,
710
+ activeProcessCount: value.activeIds.size,
711
+ helperProcessCount: value.helperIds.size,
712
+ scopedProcessCount: value.scopedIds.size,
713
+ ambientProcessCount: value.ambientIds.size,
714
+ detectedBy: [...value.detectedBy.values()],
715
+ exampleProcesses: value.exampleProcesses.slice(0, 4),
716
+ scoped: value.scopedIds.size > 0,
717
+ ...(value.scopedCpuSamples > 0 ? { cpuPercent: roundMetric(value.scopedCpuPercentTotal) } : {}),
718
+ ...(value.scopedMemorySamples > 0 ? { memoryMb: roundMetric(value.scopedMemoryMbTotal) } : {}),
719
+ ...(details ? { details } : {}),
720
+ };
721
+ })
722
+ .sort((left, right) => {
723
+ if (left.scoped !== right.scoped)
724
+ return left.scoped ? -1 : 1;
725
+ return right.processCount - left.processCount;
726
+ });
727
+ }
728
+ function extractObservationWorkingFolders(repo, observation) {
729
+ const details = observation.details && typeof observation.details === 'object' && !Array.isArray(observation.details)
730
+ ? observation.details
731
+ : null;
732
+ const cwdPaths = Array.isArray(details?.cwdPaths) ? details.cwdPaths : [];
733
+ const workingDirs = Array.isArray(details?.workingDirs) ? details.workingDirs : [];
734
+ const folders = [...new Set([
735
+ ...cwdPaths.map((value) => normalizeRelativeToRuntime(repo, value)),
736
+ ...workingDirs.map((value) => normalizeRelativeToRuntime(repo, value)),
737
+ ].filter((value) => Boolean(value)))].sort((left, right) => {
738
+ if (left === '.')
739
+ return -1;
740
+ if (right === '.')
741
+ return 1;
742
+ return left.localeCompare(right);
743
+ });
744
+ return folders.length > 0
745
+ ? folders.slice(0, 6)
746
+ : [runtimeRelativeEntrypoint(repo)];
747
+ }
748
+ function extractObservedFolderLabels(repo, observations) {
749
+ return [...new Set(observations.flatMap((observation) => extractObservationWorkingFolders(repo, observation)))]
750
+ .slice(0, 6);
751
+ }
752
+ function extractObservedModelLabels(observations) {
753
+ return Array.from(new Set(observations.flatMap((observation) => {
754
+ const details = observation.details && typeof observation.details === 'object' && !Array.isArray(observation.details)
755
+ ? observation.details
756
+ : null;
757
+ if (!details)
758
+ return [];
759
+ const values = [
760
+ typeof details.model === 'string' ? details.model : null,
761
+ typeof details.modelName === 'string' ? details.modelName : null,
762
+ ...(Array.isArray(details.modelLabels) ? details.modelLabels : []),
763
+ ...(Array.isArray(details.models) ? details.models : []),
764
+ ];
765
+ return values
766
+ .filter((value) => typeof value === 'string' && value.trim().length > 0)
767
+ .map((value) => value.trim())
768
+ .filter((value) => value.toLowerCase() !== 'runtime-observer-v1');
769
+ }))).slice(0, 6);
770
+ }
771
+ function buildObservationReport(observation) {
772
+ const compactDetails = observation.details && typeof observation.details === 'object' && !Array.isArray(observation.details)
773
+ ? observation.details
774
+ : null;
775
+ const parts = [];
776
+ const primaryRole = typeof compactDetails?.primaryRole === 'string' ? compactDetails.primaryRole.trim() : '';
777
+ parts.push(`${observation.label}${primaryRole ? ` is active as ${primaryRole}` : ' is active'} in this runtime`);
778
+ parts.push(`${observation.activeProcessCount} active process${observation.activeProcessCount === 1 ? '' : 'es'} observed`);
779
+ if (observation.helperProcessCount > 0) {
780
+ parts.push(`${observation.helperProcessCount} helper process${observation.helperProcessCount === 1 ? '' : 'es'} also visible`);
781
+ }
782
+ if (observation.detectedBy.length > 0) {
783
+ parts.push(`scope evidence: ${observation.detectedBy.join(', ')}`);
784
+ }
785
+ const detailFragments = [
786
+ Array.isArray(compactDetails?.hosts) ? summarizeObservationList(compactDetails.hosts, 'hosts') : null,
787
+ Array.isArray(compactDetails?.extensionIds) ? summarizeObservationList(compactDetails.extensionIds, 'extensions') : null,
788
+ Array.isArray(compactDetails?.workspaceMarkers) ? summarizeObservationList(compactDetails.workspaceMarkers, 'workspace markers', 2) : null,
789
+ Array.isArray(compactDetails?.cwdPaths) ? summarizeObservationList(compactDetails.cwdPaths, 'cwd paths', 2) : null,
790
+ Array.isArray(compactDetails?.cloudCodeEndpoints) ? summarizeObservationList(compactDetails.cloudCodeEndpoints, 'cloud endpoints', 2) : null,
791
+ Array.isArray(compactDetails?.lspPorts) ? summarizeObservationList(compactDetails.lspPorts, 'LSP ports') : null,
792
+ Array.isArray(compactDetails?.extensionServerPorts) ? summarizeObservationList(compactDetails.extensionServerPorts, 'extension server ports') : null,
793
+ Array.isArray(compactDetails?.httpsServerPorts) ? summarizeObservationList(compactDetails.httpsServerPorts, 'HTTPS ports') : null,
794
+ compactDetails?.enableLsp === true ? 'LSP is enabled' : null,
795
+ compactDetails?.trustedRuntimeRoot === true ? 'runtime root is trusted locally' : null,
796
+ compactDetails?.worktreeTracked === true ? 'worktree is tracked locally' : null,
797
+ ].filter((value) => Boolean(value));
798
+ if (detailFragments.length > 0) {
799
+ parts.push(detailFragments.join('; '));
800
+ }
801
+ const resourceParts = [];
802
+ if (typeof observation.cpuPercent === 'number' && Number.isFinite(observation.cpuPercent)) {
803
+ resourceParts.push(`CPU ${roundMetric(observation.cpuPercent)}%`);
804
+ }
805
+ if (typeof observation.memoryMb === 'number' && Number.isFinite(observation.memoryMb)) {
806
+ resourceParts.push(`memory ${roundMetric(observation.memoryMb)} MB`);
807
+ }
808
+ if (resourceParts.length > 0) {
809
+ parts.push(resourceParts.join(' · '));
810
+ }
811
+ return `${parts.join('. ')}.`;
812
+ }
813
+ function slugifyRuntimeLabel(value, maxLength = 80) {
814
+ const normalized = value
815
+ .trim()
816
+ .replace(/[\\/]+/g, '-')
817
+ .replace(/[^a-zA-Z0-9._-]+/g, '-')
818
+ .replace(/-{2,}/g, '-')
819
+ .replace(/^-+|-+$/g, '');
820
+ return (normalized || 'runtime').slice(0, maxLength);
821
+ }
822
+ function buildRuntimeObservationSteps(runId, observations, options = {}) {
823
+ const parentRunId = trimOptional(runId);
824
+ if (!parentRunId) {
825
+ throw new Error('Run id is required to build runtime observation steps.');
826
+ }
827
+ const observedAt = trimOptional(options.observedAt) ?? new Date().toISOString();
828
+ const provider = trimOptional(options.provider) ?? 'forkit-connect';
829
+ const model = trimOptional(options.model) ?? 'runtime-observer-v1';
830
+ return observations.map((observation, index) => {
831
+ const summary = buildObservationReport(observation);
832
+ const details = observation.details && typeof observation.details === 'object' && !Array.isArray(observation.details)
833
+ ? observation.details
834
+ : undefined;
835
+ return {
836
+ stepId: `${parentRunId}:observe:${slugifyRuntimeLabel(observation.key, 32)}:${index + 1}`,
837
+ parentRunId,
838
+ sequence: index,
839
+ stepKind: 'custom',
840
+ title: `${observation.label} activity observed`,
841
+ status: 'completed',
842
+ provider,
843
+ model,
844
+ startedAt: observedAt,
845
+ endedAt: observedAt,
846
+ summary,
847
+ toolCalls: [],
848
+ metadata: {
849
+ actor: {
850
+ key: observation.key,
851
+ label: observation.label,
852
+ scoped: observation.scoped,
853
+ processCount: observation.processCount,
854
+ activeProcessCount: observation.activeProcessCount,
855
+ helperProcessCount: observation.helperProcessCount,
856
+ scopedProcessCount: observation.scopedProcessCount,
857
+ ambientProcessCount: observation.ambientProcessCount,
858
+ detectedBy: observation.detectedBy,
859
+ exampleProcesses: observation.exampleProcesses.slice(0, 4),
860
+ ...(details ? { details } : {}),
861
+ },
862
+ report: summary,
863
+ },
864
+ };
865
+ });
866
+ }
867
+ //# sourceMappingURL=runtime-observer.js.map