forkit-connect 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QUICKSTART.md +10 -1
- package/README.md +12 -1
- package/dist/cli.js +774 -40
- package/dist/launcher.js +471 -73
- package/dist/resource-meter.d.ts +15 -0
- package/dist/resource-meter.js +60 -0
- package/dist/v1/api.d.ts +41 -0
- package/dist/v1/api.js +57 -0
- package/dist/v1/daemon.js +9 -0
- package/dist/v1/repo-discovery.d.ts +42 -0
- package/dist/v1/repo-discovery.js +206 -0
- package/dist/v1/runtime-activity.d.ts +55 -0
- package/dist/v1/runtime-activity.js +388 -0
- package/dist/v1/runtime-context.d.ts +18 -0
- package/dist/v1/runtime-context.js +96 -0
- package/dist/v1/runtime-editor-activity.d.ts +47 -0
- package/dist/v1/runtime-editor-activity.js +821 -0
- package/dist/v1/runtime-observation-runner.d.ts +49 -0
- package/dist/v1/runtime-observation-runner.js +508 -0
- package/dist/v1/runtime-observer.d.ts +50 -0
- package/dist/v1/runtime-observer.js +867 -0
- package/dist/v1/runtime-registration.d.ts +58 -0
- package/dist/v1/runtime-registration.js +319 -0
- package/dist/v1/service.d.ts +44 -1
- package/dist/v1/service.js +166 -11
- package/dist/v1/state.d.ts +4 -1
- package/dist/v1/state.js +28 -0
- package/dist/v1/types.d.ts +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type RuntimeObservedTool } from './runtime-observer';
|
|
2
|
+
import { type RuntimeContextRecord } from './runtime-context';
|
|
3
|
+
import type { ApiCallResult } from './api';
|
|
4
|
+
import type { ProcessListEntry } from './process-scout';
|
|
5
|
+
import type { RuntimeObserverTarget } from './types';
|
|
6
|
+
import { ConnectV1Service } from './service';
|
|
7
|
+
export interface RuntimeObservationCollection {
|
|
8
|
+
accepted: boolean;
|
|
9
|
+
reason: 'no_scoped_runtime_activity' | null;
|
|
10
|
+
gaid: string;
|
|
11
|
+
runId: string | null;
|
|
12
|
+
observedAt: string;
|
|
13
|
+
repoRoot: string;
|
|
14
|
+
relativePath: string;
|
|
15
|
+
runtimeContext: RuntimeContextRecord | null;
|
|
16
|
+
scopedObservations: RuntimeObservedTool[];
|
|
17
|
+
ambientObservations: RuntimeObservedTool[];
|
|
18
|
+
modelLabels: string[];
|
|
19
|
+
folderLabels: string[];
|
|
20
|
+
summary: string | null;
|
|
21
|
+
metadata: Record<string, unknown> | null;
|
|
22
|
+
steps: Array<Record<string, unknown>>;
|
|
23
|
+
fingerprint: string | null;
|
|
24
|
+
}
|
|
25
|
+
export interface CollectRuntimeObservationOptions {
|
|
26
|
+
gaid: string;
|
|
27
|
+
cwd: string;
|
|
28
|
+
clientName?: string | null;
|
|
29
|
+
commandLabel?: string | null;
|
|
30
|
+
processEntries?: ProcessListEntry[];
|
|
31
|
+
target?: RuntimeObserverTarget | null;
|
|
32
|
+
}
|
|
33
|
+
export declare function collectRuntimeObservation(options: CollectRuntimeObservationOptions): Promise<RuntimeObservationCollection>;
|
|
34
|
+
export declare function emitCollectedRuntimeObservation(service: ConnectV1Service, collection: RuntimeObservationCollection, options?: {
|
|
35
|
+
apiKey?: string | null;
|
|
36
|
+
}): Promise<ApiCallResult>;
|
|
37
|
+
export interface ObserveRegisteredRuntimeTargetsResult {
|
|
38
|
+
attempted: number;
|
|
39
|
+
emitted: number;
|
|
40
|
+
skipped: number;
|
|
41
|
+
idle: number;
|
|
42
|
+
failed: number;
|
|
43
|
+
checkedIn: number;
|
|
44
|
+
}
|
|
45
|
+
export declare function observeRegisteredRuntimeTargets(service: ConnectV1Service, options?: {
|
|
46
|
+
processEntries?: ProcessListEntry[];
|
|
47
|
+
minRepeatMs?: number;
|
|
48
|
+
}): Promise<ObserveRegisteredRuntimeTargetsResult>;
|
|
49
|
+
//# sourceMappingURL=runtime-observation-runner.d.ts.map
|
|
@@ -0,0 +1,508 @@
|
|
|
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.collectRuntimeObservation = collectRuntimeObservation;
|
|
7
|
+
exports.emitCollectedRuntimeObservation = emitCollectedRuntimeObservation;
|
|
8
|
+
exports.observeRegisteredRuntimeTargets = observeRegisteredRuntimeTargets;
|
|
9
|
+
const node_crypto_1 = require("node:crypto");
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const resource_meter_1 = require("../resource-meter");
|
|
12
|
+
const ps_list_loader_1 = require("../ps-list-loader");
|
|
13
|
+
const runtime_editor_activity_1 = require("./runtime-editor-activity");
|
|
14
|
+
const repo_discovery_1 = require("./repo-discovery");
|
|
15
|
+
const runtime_activity_1 = require("./runtime-activity");
|
|
16
|
+
const runtime_observer_1 = require("./runtime-observer");
|
|
17
|
+
const runtime_context_1 = require("./runtime-context");
|
|
18
|
+
function compactActiveToolDetails(details) {
|
|
19
|
+
if (!details || typeof details !== 'object' || Array.isArray(details))
|
|
20
|
+
return undefined;
|
|
21
|
+
const value = details;
|
|
22
|
+
const compact = {};
|
|
23
|
+
if (typeof value.adapter === 'string' && value.adapter.trim())
|
|
24
|
+
compact.adapter = value.adapter.trim();
|
|
25
|
+
if (typeof value.primaryRole === 'string' && value.primaryRole.trim())
|
|
26
|
+
compact.primaryRole = value.primaryRole.trim();
|
|
27
|
+
if (Array.isArray(value.hosts) && value.hosts.length > 0)
|
|
28
|
+
compact.hosts = value.hosts.slice(0, 2);
|
|
29
|
+
if (Array.isArray(value.extensionIds) && value.extensionIds.length > 0)
|
|
30
|
+
compact.extensionIds = value.extensionIds.slice(0, 2);
|
|
31
|
+
if (Array.isArray(value.workspaceMarkers) && value.workspaceMarkers.length > 0)
|
|
32
|
+
compact.workspaceMarkers = value.workspaceMarkers.slice(0, 1);
|
|
33
|
+
if (value.enableLsp === true)
|
|
34
|
+
compact.enableLsp = true;
|
|
35
|
+
if (value.trustedRuntimeRoot === true)
|
|
36
|
+
compact.trustedRuntimeRoot = true;
|
|
37
|
+
if (value.worktreeTracked === true)
|
|
38
|
+
compact.worktreeTracked = true;
|
|
39
|
+
return Object.keys(compact).length > 0 ? compact : undefined;
|
|
40
|
+
}
|
|
41
|
+
function compactObservationForMetadata(observation, mode = 'scoped') {
|
|
42
|
+
const base = {
|
|
43
|
+
key: observation.key,
|
|
44
|
+
label: observation.label,
|
|
45
|
+
scoped: observation.scoped,
|
|
46
|
+
};
|
|
47
|
+
if (mode !== 'ambient') {
|
|
48
|
+
base.processCount = observation.processCount;
|
|
49
|
+
base.activeProcessCount = observation.activeProcessCount;
|
|
50
|
+
base.helperProcessCount = observation.helperProcessCount;
|
|
51
|
+
base.scopedProcessCount = observation.scopedProcessCount;
|
|
52
|
+
base.detectedBy = observation.detectedBy;
|
|
53
|
+
}
|
|
54
|
+
const compactDetails = compactActiveToolDetails(observation.details);
|
|
55
|
+
if (compactDetails) {
|
|
56
|
+
base.details = compactDetails;
|
|
57
|
+
}
|
|
58
|
+
if (mode === 'active' && observation.exampleProcesses.length > 0) {
|
|
59
|
+
base.exampleProcesses = observation.exampleProcesses.slice(0, 2);
|
|
60
|
+
}
|
|
61
|
+
if (mode === 'ambient' && compactDetails && typeof compactDetails.primaryRole === 'string') {
|
|
62
|
+
base.primaryRole = compactDetails.primaryRole;
|
|
63
|
+
}
|
|
64
|
+
return base;
|
|
65
|
+
}
|
|
66
|
+
function hashObservationFingerprint(value) {
|
|
67
|
+
return (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(value), 'utf8').digest('hex');
|
|
68
|
+
}
|
|
69
|
+
function deriveFolderLabelsFromFileLabels(fileLabels) {
|
|
70
|
+
const labels = [];
|
|
71
|
+
const seen = new Set();
|
|
72
|
+
for (const fileLabel of fileLabels) {
|
|
73
|
+
const normalized = String(fileLabel || '').trim().replace(/\\/g, '/');
|
|
74
|
+
if (!normalized)
|
|
75
|
+
continue;
|
|
76
|
+
const folder = node_path_1.default.posix.dirname(normalized);
|
|
77
|
+
if (!folder || folder === '.' || seen.has(folder))
|
|
78
|
+
continue;
|
|
79
|
+
seen.add(folder);
|
|
80
|
+
labels.push(folder);
|
|
81
|
+
if (labels.length >= 6)
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
return labels;
|
|
85
|
+
}
|
|
86
|
+
function readRuntimeActivityResources(metadata) {
|
|
87
|
+
const activitySummary = metadata?.activitySummary;
|
|
88
|
+
const resources = activitySummary && typeof activitySummary === 'object' && !Array.isArray(activitySummary)
|
|
89
|
+
? activitySummary.resources
|
|
90
|
+
: null;
|
|
91
|
+
if (!resources || typeof resources !== 'object' || Array.isArray(resources)) {
|
|
92
|
+
return { accuracy: 'unknown', note: null };
|
|
93
|
+
}
|
|
94
|
+
const record = resources;
|
|
95
|
+
const accuracy = typeof record.accuracy === 'string' && ['reported', 'estimated', 'unknown'].includes(record.accuracy)
|
|
96
|
+
? record.accuracy
|
|
97
|
+
: 'unknown';
|
|
98
|
+
const numeric = (value) => {
|
|
99
|
+
const parsed = Number(value);
|
|
100
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
101
|
+
};
|
|
102
|
+
const result = {
|
|
103
|
+
accuracy,
|
|
104
|
+
note: typeof record.note === 'string' && record.note.trim() ? record.note.trim() : null,
|
|
105
|
+
};
|
|
106
|
+
const cpuPercent = numeric(record.cpuPercent);
|
|
107
|
+
const memoryMb = numeric(record.memoryMb);
|
|
108
|
+
const vramMb = numeric(record.vramMb);
|
|
109
|
+
if (cpuPercent !== undefined)
|
|
110
|
+
result.cpuPercent = cpuPercent;
|
|
111
|
+
if (memoryMb !== undefined)
|
|
112
|
+
result.memoryMb = memoryMb;
|
|
113
|
+
if (vramMb !== undefined)
|
|
114
|
+
result.vramMb = vramMb;
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
function buildObserverCheckinSummary(target, collection) {
|
|
118
|
+
if (collection.summary) {
|
|
119
|
+
return collection.summary;
|
|
120
|
+
}
|
|
121
|
+
return `${collection.runtimeContext?.clientName ?? target.client_name ?? node_path_1.default.basename(collection.repoRoot)} standby`;
|
|
122
|
+
}
|
|
123
|
+
async function emitRuntimeObserverCheckin(service, target, collection) {
|
|
124
|
+
const state = service.getStateStore().readState();
|
|
125
|
+
const runtimeLabel = collection.runtimeContext?.clientName ?? target.client_name ?? node_path_1.default.basename(collection.repoRoot);
|
|
126
|
+
const accepted = collection.accepted;
|
|
127
|
+
const resources = readRuntimeActivityResources(collection.metadata);
|
|
128
|
+
const metadata = {
|
|
129
|
+
safe_metadata_only: true,
|
|
130
|
+
observerMode: accepted ? 'active' : 'standby',
|
|
131
|
+
summary: buildObserverCheckinSummary(target, collection),
|
|
132
|
+
observationRuntimeRoot: collection.repoRoot,
|
|
133
|
+
observationRelativeEntrypoint: collection.relativePath,
|
|
134
|
+
...(collection.runtimeContext ? { runtimeContext: collection.runtimeContext } : {}),
|
|
135
|
+
...(collection.metadata?.projectSubject && typeof collection.metadata.projectSubject === 'object'
|
|
136
|
+
? { projectSubject: collection.metadata.projectSubject }
|
|
137
|
+
: {}),
|
|
138
|
+
...(collection.metadata?.activitySummary && typeof collection.metadata.activitySummary === 'object'
|
|
139
|
+
? { activitySummary: collection.metadata.activitySummary }
|
|
140
|
+
: {}),
|
|
141
|
+
};
|
|
142
|
+
return service.emitRuntimeObserverCheckin({
|
|
143
|
+
gaid: target.runtime_gaid,
|
|
144
|
+
sessionLabel: `${runtimeLabel} local session`,
|
|
145
|
+
environment: 'device-local',
|
|
146
|
+
...(state.runtime_identity.hostname ? { hostLabel: state.runtime_identity.hostname } : {}),
|
|
147
|
+
runtimeLabel,
|
|
148
|
+
runtimeMode: accepted ? 'active' : 'standby',
|
|
149
|
+
resourceAccuracy: resources.accuracy,
|
|
150
|
+
...(resources.note ? { resourceNote: resources.note } : {}),
|
|
151
|
+
...(resources.cpuPercent !== undefined ? { cpuPercent: resources.cpuPercent } : {}),
|
|
152
|
+
...(resources.memoryMb !== undefined ? { memoryMb: resources.memoryMb } : {}),
|
|
153
|
+
...(resources.vramMb !== undefined ? { vramMb: resources.vramMb } : {}),
|
|
154
|
+
metadata,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function normalizeRuntimeContext(repoRoot, clientName, target) {
|
|
158
|
+
if (!repoRoot)
|
|
159
|
+
return null;
|
|
160
|
+
const existing = (0, runtime_context_1.readRuntimeContext)(repoRoot);
|
|
161
|
+
if (existing) {
|
|
162
|
+
return existing;
|
|
163
|
+
}
|
|
164
|
+
return (0, runtime_context_1.buildRuntimeContextRecord)({
|
|
165
|
+
existing: target
|
|
166
|
+
? {
|
|
167
|
+
schemaVersion: 'forkit.runtime-context.v1',
|
|
168
|
+
...(target.client_name ? { clientName: target.client_name } : {}),
|
|
169
|
+
...(target.subject_id ? { subjectId: target.subject_id } : {}),
|
|
170
|
+
...(target.worktree_id ? { worktreeId: target.worktree_id } : {}),
|
|
171
|
+
}
|
|
172
|
+
: null,
|
|
173
|
+
clientName,
|
|
174
|
+
defaultLabel: node_path_1.default.basename(repoRoot),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
async function collectRuntimeObservation(options) {
|
|
178
|
+
const repo = (0, repo_discovery_1.detectRepository)(options.cwd);
|
|
179
|
+
const runtimeContext = normalizeRuntimeContext(repo.isGitRepo ? repo.rootPath : null, options.clientName ?? options.target?.client_name ?? null, options.target);
|
|
180
|
+
const processEntries = options.processEntries ?? await (0, ps_list_loader_1.listProcesses)();
|
|
181
|
+
const observations = (0, runtime_observer_1.detectActiveRuntimeTooling)(repo, processEntries);
|
|
182
|
+
const scopedObservations = observations.filter((item) => item.scoped);
|
|
183
|
+
const ambientObservations = observations.filter((item) => !item.scoped);
|
|
184
|
+
const observedAt = new Date().toISOString();
|
|
185
|
+
if (scopedObservations.length === 0) {
|
|
186
|
+
return {
|
|
187
|
+
accepted: false,
|
|
188
|
+
reason: 'no_scoped_runtime_activity',
|
|
189
|
+
gaid: options.gaid,
|
|
190
|
+
runId: null,
|
|
191
|
+
observedAt,
|
|
192
|
+
repoRoot: repo.rootPath || repo.cwd,
|
|
193
|
+
relativePath: repo.relativeCwd || '.',
|
|
194
|
+
runtimeContext,
|
|
195
|
+
scopedObservations,
|
|
196
|
+
ambientObservations,
|
|
197
|
+
modelLabels: [],
|
|
198
|
+
folderLabels: [],
|
|
199
|
+
summary: null,
|
|
200
|
+
metadata: null,
|
|
201
|
+
steps: [],
|
|
202
|
+
fingerprint: null,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const scopedKeys = new Set(scopedObservations.map((item) => item.key));
|
|
206
|
+
const codexThreads = scopedKeys.has('codex') ? (0, runtime_editor_activity_1.collectCodexEditorThreads)(repo) : [];
|
|
207
|
+
const copilotActivity = (scopedKeys.has('vscode') || scopedKeys.has('cursor'))
|
|
208
|
+
? (0, runtime_editor_activity_1.collectCopilotEditorActivity)(repo)
|
|
209
|
+
: { threads: [], modelLabels: [], fileLabels: [], scopeFileLabels: [] };
|
|
210
|
+
const claudeActivity = scopedKeys.has('claude')
|
|
211
|
+
? (0, runtime_editor_activity_1.collectClaudeProjectActivity)(repo)
|
|
212
|
+
: { threads: [], modelLabels: [], fileLabels: [], scopeFileLabels: [] };
|
|
213
|
+
const activityThreads = [
|
|
214
|
+
...codexThreads,
|
|
215
|
+
...copilotActivity.threads,
|
|
216
|
+
...claudeActivity.threads,
|
|
217
|
+
];
|
|
218
|
+
const fileLabels = claudeActivity.fileLabels;
|
|
219
|
+
const scopeFileLabels = Array.from(new Set([
|
|
220
|
+
...copilotActivity.scopeFileLabels,
|
|
221
|
+
])).slice(0, 6);
|
|
222
|
+
const folderLabels = Array.from(new Set([
|
|
223
|
+
...(0, runtime_observer_1.extractObservedFolderLabels)(repo, scopedObservations),
|
|
224
|
+
...deriveFolderLabelsFromFileLabels(fileLabels),
|
|
225
|
+
...deriveFolderLabelsFromFileLabels(scopeFileLabels),
|
|
226
|
+
])).slice(0, 6);
|
|
227
|
+
const modelLabels = Array.from(new Set([
|
|
228
|
+
...(0, runtime_observer_1.extractObservedModelLabels)(scopedObservations),
|
|
229
|
+
...copilotActivity.modelLabels,
|
|
230
|
+
...claudeActivity.modelLabels,
|
|
231
|
+
])).slice(0, 6);
|
|
232
|
+
const totalScopedCpuPercent = scopedObservations.reduce((sum, item) => sum + (typeof item.cpuPercent === 'number' ? item.cpuPercent : 0), 0);
|
|
233
|
+
const totalScopedMemoryMb = scopedObservations.reduce((sum, item) => sum + (typeof item.memoryMb === 'number' ? item.memoryMb : 0), 0);
|
|
234
|
+
const systemMetrics = await new resource_meter_1.ResourceMeter().captureMetrics();
|
|
235
|
+
const resourceAccuracy = (totalScopedCpuPercent > 0
|
|
236
|
+
|| totalScopedMemoryMb > 0
|
|
237
|
+
|| systemMetrics.vram_mb > 0) ? 'reported' : 'unknown';
|
|
238
|
+
const envelope = (0, runtime_activity_1.buildRuntimeActivityMetadata)({
|
|
239
|
+
cwd: options.cwd,
|
|
240
|
+
repo,
|
|
241
|
+
stableSubjectId: runtimeContext?.subjectId,
|
|
242
|
+
stableWorktreeId: runtimeContext?.worktreeId,
|
|
243
|
+
provider: 'forkit-connect',
|
|
244
|
+
model: modelLabels[0] ?? 'runtime-observer-v1',
|
|
245
|
+
serviceName: 'Forkit Connect Observer',
|
|
246
|
+
serviceKind: 'custom',
|
|
247
|
+
status: 'completed',
|
|
248
|
+
startedAt: observedAt,
|
|
249
|
+
endedAt: observedAt,
|
|
250
|
+
clientName: options.clientName ?? runtimeContext?.clientName ?? null,
|
|
251
|
+
commandLabel: options.commandLabel ?? 'runtime-observer',
|
|
252
|
+
includeServiceNameAsActor: false,
|
|
253
|
+
actorLabels: scopedObservations.map((item) => item.label),
|
|
254
|
+
activityThreads: activityThreads.map((thread) => ({
|
|
255
|
+
title: thread.threadLabel,
|
|
256
|
+
taskId: thread.threadId ?? null,
|
|
257
|
+
openedAt: thread.createdAt ?? null,
|
|
258
|
+
lastActiveAt: thread.lastActiveAt ?? null,
|
|
259
|
+
provider: thread.provider,
|
|
260
|
+
actorLabel: thread.actorLabel,
|
|
261
|
+
workspaceHost: thread.workspaceHost,
|
|
262
|
+
folderScope: thread.folderScope ?? null,
|
|
263
|
+
})),
|
|
264
|
+
folderLabels,
|
|
265
|
+
fileLabels,
|
|
266
|
+
scopeFileLabels,
|
|
267
|
+
modelLabels,
|
|
268
|
+
resources: {
|
|
269
|
+
...(totalScopedCpuPercent > 0 ? { cpuPercent: totalScopedCpuPercent } : {}),
|
|
270
|
+
...(totalScopedMemoryMb > 0 ? { memoryMb: totalScopedMemoryMb } : {}),
|
|
271
|
+
...(systemMetrics.vram_mb > 0 ? { vramMb: systemMetrics.vram_mb } : {}),
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
const activitySummary = envelope.metadata.activitySummary;
|
|
275
|
+
if (activitySummary && typeof activitySummary === 'object' && !Array.isArray(activitySummary)) {
|
|
276
|
+
const summaryRecord = activitySummary;
|
|
277
|
+
summaryRecord.actors = scopedObservations.map((observation) => {
|
|
278
|
+
const details = observation.details && typeof observation.details === 'object' && !Array.isArray(observation.details)
|
|
279
|
+
? observation.details
|
|
280
|
+
: null;
|
|
281
|
+
const hosts = Array.isArray(details?.hosts) ? details.hosts : [];
|
|
282
|
+
const observationModelLabels = Array.isArray(details?.modelLabels)
|
|
283
|
+
? details.modelLabels.filter((value) => typeof value === 'string' && value.trim().length > 0)
|
|
284
|
+
: [];
|
|
285
|
+
return {
|
|
286
|
+
label: observation.label,
|
|
287
|
+
name: observation.label,
|
|
288
|
+
host: typeof hosts[0] === 'string' ? hosts[0] : 'cli',
|
|
289
|
+
primaryRole: typeof details?.primaryRole === 'string' ? details.primaryRole : 'observer',
|
|
290
|
+
...(observationModelLabels[0] ? { model: observationModelLabels[0] } : {}),
|
|
291
|
+
activeProcessCount: observation.activeProcessCount,
|
|
292
|
+
helperProcessCount: observation.helperProcessCount,
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
summaryRecord.actorCount = scopedObservations.length;
|
|
296
|
+
summaryRecord.modelLabels = modelLabels;
|
|
297
|
+
summaryRecord.folderLabels = folderLabels;
|
|
298
|
+
summaryRecord.folderCount = folderLabels.length;
|
|
299
|
+
summaryRecord.resources = {
|
|
300
|
+
accuracy: resourceAccuracy,
|
|
301
|
+
...(totalScopedCpuPercent > 0 ? { cpuPercent: Math.round(totalScopedCpuPercent * 100) / 100 } : {}),
|
|
302
|
+
...(totalScopedMemoryMb > 0 ? { memoryMb: Math.round(totalScopedMemoryMb * 100) / 100 } : {}),
|
|
303
|
+
...(systemMetrics.vram_mb > 0 ? { vramMb: systemMetrics.vram_mb } : {}),
|
|
304
|
+
note: [
|
|
305
|
+
totalScopedCpuPercent > 0 || totalScopedMemoryMb > 0
|
|
306
|
+
? 'CPU and memory are aggregated from repo-scoped process samples.'
|
|
307
|
+
: null,
|
|
308
|
+
systemMetrics.vram_mb > 0
|
|
309
|
+
? 'VRAM is sampled from the local GPU device.'
|
|
310
|
+
: null,
|
|
311
|
+
].filter(Boolean).join(' '),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const runId = `runtime-observer-${Date.now()}`;
|
|
315
|
+
const steps = [
|
|
316
|
+
...envelope.steps,
|
|
317
|
+
...(0, runtime_observer_1.buildRuntimeObservationSteps)(runId, scopedObservations, {
|
|
318
|
+
observedAt,
|
|
319
|
+
provider: 'forkit-connect',
|
|
320
|
+
model: modelLabels[0] ?? 'runtime-observer-v1',
|
|
321
|
+
}),
|
|
322
|
+
];
|
|
323
|
+
const metadata = {
|
|
324
|
+
...envelope.metadata,
|
|
325
|
+
safe_metadata_only: true,
|
|
326
|
+
observedTooling: scopedObservations.map((item) => compactObservationForMetadata(item, 'active')),
|
|
327
|
+
ambientTooling: ambientObservations.map((item) => compactObservationForMetadata(item, 'ambient')).slice(0, 3),
|
|
328
|
+
observedToolCount: observations.length,
|
|
329
|
+
scopedToolCount: scopedObservations.length,
|
|
330
|
+
ambientToolCount: ambientObservations.length,
|
|
331
|
+
observationRuntimeRoot: repo.rootPath || repo.cwd,
|
|
332
|
+
observationRelativeEntrypoint: repo.relativeCwd || '.',
|
|
333
|
+
};
|
|
334
|
+
const fingerprint = hashObservationFingerprint({
|
|
335
|
+
repoRoot: repo.rootPath || repo.cwd,
|
|
336
|
+
relativePath: repo.relativeCwd || '.',
|
|
337
|
+
subjectId: runtimeContext?.subjectId ?? null,
|
|
338
|
+
worktreeId: runtimeContext?.worktreeId ?? null,
|
|
339
|
+
scopedObservations: scopedObservations.map((item) => compactObservationForMetadata(item, 'active')),
|
|
340
|
+
threads: activityThreads.map((thread) => ({
|
|
341
|
+
provider: thread.provider,
|
|
342
|
+
actorLabel: thread.actorLabel,
|
|
343
|
+
threadLabel: thread.threadLabel,
|
|
344
|
+
threadId: thread.threadId ?? null,
|
|
345
|
+
lastActiveAt: thread.lastActiveAt ?? null,
|
|
346
|
+
})),
|
|
347
|
+
modelLabels,
|
|
348
|
+
folderLabels,
|
|
349
|
+
fileLabels,
|
|
350
|
+
scopeFileLabels,
|
|
351
|
+
});
|
|
352
|
+
return {
|
|
353
|
+
accepted: true,
|
|
354
|
+
reason: null,
|
|
355
|
+
gaid: options.gaid,
|
|
356
|
+
runId,
|
|
357
|
+
observedAt,
|
|
358
|
+
repoRoot: repo.rootPath || repo.cwd,
|
|
359
|
+
relativePath: repo.relativeCwd || '.',
|
|
360
|
+
runtimeContext,
|
|
361
|
+
scopedObservations,
|
|
362
|
+
ambientObservations,
|
|
363
|
+
modelLabels,
|
|
364
|
+
folderLabels,
|
|
365
|
+
summary: envelope.summary,
|
|
366
|
+
metadata,
|
|
367
|
+
steps,
|
|
368
|
+
fingerprint,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
async function emitCollectedRuntimeObservation(service, collection, options) {
|
|
372
|
+
return await service.emitRuntimeRunLog({
|
|
373
|
+
gaid: collection.gaid,
|
|
374
|
+
apiKey: options?.apiKey ?? null,
|
|
375
|
+
provider: 'forkit-connect',
|
|
376
|
+
model: collection.modelLabels[0] ?? 'runtime-observer-v1',
|
|
377
|
+
serviceName: 'Forkit Connect Observer',
|
|
378
|
+
serviceKind: 'custom',
|
|
379
|
+
runId: collection.runId ?? `runtime-observer-${Date.now()}`,
|
|
380
|
+
status: 'completed',
|
|
381
|
+
startedAt: collection.observedAt,
|
|
382
|
+
endedAt: collection.observedAt,
|
|
383
|
+
summary: collection.summary,
|
|
384
|
+
metadata: collection.metadata,
|
|
385
|
+
steps: collection.steps,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
async function observeRegisteredRuntimeTargets(service, options) {
|
|
389
|
+
const targets = service.listRuntimeObserverTargets();
|
|
390
|
+
if (targets.length === 0) {
|
|
391
|
+
return { attempted: 0, emitted: 0, skipped: 0, idle: 0, failed: 0, checkedIn: 0 };
|
|
392
|
+
}
|
|
393
|
+
const processEntries = options?.processEntries ?? await (0, ps_list_loader_1.listProcesses)();
|
|
394
|
+
const minRepeatMs = Math.max(60_000, options?.minRepeatMs ?? 5 * 60 * 1000);
|
|
395
|
+
let emitted = 0;
|
|
396
|
+
let skipped = 0;
|
|
397
|
+
let idle = 0;
|
|
398
|
+
let failed = 0;
|
|
399
|
+
let checkedIn = 0;
|
|
400
|
+
for (const target of targets) {
|
|
401
|
+
try {
|
|
402
|
+
const collection = await collectRuntimeObservation({
|
|
403
|
+
gaid: target.runtime_gaid,
|
|
404
|
+
cwd: target.repo_root,
|
|
405
|
+
clientName: target.client_name,
|
|
406
|
+
commandLabel: 'daemon-observer',
|
|
407
|
+
processEntries,
|
|
408
|
+
target,
|
|
409
|
+
});
|
|
410
|
+
const persistedTarget = service.registerRuntimeObserverTarget({
|
|
411
|
+
gaid: target.runtime_gaid,
|
|
412
|
+
repoRoot: collection.repoRoot,
|
|
413
|
+
clientName: collection.runtimeContext?.clientName ?? target.client_name,
|
|
414
|
+
subjectId: collection.runtimeContext?.subjectId ?? target.subject_id,
|
|
415
|
+
worktreeId: collection.runtimeContext?.worktreeId ?? target.worktree_id,
|
|
416
|
+
});
|
|
417
|
+
const checkinResult = await emitRuntimeObserverCheckin(service, persistedTarget, collection);
|
|
418
|
+
const checkinBodyRecord = checkinResult.body && typeof checkinResult.body === 'object' && !Array.isArray(checkinResult.body)
|
|
419
|
+
? checkinResult.body
|
|
420
|
+
: null;
|
|
421
|
+
if (!checkinResult.ok) {
|
|
422
|
+
service.updateRuntimeObserverTargetResult({
|
|
423
|
+
gaid: target.runtime_gaid,
|
|
424
|
+
observedAt: collection.observedAt,
|
|
425
|
+
summary: buildObserverCheckinSummary(persistedTarget, collection),
|
|
426
|
+
error: typeof checkinResult.body === 'string'
|
|
427
|
+
? checkinResult.body
|
|
428
|
+
: checkinBodyRecord && typeof checkinBodyRecord.error === 'string'
|
|
429
|
+
? checkinBodyRecord.error
|
|
430
|
+
: `runtime_observer_checkin_failed_${checkinResult.status || 'local'}`,
|
|
431
|
+
});
|
|
432
|
+
failed += 1;
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
checkedIn += 1;
|
|
436
|
+
if (!collection.accepted) {
|
|
437
|
+
service.updateRuntimeObserverTargetResult({
|
|
438
|
+
gaid: target.runtime_gaid,
|
|
439
|
+
observedAt: collection.observedAt,
|
|
440
|
+
summary: buildObserverCheckinSummary(persistedTarget, collection),
|
|
441
|
+
error: null,
|
|
442
|
+
});
|
|
443
|
+
idle += 1;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
const previousEmittedAt = persistedTarget.last_emitted_at ? Date.parse(persistedTarget.last_emitted_at) : NaN;
|
|
447
|
+
const nextObservedAt = Date.parse(collection.observedAt);
|
|
448
|
+
const unchanged = persistedTarget.last_fingerprint && collection.fingerprint && persistedTarget.last_fingerprint === collection.fingerprint;
|
|
449
|
+
const withinCooldown = Number.isFinite(previousEmittedAt) && Number.isFinite(nextObservedAt) && (nextObservedAt - previousEmittedAt) < minRepeatMs;
|
|
450
|
+
if (unchanged && withinCooldown) {
|
|
451
|
+
service.updateRuntimeObserverTargetResult({
|
|
452
|
+
gaid: target.runtime_gaid,
|
|
453
|
+
observedAt: collection.observedAt,
|
|
454
|
+
fingerprint: collection.fingerprint,
|
|
455
|
+
summary: collection.summary,
|
|
456
|
+
error: null,
|
|
457
|
+
});
|
|
458
|
+
skipped += 1;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const result = await emitCollectedRuntimeObservation(service, collection);
|
|
462
|
+
const bodyRecord = result.body && typeof result.body === 'object' && !Array.isArray(result.body)
|
|
463
|
+
? result.body
|
|
464
|
+
: null;
|
|
465
|
+
if (!result.ok) {
|
|
466
|
+
service.updateRuntimeObserverTargetResult({
|
|
467
|
+
gaid: target.runtime_gaid,
|
|
468
|
+
observedAt: collection.observedAt,
|
|
469
|
+
fingerprint: collection.fingerprint,
|
|
470
|
+
summary: collection.summary,
|
|
471
|
+
error: typeof result.body === 'string'
|
|
472
|
+
? result.body
|
|
473
|
+
: bodyRecord && typeof bodyRecord.error === 'string'
|
|
474
|
+
? bodyRecord.error
|
|
475
|
+
: `runtime_observer_emit_failed_${result.status || 'local'}`,
|
|
476
|
+
});
|
|
477
|
+
failed += 1;
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
service.updateRuntimeObserverTargetResult({
|
|
481
|
+
gaid: target.runtime_gaid,
|
|
482
|
+
observedAt: collection.observedAt,
|
|
483
|
+
emittedAt: collection.observedAt,
|
|
484
|
+
fingerprint: collection.fingerprint,
|
|
485
|
+
summary: collection.summary,
|
|
486
|
+
error: null,
|
|
487
|
+
});
|
|
488
|
+
emitted += 1;
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
service.updateRuntimeObserverTargetResult({
|
|
492
|
+
gaid: target.runtime_gaid,
|
|
493
|
+
observedAt: new Date().toISOString(),
|
|
494
|
+
error: error instanceof Error ? error.message : 'runtime_observer_failed',
|
|
495
|
+
});
|
|
496
|
+
failed += 1;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
attempted: targets.length,
|
|
501
|
+
emitted,
|
|
502
|
+
skipped,
|
|
503
|
+
idle,
|
|
504
|
+
failed,
|
|
505
|
+
checkedIn,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
//# sourceMappingURL=runtime-observation-runner.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { RepoDetectionResult } from './repo-discovery';
|
|
2
|
+
import { type ProcessListEntry } from './process-scout';
|
|
3
|
+
export type RuntimeObservedToolKey = 'antigravity' | 'cursor' | 'vscode' | 'claude' | 'anaconda' | 'jupyter' | 'codex';
|
|
4
|
+
export interface RuntimeObservedTool {
|
|
5
|
+
key: RuntimeObservedToolKey;
|
|
6
|
+
label: string;
|
|
7
|
+
processCount: number;
|
|
8
|
+
activeProcessCount: number;
|
|
9
|
+
helperProcessCount: number;
|
|
10
|
+
scopedProcessCount: number;
|
|
11
|
+
ambientProcessCount: number;
|
|
12
|
+
detectedBy: Array<'binary' | 'agent_signature' | 'repo_scope' | 'process_cwd'>;
|
|
13
|
+
exampleProcesses: string[];
|
|
14
|
+
scoped: boolean;
|
|
15
|
+
cpuPercent?: number | null;
|
|
16
|
+
memoryMb?: number | null;
|
|
17
|
+
details?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
export interface RuntimeObservationStepLog extends Record<string, unknown> {
|
|
20
|
+
stepId: string;
|
|
21
|
+
parentRunId: string;
|
|
22
|
+
sequence: number;
|
|
23
|
+
stepKind: 'custom';
|
|
24
|
+
title: string;
|
|
25
|
+
status: 'completed';
|
|
26
|
+
provider: string;
|
|
27
|
+
model: string;
|
|
28
|
+
startedAt: string;
|
|
29
|
+
endedAt: string;
|
|
30
|
+
summary: string;
|
|
31
|
+
toolCalls: [];
|
|
32
|
+
metadata: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
interface ClaudeLocalState {
|
|
35
|
+
trustedFolders: string[];
|
|
36
|
+
trackedWorktreePaths: string[];
|
|
37
|
+
}
|
|
38
|
+
export declare function detectActiveRuntimeTooling(repo: RepoDetectionResult, processEntries: ProcessListEntry[], options?: {
|
|
39
|
+
processCwdLookup?: (pid: number) => string | null;
|
|
40
|
+
claudeState?: ClaudeLocalState | null;
|
|
41
|
+
}): RuntimeObservedTool[];
|
|
42
|
+
export declare function extractObservedFolderLabels(repo: RepoDetectionResult, observations: RuntimeObservedTool[]): string[];
|
|
43
|
+
export declare function extractObservedModelLabels(observations: RuntimeObservedTool[]): string[];
|
|
44
|
+
export declare function buildRuntimeObservationSteps(runId: string, observations: RuntimeObservedTool[], options?: {
|
|
45
|
+
observedAt?: string | null;
|
|
46
|
+
provider?: string | null;
|
|
47
|
+
model?: string | null;
|
|
48
|
+
}): RuntimeObservationStepLog[];
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=runtime-observer.d.ts.map
|