brainclaw 0.21.0 → 0.22.0
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.
|
@@ -11,6 +11,7 @@ import { nowISO, generateId } from '../core/ids.js';
|
|
|
11
11
|
import { appendAuditEntry } from '../core/audit.js';
|
|
12
12
|
import { SessionSnapshotSchema } from '../core/schema.js';
|
|
13
13
|
import { auditLocalAgentWorkspaceFiles } from '../core/agent-files.js';
|
|
14
|
+
import { buildAgentInventory, loadAgentInventory, saveAgentInventory, diffInventory } from '../core/agent-inventory.js';
|
|
14
15
|
function sessionsDir(cwd) {
|
|
15
16
|
return resolveEntityDir('sessions', cwd ?? process.cwd(), 'read');
|
|
16
17
|
}
|
|
@@ -47,6 +48,11 @@ export function runSessionStart(options = {}) {
|
|
|
47
48
|
console.warn(' After fixing .gitignore, untrack them with `git rm --cached <path>` as needed.');
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
if (snapshot.inventory_advisory) {
|
|
52
|
+
for (const line of snapshot.inventory_advisory) {
|
|
53
|
+
console.warn(`⚠ ${line}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
50
56
|
}
|
|
51
57
|
catch (e) {
|
|
52
58
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -121,6 +127,25 @@ export function startSession(options = {}) {
|
|
|
121
127
|
}, options.cwd);
|
|
122
128
|
appendAuditEntry({ action: 'session_start', actor: actor.agent, actor_id: actor.agent_id, item_id: snapshot.session_id, item_type: 'session' }, options.cwd);
|
|
123
129
|
const agentGitHygiene = auditLocalAgentWorkspaceFiles(options.cwd ?? process.cwd());
|
|
130
|
+
// Inventory reconciliation — detect new/disappeared agents on this machine
|
|
131
|
+
let inventoryAdvisory;
|
|
132
|
+
try {
|
|
133
|
+
const previousInventory = loadAgentInventory();
|
|
134
|
+
const currentInventory = buildAgentInventory();
|
|
135
|
+
const diff = diffInventory(previousInventory, currentInventory);
|
|
136
|
+
saveAgentInventory(currentInventory);
|
|
137
|
+
const lines = [];
|
|
138
|
+
if (diff.appeared.length > 0)
|
|
139
|
+
lines.push(`New agents detected: ${diff.appeared.join(', ')}`);
|
|
140
|
+
if (diff.disappeared.length > 0)
|
|
141
|
+
lines.push(`Agents no longer detected: ${diff.disappeared.join(', ')}`);
|
|
142
|
+
for (const vc of diff.version_changed) {
|
|
143
|
+
lines.push(`${vc.name} version changed: ${vc.from ?? '?'} → ${vc.to ?? '?'}`);
|
|
144
|
+
}
|
|
145
|
+
if (lines.length > 0)
|
|
146
|
+
inventoryAdvisory = lines;
|
|
147
|
+
}
|
|
148
|
+
catch { /* non-fatal — inventory scan failure should not block session start */ }
|
|
124
149
|
return {
|
|
125
150
|
...snapshot,
|
|
126
151
|
...(agentGitHygiene.isGitRepo && (agentGitHygiene.missingGitignorePaths.length > 0 || agentGitHygiene.trackedPaths.length > 0)
|
|
@@ -131,6 +156,7 @@ export function startSession(options = {}) {
|
|
|
131
156
|
},
|
|
132
157
|
}
|
|
133
158
|
: {}),
|
|
159
|
+
...(inventoryAdvisory ? { inventory_advisory: inventoryAdvisory } : {}),
|
|
134
160
|
};
|
|
135
161
|
}
|
|
136
162
|
export function loadSessionSnapshot(sessionId, cwd) {
|
|
@@ -398,4 +398,32 @@ export function renderAgentInventorySummary(inventory) {
|
|
|
398
398
|
lines.push(`Inventory generated: ${inventory.generated_at}`);
|
|
399
399
|
return lines.join('\n');
|
|
400
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Compare two agent inventories and return what changed.
|
|
403
|
+
* Only considers agents that are `installed` in either snapshot.
|
|
404
|
+
*/
|
|
405
|
+
export function diffInventory(previous, current) {
|
|
406
|
+
const prevMap = new Map((previous?.agents ?? []).filter(a => a.installed).map(a => [a.name, a]));
|
|
407
|
+
const currMap = new Map(current.agents.filter(a => a.installed).map(a => [a.name, a]));
|
|
408
|
+
const appeared = [];
|
|
409
|
+
const disappeared = [];
|
|
410
|
+
const version_changed = [];
|
|
411
|
+
for (const [name, entry] of currMap) {
|
|
412
|
+
if (!prevMap.has(name)) {
|
|
413
|
+
appeared.push(name);
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
const prev = prevMap.get(name);
|
|
417
|
+
if (prev.version !== entry.version && (prev.version || entry.version)) {
|
|
418
|
+
version_changed.push({ name, from: prev.version, to: entry.version });
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
for (const name of prevMap.keys()) {
|
|
423
|
+
if (!currMap.has(name)) {
|
|
424
|
+
disappeared.push(name);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return { appeared, disappeared, version_changed };
|
|
428
|
+
}
|
|
401
429
|
//# sourceMappingURL=agent-inventory.js.map
|
|
@@ -2,6 +2,7 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { detectAiAgent } from './ai-agent-detection.js';
|
|
5
6
|
import { loadConfig, saveConfig } from './config.js';
|
|
6
7
|
import { nowISO } from './ids.js';
|
|
7
8
|
import { MEMORY_DIR, resolveEntityDir } from './io.js';
|
|
@@ -205,6 +206,15 @@ export function resolveCurrentAgentIdentity(cwd, preferredDirName) {
|
|
|
205
206
|
if (byEnvName)
|
|
206
207
|
return byEnvName;
|
|
207
208
|
}
|
|
209
|
+
// Auto-detect from native agent env vars (e.g. CLAUDECODE, CURSOR_TRACE_ID).
|
|
210
|
+
// Falls through to config if the detected agent is not registered.
|
|
211
|
+
const detected = detectAiAgent();
|
|
212
|
+
if (detected) {
|
|
213
|
+
const byDetected = findAgentIdentityByName(detected.name, cwd, preferredDirName);
|
|
214
|
+
if (byDetected)
|
|
215
|
+
return byDetected;
|
|
216
|
+
}
|
|
217
|
+
// Config fallback (last resort — may not reflect the actual calling agent)
|
|
208
218
|
const config = loadConfig(cwd, preferredDirName);
|
|
209
219
|
if (config.current_agent_id) {
|
|
210
220
|
const byId = findAgentIdentityById(config.current_agent_id, cwd, preferredDirName);
|
|
@@ -32,13 +32,17 @@ export function detectAiAgent(env = process.env, homeDir = os.homedir()) {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
// Claude Code — tested BEFORE Copilot because both can be present in VS Code.
|
|
35
|
-
// CLAUDE_CODE_VERSION is set by Claude Code
|
|
36
|
-
if (env.CLAUDE_CODE_VERSION || env.ANTHROPIC_AI_PRODUCT === 'claude-code') {
|
|
35
|
+
// CLAUDE_CODE_VERSION is set by Claude Code CLI; CLAUDECODE is set by the VS Code extension.
|
|
36
|
+
if (env.CLAUDE_CODE_VERSION || env.CLAUDECODE || env.ANTHROPIC_AI_PRODUCT === 'claude-code') {
|
|
37
37
|
return {
|
|
38
38
|
name: 'claude-code',
|
|
39
39
|
kind: 'agent',
|
|
40
40
|
trust_level: 'trusted',
|
|
41
|
-
detection_source: env.CLAUDE_CODE_VERSION
|
|
41
|
+
detection_source: env.CLAUDE_CODE_VERSION
|
|
42
|
+
? 'CLAUDE_CODE_VERSION env var'
|
|
43
|
+
: env.CLAUDECODE
|
|
44
|
+
? 'CLAUDECODE env var'
|
|
45
|
+
: 'ANTHROPIC_AI_PRODUCT env var',
|
|
42
46
|
};
|
|
43
47
|
}
|
|
44
48
|
// Cursor IDE
|
|
@@ -73,7 +77,7 @@ export function detectAiAgent(env = process.env, homeDir = os.homedir()) {
|
|
|
73
77
|
// whenever the Copilot extension is installed, even when another agent is active.
|
|
74
78
|
// We only match if no higher-priority agent was detected above.
|
|
75
79
|
if (env.GITHUB_COPILOT_PRODUCT ||
|
|
76
|
-
(env.GITHUB_COPILOT_TOKEN && !env.CLAUDE_CODE_VERSION && !env.CURSOR_TRACE_ID && !env.WINDSURF_SESSION_ID) ||
|
|
80
|
+
(env.GITHUB_COPILOT_TOKEN && !env.CLAUDE_CODE_VERSION && !env.CLAUDECODE && !env.CURSOR_TRACE_ID && !env.WINDSURF_SESSION_ID) ||
|
|
77
81
|
(env.VSCODE_GIT_IPC_HANDLE && (env.AGENT_NAME?.toLowerCase().includes('copilot') || env.GH_COPILOT_AGENT))) {
|
|
78
82
|
return {
|
|
79
83
|
name: 'github-copilot',
|