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.
package/dist/cli.js CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
4
7
  const node_child_process_1 = require("node:child_process");
8
+ const node_path_1 = __importDefault(require("node:path"));
5
9
  const node_readline_1 = require("node:readline");
6
10
  const promises_1 = require("node:readline/promises");
7
11
  const node_process_1 = require("node:process");
@@ -9,9 +13,15 @@ const daemon_1 = require("./v1/daemon");
9
13
  const service_1 = require("./v1/service");
10
14
  const discovery_1 = require("./v1/discovery");
11
15
  const heartbeat_1 = require("./v1/heartbeat");
16
+ const runtime_activity_1 = require("./v1/runtime-activity");
17
+ const runtime_editor_activity_1 = require("./v1/runtime-editor-activity");
18
+ const runtime_observation_runner_1 = require("./v1/runtime-observation-runner");
19
+ const runtime_context_1 = require("./v1/runtime-context");
12
20
  const update_1 = require("./v1/update");
21
+ const repo_discovery_1 = require("./v1/repo-discovery");
13
22
  const api_1 = require("./v1/api");
14
23
  const credential_store_1 = require("./v1/credential-store");
24
+ const runtime_registration_1 = require("./v1/runtime-registration");
15
25
  const DEFAULT_BASE_URL = process.env.FORKIT_API_URL ?? process.env.FORKIT_BASE_URL ?? 'https://www.forkit.dev';
