@optimuslabs/harness-map-staging 1.5.24 → 1.5.26

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.
@@ -15,7 +15,7 @@ import { ensureAuthentication } from '../auth/auth_flow.js';
15
15
  import { readJSONFile, readMarkdownFile } from '../readers/file_readers.js';
16
16
  import { isVscdbVirtualPath, tryReadVscdbVirtualFile, summarizeComposerPayloadForDiagnostics, } from '../readers/vscdb_config_builder.js';
17
17
  import { persistVscdbComposerContractFromPatternsResponse } from '../readers/vscdb_reader.js';
18
- import { collectConfigFilesFromPatterns, collectMcpToolFiles, collectConfigFilesFromInstalledPlugins, collectPluginCacheMcpFiles, collectMcpFromClaudeJsonProjects, collectClaudeTokenUsage, collectCopilotTokenUsage, collectOpencodeTokenUsage, collectCodexTokenUsage, collectCursorTokenUsage, collectHermesTokenUsage, collectOpenclawTokenUsage, collectPiTokenUsage, collectCoworkDesktopVersion, collectGrokCliVersion, collectClaudeDesktopExtensionManifests, collectClaudeDesktopExtensionSettingsFiles, enrichClaudeDesktopExtensionsInstallationsUpload, determineFileTypeFromPath, } from '../collection/config_collector.js';
18
+ import { collectConfigFilesFromPatterns, collectMcpToolFiles, collectConfigFilesFromInstalledPlugins, collectPluginCacheMcpFiles, collectMcpFromClaudeJsonProjects, collectClaudeTokenUsage, collectCopilotTokenUsage, collectOpencodeTokenUsage, collectCodexTokenUsage, collectCursorTokenUsage, collectHermesTokenUsage, collectOpenclawTokenUsage, collectPiTokenUsage, collectCoworkDesktopVersion, collectClaudeDesktopOrgs, collectGrokCliVersion, collectClaudeDesktopExtensionManifests, collectClaudeDesktopExtensionSettingsFiles, enrichClaudeDesktopExtensionsInstallationsUpload, determineFileTypeFromPath, } from '../collection/config_collector.js';
19
19
  import { ensureCursorUserSettingsSnapshotInBatch } from '../collection/ensure_cursor_user_settings_snapshot.js';
20
20
  import { collectSkillsCliInstalled } from '../collection/skills_cli_collector.js';
21
21
  import { collectWorkspaceVscdbs } from '../collection/mcp_tool_collector.js';
@@ -27,7 +27,34 @@ import { ensureWorkspaceRepoEnv, resolveProjectRoot } from './workspace_repo.js'
27
27
  import { scanActiveWorktrees, activeWorktreeRootSet, filterConfigFilesToActiveWorktrees, } from './worktree_scanner.js';
28
28
  import { createSignature, canonicalizePayload } from '../sender/signing.js';
29
29
  const PROJECT_ROOT = resolveProjectRoot();
