@optimuslabs/harness-map-staging 1.5.16
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/README.md +46 -0
- package/dist/apply_deferred_vscdb.js +38 -0
- package/dist/bootstrap_constants.js +15 -0
- package/dist/cli/bash_script_generator.js +95 -0
- package/dist/cli.js +144 -0
- package/dist/cli_invocation_match.js +30 -0
- package/dist/compliance_check_runner.js +57 -0
- package/dist/compliance_prompt_gate.js +396 -0
- package/dist/dialog_prefs_cli.js +109 -0
- package/dist/endpoint_client/compress_payload.js +25 -0
- package/dist/endpoint_client/http_transport.js +90 -0
- package/dist/endpoint_client/index.js +3 -0
- package/dist/endpoint_client/registry_api.js +41 -0
- package/dist/endpoint_client/startup_api.js +44 -0
- package/dist/endpoint_client/types.js +4 -0
- package/dist/execute_trusted_restarts.js +60 -0
- package/dist/log_config_files/auth/auth_flow.js +22 -0
- package/dist/log_config_files/auth/auth_key_store.js +14 -0
- package/dist/log_config_files/collection/claude_desktop_extensions_collector.js +248 -0
- package/dist/log_config_files/collection/claude_known_projects_collector.js +42 -0
- package/dist/log_config_files/collection/claude_token_usage_collector.js +203 -0
- package/dist/log_config_files/collection/codex_token_usage_collector.js +342 -0
- package/dist/log_config_files/collection/config_collector.js +245 -0
- package/dist/log_config_files/collection/copilot_token_usage_collector.js +167 -0
- package/dist/log_config_files/collection/cowork_desktop_version_collector.js +60 -0
- package/dist/log_config_files/collection/cowork_session_whitelist.js +89 -0
- package/dist/log_config_files/collection/cursor_project_mcp_collector.js +230 -0
- package/dist/log_config_files/collection/cursor_project_workspace_activity.js +73 -0
- package/dist/log_config_files/collection/cursor_token_usage_collector.js +192 -0
- package/dist/log_config_files/collection/directory_collector.js +126 -0
- package/dist/log_config_files/collection/enrichment_helpers.js +53 -0
- package/dist/log_config_files/collection/ensure_cursor_user_settings_snapshot.js +50 -0
- package/dist/log_config_files/collection/file_type_rules.js +47 -0
- package/dist/log_config_files/collection/grok_cli_version_collector.js +53 -0
- package/dist/log_config_files/collection/hermes_token_usage_collector.js +241 -0
- package/dist/log_config_files/collection/mcp_tool_collector.js +106 -0
- package/dist/log_config_files/collection/metadata_merge.js +43 -0
- package/dist/log_config_files/collection/openclaw_helpers.js +55 -0
- package/dist/log_config_files/collection/openclaw_token_usage_collector.js +223 -0
- package/dist/log_config_files/collection/opencode_token_usage_collector.js +177 -0
- package/dist/log_config_files/collection/pi_token_usage_collector.js +239 -0
- package/dist/log_config_files/collection/plugin_collector.js +223 -0
- package/dist/log_config_files/collection/plugin_version_helpers.js +37 -0
- package/dist/log_config_files/collection/skills_cli_collector.js +162 -0
- package/dist/log_config_files/index.js +19 -0
- package/dist/log_config_files/paths/path_constants_helpers.js +71 -0
- package/dist/log_config_files/paths/pattern_resolver.js +326 -0
- package/dist/log_config_files/readers/composer_shadow_merge.js +67 -0
- package/dist/log_config_files/readers/cursor_shadow_merge_ruleset.js +46 -0
- package/dist/log_config_files/readers/file_readers.js +196 -0
- package/dist/log_config_files/readers/vscdb_config_builder.js +148 -0
- package/dist/log_config_files/readers/vscdb_reactive_storage.js +62 -0
- package/dist/log_config_files/readers/vscdb_reader.js +337 -0
- package/dist/log_config_files/runtime/client_event_reporter.js +347 -0
- package/dist/log_config_files/runtime/compliance_check.js +1013 -0
- package/dist/log_config_files/runtime/compliance_session_log.js +378 -0
- package/dist/log_config_files/runtime/dialog_preferences.js +31 -0
- package/dist/log_config_files/runtime/hardware_uuid.js +36 -0
- package/dist/log_config_files/runtime/hook_logger.js +267 -0
- package/dist/log_config_files/runtime/hook_type_for_request.js +30 -0
- package/dist/log_config_files/runtime/log_metadata.js +122 -0
- package/dist/log_config_files/runtime/main_runner.js +376 -0
- package/dist/log_config_files/runtime/management_storage.js +144 -0
- package/dist/log_config_files/runtime/ops_target_path.js +18 -0
- package/dist/log_config_files/runtime/remediation_apply_tracking.js +152 -0
- package/dist/log_config_files/runtime/remediation_config_path.js +140 -0
- package/dist/log_config_files/runtime/remediation_sync.js +1705 -0
- package/dist/log_config_files/runtime/secret_regex_scan.js +126 -0
- package/dist/log_config_files/runtime/sqlite_binary.js +124 -0
- package/dist/log_config_files/runtime/trusted_restarts.js +113 -0
- package/dist/log_config_files/runtime/workspace_repo.js +170 -0
- package/dist/log_config_files/runtime/worktree_absent.js +50 -0
- package/dist/log_config_files/runtime/worktree_scanner.js +137 -0
- package/dist/log_config_files/sender/batch_sender.js +283 -0
- package/dist/log_config_files/sender/endpoint_config.js +24 -0
- package/dist/log_config_files/sender/signing.js +1 -0
- package/dist/log_sensitive_paths_audit.js +89 -0
- package/dist/log_uuid/auth_key_store.js +37 -0
- package/dist/log_uuid/hardware_uuid.js +35 -0
- package/dist/log_uuid/index.js +11 -0
- package/dist/log_uuid/log_uuid_helper.js +30 -0
- package/dist/log_uuid/startup_sender.js +131 -0
- package/dist/log_uuid/user_profile.js +192 -0
- package/dist/remediation_change_preview.js +182 -0
- package/dist/tofu.js +22 -0
- package/dist/tofu_environment.js +36 -0
- package/dist/types/config_file_types.js +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep in sync with file_path_registry/cursor_composer_shadow_keys.py
|
|
3
|
+
* (CURSOR_REACTIVE_BLOB_SHADOW_KEYS + merge sets).
|
|
4
|
+
*/
|
|
5
|
+
/** Keys Cursor duplicates on the reactive blob root and inside composerState. */
|
|
6
|
+
export const CURSOR_COMPOSER_SHADOW_KEYS = [
|
|
7
|
+
'lastBrowserConnectionMode',
|
|
8
|
+
'playwrightProtection',
|
|
9
|
+
'isWebSearchToolEnabled',
|
|
10
|
+
'isWebSearchToolEnabled2',
|
|
11
|
+
'isWebSearchToolEnabled3',
|
|
12
|
+
'isWebFetchToolEnabled',
|
|
13
|
+
'autoAcceptWebSearchTool',
|
|
14
|
+
];
|
|
15
|
+
/** Cursor Settings UI writes these on nested composerState; blob root copies are often stale. */
|
|
16
|
+
export const CURSOR_SHADOW_KEYS_NESTED_WINS = new Set([
|
|
17
|
+
'playwrightProtection',
|
|
18
|
+
'isWebFetchToolEnabled',
|
|
19
|
+
'isWebSearchToolEnabled3',
|
|
20
|
+
'autoAcceptWebSearchTool',
|
|
21
|
+
]);
|
|
22
|
+
export function cursorShadowValueIsEnabled(value) {
|
|
23
|
+
return (value === true ||
|
|
24
|
+
value === 1 ||
|
|
25
|
+
(typeof value === 'string' && ['true', '1', 'yes'].includes(value.toLowerCase())));
|
|
26
|
+
}
|
|
27
|
+
/** Prefer nested when enabled (web search / auto-accept); else root. */
|
|
28
|
+
export function coalesceCursorEnabledShadowValue(root, nested) {
|
|
29
|
+
if (cursorShadowValueIsEnabled(nested))
|
|
30
|
+
return nested;
|
|
31
|
+
if (cursorShadowValueIsEnabled(root))
|
|
32
|
+
return root;
|
|
33
|
+
if (root !== undefined && root !== null)
|
|
34
|
+
return root;
|
|
35
|
+
return nested;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* After merge, ruleset scanning and uploads use composerState.<key> only — drop stale blob-root copies.
|
|
39
|
+
*/
|
|
40
|
+
export function stripShadowKeysFromReactiveBlobUpload(stateData) {
|
|
41
|
+
if (!('composerState' in stateData))
|
|
42
|
+
return;
|
|
43
|
+
for (const key of CURSOR_COMPOSER_SHADOW_KEYS) {
|
|
44
|
+
delete stateData[key];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { parse as parseToml, stringify as stringifyTomlValue } from 'smol-toml';
|
|
3
|
+
function stripJsoncComments(input) {
|
|
4
|
+
let out = '';
|
|
5
|
+
let inString = false;
|
|
6
|
+
let quote = '';
|
|
7
|
+
let escaped = false;
|
|
8
|
+
let inLineComment = false;
|
|
9
|
+
let inBlockComment = false;
|
|
10
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
11
|
+
const ch = input[i];
|
|
12
|
+
const next = i + 1 < input.length ? input[i + 1] : '';
|
|
13
|
+
if (inLineComment) {
|
|
14
|
+
if (ch === '\n') {
|
|
15
|
+
inLineComment = false;
|
|
16
|
+
out += ch;
|
|
17
|
+
}
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (inBlockComment) {
|
|
21
|
+
if (ch === '*' && next === '/') {
|
|
22
|
+
inBlockComment = false;
|
|
23
|
+
i += 1;
|
|
24
|
+
}
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (inString) {
|
|
28
|
+
out += ch;
|
|
29
|
+
if (escaped) {
|
|
30
|
+
escaped = false;
|
|
31
|
+
}
|
|
32
|
+
else if (ch === '\\') {
|
|
33
|
+
escaped = true;
|
|
34
|
+
}
|
|
35
|
+
else if (ch === quote) {
|
|
36
|
+
inString = false;
|
|
37
|
+
quote = '';
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (ch === '"' || ch === "'") {
|
|
42
|
+
inString = true;
|
|
43
|
+
quote = ch;
|
|
44
|
+
out += ch;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (ch === '/' && next === '/') {
|
|
48
|
+
inLineComment = true;
|
|
49
|
+
i += 1;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (ch === '/' && next === '*') {
|
|
53
|
+
inBlockComment = true;
|
|
54
|
+
i += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
out += ch;
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
function stripTrailingCommas(input) {
|
|
62
|
+
let out = '';
|
|
63
|
+
let inString = false;
|
|
64
|
+
let quote = '';
|
|
65
|
+
let escaped = false;
|
|
66
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
67
|
+
const ch = input[i];
|
|
68
|
+
if (inString) {
|
|
69
|
+
out += ch;
|
|
70
|
+
if (escaped) {
|
|
71
|
+
escaped = false;
|
|
72
|
+
}
|
|
73
|
+
else if (ch === '\\') {
|
|
74
|
+
escaped = true;
|
|
75
|
+
}
|
|
76
|
+
else if (ch === quote) {
|
|
77
|
+
inString = false;
|
|
78
|
+
quote = '';
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (ch === '"' || ch === "'") {
|
|
83
|
+
inString = true;
|
|
84
|
+
quote = ch;
|
|
85
|
+
out += ch;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (ch === ',') {
|
|
89
|
+
let j = i + 1;
|
|
90
|
+
while (j < input.length && /\s/.test(input[j]))
|
|
91
|
+
j += 1;
|
|
92
|
+
if (j < input.length && (input[j] === '}' || input[j] === ']'))
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
out += ch;
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
function parseJsonWithJsoncFallback(raw) {
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(raw);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
try {
|
|
105
|
+
const sanitized = stripTrailingCommas(stripJsoncComments(raw));
|
|
106
|
+
return JSON.parse(sanitized);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Read and parse an MCP config file. Returns null on any error. */
|
|
114
|
+
function readMCPConfig(filePath) {
|
|
115
|
+
try {
|
|
116
|
+
if (!existsSync(filePath))
|
|
117
|
+
return null;
|
|
118
|
+
return parseJsonWithJsoncFallback(readFileSync(filePath, 'utf-8'));
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error(`Error reading ${filePath}:`, error instanceof Error ? error.message : String(error));
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** Read and parse a JSON file. Warns on permission errors, returns null on any failure. */
|
|
126
|
+
function readJSONFile(filePath) {
|
|
127
|
+
try {
|
|
128
|
+
if (!existsSync(filePath))
|
|
129
|
+
return null;
|
|
130
|
+
return parseJsonWithJsoncFallback(readFileSync(filePath, 'utf-8'));
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
|
134
|
+
console.warn(`Permission denied reading ${filePath} (may require elevated permissions)`);
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Read a text/markdown file. Warns on permission errors, returns null on any failure. */
|
|
140
|
+
function readMarkdownFile(filePath) {
|
|
141
|
+
try {
|
|
142
|
+
if (!existsSync(filePath))
|
|
143
|
+
return null;
|
|
144
|
+
return readFileSync(filePath, 'utf-8');
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
|
148
|
+
console.warn(`Permission denied reading ${filePath} (may require elevated permissions)`);
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Read installed extensions from Cursor extensions cache file. */
|
|
154
|
+
function readInstalledExtensions(extensionsCachePath) {
|
|
155
|
+
const pathToRead = extensionsCachePath;
|
|
156
|
+
const extensions = [];
|
|
157
|
+
try {
|
|
158
|
+
if (!existsSync(pathToRead))
|
|
159
|
+
return extensions;
|
|
160
|
+
const data = JSON.parse(readFileSync(pathToRead, 'utf-8'));
|
|
161
|
+
if (data.result && Array.isArray(data.result)) {
|
|
162
|
+
for (const ext of data.result) {
|
|
163
|
+
const id = ext.identifier?.id;
|
|
164
|
+
if (id) {
|
|
165
|
+
extensions.push({
|
|
166
|
+
id,
|
|
167
|
+
displayName: ext.manifest?.displayName || ext.manifest?.publisher || '',
|
|
168
|
+
version: ext.manifest?.version || '',
|
|
169
|
+
publisher: ext.manifest?.publisher,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.warn(`Error reading installed extensions from cache (${pathToRead}):`, error instanceof Error ? error.message : String(error));
|
|
177
|
+
}
|
|
178
|
+
return extensions;
|
|
179
|
+
}
|
|
180
|
+
/** Parse TOML text into a nested object. Returns null on parse failure. */
|
|
181
|
+
function parseTomlText(raw) {
|
|
182
|
+
try {
|
|
183
|
+
const parsed = parseToml(raw);
|
|
184
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
185
|
+
? parsed
|
|
186
|
+
: null;
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** Serialize a nested object to TOML (used when writing remediated .toml configs). */
|
|
193
|
+
function stringifyToml(value) {
|
|
194
|
+
return stringifyTomlValue(value);
|
|
195
|
+
}
|
|
196
|
+
export { readMCPConfig, readJSONFile, readMarkdownFile, readInstalledExtensions, parseJsonWithJsoncFallback, parseTomlText, stringifyToml, };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { readVSCDBState } from './vscdb_reader.js';
|
|
3
|
+
import { getVscdbPath } from '../paths/path_constants_helpers.js';
|
|
4
|
+
import { getByPath } from '../collection/enrichment_helpers.js';
|
|
5
|
+
import { hookRunLog } from '../runtime/hook_logger.js';
|
|
6
|
+
/** True if path is a vscdb virtual path (db file or db#fragment). Uses backend vscdb_basename and vscdb_entry_specs only. */
|
|
7
|
+
export function isVscdbVirtualPath(filePath, response) {
|
|
8
|
+
if (!response?.vscdb_entry_specs?.length)
|
|
9
|
+
return false;
|
|
10
|
+
const norm = filePath.replace(/\\/g, '/');
|
|
11
|
+
if (norm.includes('#')) {
|
|
12
|
+
const fragment = '#' + norm.split('#').slice(1).join('#');
|
|
13
|
+
if (response.vscdb_entry_specs.some((s) => s.path_suffix === fragment))
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
if (response.vscdb_basename && (norm.endsWith(response.vscdb_basename) || norm.endsWith(response.vscdb_basename + '#')))
|
|
17
|
+
return true;
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* If path is a vscdb virtual path per API response, read DB and build raw_content + file_type; else return null.
|
|
22
|
+
* Caller uses this so main_runner has no state.vscdb branches.
|
|
23
|
+
*/
|
|
24
|
+
export function tryReadVscdbVirtualFile(filePath, response) {
|
|
25
|
+
if (!response?.vscdb_entry_specs?.length)
|
|
26
|
+
return null;
|
|
27
|
+
const norm = filePath.replace(/\\/g, '/');
|
|
28
|
+
const actualPath = norm.split('#')[0];
|
|
29
|
+
const pathSuffix = norm.includes('#') ? '#' + norm.split('#').slice(1).join('#') : '#composerState';
|
|
30
|
+
const spec = response.vscdb_entry_specs.find((s) => s.path_suffix === pathSuffix) ?? response.vscdb_entry_specs.find((s) => s.path_suffix === '#composerState');
|
|
31
|
+
if (!spec)
|
|
32
|
+
return null;
|
|
33
|
+
const isAbsolute = actualPath.startsWith('/') || (actualPath.length >= 2 && actualPath[1] === ':');
|
|
34
|
+
if (!response.client_path_constants)
|
|
35
|
+
throw new Error('client_path_constants required from API response to resolve vscdb path');
|
|
36
|
+
const defaultVscdbPath = getVscdbPath(homedir(), response.client_path_constants);
|
|
37
|
+
const dbPath = isAbsolute ? actualPath : defaultVscdbPath;
|
|
38
|
+
const vscdbState = readVSCDBState(dbPath, response.vscdb_read_queries ?? undefined, response.vscdb_merge_from_composer_state ?? undefined);
|
|
39
|
+
if (!vscdbState)
|
|
40
|
+
return null;
|
|
41
|
+
const rawContent = buildVscdbRawContentFromSpec(vscdbState, spec);
|
|
42
|
+
if (!rawContent)
|
|
43
|
+
return null;
|
|
44
|
+
return { rawContent, fileType: spec.file_type };
|
|
45
|
+
}
|
|
46
|
+
function setNested(obj, dotPath, value) {
|
|
47
|
+
const parts = dotPath.split('.');
|
|
48
|
+
const safe = (k) => k !== '__proto__' && k !== 'constructor' && k !== 'prototype';
|
|
49
|
+
if (!parts.every(safe))
|
|
50
|
+
return;
|
|
51
|
+
let cur = obj;
|
|
52
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
53
|
+
const p = parts[i];
|
|
54
|
+
if (!(p in cur) || typeof cur[p] !== 'object')
|
|
55
|
+
cur[p] = Object.create(null);
|
|
56
|
+
cur = cur[p];
|
|
57
|
+
}
|
|
58
|
+
cur[parts[parts.length - 1]] = value;
|
|
59
|
+
}
|
|
60
|
+
function buildRawContent(state, stateKey, value, includeKeys, ts) {
|
|
61
|
+
const raw = { source: 'state.vscdb', extracted_at: ts };
|
|
62
|
+
if (stateKey.includes('.')) {
|
|
63
|
+
setNested(raw, stateKey, value);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
raw[stateKey] = value;
|
|
67
|
+
}
|
|
68
|
+
// Shadow keys are merged into composerState during readVSCDBState; do not copy stale blob-root
|
|
69
|
+
// copies onto the upload payload (policy paths are composerState.<key> only).
|
|
70
|
+
if (includeKeys?.length && stateKey !== 'composerState') {
|
|
71
|
+
for (const k of includeKeys) {
|
|
72
|
+
if (k in state && state[k] !== undefined)
|
|
73
|
+
raw[k] = state[k];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return raw;
|
|
77
|
+
}
|
|
78
|
+
/** Build raw_content for a single state.vscdb fragment from a backend spec. Validation uses spec.value_constraint only. */
|
|
79
|
+
function buildVscdbRawContentFromSpec(state, spec) {
|
|
80
|
+
const value = getByPath(state, spec.state_key);
|
|
81
|
+
if (value === undefined || value === null)
|
|
82
|
+
return null;
|
|
83
|
+
if (spec.value_constraint === 'non_empty_array') {
|
|
84
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (spec.value_constraint === 'boolean') {
|
|
88
|
+
const okBool = typeof value === 'boolean';
|
|
89
|
+
const okSqliteInt = typeof value === 'number' && (value === 0 || value === 1);
|
|
90
|
+
if (!okBool && !okSqliteInt)
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return buildRawContent(state, spec.state_key, value, spec.include_keys, new Date().toISOString());
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Build config file entries from state.vscdb using backend-driven specs and read queries.
|
|
97
|
+
* No hardcoded file_type, path fragments, or ItemTable keys; all from API.
|
|
98
|
+
*/
|
|
99
|
+
function summarizeComposerStateValue(composerState) {
|
|
100
|
+
if (composerState === null || composerState === undefined || typeof composerState !== 'object' || Array.isArray(composerState)) {
|
|
101
|
+
return `composerState_type=${composerState === null ? 'null' : typeof composerState}`;
|
|
102
|
+
}
|
|
103
|
+
const o = composerState;
|
|
104
|
+
const m4 = o.modes4;
|
|
105
|
+
if (!Array.isArray(m4))
|
|
106
|
+
return `modes4=${m4 === undefined ? 'absent' : typeof m4}`;
|
|
107
|
+
const agent = m4.find((x) => x !== null && typeof x === 'object' && !Array.isArray(x) && x.id === 'agent');
|
|
108
|
+
if (!agent)
|
|
109
|
+
return `modes4_len=${m4.length} agent_row=missing`;
|
|
110
|
+
return `modes4_len=${m4.length} agent_autoRun=${JSON.stringify(agent.autoRun)} agent_fullAutoRun=${JSON.stringify(agent.fullAutoRun)}`;
|
|
111
|
+
}
|
|
112
|
+
/** Exported for main_runner: one-line summary of uploaded #composerState payload (policy full-auto-run input). */
|
|
113
|
+
export function summarizeComposerPayloadForDiagnostics(raw) {
|
|
114
|
+
const inner = summarizeComposerStateValue(raw.composerState);
|
|
115
|
+
const rawKeys = Object.keys(raw)
|
|
116
|
+
.filter((k) => k !== 'source' && k !== 'extracted_at')
|
|
117
|
+
.slice(0, 16);
|
|
118
|
+
return `${inner} payload_keys=[${rawKeys.join(',')}]`;
|
|
119
|
+
}
|
|
120
|
+
function collectVscdbEntries(vscdbPath, specs, readQueries, mergeFromComposerState) {
|
|
121
|
+
if (!specs?.length)
|
|
122
|
+
return [];
|
|
123
|
+
const state = readVSCDBState(vscdbPath, readQueries, mergeFromComposerState);
|
|
124
|
+
if (!state || typeof state !== 'object') {
|
|
125
|
+
hookRunLog(`diag vscdb collect: readVSCDBState returned null/invalid vscdb=${vscdbPath}`);
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
const sk = Object.keys(state);
|
|
129
|
+
hookRunLog(`diag vscdb collect: readVSCDBState ok vscdb=${vscdbPath} top_keys=[${sk.slice(0, 24).join(',')}] ${summarizeComposerStateValue(state.composerState)}`);
|
|
130
|
+
const entries = [];
|
|
131
|
+
for (const spec of specs) {
|
|
132
|
+
const atPath = getByPath(state, spec.state_key);
|
|
133
|
+
const rawContent = buildVscdbRawContentFromSpec(state, spec);
|
|
134
|
+
if (!rawContent) {
|
|
135
|
+
if (spec.state_key === 'composerState' || spec.path_suffix === '#composerState') {
|
|
136
|
+
hookRunLog(`diag vscdb collect: skipped composer emit value_constraint=${spec.value_constraint ?? 'none'} at_path=${atPath === undefined ? 'undefined' : atPath === null ? 'null' : typeof atPath} path_suffix=${spec.path_suffix}`);
|
|
137
|
+
}
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
entries.push({
|
|
141
|
+
file_type: spec.file_type,
|
|
142
|
+
file_path: `${vscdbPath}${spec.path_suffix}`,
|
|
143
|
+
raw_content: rawContent,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return entries;
|
|
147
|
+
}
|
|
148
|
+
export { collectVscdbEntries, buildVscdbRawContentFromSpec };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { readFileCollectionVscdbContract } from '../runtime/management_storage.js';
|
|
4
|
+
import { resolveSqlite3Binary } from '../runtime/sqlite_binary.js';
|
|
5
|
+
import { CURSOR_COMPOSER_SHADOW_KEYS } from './cursor_shadow_merge_ruleset.js';
|
|
6
|
+
/** Suffix shared by Cursor reactive-storage ItemTable keys (see file_path_registry cursor agent). */
|
|
7
|
+
export const REACTIVE_STORAGE_ITEM_KEY_SUFFIX = 'reactiveStorageServiceImpl.persistentStorage.applicationUser';
|
|
8
|
+
function querySqliteValue(dbPath, key) {
|
|
9
|
+
const bin = resolveSqlite3Binary();
|
|
10
|
+
if (!bin)
|
|
11
|
+
return '';
|
|
12
|
+
const safe = key.replace(/'/g, "''");
|
|
13
|
+
const script = `.timeout 60000\nSELECT value FROM ItemTable WHERE key='${safe}';\n`;
|
|
14
|
+
return execFileSync(bin, ['-noheader', dbPath], {
|
|
15
|
+
input: script,
|
|
16
|
+
encoding: 'utf8',
|
|
17
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
18
|
+
}).trim();
|
|
19
|
+
}
|
|
20
|
+
/** When the cached contract is empty/stale, discover the live reactive blob row in state.vscdb. */
|
|
21
|
+
export function discoverReactiveStorageItemKeyInVscdb(dbPath) {
|
|
22
|
+
if (!existsSync(dbPath) || !resolveSqlite3Binary())
|
|
23
|
+
return undefined;
|
|
24
|
+
try {
|
|
25
|
+
const suffix = REACTIVE_STORAGE_ITEM_KEY_SUFFIX.replace(/'/g, "''");
|
|
26
|
+
const key = execFileSync(resolveSqlite3Binary(), [
|
|
27
|
+
dbPath,
|
|
28
|
+
`SELECT key FROM ItemTable WHERE key LIKE '%${suffix}%' LIMIT 1;`,
|
|
29
|
+
], { encoding: 'utf8' }).trim();
|
|
30
|
+
if (!key || /['"\\]/.test(key))
|
|
31
|
+
return undefined;
|
|
32
|
+
const raw = querySqliteValue(dbPath, key);
|
|
33
|
+
if (!raw || raw === '{}')
|
|
34
|
+
return undefined;
|
|
35
|
+
return key;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function reactiveStorageItemKeyFromContract() {
|
|
42
|
+
const k = readFileCollectionVscdbContract()?.reactive_storage_item_key;
|
|
43
|
+
return typeof k === 'string' && k.trim() !== '' ? k.trim() : undefined;
|
|
44
|
+
}
|
|
45
|
+
export function reactiveStorageItemKeyForVscdb(dbPath) {
|
|
46
|
+
const fromContract = reactiveStorageItemKeyFromContract();
|
|
47
|
+
if (fromContract)
|
|
48
|
+
return fromContract;
|
|
49
|
+
if (dbPath)
|
|
50
|
+
return discoverReactiveStorageItemKeyInVscdb(dbPath);
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
export function composerShadowKeysFromContract() {
|
|
54
|
+
const fromContract = readFileCollectionVscdbContract()?.composer_shadow_keys ?? [];
|
|
55
|
+
if (fromContract.length > 0)
|
|
56
|
+
return [...fromContract];
|
|
57
|
+
return [...CURSOR_COMPOSER_SHADOW_KEYS];
|
|
58
|
+
}
|
|
59
|
+
export function reactiveBlobHasUsableContent(dbPath, reactiveKey) {
|
|
60
|
+
const raw = querySqliteValue(dbPath, reactiveKey).trim();
|
|
61
|
+
return raw !== '' && raw !== '{}';
|
|
62
|
+
}
|