16
26
  const TRAIN_EVENT_TYPES = [
17
27
  'training_started',
@@ -78,13 +88,14 @@ const PUBLIC_COMMANDS = [
78
88
  ['inbox', 'Review the Smart Registration Inbox'],
79
89
  ['sync', 'Flush queued drafts and lifecycle metadata'],
80
90
  ['workspace', 'List, select, or inspect optional governed workspace/project scope'],
81
- ['runtime', 'Review local runtime discovery and runtime health'],
91
+ ['runtime', 'Register or review governed runtimes for the current repo/worktree'],
82
92
  ['register', 'Register ready local models into the current scope'],
83
93
  ['ignore', 'Ignore one detected local model'],
84
94
  ['doctor', 'Run local environment diagnostics'],
85
95
  ];
86
96
  const ADVANCED_COMMAND_GROUPS = [
87
97
  ['connect', 'Model connection, runtime review, and handoff utilities'],
98
+ ['runtime', 'Repo-scoped runtime observation and journaling utilities'],
88
99
  ['review', 'Summarize discovered but unconnected items'],
89
100
  ['doctor', 'Run local environment diagnostics'],
90
101
  ['workspaces', 'List accessible workspaces after sign-in'],
@@ -108,7 +119,7 @@ const ADVANCED_COMMAND_GROUPS = [
108
119
  function usage() {
109
120
  console.log('Usage: forkit-connect <init|login|logout|status|changes|start|stop|scan|inbox|sync|workspace|runtime|register|ignore|doctor> [options]');
110
121
  console.log(' forkit-connect workspace <list|select|create|status> [options]');
111
- console.log(' forkit-connect runtime <review|status> [options]');
122
+ console.log(' forkit-connect runtime <register|review|status> [options]');
112
123
  console.log('Public commands:');
113
124
  for (const [command, description] of PUBLIC_COMMANDS) {
114
125
  console.log(` ${command.padEnd(10)} ${description}`);
@@ -118,7 +129,15 @@ function usage() {
118
129
  console.log(' --project-name <text> Project name used by workspace create/select');
119
130
  console.log(' --project-description <text> Optional project description used by workspace create/select');
120
131
  console.log(' --json Print machine-readable output when supported');
132
+ console.log(' --name <value> Runtime or metric name depending on command');
121
133
  console.log(' --model <value> Model name used by register');
134
+ console.log(' --entrypoint <path> Relative project path used by runtime register');
135
+ console.log(' --source-url <url> Source URL override used by runtime register');
136
+ console.log(' --subject-type <type> Runtime subject type used by runtime register');
137
+ console.log(' --deployment-environment <value> Runtime environment used by runtime register');
138
+ console.log(' --show-key Print the raw runtime API key when one is created');
139
+ console.log(' --no-api-key Skip automatic runtime API key creation');
140
+ console.log(' --dry-run Show the inferred runtime payload without creating it');
122
141
  console.log(' --all-ready Register every ready local model in the current scope');
123
142
  console.log(' --ignore-model <name> Ignore detected model by name with --ignore-digest');
124
143
  console.log(' --ignore-digest <sha> Digest used with --ignore-model');
@@ -133,6 +152,8 @@ function advancedUsage() {
133
152
  console.log('Advanced commands:');
134
153
  console.log(' forkit-connect connect <modelNameOrDiscoveryHash>');
135
154
  console.log(' forkit-connect connect <start|init|status|inbox|services|permissions|handoff|evolution review|runtime review|runtime status>');
155
+ console.log(' forkit-connect runtime target <add|list|remove|status> [options]');
156
+ console.log(' forkit-connect runtime observe --gaid <gaid> [options]');
136
157
  console.log(' forkit-connect <review|workspaces|projects|bind|drafts|publish|bound|heartbeat|update-check|config|daemon|pulse|c2|train|agent|tray|notify> [options]');
137
158
  for (const [command, description] of ADVANCED_COMMAND_GROUPS) {
138
159
  console.log(` ${command.padEnd(12)} ${description}`);
@@ -157,13 +178,15 @@ function advancedUsage() {
157
178
  console.log(' --heartbeat-gaid <gaid> Queue heartbeat runtime signal event for GAID');
158
179
  console.log(' --heartbeat-key <key> API key used for heartbeat runtime signal event');
159
180
  console.log(' Also used by: c2 set-key (stores key + backfills events)');
160
- console.log(' --gaid <gaid> Passport GAID used by c2 run-log emit');
161
- console.log(' --api-key <key> Runtime signal API key used by c2 run-log emit');
181
+ console.log(' --gaid <gaid> Runtime or passport GAID used by runtime observe and c2 run-log emit');
182
+ console.log(' --api-key <key> Runtime signal API key used by runtime observe and c2 run-log emit');
183
+ console.log(' --repo-root <path> Repo root used by runtime target add (defaults to current repo/worktree)');
162
184
  console.log(' --provider <name> Provider label used by c2 run-log emit');
163
185
  console.log(' --service-name <name> Service/agent/workflow label used by c2 run-log emit');
164
186
  console.log(' --prompt-tokens <n> Prompt tokens used by c2 run-log emit');
165
187
  console.log(' --completion-tokens <n> Completion tokens used by c2 run-log emit');
166
188
  console.log(' --client-name <name> Optional repo/runtime client label used by c2 run-log emit');
189
+ console.log(' --command-label <name> Operator label used by runtime observe and c2 run-log emit');
167
190
  console.log(' --actor-labels <csv> Actor labels used by c2 run-log emit (for example: Codex,Claude)');
168
191
  console.log(' --task-labels <csv> Task or chat labels used by c2 run-log emit');
169
192
  console.log(' --folder-labels <csv> Relative folder labels used by c2 run-log emit');
@@ -172,6 +195,7 @@ function advancedUsage() {
172
195
  console.log(' --cpu-percent <n> Scoped CPU percentage used by c2 run-log emit');
173
196
  console.log(' --memory-mb <n> Scoped memory usage used by c2 run-log emit');
174
197
  console.log(' --vram-mb <n> Device VRAM usage used by c2 run-log emit');
198
+ console.log(' --emit-ambient Include ambient tool detections in runtime observe JSON output');
175
199
  console.log(' --draft-only Allow draft creation even when governed publish capacity is full');
176
200
  }
177
201
  function showUsage() {
@@ -198,6 +222,15 @@ function getNumericArg(flag) {
198
222
  const parsed = Number(value);
199
223
  return Number.isFinite(parsed) ? parsed : undefined;
200
224
  }
225
+ function getListArg(flag) {
226
+ const value = getArg(flag);
227
+ if (!value)
228
+ return [];
229
+ return value
230
+ .split(',')
231
+ .map((entry) => entry.trim())
232
+ .filter(Boolean);
233
+ }
201
234
  function isHelpCommand(command) {
202
235
  return command === 'help' || command === '--help' || command === '-h' || command === '--advanced-help';
203
236
  }
@@ -345,6 +378,132 @@ function formatRemainingLimit(limit, used, singular, plural = `${singular}s`) {
345
378
  function isCapacityExhausted(limit, remaining) {
346
379
  return Number.isFinite(limit) && Number(remaining) <= 0;
347
380
  }
381
+ function shouldCollectCodexThreads(input) {
382
+ const haystack = [
383
+ input.provider,
384
+ input.serviceName,
385
+ ...input.actorLabels,
386
+ ]
387
+ .join(' ')
388
+ .trim()
389
+ .toLowerCase();
390
+ return haystack.includes('codex');
391
+ }
392
+ function shouldCollectCopilotActivity(input) {
393
+ const haystack = [
394
+ input.provider,
395
+ input.serviceName,
396
+ ...input.actorLabels,
397
+ ]
398
+ .join(' ')
399
+ .trim()
400
+ .toLowerCase();
401
+ return haystack.includes('copilot');
402
+ }
403
+ function shouldCollectClaudeActivity(input) {
404
+ const haystack = [
405
+ input.provider,
406
+ input.serviceName,
407
+ ...input.actorLabels,
408
+ ]
409
+ .join(' ')
410
+ .trim()
411
+ .toLowerCase();
412
+ return haystack.includes('claude');
413
+ }
414
+ function buildCliRuntimeRunActivityEnvelope(input) {
415
+ const cwd = process.cwd();
416
+ const repo = (0, repo_discovery_1.detectRepository)(cwd);
417
+ const changedFiles = (0, repo_discovery_1.listRepositoryChangedFiles)(cwd);
418
+ const runtimeContext = repo.isGitRepo && repo.rootPath
419
+ ? ((0, runtime_context_1.readRuntimeContext)(repo.rootPath) ?? (0, runtime_context_1.buildRuntimeContextRecord)({
420
+ clientName: input.clientName || null,
421
+ defaultLabel: node_path_1.default.basename(repo.rootPath),
422
+ }))
423
+ : null;
424
+ const promptTokens = Number.isFinite(input.promptTokens) ? Math.max(0, Math.trunc(Number(input.promptTokens))) : 0;
425
+ const completionTokens = Number.isFinite(input.completionTokens) ? Math.max(0, Math.trunc(Number(input.completionTokens))) : 0;
426
+ const cachedPromptTokens = Number.isFinite(input.cachedPromptTokens) ? Math.max(0, Math.trunc(Number(input.cachedPromptTokens))) : undefined;
427
+ const reasoningTokens = Number.isFinite(input.reasoningTokens) ? Math.max(0, Math.trunc(Number(input.reasoningTokens))) : undefined;
428
+ const totalTokens = promptTokens + completionTokens;
429
+ const actorLabels = getListArg('--actor-labels');
430
+ const explicitTaskLabels = getListArg('--task-labels');
431
+ const explicitModelLabels = getListArg('--model-labels');
432
+ const codexAutoThreads = explicitTaskLabels.length === 0 && shouldCollectCodexThreads({
433
+ provider: input.provider,
434
+ serviceName: input.serviceName,
435
+ actorLabels,
436
+ })
437
+ ? (0, runtime_editor_activity_1.collectCodexEditorThreads)(repo)
438
+ : [];
439
+ const copilotAutoActivity = (explicitTaskLabels.length === 0 || explicitModelLabels.length === 0) && shouldCollectCopilotActivity({
440
+ provider: input.provider,
441
+ serviceName: input.serviceName,
442
+ actorLabels,
443
+ })
444
+ ? (0, runtime_editor_activity_1.collectCopilotEditorActivity)(repo)
445
+ : { threads: [], modelLabels: [] };
446
+ const claudeAutoActivity = (explicitTaskLabels.length === 0 || explicitModelLabels.length === 0) && shouldCollectClaudeActivity({
447
+ provider: input.provider,
448
+ serviceName: input.serviceName,
449
+ actorLabels,
450
+ })
451
+ ? (0, runtime_editor_activity_1.collectClaudeProjectActivity)(repo)
452
+ : { threads: [], modelLabels: [] };
453
+ const autoThreads = [
454
+ ...codexAutoThreads,
455
+ ...(explicitTaskLabels.length === 0 ? copilotAutoActivity.threads : []),
456
+ ...(explicitTaskLabels.length === 0 ? claudeAutoActivity.threads : []),
457
+ ];
458
+ const taskLabels = explicitTaskLabels.length > 0 ? explicitTaskLabels : autoThreads.map((thread) => thread.threadLabel);
459
+ const modelLabels = explicitModelLabels.length > 0
460
+ ? explicitModelLabels
461
+ : [...copilotAutoActivity.modelLabels, ...claudeAutoActivity.modelLabels];
462
+ const clientName = input.clientName || runtimeContext?.clientName || null;
463
+ return (0, runtime_activity_1.buildRuntimeActivityMetadata)({
464
+ cwd,
465
+ repo,
466
+ changedFiles,
467
+ provider: input.provider,
468
+ model: input.model,
469
+ serviceName: input.serviceName,
470
+ serviceKind: input.serviceKind ?? null,
471
+ commandLabel: input.commandLabel ?? null,
472
+ status: input.status ?? null,
473
+ startedAt: input.startedAt || new Date().toISOString(),
474
+ endedAt: input.endedAt || null,
475
+ summary: input.summary ?? null,
476
+ clientName,
477
+ stableSubjectId: runtimeContext?.subjectId,
478
+ stableWorktreeId: runtimeContext?.worktreeId,
479
+ actorLabels,
480
+ taskLabels,
481
+ activityThreads: autoThreads.map((thread) => ({
482
+ title: thread.threadLabel,
483
+ taskId: thread.threadId ?? null,
484
+ openedAt: thread.createdAt ?? null,
485
+ lastActiveAt: thread.lastActiveAt ?? null,
486
+ provider: thread.provider,
487
+ actorLabel: thread.actorLabel,
488
+ workspaceHost: thread.workspaceHost,
489
+ folderScope: thread.folderScope ?? null,
490
+ })),
491
+ folderLabels: getListArg('--folder-labels'),
492
+ fileLabels: getListArg('--file-labels'),
493
+ modelLabels,
494
+ promptTokens,
495
+ completionTokens,
496
+ cachedPromptTokens,
497
+ reasoningTokens,
498
+ totalTokens,
499
+ latencyMs: input.latencyMs,
500
+ resources: {
501
+ cpuPercent: getNumericArg('--cpu-percent'),
502
+ memoryMb: getNumericArg('--memory-mb'),
503
+ vramMb: getNumericArg('--vram-mb'),
504
+ },
505
+ });
506
+ }
348
507
  function formatWorkspaceAccessLine(workspace) {
349
508
  const workspaceId = String(workspace.id || workspace.gaid || workspace.passportGaid || 'unknown');
350
509
  return `- ${summarizeWorkspaceLabel(workspace)} | id=${workspaceId}`;
@@ -481,6 +640,72 @@ function readPassportList(body) {
481
640
  }
482
641
  return passports.filter((item) => !!item && typeof item === 'object' && !Array.isArray(item));
483
642
  }
643
+ function isRuntimeListResponse(body) {
644
+ return Boolean(body) && typeof body === 'object' && !Array.isArray(body);
645
+ }
646
+ function isRuntimeCreateResponse(body) {
647
+ return Boolean(body) && typeof body === 'object' && !Array.isArray(body);
648
+ }
649
+ function isApiKeyCreateResponse(body) {
650
+ return Boolean(body) && typeof body === 'object' && !Array.isArray(body);
651
+ }
652
+ function readRuntimeList(body) {
653
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
654
+ return [];
655
+ }
656
+ const runtimes = body.runtimes;
657
+ if (!Array.isArray(runtimes)) {
658
+ return [];
659
+ }
660
+ return runtimes.filter((item) => !!item && typeof item === 'object' && !Array.isArray(item));
661
+ }
662
+ function readRuntimeIdentityMetadata(runtime) {
663
+ const metadata = runtime.metadata;
664
+ if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
665
+ return null;
666
+ }
667
+ const runtimeIdentity = metadata.runtimeIdentity;
668
+ return runtimeIdentity && typeof runtimeIdentity === 'object' && !Array.isArray(runtimeIdentity)
669
+ ? runtimeIdentity
670
+ : null;
671
+ }
672
+ function findExistingRuntimeForIdentity(runtimes, input) {
673
+ const targetWorkspaceId = input.workspaceId.trim();
674
+ const targetProjectId = input.projectId.trim();
675
+ for (const runtime of runtimes) {
676
+ const runtimeWorkspaceId = String(runtime.workspaceId || runtime.workspace_id || '').trim();
677
+ const runtimeProjectId = String(runtime.projectId || runtime.project_id || '').trim();
678
+ if (runtimeWorkspaceId !== targetWorkspaceId || runtimeProjectId !== targetProjectId) {
679
+ continue;
680
+ }
681
+ const runtimeIdentity = readRuntimeIdentityMetadata(runtime);
682
+ if (!runtimeIdentity) {
683
+ continue;
684
+ }
685
+ const repoRootFingerprint = String(runtimeIdentity.repoRootFingerprint || '').trim();
686
+ const worktreeId = String(runtimeIdentity.worktreeId || '').trim();
687
+ const entrypoint = String(runtimeIdentity.entrypointRelativePath || '.').trim() || '.';
688
+ if (repoRootFingerprint === input.repoRootFingerprint
689
+ && worktreeId === input.worktreeId
690
+ && entrypoint === input.entrypointRelativePath) {
691
+ return runtime;
692
+ }
693
+ }
694
+ return null;
695
+ }
696
+ function formatRuntimeToolingHintList(value) {
697
+ if (!Array.isArray(value))
698
+ return 'No tooling hints recorded';
699
+ const labels = value
700
+ .map((entry) => {
701
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry))
702
+ return null;
703
+ const label = String(entry.label || entry.key || '').trim();
704
+ return label || null;
705
+ })
706
+ .filter((entry) => Boolean(entry));
707
+ return labels.length ? labels.join(', ') : 'No tooling hints recorded';
708
+ }
484
709
  function formatPassportLine(passport) {
485
710
  const gaid = String(passport.gaid || passport.id || 'unknown');
486
711
  const name = String(passport.name || 'Unnamed passport');
@@ -1156,6 +1381,25 @@ function printRuntimeStatus(status) {
1156
1381
  console.log(`- latest event=${status.latest_runtime_lifecycle_event}`);
1157
1382
  }
1158
1383
  }
1384
+ function printRuntimeTargetList(service, options) {
1385
+ const targets = service.listRuntimeObserverTargets();
1386
+ const daemon = (0, daemon_1.getDaemonHealth)(service);
1387
+ if (options?.json) {
1388
+ printJson({
1389
+ daemon,
1390
+ totalTargets: targets.length,
1391
+ targets: targets.map((target) => ({
1392
+ ...target,
1393
+ runtimeSignalKeyConfigured: service.hasRuntimeSignalKey(target.runtime_gaid),
1394
+ })),
1395
+ });
1396
+ return;
1397
+ }
1398
+ console.log(`[forkit-connect] Runtime targets=${targets.length} | daemon=${daemon.running ? `running:${daemon.pid}` : 'stopped'}`);
1399
+ for (const target of targets) {
1400
+ console.log(`- gaid=${shortId(target.runtime_gaid)} | repo=${target.repo_root} | client=${target.client_name || 'n/a'} | key=${service.hasRuntimeSignalKey(target.runtime_gaid) ? 'ready' : 'missing'} | last_emit=${target.last_emitted_at || 'never'}${target.last_error ? ` | error=${target.last_error}` : ''}`);
1401
+ }
1402
+ }
1159
1403
  function formatSmartInboxActionValue(action, itemType, connectableModelName) {
1160
1404
  switch (action) {
1161
1405
  case 'connect_existing_passport':
@@ -2465,6 +2709,9 @@ async function run() {
2465
2709
  else if (message === 'SIMILAR_PASSPORT_EXISTS') {
2466
2710
  console.error('A similar or existing passport already exists in your Forkit.dev account. Open inbox and review the connected/passport match before creating a new draft.');
2467
2711
  }
2712
+ else if (message === 'INVALID_DRAFT_VISIBILITY') {
2713
+ console.error('This registration must start as a private draft. Review it in inbox/start first, or choose publish only when Forkit is ready to finish the passport.');
2714
+ }
2468
2715
  else if (message.startsWith('DRAFT_CREATE_FAILED:')) {
2469
2716
  console.error(`[forkit-connect] Draft creation failed (${message.split(':')[1]}).`);
2470
2717
  }
@@ -3172,6 +3419,221 @@ async function run() {
3172
3419
  cachedCliAccountLimitsAt = now;
3173
3420
  return resolved;
3174
3421
  };
3422
+ const runRuntimeRegister = async () => {
3423
+ const storedSessionRef = String(service.readSessionRef() || '').trim();
3424
+ if (!storedSessionRef) {
3425
+ console.error('Not authenticated. Run forkit-connect login first.');
3426
+ process.exitCode = 2;
3427
+ return;
3428
+ }
3429
+ const sessionState = await checkBackendSessionState(service);
3430
+ if (sessionState === 'expired') {
3431
+ console.error('Session expired. Run forkit-connect login again.');
3432
+ process.exitCode = 2;
3433
+ return;
3434
+ }
3435
+ if (sessionState === 'missing') {
3436
+ console.error('Not authenticated. Run forkit-connect login first.');
3437
+ process.exitCode = 2;
3438
+ return;
3439
+ }
3440
+ if (sessionState === 'unavailable') {
3441
+ console.error('Forkit.dev is unavailable right now. Try again when the backend is reachable.');
3442
+ process.exitCode = 2;
3443
+ return;
3444
+ }
3445
+ const state = service.getStateStore().readState();
3446
+ const boundWorkspaceId = String(workspaceId || state.workspace_binding.workspaceId || '').trim();
3447
+ const boundProjectId = String(projectId || state.project_binding.projectId || '').trim();
3448
+ if (!boundWorkspaceId || !boundProjectId) {
3449
+ console.error('Runtime registration needs a governed workspace and project. Run forkit-connect workspace select first or pass --workspace and --project.');
3450
+ process.exitCode = 2;
3451
+ return;
3452
+ }
3453
+ let runtimeDraft;
3454
+ try {
3455
+ runtimeDraft = (0, runtime_registration_1.inspectRuntimeProject)({
3456
+ cwd: process.cwd(),
3457
+ name: getArg('--name'),
3458
+ description: descriptionArg,
3459
+ sourceUrl: getArg('--source-url'),
3460
+ entrypointRelativePath: getArg('--entrypoint'),
3461
+ subjectType: (getArg('--subject-type') || null),
3462
+ deploymentEnvironment: getArg('--deployment-environment'),
3463
+ connectRuntimeId: state.runtime_identity.runtimeId,
3464
+ });
3465
+ }
3466
+ catch (error) {
3467
+ console.error(error instanceof Error ? error.message : 'Runtime inspection failed.');
3468
+ process.exitCode = 2;
3469
+ return;
3470
+ }
3471
+ const resultPayload = {
3472
+ name: runtimeDraft.name,
3473
+ description: runtimeDraft.description,
3474
+ sourceUrl: runtimeDraft.sourceUrl,
3475
+ workspaceId: boundWorkspaceId,
3476
+ projectId: boundProjectId,
3477
+ metadata: runtimeDraft.metadata,
3478
+ runtimeIdentity: runtimeDraft.runtimeIdentity,
3479
+ };
3480
+ if (hasFlag('--dry-run')) {
3481
+ printJson({
3482
+ ok: true,
3483
+ action: 'dry_run',
3484
+ payload: resultPayload,
3485
+ inferred_project: {
3486
+ repo_root: runtimeDraft.snapshot.repoRootName,
3487
+ branch: runtimeDraft.snapshot.branch,
3488
+ commit: runtimeDraft.snapshot.commit,
3489
+ dirty: runtimeDraft.snapshot.dirty,
3490
+ entrypoint: runtimeDraft.snapshot.relativeEntrypoint,
3491
+ tooling: runtimeDraft.snapshot.detectedTooling,
3492
+ },
3493
+ });
3494
+ return;
3495
+ }
3496
+ const api = buildWorkspaceApi();
3497
+ const runtimeListResult = await api.getRuntimesMine(100);
3498
+ if (!runtimeListResult.ok || !isRuntimeListResponse(runtimeListResult.body)) {
3499
+ console.error(`[forkit-connect] Runtime lookup failed (${runtimeListResult.status}).`);
3500
+ if (runtimeListResult.body) {
3501
+ console.error(typeof runtimeListResult.body === 'string' ? runtimeListResult.body : JSON.stringify(runtimeListResult.body));
3502
+ }
3503
+ process.exitCode = 2;
3504
+ return;
3505
+ }
3506
+ const runtimes = readRuntimeList(runtimeListResult.body);
3507
+ const existingRuntime = findExistingRuntimeForIdentity(runtimes, {
3508
+ workspaceId: boundWorkspaceId,
3509
+ projectId: boundProjectId,
3510
+ repoRootFingerprint: runtimeDraft.runtimeIdentity.repoRootFingerprint,
3511
+ worktreeId: runtimeDraft.runtimeIdentity.worktreeId,
3512
+ entrypointRelativePath: runtimeDraft.runtimeIdentity.entrypointRelativePath,
3513
+ });
3514
+ let action = existingRuntime ? 'existing' : 'created';
3515
+ let runtimeRecord = existingRuntime;
3516
+ if (!runtimeRecord) {
3517
+ const createResult = await api.createRuntime(resultPayload);
3518
+ if (!createResult.ok || !isRuntimeCreateResponse(createResult.body) || !createResult.body.runtime) {
3519
+ const errorBody = createResult.body && typeof createResult.body === 'object'
3520
+ ? createResult.body
3521
+ : null;
3522
+ const message = typeof errorBody?.error === 'string'
3523
+ ? errorBody.error
3524
+ : `[forkit-connect] Runtime registration failed (${createResult.status}).`;
3525
+ console.error(message);
3526
+ if (createResult.body) {
3527
+ console.error(typeof createResult.body === 'string' ? createResult.body : JSON.stringify(createResult.body));
3528
+ }
3529
+ process.exitCode = 2;
3530
+ return;
3531
+ }
3532
+ runtimeRecord = createResult.body.runtime;
3533
+ }
3534
+ const runtimeGaid = String(runtimeRecord?.gaid || '').trim();
3535
+ if (!runtimeGaid) {
3536
+ console.error('Runtime registration returned no GAID.');
3537
+ process.exitCode = 2;
3538
+ return;
3539
+ }
3540
+ const shouldCreateApiKey = !hasFlag('--no-api-key') && !service.hasStoredRuntimeSignalKey(runtimeGaid);
3541
+ let apiKeyStored = false;
3542
+ let apiKeyCreated = false;
3543
+ let keyId = null;
3544
+ let rawKeyToDisplay = null;
3545
+ let keyStorageWarning = null;
3546
+ if (shouldCreateApiKey) {
3547
+ const apiKeyResult = await api.createRuntimeApiKey(runtimeGaid, 'Forkit Connect runtime');
3548
+ if (!apiKeyResult.ok || !isApiKeyCreateResponse(apiKeyResult.body) || !apiKeyResult.body.rawKey) {
3549
+ keyStorageWarning = `Runtime created, but runtime key provisioning failed (${apiKeyResult.status}).`;
3550
+ if (apiKeyResult.body) {
3551
+ const detail = typeof apiKeyResult.body === 'string' ? apiKeyResult.body : JSON.stringify(apiKeyResult.body);
3552
+ keyStorageWarning = `${keyStorageWarning} ${detail}`;
3553
+ }
3554
+ }
3555
+ else {
3556
+ apiKeyCreated = true;
3557
+ keyId = String(apiKeyResult.body.keyId || '').trim() || null;
3558
+ const rawKey = String(apiKeyResult.body.rawKey || '').trim();
3559
+ try {
3560
+ const configured = service.configureRuntimeSignalKey({
3561
+ gaid: runtimeGaid,
3562
+ apiKey: rawKey,
3563
+ });
3564
+ apiKeyStored = configured.stored;
3565
+ if (hasFlag('--show-key')) {
3566
+ rawKeyToDisplay = rawKey;
3567
+ }
3568
+ }
3569
+ catch (error) {
3570
+ if (error instanceof credential_store_1.ConnectCredentialStoreError) {
3571
+ keyStorageWarning = error.message;
3572
+ rawKeyToDisplay = rawKey;
3573
+ }
3574
+ else {
3575
+ throw error;
3576
+ }
3577
+ }
3578
+ }
3579
+ }
3580
+ else if (!hasFlag('--no-api-key')) {
3581
+ apiKeyStored = true;
3582
+ }
3583
+ if (hasFlag('--json')) {
3584
+ printJson({
3585
+ ok: true,
3586
+ action,
3587
+ runtime: runtimeRecord,
3588
+ logging_ready: hasFlag('--no-api-key') ? false : apiKeyStored,
3589
+ api_key: {
3590
+ created: apiKeyCreated,
3591
+ stored: apiKeyStored,
3592
+ keyId,
3593
+ ...(rawKeyToDisplay ? { rawKey: rawKeyToDisplay } : {}),
3594
+ warning: keyStorageWarning,
3595
+ },
3596
+ inferred_project: {
3597
+ repo_root: runtimeDraft.snapshot.repoRootName,
3598
+ branch: runtimeDraft.snapshot.branch,
3599
+ commit: runtimeDraft.snapshot.commit,
3600
+ dirty: runtimeDraft.snapshot.dirty,
3601
+ entrypoint: runtimeDraft.snapshot.relativeEntrypoint,
3602
+ tooling: runtimeDraft.snapshot.detectedTooling,
3603
+ },
3604
+ });
3605
+ return;
3606
+ }
3607
+ console.log(action === 'created' ? 'Runtime registered.' : 'Runtime already exists. Reusing governed runtime.');
3608
+ console.log(`Runtime: ${runtimeDraft.name}`);
3609
+ console.log(`GAID: ${runtimeGaid}`);
3610
+ console.log(`Scope: workspace=${boundWorkspaceId} | project=${boundProjectId}`);
3611
+ console.log(`Project: ${runtimeDraft.snapshot.repoRootName} | branch=${runtimeDraft.snapshot.branch || 'detached'} | commit=${String(runtimeDraft.snapshot.commit || 'unknown').slice(0, 12)} | dirty=${String(runtimeDraft.snapshot.dirty)}`);
3612
+ console.log(`Entrypoint: ${runtimeDraft.snapshot.relativeEntrypoint}`);
3613
+ console.log(`Tooling: ${formatRuntimeToolingHintList(runtimeDraft.snapshot.detectedTooling)}`);
3614
+ if (runtimeDraft.sourceUrl) {
3615
+ console.log(`Source URL: ${runtimeDraft.sourceUrl}`);
3616
+ }
3617
+ if (hasFlag('--no-api-key')) {
3618
+ console.log('Runtime key: skipped by request (--no-api-key).');
3619
+ return;
3620
+ }
3621
+ if (apiKeyStored) {
3622
+ console.log(apiKeyCreated
3623
+ ? `Runtime key stored securely${keyId ? ` (keyId=${keyId})` : ''}.`
3624
+ : 'Runtime key already stored locally for this runtime.');
3625
+ }
3626
+ if (keyStorageWarning) {
3627
+ console.log(`Runtime key warning: ${keyStorageWarning}`);
3628
+ }
3629
+ if (rawKeyToDisplay) {
3630
+ console.log('Runtime API key (shown once):');
3631
+ console.log(rawKeyToDisplay);
3632
+ }
3633
+ if (!apiKeyStored && !rawKeyToDisplay) {
3634
+ console.log('Runtime logging is not ready yet. Create or attach a runtime key from Forkit.dev.');
3635
+ }
3636
+ };
3175
3637
  const createProjectInWorkspace = async (api, selectedWorkspaceId, options) => {
3176
3638
  let nextProjectName = projectNameArg;
3177
3639
  let nextProjectDescription = projectDescriptionArg;
@@ -3412,6 +3874,8 @@ async function run() {
3412
3874
  return 'A backend draft already exists for this model. No duplicate draft created.';
3413
3875
  if (raw === 'SIMILAR_PASSPORT_EXISTS')
3414
3876
  return 'A similar or existing passport already exists in your Forkit.dev account. Review existing records before creating a new draft.';
3877
+ if (raw === 'INVALID_DRAFT_VISIBILITY')
3878
+ return 'This registration must start as a private draft. Review it in inbox/start first, or choose publish only when Forkit is ready to finish the passport.';
3415
3879
  if (raw === 'WORKSPACE_PROJECT_BINDING_REQUIRED')
3416
3880
  return 'No governed workspace/project scope is linked yet. Select or create scope first.';
3417
3881
  if (raw === 'DRAFT_CREATION_NOT_ALLOWED_BY_BINDING')
@@ -3616,30 +4080,6 @@ async function run() {
3616
4080
  await runPublicSync();
3617
4081
  return;
3618
4082
  }
3619
- if (command === 'runtime') {
3620
- const subcommand = args[1] || 'review';
3621
- if (subcommand === 'review') {
3622
- const summary = service.getRuntimePassportReview();
3623
- if (hasFlag('--json')) {
3624
- printJson(summary);
3625
- return;
3626
- }
3627
- printRuntimeReview(summary);
3628
- return;
3629
- }
3630
- if (subcommand === 'status') {
3631
- const status = service.getRuntimePassportStatus();
3632
- if (hasFlag('--json')) {
3633
- printJson(status);
3634
- return;
3635
- }
3636
- printRuntimeStatus(status);
3637
- return;
3638
- }
3639
- console.error('Usage: forkit-connect runtime <review|status>');
3640
- process.exitCode = 2;
3641
- return;
3642
- }
3643
4083
  if (command === 'workspace') {
3644
4084
  const subcommand = args[1] || 'status';
3645
4085
  try {
@@ -3693,6 +4133,269 @@ async function run() {
3693
4133
  process.exitCode = 2;
3694
4134
  return;
3695
4135
  }
4136
+ if (command === 'runtime') {
4137
+ const subcommand = args[1];
4138
+ const nestedCommand = args[2];
4139
+ if (isHelpCommand(subcommand) || (subcommand === 'register' && isHelpCommand(nestedCommand))) {
4140
+ showUsage();
4141
+ return;
4142
+ }
4143
+ if ((subcommand === 'target' || subcommand === 'observe') && isHelpCommand(nestedCommand)) {
4144
+ advancedUsage();
4145
+ return;
4146
+ }
4147
+ if (subcommand === 'review') {
4148
+ const summary = service.getRuntimePassportReview();
4149
+ if (hasFlag('--json')) {
4150
+ printJson(summary);
4151
+ return;
4152
+ }
4153
+ printRuntimeReview(summary);
4154
+ return;
4155
+ }
4156
+ if (subcommand === 'status') {
4157
+ const status = service.getRuntimePassportStatus();
4158
+ if (hasFlag('--json')) {
4159
+ printJson(status);
4160
+ return;
4161
+ }
4162
+ printRuntimeStatus(status);
4163
+ return;
4164
+ }
4165
+ if (subcommand === 'register' || !subcommand) {
4166
+ await runRuntimeRegister();
4167
+ return;
4168
+ }
4169
+ if (subcommand === 'target') {
4170
+ const action = args[2];
4171
+ if (action === 'list' || action === 'status') {
4172
+ printRuntimeTargetList(service, { json: hasFlag('--json') });
4173
+ return;
4174
+ }
4175
+ if (action === 'remove') {
4176
+ const gaid = getArg('--gaid') ?? getArg('--heartbeat-gaid');
4177
+ if (!gaid) {
4178
+ console.error('[forkit-connect] runtime target remove requires --gaid.');
4179
+ process.exitCode = 1;
4180
+ return;
4181
+ }
4182
+ const removed = service.removeRuntimeObserverTarget(gaid);
4183
+ if (!removed) {
4184
+ console.error('[forkit-connect] Runtime target not found for that GAID.');
4185
+ process.exitCode = 2;
4186
+ return;
4187
+ }
4188
+ if (hasFlag('--json')) {
4189
+ printJson({ removed: true, gaid });
4190
+ return;
4191
+ }
4192
+ console.log(`Removed runtime target ${gaid}.`);
4193
+ return;
4194
+ }
4195
+ if (action === 'add') {
4196
+ const gaid = getArg('--gaid') ?? getArg('--heartbeat-gaid');
4197
+ if (!gaid) {
4198
+ console.error('[forkit-connect] runtime target add requires --gaid.');
4199
+ process.exitCode = 1;
4200
+ return;
4201
+ }
4202
+ const repoRootArg = getArg('--repo-root');
4203
+ const cwd = repoRootArg ? node_path_1.default.resolve(repoRootArg) : process.cwd();
4204
+ const repo = (0, repo_discovery_1.detectRepository)(cwd);
4205
+ const rootPath = repo.rootPath || repo.cwd;
4206
+ const existingTarget = service.listRuntimeObserverTargets().find((item) => item.runtime_gaid === gaid) ?? null;
4207
+ const runtimeContext = repo.isGitRepo && repo.rootPath
4208
+ ? ((0, runtime_context_1.readRuntimeContext)(repo.rootPath) ?? (0, runtime_context_1.buildRuntimeContextRecord)({
4209
+ existing: existingTarget
4210
+ ? {
4211
+ schemaVersion: 'forkit.runtime-context.v1',
4212
+ ...(existingTarget.client_name ? { clientName: existingTarget.client_name } : {}),
4213
+ ...(existingTarget.subject_id ? { subjectId: existingTarget.subject_id } : {}),
4214
+ ...(existingTarget.worktree_id ? { worktreeId: existingTarget.worktree_id } : {}),
4215
+ }
4216
+ : null,
4217
+ clientName: getArg('--client-name') || existingTarget?.client_name || null,
4218
+ defaultLabel: node_path_1.default.basename(repo.rootPath),
4219
+ }))
4220
+ : null;
4221
+ const apiKey = getArg('--api-key') ?? getArg('--heartbeat-key') ?? process.env.FORKIT_RUNTIME_SIGNAL_API_KEY ?? null;
4222
+ if (apiKey) {
4223
+ service.configureRuntimeSignalKey({ gaid, apiKey });
4224
+ }
4225
+ const target = service.registerRuntimeObserverTarget({
4226
+ gaid,
4227
+ repoRoot: rootPath,
4228
+ clientName: getArg('--client-name') || runtimeContext?.clientName || existingTarget?.client_name || node_path_1.default.basename(rootPath),
4229
+ subjectId: runtimeContext?.subjectId ?? existingTarget?.subject_id ?? null,
4230
+ worktreeId: runtimeContext?.worktreeId ?? existingTarget?.worktree_id ?? null,
4231
+ });
4232
+ let autoProvisioned = false;
4233
+ if (!service.hasRuntimeSignalKey(gaid)) {
4234
+ try {
4235
+ const provisioning = await service.autoProvisionRuntimeSignalKeys({ suppressErrors: true });
4236
+ autoProvisioned = provisioning.provisioned.includes(gaid);
4237
+ }
4238
+ catch {
4239
+ autoProvisioned = false;
4240
+ }
4241
+ }
4242
+ const daemon = (0, daemon_1.getDaemonHealth)(service);
4243
+ const runtimeSignalKeyConfigured = service.hasRuntimeSignalKey(gaid);
4244
+ if (hasFlag('--json')) {
4245
+ printJson({
4246
+ stored: true,
4247
+ daemon,
4248
+ target,
4249
+ runtimeSignalKeyConfigured,
4250
+ autoProvisioned,
4251
+ });
4252
+ return;
4253
+ }
4254
+ console.log(`Stored runtime target ${gaid}.`);
4255
+ console.log(`Repo root: ${target.repo_root}`);
4256
+ console.log(`Client: ${target.client_name || 'n/a'}`);
4257
+ console.log(`Runtime key: ${runtimeSignalKeyConfigured ? (autoProvisioned ? 'ready (auto-provisioned)' : 'ready') : 'missing'}`);
4258
+ console.log(`Daemon: ${daemon.running ? `running (${daemon.pid})` : 'stopped'}`);
4259
+ if (!daemon.running) {
4260
+ console.log('Next: run `forkit-connect start` to keep this runtime connected in the background.');
4261
+ }
4262
+ return;
4263
+ }
4264
+ console.error('Usage: forkit-connect runtime target <add|list|remove|status> [options]');
4265
+ process.exitCode = 2;
4266
+ return;
4267
+ }
4268
+ if (subcommand === 'observe') {
4269
+ const gaid = getArg('--gaid') ?? getArg('--heartbeat-gaid');
4270
+ if (!gaid) {
4271
+ console.error('[forkit-connect] runtime observe requires --gaid.');
4272
+ console.error('[forkit-connect] Use the governed runtime GAID from Forkit.dev.');
4273
+ process.exitCode = 1;
4274
+ return;
4275
+ }
4276
+ try {
4277
+ const cwd = process.cwd();
4278
+ const repo = (0, repo_discovery_1.detectRepository)(cwd);
4279
+ const apiKey = getArg('--api-key') ?? getArg('--heartbeat-key') ?? process.env.FORKIT_RUNTIME_SIGNAL_API_KEY ?? null;
4280
+ if (apiKey) {
4281
+ service.configureRuntimeSignalKey({ gaid, apiKey });
4282
+ }
4283
+ const existingTarget = service.listRuntimeObserverTargets().find((item) => item.runtime_gaid === gaid) ?? null;
4284
+ const runtimeContext = repo.isGitRepo && repo.rootPath
4285
+ ? ((0, runtime_context_1.readRuntimeContext)(repo.rootPath) ?? (0, runtime_context_1.buildRuntimeContextRecord)({
4286
+ existing: existingTarget
4287
+ ? {
4288
+ schemaVersion: 'forkit.runtime-context.v1',
4289
+ ...(existingTarget.client_name ? { clientName: existingTarget.client_name } : {}),
4290
+ ...(existingTarget.subject_id ? { subjectId: existingTarget.subject_id } : {}),
4291
+ ...(existingTarget.worktree_id ? { worktreeId: existingTarget.worktree_id } : {}),
4292
+ }
4293
+ : null,
4294
+ clientName: getArg('--client-name') || existingTarget?.client_name || null,
4295
+ defaultLabel: node_path_1.default.basename(repo.rootPath),
4296
+ }))
4297
+ : null;
4298
+ const target = service.registerRuntimeObserverTarget({
4299
+ gaid,
4300
+ repoRoot: repo.rootPath || repo.cwd,
4301
+ clientName: getArg('--client-name') || runtimeContext?.clientName || existingTarget?.client_name || null,
4302
+ subjectId: runtimeContext?.subjectId ?? existingTarget?.subject_id ?? null,
4303
+ worktreeId: runtimeContext?.worktreeId ?? existingTarget?.worktree_id ?? null,
4304
+ });
4305
+ const collection = await (0, runtime_observation_runner_1.collectRuntimeObservation)({
4306
+ gaid,
4307
+ cwd,
4308
+ clientName: target.client_name,
4309
+ commandLabel: getArg('--command-label') ?? 'runtime-observer',
4310
+ target,
4311
+ });
4312
+ if (!collection.accepted) {
4313
+ service.updateRuntimeObserverTargetResult({
4314
+ gaid,
4315
+ observedAt: collection.observedAt,
4316
+ summary: null,
4317
+ error: collection.reason,
4318
+ });
4319
+ if (hasFlag('--json')) {
4320
+ printJson({
4321
+ accepted: false,
4322
+ reason: collection.reason,
4323
+ runtime: { gaid },
4324
+ observations: hasFlag('--emit-ambient') ? [...collection.scopedObservations, ...collection.ambientObservations] : [],
4325
+ ambient_observations: collection.ambientObservations,
4326
+ });
4327
+ return;
4328
+ }
4329
+ console.log('No repo-scoped active tooling was observed for this runtime.');
4330
+ if (collection.ambientObservations.length > 0) {
4331
+ console.log(`Ambient tooling seen but not attributed: ${collection.ambientObservations.map((item) => item.label).join(', ')}`);
4332
+ }
4333
+ return;
4334
+ }
4335
+ const result = await (0, runtime_observation_runner_1.emitCollectedRuntimeObservation)(service, collection, { apiKey });
4336
+ if (!result.ok) {
4337
+ const bodyRecord = result.body && typeof result.body === 'object' && !Array.isArray(result.body)
4338
+ ? result.body
4339
+ : null;
4340
+ service.updateRuntimeObserverTargetResult({
4341
+ gaid,
4342
+ observedAt: collection.observedAt,
4343
+ fingerprint: collection.fingerprint,
4344
+ summary: collection.summary,
4345
+ error: typeof result.body === 'string'
4346
+ ? result.body
4347
+ : bodyRecord && typeof bodyRecord.error === 'string'
4348
+ ? bodyRecord.error
4349
+ : `runtime_observer_emit_failed_${result.status || 'local'}`,
4350
+ });
4351
+ console.error(`[forkit-connect] Runtime observe failed (${result.status || 'local'}).`);
4352
+ if (result.body) {
4353
+ console.error(typeof result.body === 'string' ? result.body : JSON.stringify(result.body));
4354
+ }
4355
+ process.exitCode = 2;
4356
+ return;
4357
+ }
4358
+ service.updateRuntimeObserverTargetResult({
4359
+ gaid,
4360
+ observedAt: collection.observedAt,
4361
+ emittedAt: collection.observedAt,
4362
+ fingerprint: collection.fingerprint,
4363
+ summary: collection.summary,
4364
+ error: null,
4365
+ });
4366
+ if (hasFlag('--json')) {
4367
+ printJson({
4368
+ accepted: true,
4369
+ runtime: {
4370
+ gaid,
4371
+ rootPath: collection.repoRoot,
4372
+ relativePath: collection.relativePath,
4373
+ },
4374
+ observations: collection.scopedObservations,
4375
+ ambient_observations: hasFlag('--emit-ambient') ? collection.ambientObservations : [],
4376
+ response: result.body,
4377
+ });
4378
+ return;
4379
+ }
4380
+ console.log('Runtime observation recorded.');
4381
+ console.log(`Runtime GAID: ${gaid}`);
4382
+ console.log(`Repo root: ${collection.repoRoot}`);
4383
+ console.log(`Scoped tooling: ${collection.scopedObservations.map((item) => `${item.label}${item.details && typeof item.details.primaryRole === 'string' ? `/${item.details.primaryRole}` : ''} (${item.activeProcessCount}${item.helperProcessCount > 0 ? `+${item.helperProcessCount} helper` : ''})`).join(', ')}`);
4384
+ if (collection.ambientObservations.length > 0) {
4385
+ console.log(`Ambient tooling ignored: ${collection.ambientObservations.map((item) => item.label).join(', ')}`);
4386
+ }
4387
+ return;
4388
+ }
4389
+ catch (error) {
4390
+ console.error(error instanceof Error ? error.message : 'runtime_observe_failed');
4391
+ process.exitCode = 2;
4392
+ return;
4393
+ }
4394
+ }
4395
+ console.error('Usage: forkit-connect runtime <register|target|observe> [options]');
4396
+ process.exitCode = 2;
4397
+ return;
4398
+ }
3696
4399
  if (command === 'ignore') {
3697
4400
  runPublicIgnore();
3698
4401
  return;
@@ -3840,6 +4543,11 @@ async function run() {
3840
4543
  process.exitCode = 2;
3841
4544
  return;
3842
4545
  }
4546
+ if (message === 'INVALID_DRAFT_VISIBILITY') {
4547
+ console.error('This registration must start as a private draft. Review it in inbox/start first, or choose publish only when Forkit is ready to finish the passport.');
4548
+ process.exitCode = 2;
4549
+ return;
4550
+ }
3843
4551
  if (message.startsWith('DRAFT_CREATE_FAILED:')) {
3844
4552
  console.error(`[forkit-connect] Draft creation failed (${message.split(':')[1]}).`);
3845
4553
  process.exitCode = 2;
@@ -3890,6 +4598,32 @@ async function run() {
3890
4598
  process.exitCode = 1;
3891
4599
  return;
3892
4600
  }
4601
+ const status = getArg('--status') ?? 'completed';
4602
+ const startedAt = getArg('--started-at');
4603
+ const endedAt = getArg('--ended-at');
4604
+ const promptTokens = getNumericArg('--prompt-tokens');
4605
+ const completionTokens = getNumericArg('--completion-tokens');
4606
+ const cachedPromptTokens = getNumericArg('--cached-prompt-tokens');
4607
+ const reasoningTokens = getNumericArg('--reasoning-tokens');
4608
+ const latencyMs = getNumericArg('--latency-ms');
4609
+ const summary = getArg('--summary');
4610
+ const activityEnvelope = buildCliRuntimeRunActivityEnvelope({
4611
+ provider,
4612
+ model: runModel,
4613
+ serviceName,
4614
+ serviceKind: getArg('--service-kind') ?? 'custom',
4615
+ commandLabel: getArg('--command-label'),
4616
+ status,
4617
+ startedAt,
4618
+ endedAt,
4619
+ summary,
4620
+ clientName: getArg('--client-name'),
4621
+ promptTokens,
4622
+ completionTokens,
4623
+ cachedPromptTokens,
4624
+ reasoningTokens,
4625
+ latencyMs,
4626
+ });
3893
4627
  const result = await service.emitRuntimeRunLog({
3894
4628
  gaid,
3895
4629
  apiKey,
@@ -3899,17 +4633,19 @@ async function run() {
3899
4633
  serviceKind: getArg('--service-kind') ?? 'custom',
3900
4634
  runId: getArg('--run-id'),
3901
4635
  externalRunId: getArg('--external-run-id'),
3902
- status: getArg('--status') ?? 'completed',
3903
- startedAt: getArg('--started-at'),
3904
- endedAt: getArg('--ended-at'),
3905
- promptTokens: getNumericArg('--prompt-tokens'),
3906
- completionTokens: getNumericArg('--completion-tokens'),
3907
- cachedPromptTokens: getNumericArg('--cached-prompt-tokens'),
3908
- reasoningTokens: getNumericArg('--reasoning-tokens'),
3909
- latencyMs: getNumericArg('--latency-ms'),
4636
+ status,
4637
+ startedAt,
4638
+ endedAt,
4639
+ promptTokens,
4640
+ completionTokens,
4641
+ cachedPromptTokens,
4642
+ reasoningTokens,
4643
+ latencyMs,
3910
4644
  estimatedCostCents: getNumericArg('--estimated-cost-cents'),
3911
4645
  currency: getArg('--currency') ?? 'USD',
3912
- summary: getArg('--summary'),
4646
+ summary: activityEnvelope.summary ?? summary,
4647
+ metadata: activityEnvelope.metadata,
4648
+ steps: activityEnvelope.steps,
3913
4649
  });
3914
4650
  if (!result.ok) {
3915
4651
  console.error(`[forkit-connect] Runtime run log emit failed (${result.status || 'local'}).`);