log-llm-config-staging 1.3.96 → 1.3.98
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/log_config_files/collection/config_collector.js +2 -1
- package/dist/log_config_files/collection/directory_collector.js +2 -0
- package/dist/log_config_files/collection/metadata_merge.js +43 -0
- package/dist/log_config_files/paths/pattern_resolver.js +64 -29
- package/dist/log_config_files/runtime/compliance_check.js +5 -1
- package/dist/log_config_files/runtime/hook_type_for_request.js +28 -0
- package/dist/log_config_files/runtime/main_runner.js +2 -2
- package/dist/log_config_files/runtime/trusted_restarts.js +4 -0
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ import { enrichRawFromRecipe } from './enrichment_helpers.js';
|
|
|
7
7
|
import { collectDirectoryEntries, collectDirectoryMetadata } from './directory_collector.js';
|
|
8
8
|
import { collectVscdbEntries } from '../readers/vscdb_config_builder.js';
|
|
9
9
|
import { pushDerivedFilesFromRecipe } from './openclaw_helpers.js';
|
|
10
|
+
import { collapseMetadataByFileType } from './metadata_merge.js';
|
|
10
11
|
function buildCollectionContext(patterns, projectRoot, home, homeRecurseSkipDirs = [], absolutePathPrefixes = [], mcpToolGlobSpec = null, clientPathConstants = null, pathResolutionSpecs = null) {
|
|
11
12
|
const seenPaths = new Set();
|
|
12
13
|
const targets = [];
|
|
@@ -148,7 +149,7 @@ function collectConfigFilesFromPatterns(patterns, projectRoot, onProgress, optio
|
|
|
148
149
|
pushDerivedFilesFromRecipe(entry.raw_content, configFiles, recipe);
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
|
-
return configFiles;
|
|
152
|
+
return collapseMetadataByFileType(configFiles);
|
|
152
153
|
}
|
|
153
154
|
// ─── Re-exports (barrel for public API) ──────────────────────────────────────
|
|
154
155
|
export { collectConfigFilesFromPatterns };
|
|
@@ -57,6 +57,8 @@ function matchesDirGlob(name, glob) {
|
|
|
57
57
|
// **/*.jsonl → match any extension suffix after **/
|
|
58
58
|
if (glob.startsWith('**/')) {
|
|
59
59
|
const suffix = glob.slice(3);
|
|
60
|
+
if (suffix === '*' || suffix === '**')
|
|
61
|
+
return true;
|
|
60
62
|
if (suffix.startsWith('*.'))
|
|
61
63
|
return name.endsWith(suffix.slice(1));
|
|
62
64
|
return name === suffix;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** When several install/log paths exist for one agent, keep a single metadata row (newest mtime). */
|
|
2
|
+
const METADATA_COLLAPSE_BY_FILE_TYPE = new Set(['opencode_presence', 'opencode_log']);
|
|
3
|
+
function metadataLastModifiedMs(entry) {
|
|
4
|
+
const raw = entry.raw_content;
|
|
5
|
+
if (!raw || typeof raw !== 'object')
|
|
6
|
+
return 0;
|
|
7
|
+
const lm = raw.last_modified;
|
|
8
|
+
if (typeof lm !== 'string')
|
|
9
|
+
return 0;
|
|
10
|
+
const ms = Date.parse(lm);
|
|
11
|
+
return Number.isFinite(ms) ? ms : 0;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Collapse multiple metadata uploads of the same file_type to one row: highest last_modified wins.
|
|
15
|
+
*/
|
|
16
|
+
function collapseMetadataByFileType(files) {
|
|
17
|
+
const passthrough = [];
|
|
18
|
+
const groups = new Map();
|
|
19
|
+
for (const f of files) {
|
|
20
|
+
if (f.collect_style === 'metadata' && METADATA_COLLAPSE_BY_FILE_TYPE.has(f.file_type)) {
|
|
21
|
+
const list = groups.get(f.file_type) ?? [];
|
|
22
|
+
list.push(f);
|
|
23
|
+
groups.set(f.file_type, list);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
passthrough.push(f);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const merged = [...passthrough];
|
|
30
|
+
for (const [, list] of groups) {
|
|
31
|
+
if (list.length === 0)
|
|
32
|
+
continue;
|
|
33
|
+
let best = list[0];
|
|
34
|
+
for (let i = 1; i < list.length; i++) {
|
|
35
|
+
if (metadataLastModifiedMs(list[i]) > metadataLastModifiedMs(best)) {
|
|
36
|
+
best = list[i];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
merged.push(best);
|
|
40
|
+
}
|
|
41
|
+
return merged;
|
|
42
|
+
}
|
|
43
|
+
export { collapseMetadataByFileType, METADATA_COLLAPSE_BY_FILE_TYPE };
|
|
@@ -54,24 +54,18 @@ function expandRecursiveGlobPathPattern(pathPattern, fileType, home, contentForm
|
|
|
54
54
|
}
|
|
55
55
|
function expandGlobPathPattern(pathPattern, fileType, home, projectRoot, contentFormat, dirGlob, absolutePathPrefixes = []) {
|
|
56
56
|
const norm = pathPattern.replace(/\\/g, '/');
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
if (!norm.includes('*'))
|
|
58
|
+
return [];
|
|
59
|
+
// `**` is the recursive-glob sigil handled by expandRecursiveGlobPathPattern.
|
|
60
|
+
if (norm.includes('**'))
|
|
61
|
+
return [];
|
|
61
62
|
const isDir = norm.endsWith('/');
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (!before.endsWith('/')) {
|
|
69
|
-
const lastSlash = parentPart.lastIndexOf('/');
|
|
70
|
-
if (lastSlash !== -1) {
|
|
71
|
-
namePrefix = parentPart.slice(lastSlash + 1);
|
|
72
|
-
parentPart = parentPart.slice(0, lastSlash);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
63
|
+
// Resolve the literal prefix that ends before the first wildcard segment.
|
|
64
|
+
const firstStarIndex = norm.indexOf('*');
|
|
65
|
+
const lastSlashBeforeStar = norm.lastIndexOf('/', firstStarIndex);
|
|
66
|
+
if (lastSlashBeforeStar === -1)
|
|
67
|
+
return [];
|
|
68
|
+
const parentPart = norm.slice(0, lastSlashBeforeStar);
|
|
75
69
|
let basePath;
|
|
76
70
|
if (parentPart.startsWith('~/')) {
|
|
77
71
|
basePath = join(home, parentPart.slice(2));
|
|
@@ -83,20 +77,61 @@ function expandGlobPathPattern(pathPattern, fileType, home, projectRoot, content
|
|
|
83
77
|
basePath = join(projectRoot, parentPart.startsWith('/') ? parentPart.slice(1) : parentPart);
|
|
84
78
|
}
|
|
85
79
|
if (!existsSync(basePath))
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
80
|
+
return [];
|
|
81
|
+
// Walk remaining segments. Each segment may be a bare wildcard '*' (matches
|
|
82
|
+
// any single directory), a prefix/suffix wildcard like 'foo*' or 'foo*bar',
|
|
83
|
+
// or a literal name. Recursion correctly handles multi-wildcard patterns
|
|
84
|
+
// such as ``*/*/cowork_plugins/marketplaces/*/.claude-plugin/marketplace.json``
|
|
85
|
+
// — the prior single-split implementation only expanded the first wildcard
|
|
86
|
+
// and silently pushed a partial directory path as the target, which then
|
|
87
|
+
// surfaced as EISDIR when downstream collectors tried to read it as a file.
|
|
88
|
+
const remaining = norm.slice(lastSlashBeforeStar + 1);
|
|
89
|
+
const segments = remaining.split('/').filter((s) => s !== '');
|
|
90
|
+
const targets = [];
|
|
91
|
+
function recurse(currentPath, segIdx) {
|
|
92
|
+
if (segIdx === segments.length) {
|
|
93
|
+
if (!existsSync(currentPath))
|
|
94
|
+
return;
|
|
95
|
+
targets.push({ path: currentPath, file_type: fileType, isDirectory: isDir, dir_glob: dirGlob, content_format: contentFormat });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const seg = segments[segIdx];
|
|
99
|
+
if (seg === '*') {
|
|
100
|
+
if (!existsSync(currentPath))
|
|
101
|
+
return;
|
|
102
|
+
try {
|
|
103
|
+
for (const entry of readdirSync(currentPath, { withFileTypes: true })) {
|
|
104
|
+
if (!entry.isDirectory())
|
|
105
|
+
continue;
|
|
106
|
+
recurse(join(currentPath, entry.name), segIdx + 1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch { /* ignore read errors */ }
|
|
110
|
+
}
|
|
111
|
+
else if (seg.includes('*')) {
|
|
112
|
+
const wildcardIdx = seg.indexOf('*');
|
|
113
|
+
const prefix = seg.slice(0, wildcardIdx);
|
|
114
|
+
const suffix = seg.slice(wildcardIdx + 1);
|
|
115
|
+
if (!existsSync(currentPath))
|
|
116
|
+
return;
|
|
117
|
+
try {
|
|
118
|
+
for (const entry of readdirSync(currentPath, { withFileTypes: true })) {
|
|
119
|
+
if (!entry.isDirectory())
|
|
120
|
+
continue;
|
|
121
|
+
if (prefix && !entry.name.startsWith(prefix))
|
|
122
|
+
continue;
|
|
123
|
+
if (suffix && !entry.name.endsWith(suffix))
|
|
124
|
+
continue;
|
|
125
|
+
recurse(join(currentPath, entry.name), segIdx + 1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch { /* ignore read errors */ }
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
recurse(join(currentPath, seg), segIdx + 1);
|
|
97
132
|
}
|
|
98
133
|
}
|
|
99
|
-
|
|
134
|
+
recurse(basePath, 0);
|
|
100
135
|
return targets;
|
|
101
136
|
}
|
|
102
137
|
/**
|
|
@@ -41,6 +41,8 @@ export function normalizeAgentToken(raw) {
|
|
|
41
41
|
return 'cursor';
|
|
42
42
|
if (s === 'copilot')
|
|
43
43
|
return 'copilot';
|
|
44
|
+
if (s === 'opencode')
|
|
45
|
+
return 'opencode';
|
|
44
46
|
return '';
|
|
45
47
|
}
|
|
46
48
|
function currentAgentFromEnv() {
|
|
@@ -48,12 +50,14 @@ function currentAgentFromEnv() {
|
|
|
48
50
|
const override = normalizeAgentToken(process.env.OPTIMUS_AGENT);
|
|
49
51
|
if (override)
|
|
50
52
|
return override;
|
|
51
|
-
// Backwards-compatible: hook wrappers set OPTIMUS_HOOK_TYPE to cursor|claude|copilot.
|
|
53
|
+
// Backwards-compatible: hook wrappers set OPTIMUS_HOOK_TYPE to cursor|claude|copilot|opencode.
|
|
52
54
|
const hookType = normalizeAgentToken(process.env.OPTIMUS_HOOK_TYPE);
|
|
53
55
|
if (hookType === 'cursor')
|
|
54
56
|
return 'cursor';
|
|
55
57
|
if (hookType === 'copilot')
|
|
56
58
|
return 'copilot';
|
|
59
|
+
if (hookType === 'opencode')
|
|
60
|
+
return 'opencode';
|
|
57
61
|
return 'claude';
|
|
58
62
|
}
|
|
59
63
|
function targetsCurrentAgent(entry, agent) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function normalizeToken(raw) {
|
|
2
|
+
if (!raw?.trim())
|
|
3
|
+
return '';
|
|
4
|
+
const s = raw.trim().toLowerCase().replace(/-/g, '_');
|
|
5
|
+
if (s === 'claude_desktop')
|
|
6
|
+
return 'claude';
|
|
7
|
+
if (s === 'github_copilot')
|
|
8
|
+
return 'copilot';
|
|
9
|
+
if (s === 'cursor' || s === 'claude' || s === 'copilot' || s === 'opencode')
|
|
10
|
+
return s;
|
|
11
|
+
// Legacy hooks set OPTIMUS_AGENT=Cursor (display casing)
|
|
12
|
+
if (raw.trim() === 'Cursor')
|
|
13
|
+
return 'cursor';
|
|
14
|
+
return s;
|
|
15
|
+
}
|
|
16
|
+
/** Resolve hook_request.hook_type from hook wrapper env (OPTIMUS_HOOK_TYPE, then OPTIMUS_AGENT). */
|
|
17
|
+
export function resolveHookTypeFromEnv(env = process.env) {
|
|
18
|
+
const fromHook = normalizeToken(env.OPTIMUS_HOOK_TYPE);
|
|
19
|
+
const fromAgent = normalizeToken(env.OPTIMUS_AGENT);
|
|
20
|
+
const token = fromHook || fromAgent || 'claude';
|
|
21
|
+
if (token === 'cursor')
|
|
22
|
+
return 'cursor';
|
|
23
|
+
if (token === 'copilot')
|
|
24
|
+
return 'copilot';
|
|
25
|
+
if (token === 'opencode')
|
|
26
|
+
return 'opencode';
|
|
27
|
+
return 'claude';
|
|
28
|
+
}
|
|
@@ -7,6 +7,7 @@ import { OPT_AI_SEC_MANAGEMENT_REL } from '../../bootstrap_constants.js';
|
|
|
7
7
|
import { runSensitivePathsAudit } from '../../log_sensitive_paths_audit.js';
|
|
8
8
|
import { loadEndpointBase, getEndpointSource } from '../sender/endpoint_config.js';
|
|
9
9
|
import { hookLogReplace, hookRunLog } from './hook_logger.js';
|
|
10
|
+
import { resolveHookTypeFromEnv } from './hook_type_for_request.js';
|
|
10
11
|
import { resolveHardwareUuid } from './hardware_uuid.js';
|
|
11
12
|
import { ensureAuthentication } from '../auth/auth_flow.js';
|
|
12
13
|
import { readJSONFile, readMarkdownFile } from '../readers/file_readers.js';
|
|
@@ -128,8 +129,7 @@ async function addSensitivePathsAudit(endpointBase, configFiles) {
|
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
async function sendAllConfigFiles(configFiles, worktreeReport, hardwareUuid, authKey) {
|
|
131
|
-
const
|
|
132
|
-
const hookType = hookTypeRaw === 'cursor' ? 'cursor' : hookTypeRaw === 'copilot' ? 'copilot' : 'claude';
|
|
132
|
+
const hookType = resolveHookTypeFromEnv();
|
|
133
133
|
const manifest = configFiles.map((c) => canonicalCursorUserStateVscdbPath(c.file_path));
|
|
134
134
|
const workspaceRepo = ensureWorkspaceRepoEnv(manifest);
|
|
135
135
|
const hookRequestId = await sendHookRequestCreate(hardwareUuid, authKey, hookType, workspaceRepo);
|
|
@@ -34,6 +34,8 @@ function currentAgentFromEnv() {
|
|
|
34
34
|
return 'cursor';
|
|
35
35
|
if (override === 'copilot')
|
|
36
36
|
return 'copilot';
|
|
37
|
+
if (override === 'opencode')
|
|
38
|
+
return 'opencode';
|
|
37
39
|
if (override === 'claude' || override === 'claude_desktop')
|
|
38
40
|
return 'claude';
|
|
39
41
|
const hookType = normalizeAgentToken(process.env.OPTIMUS_HOOK_TYPE);
|
|
@@ -41,6 +43,8 @@ function currentAgentFromEnv() {
|
|
|
41
43
|
return 'cursor';
|
|
42
44
|
if (hookType === 'copilot')
|
|
43
45
|
return 'copilot';
|
|
46
|
+
if (hookType === 'opencode')
|
|
47
|
+
return 'opencode';
|
|
44
48
|
return 'claude';
|
|
45
49
|
}
|
|
46
50
|
/** Spawn each trusted command detached (same pattern as former compliance_prompt_gate fireRestartCommands). */
|