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,388 @@
|
|
|
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.buildRuntimeActivityMetadata = buildRuntimeActivityMetadata;
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const MAX_ACTIVITY_ACTORS = 6;
|
|
11
|
+
const MAX_ACTIVITY_TASKS = 6;
|
|
12
|
+
const MAX_ACTIVITY_FILES = 12;
|
|
13
|
+
const MAX_ACTIVITY_FOLDERS = 6;
|
|
14
|
+
const MAX_LABEL_LENGTH = 160;
|
|
15
|
+
const MAX_TASK_TITLE_LENGTH = 120;
|
|
16
|
+
const MAX_SUMMARY_LENGTH = 4000;
|
|
17
|
+
const RUNTIME_PROJECT_SUBJECT_VERSION = 'runtime.subject.v1';
|
|
18
|
+
function normalizeLabel(value, maxLength = MAX_LABEL_LENGTH) {
|
|
19
|
+
const normalized = String(value || '').trim().replace(/\s+/g, ' ');
|
|
20
|
+
if (!normalized)
|
|
21
|
+
return null;
|
|
22
|
+
return normalized.slice(0, maxLength);
|
|
23
|
+
}
|
|
24
|
+
function dedupeBoundedLabels(values, maxItems, maxLength = MAX_LABEL_LENGTH) {
|
|
25
|
+
const result = [];
|
|
26
|
+
const seen = new Set();
|
|
27
|
+
for (const value of values) {
|
|
28
|
+
const normalized = normalizeLabel(value, maxLength);
|
|
29
|
+
if (!normalized)
|
|
30
|
+
continue;
|
|
31
|
+
const key = normalized.toLowerCase();
|
|
32
|
+
if (seen.has(key))
|
|
33
|
+
continue;
|
|
34
|
+
seen.add(key);
|
|
35
|
+
result.push(normalized);
|
|
36
|
+
if (result.length >= maxItems)
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
function readFiniteNumber(value) {
|
|
42
|
+
const numeric = typeof value === 'number' ? value : Number(value);
|
|
43
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
44
|
+
}
|
|
45
|
+
function normalizeRelativeLabel(value, cwd, rootPath, entrypointRelativePath) {
|
|
46
|
+
const raw = String(value || '').trim();
|
|
47
|
+
if (!raw)
|
|
48
|
+
return null;
|
|
49
|
+
const normalizedRaw = raw.replace(/\\/g, '/');
|
|
50
|
+
const preferRepoRoot = Boolean(rootPath
|
|
51
|
+
&& entrypointRelativePath
|
|
52
|
+
&& (normalizedRaw === entrypointRelativePath || normalizedRaw.startsWith(`${entrypointRelativePath}/`)));
|
|
53
|
+
const resolved = (() => {
|
|
54
|
+
if (node_path_1.default.isAbsolute(raw)) {
|
|
55
|
+
return node_path_1.default.resolve(raw);
|
|
56
|
+
}
|
|
57
|
+
const cwdCandidate = node_path_1.default.resolve(cwd, raw);
|
|
58
|
+
const repoCandidate = rootPath ? node_path_1.default.resolve(rootPath, raw) : null;
|
|
59
|
+
if (repoCandidate && node_fs_1.default.existsSync(repoCandidate) && !node_fs_1.default.existsSync(cwdCandidate)) {
|
|
60
|
+
return repoCandidate;
|
|
61
|
+
}
|
|
62
|
+
if (node_fs_1.default.existsSync(cwdCandidate) && (!repoCandidate || !node_fs_1.default.existsSync(repoCandidate))) {
|
|
63
|
+
return cwdCandidate;
|
|
64
|
+
}
|
|
65
|
+
if (preferRepoRoot && repoCandidate) {
|
|
66
|
+
return repoCandidate;
|
|
67
|
+
}
|
|
68
|
+
return cwdCandidate;
|
|
69
|
+
})();
|
|
70
|
+
if (rootPath) {
|
|
71
|
+
const relative = node_path_1.default.relative(rootPath, resolved).replace(/\\/g, '/');
|
|
72
|
+
if (!relative || relative.startsWith('..'))
|
|
73
|
+
return null;
|
|
74
|
+
return normalizeLabel(relative);
|
|
75
|
+
}
|
|
76
|
+
return normalizeLabel(node_path_1.default.basename(resolved));
|
|
77
|
+
}
|
|
78
|
+
function deriveFolderLabelsFromFiles(files) {
|
|
79
|
+
const folders = [];
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
for (const file of files) {
|
|
82
|
+
const dirname = node_path_1.default.posix.dirname(file);
|
|
83
|
+
if (!dirname || dirname === '.' || seen.has(dirname))
|
|
84
|
+
continue;
|
|
85
|
+
seen.add(dirname);
|
|
86
|
+
folders.push(dirname);
|
|
87
|
+
if (folders.length >= MAX_ACTIVITY_FOLDERS)
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
return folders;
|
|
91
|
+
}
|
|
92
|
+
function collapseFolderLabels(labels) {
|
|
93
|
+
const result = [];
|
|
94
|
+
for (const label of labels) {
|
|
95
|
+
if (result.some((existing) => label === existing || label.startsWith(`${existing}/`))) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
result.push(label);
|
|
99
|
+
if (result.length >= MAX_ACTIVITY_FOLDERS)
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
function buildWorktreeId(rootPath, repoSlug) {
|
|
105
|
+
return (0, node_crypto_1.createHash)('sha256')
|
|
106
|
+
.update(`${repoSlug || 'local'}:${rootPath}`)
|
|
107
|
+
.digest('hex')
|
|
108
|
+
.slice(0, 16);
|
|
109
|
+
}
|
|
110
|
+
function hashHex(value) {
|
|
111
|
+
return (0, node_crypto_1.createHash)('sha256').update(value, 'utf8').digest('hex');
|
|
112
|
+
}
|
|
113
|
+
function slugifyLabel(value, maxLength = 80) {
|
|
114
|
+
const normalized = value
|
|
115
|
+
.trim()
|
|
116
|
+
.replace(/[\\/]+/g, '-')
|
|
117
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '-')
|
|
118
|
+
.replace(/-{2,}/g, '-')
|
|
119
|
+
.replace(/^-+|-+$/g, '');
|
|
120
|
+
return (normalized || 'runtime').slice(0, maxLength);
|
|
121
|
+
}
|
|
122
|
+
function fallbackTaskLabel(summary) {
|
|
123
|
+
const normalized = normalizeLabel(summary, MAX_TASK_TITLE_LENGTH);
|
|
124
|
+
if (!normalized)
|
|
125
|
+
return [];
|
|
126
|
+
return [normalized];
|
|
127
|
+
}
|
|
128
|
+
function normalizeActivityThreads(threads) {
|
|
129
|
+
if (!Array.isArray(threads))
|
|
130
|
+
return [];
|
|
131
|
+
const result = [];
|
|
132
|
+
const seen = new Set();
|
|
133
|
+
for (const thread of threads) {
|
|
134
|
+
if (!thread || typeof thread !== 'object')
|
|
135
|
+
continue;
|
|
136
|
+
const title = normalizeLabel(thread.title, MAX_TASK_TITLE_LENGTH);
|
|
137
|
+
if (!title)
|
|
138
|
+
continue;
|
|
139
|
+
const taskId = normalizeLabel(thread.taskId, 64);
|
|
140
|
+
const key = `${taskId || title}`.toLowerCase();
|
|
141
|
+
if (seen.has(key))
|
|
142
|
+
continue;
|
|
143
|
+
seen.add(key);
|
|
144
|
+
result.push({
|
|
145
|
+
title,
|
|
146
|
+
...(taskId ? { taskId } : {}),
|
|
147
|
+
...(normalizeLabel(thread.openedAt, 64) ? { openedAt: normalizeLabel(thread.openedAt, 64) } : {}),
|
|
148
|
+
...(normalizeLabel(thread.lastActiveAt, 64) ? { lastActiveAt: normalizeLabel(thread.lastActiveAt, 64) } : {}),
|
|
149
|
+
...(normalizeLabel(thread.provider, 40) ? { provider: normalizeLabel(thread.provider, 40) } : {}),
|
|
150
|
+
...(normalizeLabel(thread.actorLabel, 40) ? { actorLabel: normalizeLabel(thread.actorLabel, 40) } : {}),
|
|
151
|
+
...(normalizeLabel(thread.workspaceHost, 20) ? { workspaceHost: normalizeLabel(thread.workspaceHost, 20) } : {}),
|
|
152
|
+
...(normalizeLabel(thread.folderScope, 96) ? { folderScope: normalizeLabel(thread.folderScope, 96) } : {}),
|
|
153
|
+
});
|
|
154
|
+
if (result.length >= MAX_ACTIVITY_TASKS)
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
function buildStepTitle(actorLabels, taskLabels) {
|
|
160
|
+
const firstTask = taskLabels[0];
|
|
161
|
+
if (taskLabels.length === 1 && firstTask)
|
|
162
|
+
return firstTask;
|
|
163
|
+
const firstActor = actorLabels[0];
|
|
164
|
+
if (actorLabels.length === 1 && firstActor)
|
|
165
|
+
return `${firstActor} activity observed`.slice(0, 255);
|
|
166
|
+
return 'Runtime activity observed';
|
|
167
|
+
}
|
|
168
|
+
function buildNarrativeSummary(input) {
|
|
169
|
+
const lead = input.actorLabels.length === 1
|
|
170
|
+
? `${input.actorLabels[0]} active`
|
|
171
|
+
: input.actorLabels.length > 1
|
|
172
|
+
? `${input.actorLabels.length} actors active`
|
|
173
|
+
: null;
|
|
174
|
+
const taskPart = input.taskLabels.length === 1
|
|
175
|
+
? input.taskLabels[0]
|
|
176
|
+
: input.taskLabels.length > 1
|
|
177
|
+
? `${input.taskLabels.length} tasks`
|
|
178
|
+
: null;
|
|
179
|
+
const repoPart = input.repoRootLabel ? `in ${input.repoRootLabel}` : null;
|
|
180
|
+
const filePart = input.fileCount > 0 ? `${input.fileCount} files touched` : null;
|
|
181
|
+
const scopeFilePart = input.fileCount === 0 && input.scopeFileCount > 0 ? `${input.scopeFileCount} files in scope` : null;
|
|
182
|
+
const modelPart = input.modelLabels.length === 1
|
|
183
|
+
? `model ${input.modelLabels[0]}`
|
|
184
|
+
: input.modelLabels.length > 1
|
|
185
|
+
? `${input.modelLabels.length} models`
|
|
186
|
+
: null;
|
|
187
|
+
const summary = [lead, taskPart, repoPart, filePart || scopeFilePart, modelPart]
|
|
188
|
+
.filter((value) => Boolean(value))
|
|
189
|
+
.join(' · ')
|
|
190
|
+
.slice(0, MAX_SUMMARY_LENGTH);
|
|
191
|
+
return summary || null;
|
|
192
|
+
}
|
|
193
|
+
function buildRuntimeActivityMetadata(input) {
|
|
194
|
+
const repoRootLabel = input.repo.rootPath ? normalizeLabel(node_path_1.default.basename(input.repo.rootPath)) : null;
|
|
195
|
+
const entrypointRelativePath = normalizeLabel(input.repo.relativeCwd || '.', 255) || '.';
|
|
196
|
+
const normalizedThreads = normalizeActivityThreads(input.activityThreads);
|
|
197
|
+
const explicitFiles = dedupeBoundedLabels((input.fileLabels || [])
|
|
198
|
+
.map((value) => normalizeRelativeLabel(value, input.cwd, input.repo.rootPath, input.repo.relativeCwd))
|
|
199
|
+
.filter((value) => Boolean(value)), MAX_ACTIVITY_FILES);
|
|
200
|
+
const allFiles = explicitFiles;
|
|
201
|
+
const scopeFiles = dedupeBoundedLabels((input.scopeFileLabels || [])
|
|
202
|
+
.map((value) => normalizeRelativeLabel(value, input.cwd, input.repo.rootPath, input.repo.relativeCwd))
|
|
203
|
+
.filter((value) => Boolean(value)), MAX_ACTIVITY_FILES);
|
|
204
|
+
const explicitFolders = dedupeBoundedLabels((input.folderLabels || [])
|
|
205
|
+
.map((value) => normalizeRelativeLabel(value, input.cwd, input.repo.rootPath, input.repo.relativeCwd))
|
|
206
|
+
.filter((value) => Boolean(value)), MAX_ACTIVITY_FOLDERS);
|
|
207
|
+
const derivedFolders = explicitFolders.length > 0
|
|
208
|
+
? explicitFolders
|
|
209
|
+
: dedupeBoundedLabels([
|
|
210
|
+
input.repo.relativeCwd,
|
|
211
|
+
...deriveFolderLabelsFromFiles(explicitFiles),
|
|
212
|
+
], MAX_ACTIVITY_FOLDERS);
|
|
213
|
+
const folderLabels = collapseFolderLabels(explicitFolders.length > 0 ? explicitFolders : derivedFolders);
|
|
214
|
+
const actorLabels = dedupeBoundedLabels([
|
|
215
|
+
...(input.actorLabels || []),
|
|
216
|
+
...(input.includeServiceNameAsActor === false ? [] : [input.serviceName]),
|
|
217
|
+
], MAX_ACTIVITY_ACTORS);
|
|
218
|
+
const taskLabels = dedupeBoundedLabels((input.taskLabels && input.taskLabels.length > 0
|
|
219
|
+
? input.taskLabels
|
|
220
|
+
: normalizedThreads.length > 0
|
|
221
|
+
? normalizedThreads.map((thread) => thread.title)
|
|
222
|
+
: fallbackTaskLabel(input.summary ?? null)).map((value) => normalizeLabel(value, MAX_TASK_TITLE_LENGTH)), MAX_ACTIVITY_TASKS, MAX_TASK_TITLE_LENGTH);
|
|
223
|
+
const modelLabels = dedupeBoundedLabels([...(input.modelLabels || []), input.model], MAX_ACTIVITY_TASKS);
|
|
224
|
+
const fileCount = explicitFiles.length;
|
|
225
|
+
const scopeFileCount = scopeFiles.length;
|
|
226
|
+
const activitySummary = buildNarrativeSummary({
|
|
227
|
+
actorLabels,
|
|
228
|
+
taskLabels,
|
|
229
|
+
fileCount,
|
|
230
|
+
scopeFileCount,
|
|
231
|
+
repoRootLabel,
|
|
232
|
+
modelLabels,
|
|
233
|
+
});
|
|
234
|
+
const metadata = {
|
|
235
|
+
sourceSurface: 'connect_assisted',
|
|
236
|
+
};
|
|
237
|
+
if (input.repo.isGitRepo && input.repo.rootPath && input.repo.headCommit) {
|
|
238
|
+
const repoRootFingerprint = hashHex(`repo-root:${input.repo.rootPath}`);
|
|
239
|
+
const preferredRemote = input.repo.preferredRemote?.fetchUrl || input.repo.preferredRemote?.pushUrl || input.repo.repoSlug || '';
|
|
240
|
+
const repoRemoteFingerprint = preferredRemote ? hashHex(`repo-remote:${preferredRemote}`) : undefined;
|
|
241
|
+
const canonicalWorktreeId = normalizeLabel(input.stableWorktreeId, 120)
|
|
242
|
+
|| slugifyLabel(`${repoRootLabel || 'runtime'}-${buildWorktreeId(input.repo.rootPath, input.repo.repoSlug)}`, 120);
|
|
243
|
+
const subjectSeed = [repoRootFingerprint, canonicalWorktreeId, entrypointRelativePath].join('|');
|
|
244
|
+
const subjectId = normalizeLabel(input.stableSubjectId, 160)
|
|
245
|
+
|| `subj_${slugifyLabel(repoRootLabel || 'runtime', 48)}_${hashHex(subjectSeed).slice(0, 12)}`;
|
|
246
|
+
metadata.projectSubject = {
|
|
247
|
+
schemaVersion: RUNTIME_PROJECT_SUBJECT_VERSION,
|
|
248
|
+
subjectType: 'git_worktree',
|
|
249
|
+
subjectId,
|
|
250
|
+
subjectLabel: normalizeLabel(input.clientName || repoRootLabel || 'runtime', 160),
|
|
251
|
+
repoRootName: normalizeLabel(repoRootLabel || 'runtime', 160),
|
|
252
|
+
repoRootFingerprint,
|
|
253
|
+
...(repoRemoteFingerprint ? { repoRemoteFingerprint } : {}),
|
|
254
|
+
worktreeId: canonicalWorktreeId,
|
|
255
|
+
branch: normalizeLabel(input.repo.branch || 'detached', 160),
|
|
256
|
+
commit: normalizeLabel(input.repo.headCommit, 64),
|
|
257
|
+
dirty: input.repo.dirty === true,
|
|
258
|
+
entrypointRelativePath,
|
|
259
|
+
commandLabel: normalizeLabel(input.commandLabel || `${input.serviceName} session`, 255),
|
|
260
|
+
sourceSurface: 'connect_assisted',
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (input.clientName) {
|
|
264
|
+
metadata.runtimeContext = {
|
|
265
|
+
clientName: normalizeLabel(input.clientName),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (fileCount > 0) {
|
|
269
|
+
metadata.fileImpact = {
|
|
270
|
+
count: fileCount,
|
|
271
|
+
files: allFiles,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const resourceSummary = {};
|
|
275
|
+
const cpuPercent = readFiniteNumber(input.resources?.cpuPercent);
|
|
276
|
+
const memoryMb = readFiniteNumber(input.resources?.memoryMb);
|
|
277
|
+
const vramMb = readFiniteNumber(input.resources?.vramMb);
|
|
278
|
+
if (cpuPercent !== null && cpuPercent >= 0)
|
|
279
|
+
resourceSummary.cpuPercent = cpuPercent;
|
|
280
|
+
if (memoryMb !== null && memoryMb >= 0)
|
|
281
|
+
resourceSummary.memoryMb = memoryMb;
|
|
282
|
+
if (vramMb !== null && vramMb >= 0)
|
|
283
|
+
resourceSummary.vramMb = vramMb;
|
|
284
|
+
metadata.activitySummary = {
|
|
285
|
+
actorCount: actorLabels.length,
|
|
286
|
+
taskCount: taskLabels.length,
|
|
287
|
+
folderCount: folderLabels.length || (repoRootLabel ? 1 : 0),
|
|
288
|
+
...(fileCount > 0 ? { fileCount } : {}),
|
|
289
|
+
...(scopeFileCount > 0 ? { scopeFileCount } : {}),
|
|
290
|
+
...(modelLabels.length > 0 ? { modelLabels } : {}),
|
|
291
|
+
...(folderLabels.length > 0 ? { folderLabels } : {}),
|
|
292
|
+
...(allFiles.length > 0 ? { fileLabels: allFiles } : {}),
|
|
293
|
+
...(scopeFiles.length > 0 ? { scopeFileLabels: scopeFiles } : {}),
|
|
294
|
+
actors: actorLabels.map((label) => ({
|
|
295
|
+
label,
|
|
296
|
+
name: label,
|
|
297
|
+
host: 'cli',
|
|
298
|
+
primaryRole: normalizeLabel(input.serviceKind || 'custom'),
|
|
299
|
+
...(modelLabels[0] ? { model: modelLabels[0] } : {}),
|
|
300
|
+
activeProcessCount: 1,
|
|
301
|
+
helperProcessCount: 0,
|
|
302
|
+
})),
|
|
303
|
+
threads: (normalizedThreads.length > 0
|
|
304
|
+
? normalizedThreads.map((thread) => ({
|
|
305
|
+
title: thread.title,
|
|
306
|
+
taskTitle: thread.title,
|
|
307
|
+
taskId: thread.taskId ?? (0, node_crypto_1.createHash)('sha1').update(thread.title).digest('hex').slice(0, 12),
|
|
308
|
+
openedAt: thread.openedAt ?? input.startedAt,
|
|
309
|
+
lastActiveAt: thread.lastActiveAt ?? (input.endedAt || input.startedAt),
|
|
310
|
+
...(thread.provider ? { provider: thread.provider } : {}),
|
|
311
|
+
...(thread.actorLabel ? { actorLabel: thread.actorLabel } : {}),
|
|
312
|
+
...(thread.workspaceHost ? { workspaceHost: thread.workspaceHost } : {}),
|
|
313
|
+
...(thread.folderScope ? { folderScope: thread.folderScope } : {}),
|
|
314
|
+
}))
|
|
315
|
+
: taskLabels.map((title, index) => ({
|
|
316
|
+
title,
|
|
317
|
+
taskTitle: title,
|
|
318
|
+
taskId: (0, node_crypto_1.createHash)('sha1').update(`${title}:${index}`).digest('hex').slice(0, 12),
|
|
319
|
+
openedAt: input.startedAt,
|
|
320
|
+
lastActiveAt: input.endedAt || input.startedAt,
|
|
321
|
+
}))),
|
|
322
|
+
...(Object.keys(resourceSummary).length > 0 ? { resources: resourceSummary } : {}),
|
|
323
|
+
};
|
|
324
|
+
if (taskLabels.length > 0) {
|
|
325
|
+
metadata.activityThreads = normalizedThreads.length > 0
|
|
326
|
+
? normalizedThreads.map((thread) => ({
|
|
327
|
+
title: thread.title,
|
|
328
|
+
taskTitle: thread.title,
|
|
329
|
+
taskId: thread.taskId ?? (0, node_crypto_1.createHash)('sha1').update(`${thread.title}:thread`).digest('hex').slice(0, 12),
|
|
330
|
+
openedAt: thread.openedAt ?? input.startedAt,
|
|
331
|
+
lastActiveAt: thread.lastActiveAt ?? (input.endedAt || input.startedAt),
|
|
332
|
+
...(thread.provider ? { provider: thread.provider } : {}),
|
|
333
|
+
...(thread.actorLabel ? { actorLabel: thread.actorLabel } : {}),
|
|
334
|
+
...(thread.workspaceHost ? { workspaceHost: thread.workspaceHost } : {}),
|
|
335
|
+
...(thread.folderScope ? { folderScope: thread.folderScope } : {}),
|
|
336
|
+
}))
|
|
337
|
+
: taskLabels.map((title, index) => ({
|
|
338
|
+
title,
|
|
339
|
+
taskTitle: title,
|
|
340
|
+
taskId: (0, node_crypto_1.createHash)('sha1').update(`${title}:${index}:thread`).digest('hex').slice(0, 12),
|
|
341
|
+
openedAt: input.startedAt,
|
|
342
|
+
lastActiveAt: input.endedAt || input.startedAt,
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
const totalTokens = readFiniteNumber(input.totalTokens);
|
|
346
|
+
const latencyMs = readFiniteNumber(input.latencyMs);
|
|
347
|
+
const stepUsage = {};
|
|
348
|
+
if (readFiniteNumber(input.promptTokens) !== null)
|
|
349
|
+
stepUsage.promptTokens = Math.max(0, Math.trunc(Number(input.promptTokens)));
|
|
350
|
+
if (readFiniteNumber(input.completionTokens) !== null)
|
|
351
|
+
stepUsage.completionTokens = Math.max(0, Math.trunc(Number(input.completionTokens)));
|
|
352
|
+
if (readFiniteNumber(input.cachedPromptTokens) !== null)
|
|
353
|
+
stepUsage.cachedPromptTokens = Math.max(0, Math.trunc(Number(input.cachedPromptTokens)));
|
|
354
|
+
if (readFiniteNumber(input.reasoningTokens) !== null)
|
|
355
|
+
stepUsage.reasoningTokens = Math.max(0, Math.trunc(Number(input.reasoningTokens)));
|
|
356
|
+
if (totalTokens !== null && totalTokens >= 0)
|
|
357
|
+
stepUsage.totalTokens = Math.trunc(totalTokens);
|
|
358
|
+
if (latencyMs !== null && latencyMs >= 0)
|
|
359
|
+
stepUsage.latencyMs = Math.trunc(latencyMs);
|
|
360
|
+
if (Object.keys(stepUsage).length > 0)
|
|
361
|
+
stepUsage.currency = 'USD';
|
|
362
|
+
const steps = [];
|
|
363
|
+
if (activitySummary) {
|
|
364
|
+
steps.push({
|
|
365
|
+
stepId: `observe-${(0, node_crypto_1.createHash)('sha1').update(activitySummary).digest('hex').slice(0, 12)}`,
|
|
366
|
+
sequence: 0,
|
|
367
|
+
stepKind: 'custom',
|
|
368
|
+
title: buildStepTitle(actorLabels, taskLabels),
|
|
369
|
+
status: normalizeLabel(input.status || 'completed') || 'completed',
|
|
370
|
+
provider: normalizeLabel(input.provider),
|
|
371
|
+
model: modelLabels[0] || normalizeLabel(input.model),
|
|
372
|
+
startedAt: input.startedAt,
|
|
373
|
+
...(input.endedAt ? { endedAt: input.endedAt } : {}),
|
|
374
|
+
...(Object.keys(stepUsage).length > 0 ? { usage: stepUsage } : {}),
|
|
375
|
+
summary: activitySummary,
|
|
376
|
+
metadata: {
|
|
377
|
+
sourceSurface: 'connect_assisted',
|
|
378
|
+
safe_metadata_only: true,
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
metadata,
|
|
384
|
+
steps,
|
|
385
|
+
summary: normalizeLabel(input.summary, MAX_SUMMARY_LENGTH) || activitySummary,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
//# sourceMappingURL=runtime-activity.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const RUNTIME_CONTEXT_SCHEMA_VERSION = "forkit.runtime-context.v1";
|
|
2
|
+
export interface RuntimeContextRecord {
|
|
3
|
+
schemaVersion: typeof RUNTIME_CONTEXT_SCHEMA_VERSION;
|
|
4
|
+
clientName?: string;
|
|
5
|
+
subjectId?: string;
|
|
6
|
+
worktreeId?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function buildRuntimeContextRecord(options?: {
|
|
9
|
+
existing?: RuntimeContextRecord | null;
|
|
10
|
+
clientName?: string | null;
|
|
11
|
+
defaultLabel?: string | null;
|
|
12
|
+
}): RuntimeContextRecord;
|
|
13
|
+
export declare function readRuntimeContext(repoRoot: string): RuntimeContextRecord | null;
|
|
14
|
+
export declare function ensureRuntimeContext(repoRoot: string, options?: {
|
|
15
|
+
clientName?: string | null;
|
|
16
|
+
defaultLabel?: string | null;
|
|
17
|
+
}): RuntimeContextRecord | null;
|
|
18
|
+
//# sourceMappingURL=runtime-context.d.ts.map
|
|
@@ -0,0 +1,96 @@
|
|
|
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.RUNTIME_CONTEXT_SCHEMA_VERSION = void 0;
|
|
7
|
+
exports.buildRuntimeContextRecord = buildRuntimeContextRecord;
|
|
8
|
+
exports.readRuntimeContext = readRuntimeContext;
|
|
9
|
+
exports.ensureRuntimeContext = ensureRuntimeContext;
|
|
10
|
+
const node_crypto_1 = require("node:crypto");
|
|
11
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
exports.RUNTIME_CONTEXT_SCHEMA_VERSION = 'forkit.runtime-context.v1';
|
|
14
|
+
function normalizeText(value, maxLength) {
|
|
15
|
+
if (typeof value !== 'string')
|
|
16
|
+
return null;
|
|
17
|
+
const normalized = value.trim().replace(/\s+/g, ' ');
|
|
18
|
+
if (!normalized)
|
|
19
|
+
return null;
|
|
20
|
+
return normalized.slice(0, maxLength);
|
|
21
|
+
}
|
|
22
|
+
function slugifyLabel(value, maxLength = 48) {
|
|
23
|
+
const normalized = value
|
|
24
|
+
.trim()
|
|
25
|
+
.replace(/[\\/]+/g, '-')
|
|
26
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '-')
|
|
27
|
+
.replace(/-{2,}/g, '-')
|
|
28
|
+
.replace(/^-+|-+$/g, '');
|
|
29
|
+
return (normalized || 'runtime').slice(0, maxLength);
|
|
30
|
+
}
|
|
31
|
+
function createStableId(prefix, label, size) {
|
|
32
|
+
const suffix = (0, node_crypto_1.randomUUID)().replace(/-/g, '').slice(0, size);
|
|
33
|
+
return `${prefix}_${slugifyLabel(label)}_${suffix}`;
|
|
34
|
+
}
|
|
35
|
+
function getRuntimeContextPath(repoRoot) {
|
|
36
|
+
return node_path_1.default.join(repoRoot, '.forkit', 'runtime-context.json');
|
|
37
|
+
}
|
|
38
|
+
function buildRuntimeContextRecord(options) {
|
|
39
|
+
const existing = options?.existing ?? null;
|
|
40
|
+
const preferredLabel = normalizeText(options?.clientName, 160)
|
|
41
|
+
|| existing?.clientName
|
|
42
|
+
|| normalizeText(options?.defaultLabel, 160)
|
|
43
|
+
|| 'runtime';
|
|
44
|
+
return {
|
|
45
|
+
schemaVersion: exports.RUNTIME_CONTEXT_SCHEMA_VERSION,
|
|
46
|
+
...(preferredLabel ? { clientName: preferredLabel } : {}),
|
|
47
|
+
subjectId: existing?.subjectId || createStableId('subj', preferredLabel, 12),
|
|
48
|
+
worktreeId: existing?.worktreeId || createStableId('wt', preferredLabel, 16),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function readRuntimeContext(repoRoot) {
|
|
52
|
+
const contextPath = getRuntimeContextPath(repoRoot);
|
|
53
|
+
if (!node_fs_1.default.existsSync(contextPath))
|
|
54
|
+
return null;
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(contextPath, 'utf8'));
|
|
57
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
58
|
+
return null;
|
|
59
|
+
const record = parsed;
|
|
60
|
+
if (record.schemaVersion !== exports.RUNTIME_CONTEXT_SCHEMA_VERSION)
|
|
61
|
+
return null;
|
|
62
|
+
return {
|
|
63
|
+
schemaVersion: exports.RUNTIME_CONTEXT_SCHEMA_VERSION,
|
|
64
|
+
...(normalizeText(record.clientName, 160) ? { clientName: normalizeText(record.clientName, 160) } : {}),
|
|
65
|
+
...(normalizeText(record.subjectId, 160) ? { subjectId: normalizeText(record.subjectId, 160) } : {}),
|
|
66
|
+
...(normalizeText(record.worktreeId, 120) ? { worktreeId: normalizeText(record.worktreeId, 120) } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function ensureRuntimeContext(repoRoot, options) {
|
|
74
|
+
const contextPath = getRuntimeContextPath(repoRoot);
|
|
75
|
+
const existing = readRuntimeContext(repoRoot);
|
|
76
|
+
const nextRecord = buildRuntimeContextRecord({
|
|
77
|
+
existing,
|
|
78
|
+
clientName: options?.clientName ?? null,
|
|
79
|
+
defaultLabel: options?.defaultLabel ?? slugifyLabel(node_path_1.default.basename(repoRoot), 80),
|
|
80
|
+
});
|
|
81
|
+
if (existing
|
|
82
|
+
&& existing.clientName === nextRecord.clientName
|
|
83
|
+
&& existing.subjectId === nextRecord.subjectId
|
|
84
|
+
&& existing.worktreeId === nextRecord.worktreeId) {
|
|
85
|
+
return existing;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(contextPath), { recursive: true });
|
|
89
|
+
node_fs_1.default.writeFileSync(contextPath, `${JSON.stringify(nextRecord, null, 2)}\n`, 'utf8');
|
|
90
|
+
return nextRecord;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return existing;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=runtime-context.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { RepoDetectionResult } from './repo-discovery';
|
|
2
|
+
type WorkspaceHost = 'vscode' | 'cursor' | 'claude';
|
|
3
|
+
export interface RuntimeEditorActivityThread {
|
|
4
|
+
provider: string;
|
|
5
|
+
actorLabel: string;
|
|
6
|
+
threadLabel: string;
|
|
7
|
+
threadId?: string;
|
|
8
|
+
createdAt?: string;
|
|
9
|
+
lastActiveAt?: string;
|
|
10
|
+
folderScope?: string;
|
|
11
|
+
workspaceHost: WorkspaceHost;
|
|
12
|
+
}
|
|
13
|
+
export interface RuntimeEditorActivitySnapshot {
|
|
14
|
+
threads: RuntimeEditorActivityThread[];
|
|
15
|
+
modelLabels: string[];
|
|
16
|
+
fileLabels: string[];
|
|
17
|
+
scopeFileLabels: string[];
|
|
18
|
+
}
|
|
19
|
+
type ClaudeSessionSummary = {
|
|
20
|
+
thread: RuntimeEditorActivityThread | null;
|
|
21
|
+
modelLabels: string[];
|
|
22
|
+
fileLabels: string[];
|
|
23
|
+
};
|
|
24
|
+
export declare function parseClaudeSessionJsonl(raw: string | null, options: {
|
|
25
|
+
folderScope?: string | null;
|
|
26
|
+
repoRoot?: string | null;
|
|
27
|
+
}): ClaudeSessionSummary;
|
|
28
|
+
export declare function parseEditorActivityThreadsBlob(raw: string | null, options: {
|
|
29
|
+
actorLabel: string;
|
|
30
|
+
providerTypes?: Set<string>;
|
|
31
|
+
providerLabel?: string | null;
|
|
32
|
+
folderScope?: string | null;
|
|
33
|
+
workspaceHost: WorkspaceHost;
|
|
34
|
+
}): RuntimeEditorActivityThread[];
|
|
35
|
+
export declare function parseCopilotChatSessionIndexBlob(raw: string | null, options: {
|
|
36
|
+
workspaceHost: WorkspaceHost;
|
|
37
|
+
folderScope?: string | null;
|
|
38
|
+
}): RuntimeEditorActivityThread[];
|
|
39
|
+
export declare function parseCopilotSelectedModelLabels(raw: string | null): string[];
|
|
40
|
+
export declare function parseEditorOpenFileStateBlob(raw: string | null, options: {
|
|
41
|
+
repoRoot: string;
|
|
42
|
+
}): string[];
|
|
43
|
+
export declare function collectCodexEditorThreads(repo: RepoDetectionResult): RuntimeEditorActivityThread[];
|
|
44
|
+
export declare function collectCopilotEditorActivity(repo: RepoDetectionResult): RuntimeEditorActivitySnapshot;
|
|
45
|
+
export declare function collectClaudeProjectActivity(repo: RepoDetectionResult): RuntimeEditorActivitySnapshot;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=runtime-editor-activity.d.ts.map
|