30
- async function collectAllConfigFiles(endpointBase) {
30
+ const configFileKey = (c) => `${c.file_type}\t${c.file_path}`;
31
+ /** Appends the entries not in the batch yet (by file type and path), recording each as seen. */
32
+ function addUnseen(configFiles, seen, entries) {
33
+ for (const entry of entries) {
34
+ const key = configFileKey(entry);
35
+ if (seen.has(key))
36
+ continue;
37
+ seen.add(key);
38
+ configFiles.push(entry);
39
+ }
40
+ }
41
+ /** Machine-wide collectors, run in this order after the plugin caches: [hook log line, collector]. */
42
+ const HOME_COLLECTORS = [
43
+ ['scanning Claude known projects from ~/.claude.json', collectMcpFromClaudeJsonProjects],
44
+ ['aggregating Claude token usage from ~/.claude/projects transcripts', collectClaudeTokenUsage],
45
+ ['aggregating Copilot token usage from ~/.copilot/session-state transcripts', collectCopilotTokenUsage],
46
+ ['aggregating OpenCode token usage from ~/.local/share/opencode/opencode.db', collectOpencodeTokenUsage],
47
+ ['aggregating Codex token usage from ~/.codex/sessions rollouts', collectCodexTokenUsage],
48
+ ['aggregating Cursor token usage from state.vscdb', collectCursorTokenUsage],
49
+ ['aggregating Hermes token usage from ~/.hermes/state.db', collectHermesTokenUsage],
50
+ ['aggregating OpenClaw token usage from ~/.openclaw/agents/*/sessions/sessions.json', collectOpenclawTokenUsage],
51
+ ['aggregating Pi token usage from ~/.pi/agent/sessions transcripts', collectPiTokenUsage],
52
+ ["reading Claude Desktop app version for Cowork's Agent Profile", collectCoworkDesktopVersion],
53
+ ["reading the orgs Claude Desktop's signed-in account has used", collectClaudeDesktopOrgs],
54
+ ['reading Grok CLI version', collectGrokCliVersion],
55
+ ];
56
+ /** The file-path-registry patterns from the API; throws when none came back. */
57
+ async function fetchPatterns(endpointBase) {
31
58
  const patternsUrl = `${endpointBase.replace(/\/+$/, '')}${FILE_PATH_REGISTRY_FILE_PATTERNS_PATH}`;
32
59
  hookRunLog(`fetching patterns from ${patternsUrl}`);
33
60
  const patternsResponse = await getFileCollectionPatterns(endpointBase);
@@ -38,9 +65,12 @@ async function collectAllConfigFiles(endpointBase) {
38
65
  hookRunLog(msg);
39
66
  throw new Error(msg);
40
67
  }
68
+ return patternsResponse;
69
+ }
70
+ function scanPatterns(patternsResponse) {
41
71
  persistVscdbComposerContractFromPatternsResponse(patternsResponse);
42
72
  hookRunLog(`scanning config files`);
43
- const configFiles = collectConfigFilesFromPatterns(patternsResponse.patterns, PROJECT_ROOT, hookRunLog, {
73
+ return collectConfigFilesFromPatterns(patternsResponse.patterns, PROJECT_ROOT, hookRunLog, {
44
74
  vscdbEntrySpecs: patternsResponse.vscdb_entry_specs,
45
75
  vscdb_read_queries: patternsResponse.vscdb_read_queries,
46
76
  vscdb_merge_from_composer_state: patternsResponse.vscdb_merge_from_composer_state,
@@ -50,163 +80,31 @@ async function collectAllConfigFiles(endpointBase) {
50
80
  absolute_path_prefixes: patternsResponse.absolute_path_prefixes,
51
81
  mcp_tool_glob_spec: patternsResponse.mcp_tool_glob_spec ?? null,
52
82
  });
83
+ }
84
+ /** MCP tool caches: the global cache, Cursor's per-project caches and its workspace vscdb files. */
85
+ function addMcpToolCaches(patternsResponse, configFiles, seen) {
53
86
  hookRunLog(`scanning MCP tool cache`);
54
87
  const mcpToolGlobPattern = patternsResponse.patterns.find((p) => p.path_resolution === 'mcp_tool_glob');
55
88
  const mcpToolSkipPrefixes = normalizePathSkipPrefixes(mcpToolGlobPattern?.path_skip_prefixes);
56
- const existingPaths = new Set(configFiles.map((c) => `${c.file_type}\t${c.file_path}`));
57
- for (const m of collectMcpToolFiles(mcpToolSkipPrefixes, patternsResponse.client_path_constants)) {
58
- const key = `${m.file_type}\t${m.file_path}`;
59
- if (!existingPaths.has(key)) {
60
- existingPaths.add(key);
61
- configFiles.push(m);
62
- }
63
- }
89
+ addUnseen(configFiles, seen, collectMcpToolFiles(mcpToolSkipPrefixes, patternsResponse.client_path_constants));
64
90
  hookRunLog(`scanning Cursor project workspace MCP configs (~/.cursor/projects/*/mcp-cache.json)`);
65
- for (const m of collectCursorProjectWorkspaceMcpConfigs(mcpToolSkipPrefixes, patternsResponse.client_path_constants, patternsResponse.workspace_vscdb_spec, HOME_DIR)) {
66
- const key = `${m.file_type}\t${m.file_path}`;
67
- if (!existingPaths.has(key)) {
68
- existingPaths.add(key);
69
- configFiles.push(m);
70
- }
71
- }
91
+ addUnseen(configFiles, seen, collectCursorProjectWorkspaceMcpConfigs(mcpToolSkipPrefixes, patternsResponse.client_path_constants, patternsResponse.workspace_vscdb_spec, HOME_DIR));
72
92
  if (patternsResponse.workspace_vscdb_spec) {
73
93
  hookRunLog(`scanning Cursor workspace vscdb files`);
74
- for (const m of collectWorkspaceVscdbs(patternsResponse.workspace_vscdb_spec)) {
75
- const key = `${m.file_type}\t${m.file_path}`;
76
- if (!existingPaths.has(key)) {
77
- existingPaths.add(key);
78
- configFiles.push(m);
79
- }
80
- }
81
- }
82
- hookRunLog(`scanning installed plugins`);
83
- if (!patternsResponse.client_path_constants)
84
- throw new Error('client_path_constants required from API response');
85
- configFiles.push(...collectConfigFilesFromInstalledPlugins(patternsResponse.client_path_constants));
86
- hookRunLog(`scanning Claude plugin cache MCP manifests`);
87
- for (const entry of collectPluginCacheMcpFiles(patternsResponse.client_path_constants, HOME_DIR)) {
88
- const key = `${entry.file_type}\t${entry.file_path}`;
89
- if (!existingPaths.has(key)) {
90
- existingPaths.add(key);
91
- configFiles.push(entry);
92
- }
93
- }
94
- hookRunLog(`scanning Claude known projects from ~/.claude.json`);
95
- for (const entry of collectMcpFromClaudeJsonProjects(HOME_DIR)) {
96
- const key = `${entry.file_type}\t${entry.file_path}`;
97
- if (!existingPaths.has(key)) {
98
- existingPaths.add(key);
99
- configFiles.push(entry);
100
- }
101
- }
102
- hookRunLog(`aggregating Claude token usage from ~/.claude/projects transcripts`);
103
- for (const entry of collectClaudeTokenUsage(HOME_DIR)) {
104
- const key = `${entry.file_type}\t${entry.file_path}`;
105
- if (!existingPaths.has(key)) {
106
- existingPaths.add(key);
107
- configFiles.push(entry);
108
- }
109
- }
110
- hookRunLog(`aggregating Copilot token usage from ~/.copilot/session-state transcripts`);
111
- for (const entry of collectCopilotTokenUsage(HOME_DIR)) {
112
- const key = `${entry.file_type}\t${entry.file_path}`;
113
- if (!existingPaths.has(key)) {
114
- existingPaths.add(key);
115
- configFiles.push(entry);
116
- }
117
- }
118
- hookRunLog(`aggregating OpenCode token usage from ~/.local/share/opencode/opencode.db`);
119
- for (const entry of collectOpencodeTokenUsage(HOME_DIR)) {
120
- const key = `${entry.file_type}\t${entry.file_path}`;
121
- if (!existingPaths.has(key)) {
122
- existingPaths.add(key);
123
- configFiles.push(entry);
124
- }
125
- }
126
- hookRunLog(`aggregating Codex token usage from ~/.codex/sessions rollouts`);
127
- for (const entry of collectCodexTokenUsage(HOME_DIR)) {
128
- const key = `${entry.file_type}\t${entry.file_path}`;
129
- if (!existingPaths.has(key)) {
130
- existingPaths.add(key);
131
- configFiles.push(entry);
132
- }
133
- }
134
- hookRunLog(`aggregating Cursor token usage from state.vscdb`);
135
- for (const entry of collectCursorTokenUsage(HOME_DIR)) {
136
- const key = `${entry.file_type}\t${entry.file_path}`;
137
- if (!existingPaths.has(key)) {
138
- existingPaths.add(key);
139
- configFiles.push(entry);
140
- }
141
- }
142
- hookRunLog(`aggregating Hermes token usage from ~/.hermes/state.db`);
143
- for (const entry of collectHermesTokenUsage(HOME_DIR)) {
144
- const key = `${entry.file_type}\t${entry.file_path}`;
145
- if (!existingPaths.has(key)) {
146
- existingPaths.add(key);
147
- configFiles.push(entry);
148
- }
149
- }
150
- hookRunLog(`aggregating OpenClaw token usage from ~/.openclaw/agents/*/sessions/sessions.json`);
151
- for (const entry of collectOpenclawTokenUsage(HOME_DIR)) {
152
- const key = `${entry.file_type}\t${entry.file_path}`;
153
- if (!existingPaths.has(key)) {
154
- existingPaths.add(key);
155
- configFiles.push(entry);
156
- }
157
- }
158
- hookRunLog(`aggregating Pi token usage from ~/.pi/agent/sessions transcripts`);
159
- for (const entry of collectPiTokenUsage(HOME_DIR)) {
160
- const key = `${entry.file_type}\t${entry.file_path}`;
161
- if (!existingPaths.has(key)) {
162
- existingPaths.add(key);
163
- configFiles.push(entry);
164
- }
165
- }
166
- hookRunLog(`reading Claude Desktop app version for Cowork's Agent Profile`);
167
- for (const entry of collectCoworkDesktopVersion(HOME_DIR)) {
168
- const key = `${entry.file_type}\t${entry.file_path}`;
169
- if (!existingPaths.has(key)) {
170
- existingPaths.add(key);
171
- configFiles.push(entry);
172
- }
173
- }
174
- hookRunLog(`reading Grok CLI version`);
175
- for (const entry of collectGrokCliVersion(HOME_DIR)) {
176
- const key = `${entry.file_type}\t${entry.file_path}`;
177
- if (!existingPaths.has(key)) {
178
- existingPaths.add(key);
179
- configFiles.push(entry);
180
- }
94
+ addUnseen(configFiles, seen, collectWorkspaceVscdbs(patternsResponse.workspace_vscdb_spec));
181
95
  }
96
+ }
97
+ function addClaudeDesktopExtensions(configFiles, seen) {
182
98
  hookRunLog(`merging disk-only Claude Desktop extensions into extensions-installations.json`);
183
99
  const diskExtMerge = enrichClaudeDesktopExtensionsInstallationsUpload(configFiles, HOME_DIR);
184
100
  hookRunLog(`claude desktop extensions: merged=${diskExtMerge.mergedCount} had_registry_upload=${diskExtMerge.hadRegistryUpload}`);
185
- for (const entry of collectClaudeDesktopExtensionSettingsFiles(HOME_DIR)) {
186
- const key = `${entry.file_type}\t${entry.file_path}`;
187
- if (!existingPaths.has(key)) {
188
- existingPaths.add(key);
189
- configFiles.push(entry);
190
- }
191
- }
101
+ addUnseen(configFiles, seen, collectClaudeDesktopExtensionSettingsFiles(HOME_DIR));
192
102
  if (!diskExtMerge.hadRegistryUpload && diskExtMerge.mergedCount > 0) {
193
103
  hookRunLog(`scanning Claude Desktop extension manifests on disk (no registry upload in batch)`);
194
- for (const entry of collectClaudeDesktopExtensionManifests(HOME_DIR)) {
195
- const key = `${entry.file_type}\t${entry.file_path}`;
196
- if (!existingPaths.has(key)) {
197
- existingPaths.add(key);
198
- configFiles.push(entry);
199
- }
200
- }
201
- }
202
- const skillsCliEntry = collectSkillsCliInstalled(PROJECT_ROOT, hookRunLog);
203
- if (skillsCliEntry) {
204
- const key = `${skillsCliEntry.file_type}\t${skillsCliEntry.file_path}`;
205
- if (!existingPaths.has(key)) {
206
- existingPaths.add(key);
207
- configFiles.push(skillsCliEntry);
208
- }
104
+ addUnseen(configFiles, seen, collectClaudeDesktopExtensionManifests(HOME_DIR));
209
105
  }
106
+ }
107
+ function filterToActiveWorktrees(configFiles) {
210
108
  const worktreeReport = scanActiveWorktrees(PROJECT_ROOT, HOME_DIR);
211
109
  const activeRoots = activeWorktreeRootSet(worktreeReport);
212
110
  const beforeFilter = configFiles.length;
@@ -214,6 +112,29 @@ async function collectAllConfigFiles(endpointBase) {
214
112
  hookRunLog(`worktree_scan: active=${worktreeReport.length} config_files ${beforeFilter}→${filtered.length}`);
215
113
  return { configFiles: filtered, worktreeReport };
216
114
  }
115
+ async function collectAllConfigFiles(endpointBase) {
116
+ const patternsResponse = await fetchPatterns(endpointBase);
117
+ const configFiles = scanPatterns(patternsResponse);
118
+ const seen = new Set(configFiles.map(configFileKey));
119
+ addMcpToolCaches(patternsResponse, configFiles, seen);
120
+ hookRunLog(`scanning installed plugins`);
121
+ const clientPathConstants = patternsResponse.client_path_constants;
122
+ if (!clientPathConstants)
123
+ throw new Error('client_path_constants required from API response');
124
+ configFiles.push(...collectConfigFilesFromInstalledPlugins(clientPathConstants));
125
+ hookRunLog(`scanning Claude plugin cache MCP manifests`);
126
+ addUnseen(configFiles, seen, collectPluginCacheMcpFiles(clientPathConstants, HOME_DIR));
127
+ for (const [message, collect] of HOME_COLLECTORS) {
128
+ hookRunLog(message);
129
+ addUnseen(configFiles, seen, collect(HOME_DIR));
130
+ }
131
+ addClaudeDesktopExtensions(configFiles, seen);
132
+ const skillsCliEntry = collectSkillsCliInstalled(PROJECT_ROOT, hookRunLog);
133
+ if (skillsCliEntry)
134
+ addUnseen(configFiles, seen, [skillsCliEntry]);
135
+ return filterToActiveWorktrees(configFiles);
136
+ }
137
+ /** Adds the sensitive paths audit to the batch; false when it could not be run (nothing added). */
217
138
  async function addSensitivePathsAudit(endpointBase, configFiles) {
218
139
  hookRunLog(`running sensitive paths audit`);
219
140
  const auditOutputDir = join(homedir(), OPTIMUSLABS_LOGS_REL, LOG_GROUP_AUDIT);
@@ -221,12 +142,14 @@ async function addSensitivePathsAudit(endpointBase, configFiles) {
221
142
  const auditResult = await runSensitivePathsAudit(endpointBase, auditOutputDir, PROJECT_ROOT);
222
143
  hookRunLog(`sensitive_paths_audit: ${auditResult.paths.length} path(s)`);
223
144
  configFiles.push({ file_type: 'sensitive_paths_audit', file_path: 'sensitive_paths_audit.txt', raw_content: { paths: auditResult.paths } });
145
+ return true;
224
146
  }
225
147
  catch (err) {
226
148
  // Send no audit rather than an empty one: the backend keeps the machine's last good audit.
227
149
  hookRunLog(`sensitive_paths_audit_error: ${err instanceof Error ? err.message : String(err)} (audit not sent)`);
228
150
  hookLogError('sensitive_paths_audit', err, { endpoint: endpointBase, output_dir: auditOutputDir });
229
151
  await reportFunctionalFailureAwait('inventory', 'sensitive_paths_audit', err);
152
+ return false;
230
153
  }
231
154
  }
232
155
  /** POSIX mode of every collected file, plus granted folders (and their ancestors) and sensitive audit hits within a budget. stat() only. */
@@ -334,9 +257,16 @@ async function main() {
334
257
  hookRunLog(`patterns_error: ${err instanceof Error ? err.message : String(err)}`);
335
258
  throw err;
336
259
  }
337
- await addSensitivePathsAudit(endpointBase, configFiles);
260
+ const sensitiveAudited = await addSensitivePathsAudit(endpointBase, configFiles);
338
261
  ensureCursorUserSettingsSnapshotInBatch(configFiles);
339
- addPathPermissionsAudit(configFiles);
262
+ if (sensitiveAudited) {
263
+ addPathPermissionsAudit(configFiles);
264
+ }
265
+ else {
266
+ // Built without the sensitive paths, it would replace the backend's last good permissions
267
+ // audit and drop those paths' modes; skip it so both audits stay as last sent.
268
+ hookRunLog('path_permissions_audit: skipped (no sensitive paths audit this run)');
269
+ }
340
270
  const fileTypes = [...new Set(configFiles.map((c) => c.file_type))].join(', ');
341
271
  hookRunLog(`collected ${configFiles.length} config file(s) file_types=${fileTypes}`);
342
272
  for (const c of configFiles) {
@@ -2,7 +2,7 @@
2
2
  * Paths and JSON persistence for ~/.optimuslabs/management:
3
3
  * - remediation_instructions.json (remediations[])
4
4
  */
5
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
5
+ import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
6
6
  import { dirname, join } from 'node:path';
7
7
  import { homedir } from 'node:os';
8
8
  import { OPTIMUSLABS_MANAGEMENT_REL, OPTIMUSLABS_LOGS_REL } from '../../bootstrap_constants.js';
@@ -129,7 +129,51 @@ function parseInstructionsRaw(raw) {
129
129
  }
130
130
  }
131
131
  export function writeRemediationInstructionsFile(file) {
132
- atomicWriteJson(getRemediationInstructionsPath(), file);
132
+ writeInstructionsFile(file, new Date().toISOString());
133
+ }
134
+ function writeInstructionsFile(file, remediationsWrittenAt) {
135
+ // Most callers write `{ remediations }` only; keep the approval lists they didn't pass.
136
+ const onDisk = readRemediationInstructionsFile();
137
+ const approved_tenants = 'approved_tenants' in file ? file.approved_tenants : onDisk.approved_tenants;
138
+ const unapproved_email_domains = 'unapproved_email_domains' in file ? file.unapproved_email_domains : onDisk.unapproved_email_domains;
139
+ const allowed_email_domains = 'allowed_email_domains' in file ? file.allowed_email_domains : onDisk.allowed_email_domains;
140
+ const approved_agents = 'approved_agents' in file ? file.approved_agents : onDisk.approved_agents;
141
+ atomicWriteJson(getRemediationInstructionsPath(), {
142
+ ...file,
143
+ ...(approved_tenants === undefined ? {} : { approved_tenants }),
144
+ ...(unapproved_email_domains === undefined ? {} : { unapproved_email_domains }),
145
+ ...(allowed_email_domains === undefined ? {} : { allowed_email_domains }),
146
+ ...(approved_agents === undefined ? {} : { approved_agents }),
147
+ remediations_written_at: remediationsWrittenAt,
148
+ });
149
+ }
150
+ /** When the remediations on disk were written: recorded, or for an older file its mtime. */
151
+ function remediationsWrittenAtOf(onDisk) {
152
+ if (onDisk.remediations_written_at)
153
+ return onDisk.remediations_written_at;
154
+ const path = getRemediationInstructionsPath();
155
+ return existsSync(path) ? statSync(path).mtime.toISOString() : new Date().toISOString();
156
+ }
157
+ /** Replace one approval list, keeping the remediations and when they were written. */
158
+ function writeApprovalList(patch) {
159
+ const onDisk = readRemediationInstructionsFile();
160
+ writeInstructionsFile({ ...onDisk, ...patch }, remediationsWrittenAtOf(onDisk));
161
+ }
162
+ /** Replace only the unapproved email domains, keeping everything else on disk. */
163
+ export function writeUnapprovedEmailDomains(unapproved_email_domains) {
164
+ writeApprovalList({ unapproved_email_domains });
165
+ }
166
+ /** Replace only the approved agents, keeping everything else on disk. */
167
+ export function writeApprovedAgents(approved_agents) {
168
+ writeApprovalList({ approved_agents });
169
+ }
170
+ /** Replace only the email allowlist, keeping everything else on disk. */
171
+ export function writeAllowedEmailDomains(allowed_email_domains) {
172
+ writeApprovalList({ allowed_email_domains });
173
+ }
174
+ /** Replace only the approved-tenant list, keeping the remediations already on disk. */
175
+ export function writeApprovedTenants(approved_tenants) {
176
+ writeApprovalList({ approved_tenants });
133
177
  }
134
178
  export function readRemediationInstructionsFile() {
135
179
  const path = getRemediationInstructionsPath();