log-llm-config-staging 1.3.79 → 1.3.81
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.
|
@@ -176,13 +176,13 @@ function runOneStep(dbPath, stateData, step) {
|
|
|
176
176
|
? composerState
|
|
177
177
|
: undefined;
|
|
178
178
|
for (const k of step.include_keys) {
|
|
179
|
-
//
|
|
180
|
-
if (
|
|
181
|
-
stateData[k] = nested[k];
|
|
182
|
-
}
|
|
183
|
-
else if (k in obj && obj[k] !== undefined) {
|
|
179
|
+
// Reactive blob root wins over nested composerState (matches policy_engine.scan_targets merge).
|
|
180
|
+
if (k in obj && obj[k] !== undefined) {
|
|
184
181
|
stateData[k] = obj[k];
|
|
185
182
|
}
|
|
183
|
+
else if (nested && k in nested && nested[k] !== undefined) {
|
|
184
|
+
stateData[k] = nested[k];
|
|
185
|
+
}
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
return;
|
|
@@ -14,7 +14,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
14
14
|
import { homedir } from 'node:os';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
import { readVscdbItemTableJson } from '../readers/vscdb_reader.js';
|
|
17
|
-
import { readRemediationInstructionsFile, writeRemediationInstructionsFile } from './management_storage.js';
|
|
17
|
+
import { readFileCollectionVscdbContract, readRemediationInstructionsFile, writeRemediationInstructionsFile, } from './management_storage.js';
|
|
18
18
|
import { resolveRemediationConfigPath } from './remediation_config_path.js';
|
|
19
19
|
import { complianceRunnerDiag, hookRunLog, logRemediationApplyFailure } from './hook_logger.js';
|
|
20
20
|
import { loadEndpointBase } from '../sender/endpoint_config.js';
|
|
@@ -174,6 +174,47 @@ function verifyOpsApplied(configJson, settingPath, ops) {
|
|
|
174
174
|
}
|
|
175
175
|
return { ok: true, expected: null };
|
|
176
176
|
}
|
|
177
|
+
function shouldMergeComposerShadowKeys(itemKeyFromPath, checkSettingPaths) {
|
|
178
|
+
if (itemKeyFromPath === 'composerState')
|
|
179
|
+
return true;
|
|
180
|
+
return checkSettingPaths.some((p) => p === 'composerState' || p.startsWith('composerState.'));
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Cursor stores web-tool toggles on the reactive blob root and nested `composerState`.
|
|
184
|
+
* Policy scan merges root shadow keys into composerState; compliance must match or autofix
|
|
185
|
+
* never runs when nested values are stale (e.g. lastBrowserConnectionMode none vs root editor).
|
|
186
|
+
*/
|
|
187
|
+
export function mergeComposerShadowKeysFromReactiveBlob(dbPath, merged) {
|
|
188
|
+
const contract = readFileCollectionVscdbContract();
|
|
189
|
+
const reactiveKey = contract?.reactive_storage_item_key?.trim();
|
|
190
|
+
const shadowKeys = contract?.composer_shadow_keys ?? [];
|
|
191
|
+
if (!reactiveKey || shadowKeys.length === 0)
|
|
192
|
+
return;
|
|
193
|
+
const reactiveWrapped = readVscdbItemTableJson(dbPath, reactiveKey);
|
|
194
|
+
if (reactiveWrapped === null)
|
|
195
|
+
return;
|
|
196
|
+
const blob = reactiveWrapped[reactiveKey];
|
|
197
|
+
if (!blob || typeof blob !== 'object' || Array.isArray(blob))
|
|
198
|
+
return;
|
|
199
|
+
const root = blob;
|
|
200
|
+
let cs = merged.composerState;
|
|
201
|
+
if (!cs || typeof cs !== 'object' || Array.isArray(cs)) {
|
|
202
|
+
const nested = root.composerState;
|
|
203
|
+
if (nested && typeof nested === 'object' && !Array.isArray(nested)) {
|
|
204
|
+
cs = { ...nested };
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
cs = {};
|
|
208
|
+
}
|
|
209
|
+
merged.composerState = cs;
|
|
210
|
+
}
|
|
211
|
+
const composerState = cs;
|
|
212
|
+
for (const key of shadowKeys) {
|
|
213
|
+
if (Object.prototype.hasOwnProperty.call(root, key) && root[key] !== undefined) {
|
|
214
|
+
composerState[key] = root[key];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
177
218
|
/** Plain JSON file or virtual `…/state.vscdb#itemKey` path for ItemTable-backed settings. */
|
|
178
219
|
function loadRemediationConfigJson(configFilePath, checkSettingPaths = []) {
|
|
179
220
|
const resolvedPath = resolveRemediationConfigPath(configFilePath);
|
|
@@ -193,6 +234,9 @@ function loadRemediationConfigJson(configFilePath, checkSettingPaths = []) {
|
|
|
193
234
|
return { ok: false, reason: 'vscdb_read_failed' };
|
|
194
235
|
Object.assign(merged, wrapped);
|
|
195
236
|
}
|
|
237
|
+
if (shouldMergeComposerShadowKeys(itemKeyFromPath, checkSettingPaths)) {
|
|
238
|
+
mergeComposerShadowKeysFromReactiveBlob(dbPath, merged);
|
|
239
|
+
}
|
|
196
240
|
return { ok: true, json: merged };
|
|
197
241
|
}
|
|
198
242
|
if (!existsSync(resolvedPath))
|
|
@@ -101,9 +101,13 @@ async function sendAllConfigFiles(configFiles, hardwareUuid, authKey) {
|
|
|
101
101
|
const ok = await sendHookRequestUpdateManifest(hardwareUuid, authKey, hookRequestId, manifest);
|
|
102
102
|
hookRunLog(`hook-request update manifest result=${ok ? 'ok' : 'fail'}`);
|
|
103
103
|
}
|
|
104
|
+
// Report failed uploads on finish so the backend holds those paths from OpenClaw prune.
|
|
104
105
|
if (ingestSessionId && batchResult.accepted > 0) {
|
|
105
|
-
const ok = await sendIngestSessionFinish(hardwareUuid, authKey, ingestSessionId);
|
|
106
|
-
hookRunLog(`ingest-session finish result=${ok ? 'ok' : 'fail'}`
|
|
106
|
+
const ok = await sendIngestSessionFinish(hardwareUuid, authKey, ingestSessionId, batchResult.failedArtifacts);
|
|
107
|
+
hookRunLog(`ingest-session finish result=${ok ? 'ok' : 'fail'}`
|
|
108
|
+
+ (batchResult.failedArtifacts.length > 0
|
|
109
|
+
? ` held=${batchResult.failedArtifacts.length} failed upload(s)`
|
|
110
|
+
: ''));
|
|
107
111
|
}
|
|
108
112
|
// Exit 0 if anything was persisted so the shell hook keeps last_log and throttles. Exit 1 only when
|
|
109
113
|
// every item failed (e.g. SQLite locked on server) — otherwise partial success used to return 1,
|
|
@@ -11,6 +11,17 @@ import path from 'node:path';
|
|
|
11
11
|
export const BATCH_CHUNK_SIZE = 20;
|
|
12
12
|
const MAX_BATCH_SIZE_BYTES = 500 * 1024; // 500KB
|
|
13
13
|
export { resolveRepoFromPath } from '../runtime/workspace_repo.js';
|
|
14
|
+
function recordFailedArtifacts(target, files, seen) {
|
|
15
|
+
for (const file of files) {
|
|
16
|
+
const file_path = canonicalCursorUserStateVscdbPath(file.file_path);
|
|
17
|
+
const file_type = file.file_type || '';
|
|
18
|
+
const key = `${file_type}\t${file_path}`;
|
|
19
|
+
if (!file_type || seen.has(key))
|
|
20
|
+
continue;
|
|
21
|
+
seen.add(key);
|
|
22
|
+
target.push({ file_path, file_type });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
14
25
|
function resolveApiBase(endpoint) {
|
|
15
26
|
try {
|
|
16
27
|
return new URL(endpoint).origin;
|
|
@@ -92,6 +103,8 @@ async function sendConfigFilesBatch(configFiles, hardwareUuid, authKey, hookRequ
|
|
|
92
103
|
const basePayloadSize = JSON.stringify({ hardware_uuid: hardwareUuid, metadata }).length + 800;
|
|
93
104
|
const chunks = buildBatchChunks(configFiles, basePayloadSize);
|
|
94
105
|
const totals = { accepted: 0, failed: 0 };
|
|
106
|
+
const failedArtifacts = [];
|
|
107
|
+
const failedArtifactKeys = new Set();
|
|
95
108
|
let chunkIndex = 0;
|
|
96
109
|
while (chunkIndex < chunks.length) {
|
|
97
110
|
let chunk = chunks[chunkIndex];
|
|
@@ -118,11 +131,21 @@ async function sendConfigFilesBatch(configFiles, hardwareUuid, authKey, hookRequ
|
|
|
118
131
|
totals.accepted += typeof response.accepted === 'number' ? response.accepted : chunk.length;
|
|
119
132
|
const failedList = Array.isArray(response.failed) ? response.failed : [];
|
|
120
133
|
totals.failed += failedList.length;
|
|
121
|
-
if (failedList.length > 0)
|
|
134
|
+
if (failedList.length > 0) {
|
|
122
135
|
hookRunLog(`batch chunk failed items: ${JSON.stringify(failedList.slice(0, 3))}`);
|
|
136
|
+
for (const item of failedList) {
|
|
137
|
+
const idx = typeof item.index === 'number'
|
|
138
|
+
? item.index
|
|
139
|
+
: -1;
|
|
140
|
+
if (idx >= 0 && idx < chunk.length) {
|
|
141
|
+
recordFailedArtifacts(failedArtifacts, [chunk[idx]], failedArtifactKeys);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
123
145
|
}
|
|
124
146
|
else {
|
|
125
147
|
totals.failed += chunk.length;
|
|
148
|
+
recordFailedArtifacts(failedArtifacts, chunk, failedArtifactKeys);
|
|
126
149
|
hookRunLog(`batch chunk failed: ${response.error || response.message || response.status}`);
|
|
127
150
|
}
|
|
128
151
|
}
|
|
@@ -130,10 +153,11 @@ async function sendConfigFilesBatch(configFiles, hardwareUuid, authKey, hookRequ
|
|
|
130
153
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
131
154
|
hookRunLog(`batch chunk error: ${errorMessage}`);
|
|
132
155
|
totals.failed += chunk.length;
|
|
156
|
+
recordFailedArtifacts(failedArtifacts, chunk, failedArtifactKeys);
|
|
133
157
|
}
|
|
134
158
|
chunkIndex++;
|
|
135
159
|
}
|
|
136
|
-
return totals;
|
|
160
|
+
return { ...totals, failedArtifacts };
|
|
137
161
|
}
|
|
138
162
|
async function sendIngestSessionStart(hardwareUuid, authKey) {
|
|
139
163
|
const endpoint = loadEndpointBase();
|
|
@@ -153,10 +177,20 @@ async function sendIngestSessionStart(hardwareUuid, authKey) {
|
|
|
153
177
|
return null;
|
|
154
178
|
}
|
|
155
179
|
}
|
|
156
|
-
async function sendIngestSessionFinish(hardwareUuid, authKey, ingestSessionId) {
|
|
180
|
+
async function sendIngestSessionFinish(hardwareUuid, authKey, ingestSessionId, failedUploads = []) {
|
|
157
181
|
const endpoint = loadEndpointBase();
|
|
158
182
|
const apiUrl = `${resolveApiBase(endpoint)}/endpoint_security/ingest-session/finish/`;
|
|
159
|
-
const
|
|
183
|
+
const failed_uploads = [...failedUploads]
|
|
184
|
+
.map(({ file_path, file_type }) => ({ file_path, file_type }))
|
|
185
|
+
.sort((a, b) => a.file_path.localeCompare(b.file_path) || a.file_type.localeCompare(b.file_type));
|
|
186
|
+
const payload = {
|
|
187
|
+
hardware_uuid: hardwareUuid,
|
|
188
|
+
action: 'ingest_session_finish',
|
|
189
|
+
ingest_session_id: ingestSessionId,
|
|
190
|
+
};
|
|
191
|
+
if (failed_uploads.length > 0) {
|
|
192
|
+
payload.failed_uploads = failed_uploads;
|
|
193
|
+
}
|
|
160
194
|
const signature = createSignature(payload, authKey.key);
|
|
161
195
|
const body = { ...payload, signature, key_id: authKey.key_id || '' };
|
|
162
196
|
try {
|