@brainmcp/brainmcp 0.1.12 → 0.1.14
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 +79 -5
- package/dist/agent-config.js +155 -24
- package/dist/commands/auth.d.ts +47 -0
- package/dist/commands/auth.js +392 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/capture.d.ts +34 -0
- package/dist/commands/capture.js +349 -0
- package/dist/commands/capture.js.map +1 -0
- package/dist/commands/dispatch.d.ts +43 -0
- package/dist/commands/dispatch.js +287 -0
- package/dist/commands/dispatch.js.map +1 -0
- package/dist/commands/index.d.ts +4 -0
- package/dist/commands/index.js +198 -36
- package/dist/commands/index.js.map +1 -1
- package/dist/hooks/adapters.d.ts +14 -0
- package/dist/hooks/adapters.js +41 -0
- package/dist/hooks/adapters.js.map +1 -0
- package/dist/hooks/claude.d.ts +24 -0
- package/dist/hooks/claude.js +36 -0
- package/dist/hooks/claude.js.map +1 -0
- package/dist/hooks/codex.d.ts +18 -0
- package/dist/hooks/codex.js +36 -0
- package/dist/hooks/codex.js.map +1 -0
- package/dist/hooks/cursor.d.ts +22 -0
- package/dist/hooks/cursor.js +26 -0
- package/dist/hooks/cursor.js.map +1 -0
- package/dist/hooks/managed-hooks.d.ts +30 -0
- package/dist/hooks/managed-hooks.js +189 -0
- package/dist/hooks/managed-hooks.js.map +1 -0
- package/dist/hooks/vscode.d.ts +22 -0
- package/dist/hooks/vscode.js +27 -0
- package/dist/hooks/vscode.js.map +1 -0
- package/dist/index.js +7 -102
- package/dist/index.js.map +1 -1
- package/dist/lib/api-client.d.ts +32 -0
- package/dist/lib/api-client.js +207 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/callback-server.d.ts +17 -0
- package/dist/lib/callback-server.js +145 -0
- package/dist/lib/callback-server.js.map +1 -0
- package/dist/lib/cli-contracts.d.ts +193 -0
- package/dist/lib/cli-contracts.js +242 -0
- package/dist/lib/cli-contracts.js.map +1 -0
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.js +11 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/credentials.d.ts +89 -0
- package/dist/lib/credentials.js +304 -0
- package/dist/lib/credentials.js.map +1 -0
- package/dist/lib/endpoints.d.ts +5 -0
- package/dist/lib/endpoints.js +31 -0
- package/dist/lib/endpoints.js.map +1 -0
- package/dist/lib/http.d.ts +3 -0
- package/dist/lib/http.js +51 -0
- package/dist/lib/http.js.map +1 -0
- package/dist/lib/managed.d.ts +8 -0
- package/dist/lib/managed.js +72 -6
- package/dist/lib/managed.js.map +1 -1
- package/dist/lib/oauth-client.d.ts +55 -0
- package/dist/lib/oauth-client.js +155 -0
- package/dist/lib/oauth-client.js.map +1 -0
- package/dist/lib/pkce.d.ts +14 -0
- package/dist/lib/pkce.js +33 -0
- package/dist/lib/pkce.js.map +1 -0
- package/dist/lib/vault.d.ts +26 -0
- package/dist/lib/vault.js +221 -0
- package/dist/lib/vault.js.map +1 -0
- package/dist/runtime/brainmcp-hook-runtime.mjs +3243 -0
- package/dist/runtime/capture.d.ts +83 -0
- package/dist/runtime/capture.js +332 -0
- package/dist/runtime/capture.js.map +1 -0
- package/dist/runtime/diagnostics.d.ts +7 -0
- package/dist/runtime/diagnostics.js +75 -0
- package/dist/runtime/diagnostics.js.map +1 -0
- package/dist/runtime/doctor-checks.d.ts +15 -0
- package/dist/runtime/doctor-checks.js +233 -0
- package/dist/runtime/doctor-checks.js.map +1 -0
- package/dist/runtime/flush.d.ts +30 -0
- package/dist/runtime/flush.js +193 -0
- package/dist/runtime/flush.js.map +1 -0
- package/dist/runtime/hook-dispatcher.d.ts +31 -0
- package/dist/runtime/hook-dispatcher.js +381 -0
- package/dist/runtime/hook-dispatcher.js.map +1 -0
- package/dist/runtime/index.d.ts +12 -0
- package/dist/runtime/index.js +69 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/installer.d.ts +26 -0
- package/dist/runtime/installer.js +134 -0
- package/dist/runtime/installer.js.map +1 -0
- package/dist/runtime/lock.d.ts +19 -0
- package/dist/runtime/lock.js +116 -0
- package/dist/runtime/lock.js.map +1 -0
- package/dist/runtime/orient.d.ts +55 -0
- package/dist/runtime/orient.js +256 -0
- package/dist/runtime/orient.js.map +1 -0
- package/dist/runtime/queue.d.ts +7 -0
- package/dist/runtime/queue.js +80 -0
- package/dist/runtime/queue.js.map +1 -0
- package/dist/runtime/retention.d.ts +26 -0
- package/dist/runtime/retention.js +175 -0
- package/dist/runtime/retention.js.map +1 -0
- package/dist/runtime/session.d.ts +68 -0
- package/dist/runtime/session.js +283 -0
- package/dist/runtime/session.js.map +1 -0
- package/dist/runtime/storage.d.ts +32 -0
- package/dist/runtime/storage.js +236 -0
- package/dist/runtime/storage.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { validateCredentialsRecord, validatePreferencesRecord, } from '../lib/cli-contracts.js';
|
|
4
|
+
import { getBrainHome, getCurrentAccountId, readSafeJson, resolveStateDir } from './storage.js';
|
|
5
|
+
import { listInstalledRuntimes } from './installer.js';
|
|
6
|
+
import { listQueueItems } from './queue.js';
|
|
7
|
+
import { createPlatformVault, LEGACY_VAULT_ACCOUNT, vaultAccountKey } from '../lib/vault.js';
|
|
8
|
+
import { describeMisdirectedCompanionApi, resolveCompanionApiUrl } from '../lib/endpoints.js';
|
|
9
|
+
export async function checkCredentialHealth(baseDir, options) {
|
|
10
|
+
const home = baseDir ?? getBrainHome();
|
|
11
|
+
const stateDir = resolveStateDir(home);
|
|
12
|
+
const credPath = path.join(stateDir, 'credentials.json');
|
|
13
|
+
const backendMarker = path.join(stateDir, 'credentials.backend.json');
|
|
14
|
+
const markerPresent = fs.existsSync(backendMarker);
|
|
15
|
+
if (fs.existsSync(credPath)) {
|
|
16
|
+
return readFileCredentialHealth(credPath);
|
|
17
|
+
}
|
|
18
|
+
const vault = options?.vault ?? (options?.probeVault ? createPlatformVault() : null);
|
|
19
|
+
if (vault && markerPresent) {
|
|
20
|
+
try {
|
|
21
|
+
if (await vault.isAvailable()) {
|
|
22
|
+
const accountId = getCurrentAccountId(home);
|
|
23
|
+
const raw = await vault.getSecret(accountId ? vaultAccountKey(accountId) : LEGACY_VAULT_ACCOUNT);
|
|
24
|
+
if (raw) {
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(raw);
|
|
27
|
+
if (parsed.authType === 'oauth' || parsed.type === 'oauth') {
|
|
28
|
+
return oauthGrantHealth(parsed, 'OS credential vault');
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
name: 'credential health',
|
|
32
|
+
passed: true,
|
|
33
|
+
detail: 'OAuth grant stored in the OS credential vault',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return {
|
|
38
|
+
name: 'credential health',
|
|
39
|
+
passed: false,
|
|
40
|
+
detail: 'OS vault secret is unreadable',
|
|
41
|
+
repairGuidance: 'Run "brainmcp login --force" to recreate credentials',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Fall through to marker / empty handling; never leak vault errors.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (markerPresent) {
|
|
52
|
+
return {
|
|
53
|
+
name: 'credential health',
|
|
54
|
+
passed: false,
|
|
55
|
+
detail: 'OS vault marker present but no readable grant',
|
|
56
|
+
repairGuidance: 'Run "brainmcp login --force" to recreate credentials',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
name: 'credential health',
|
|
61
|
+
passed: true,
|
|
62
|
+
detail: 'no credentials stored (run brainmcp login)',
|
|
63
|
+
repairGuidance: 'Run "brainmcp login" to connect your agent to BrainMCP',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function oauthGrantHealth(raw, location) {
|
|
67
|
+
const scopes = Array.isArray(raw.scopes)
|
|
68
|
+
? raw.scopes.filter((item) => typeof item === 'string')
|
|
69
|
+
: [];
|
|
70
|
+
const isExpired = typeof raw.expiresAt === 'string' ? new Date(raw.expiresAt).getTime() < Date.now() : false;
|
|
71
|
+
return {
|
|
72
|
+
name: 'credential health',
|
|
73
|
+
passed: !isExpired,
|
|
74
|
+
detail: `OAuth grant (${scopes.join(', ') || 'no scopes'}) ${isExpired ? 'expired' : 'valid'} in ${location}`,
|
|
75
|
+
repairGuidance: isExpired ? 'Run "brainmcp login" to refresh OAuth grant' : undefined,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function readFileCredentialHealth(credPath) {
|
|
79
|
+
try {
|
|
80
|
+
const raw = JSON.parse(fs.readFileSync(credPath, 'utf8'));
|
|
81
|
+
const kind = raw.type ?? raw.authType;
|
|
82
|
+
if (kind === 'oauth') {
|
|
83
|
+
return oauthGrantHealth(raw, 'credentials.json');
|
|
84
|
+
}
|
|
85
|
+
if (kind === 'service') {
|
|
86
|
+
return {
|
|
87
|
+
name: 'credential health',
|
|
88
|
+
passed: true,
|
|
89
|
+
detail: `service token for workspace ${String(raw.workspaceId ?? 'unspecified')}`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const creds = readSafeJson(credPath, validateCredentialsRecord);
|
|
93
|
+
if (!creds) {
|
|
94
|
+
return {
|
|
95
|
+
name: 'credential health',
|
|
96
|
+
passed: false,
|
|
97
|
+
detail: 'empty or unreadable credentials file',
|
|
98
|
+
repairGuidance: 'Run "brainmcp login --force" to recreate credentials',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
name: 'credential health',
|
|
103
|
+
passed: true,
|
|
104
|
+
detail: `credentials stored (${creds.type})`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
return {
|
|
109
|
+
name: 'credential health',
|
|
110
|
+
passed: false,
|
|
111
|
+
detail: `invalid credentials schema: ${err instanceof Error ? err.message : String(err)}`,
|
|
112
|
+
repairGuidance: 'Run "brainmcp login --force" to reset corrupt credentials',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export function checkRuntimeDrift(expectedVersion = '0.1.13', baseDir) {
|
|
117
|
+
const installed = listInstalledRuntimes(baseDir);
|
|
118
|
+
if (installed.length === 0) {
|
|
119
|
+
return {
|
|
120
|
+
name: 'runtime bundle',
|
|
121
|
+
passed: true, // Optional until hooks installed
|
|
122
|
+
detail: 'no hook runtime bundle installed in ~/.brainmcp/runtime',
|
|
123
|
+
repairGuidance: 'Run "brainmcp init" to install current runtime bundle',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const hasCurrent = installed.some((r) => r.version === expectedVersion);
|
|
127
|
+
if (hasCurrent) {
|
|
128
|
+
return {
|
|
129
|
+
name: 'runtime bundle',
|
|
130
|
+
passed: true,
|
|
131
|
+
detail: `version v${expectedVersion} installed and up to date`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const versions = installed.map((r) => `v${r.version}`).join(', ');
|
|
135
|
+
return {
|
|
136
|
+
name: 'runtime bundle',
|
|
137
|
+
passed: false,
|
|
138
|
+
detail: `runtime drift detected: installed [${versions}], current CLI is v${expectedVersion}`,
|
|
139
|
+
repairGuidance: 'Run "brainmcp init" to update runtime to current version',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
export function checkConsentPreferences(baseDir) {
|
|
143
|
+
const prefPath = path.join(resolveStateDir(baseDir), 'preferences.json');
|
|
144
|
+
if (!fs.existsSync(prefPath)) {
|
|
145
|
+
return {
|
|
146
|
+
name: 'capture consent',
|
|
147
|
+
passed: true,
|
|
148
|
+
detail: 'default preferences (capture disabled by default)',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const prefs = readSafeJson(prefPath, validatePreferencesRecord);
|
|
153
|
+
if (!prefs) {
|
|
154
|
+
return {
|
|
155
|
+
name: 'capture consent',
|
|
156
|
+
passed: false,
|
|
157
|
+
detail: 'unreadable preferences file',
|
|
158
|
+
repairGuidance: 'Run "brainmcp login" to reconfigure preferences',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const capture = prefs.consent.captureEnabled ? 'enabled' : 'disabled';
|
|
162
|
+
const prompts = prefs.consent.includePrompts ? 'with prompts' : 'no prompts';
|
|
163
|
+
return {
|
|
164
|
+
name: 'capture consent',
|
|
165
|
+
passed: true,
|
|
166
|
+
detail: `capture ${capture} (${prompts}) under policy ${prefs.consent.policyVersion}`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
return {
|
|
171
|
+
name: 'capture consent',
|
|
172
|
+
passed: false,
|
|
173
|
+
detail: `corrupt preferences: ${err instanceof Error ? err.message : String(err)}`,
|
|
174
|
+
repairGuidance: 'Delete the current account preferences file under ~/.brainmcp/accounts/ or reconfigure with "brainmcp login"',
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export function checkCompanionApiTarget(env = process.env) {
|
|
179
|
+
try {
|
|
180
|
+
const apiUrl = resolveCompanionApiUrl(env);
|
|
181
|
+
const misdirected = describeMisdirectedCompanionApi(apiUrl);
|
|
182
|
+
if (misdirected) {
|
|
183
|
+
return {
|
|
184
|
+
name: 'companion API origin',
|
|
185
|
+
passed: false,
|
|
186
|
+
detail: misdirected,
|
|
187
|
+
repairGuidance: 'Unset BRAINMCP_API_URL or set it to https://api.brainmcp.ai',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
name: 'companion API origin',
|
|
192
|
+
passed: true,
|
|
193
|
+
detail: `REST ${apiUrl} (OAuth discovery stays on the MCP origin unless BRAINMCP_OAUTH_ISSUER is set)`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
return {
|
|
198
|
+
name: 'companion API origin',
|
|
199
|
+
passed: false,
|
|
200
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
201
|
+
repairGuidance: 'Unset BRAINMCP_API_URL or set it to https://api.brainmcp.ai',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
export function checkQueueStatus(baseDir) {
|
|
206
|
+
try {
|
|
207
|
+
const items = listQueueItems(baseDir);
|
|
208
|
+
const pending = items.filter((i) => i.status === 'pending');
|
|
209
|
+
const failed = items.filter((i) => i.status === 'failed' || i.status === 'delivery_unknown');
|
|
210
|
+
if (items.length === 0) {
|
|
211
|
+
return {
|
|
212
|
+
name: 'capture queue',
|
|
213
|
+
passed: true,
|
|
214
|
+
detail: 'queue clean (0 pending items)',
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const hasFailed = failed.length > 0;
|
|
218
|
+
return {
|
|
219
|
+
name: 'capture queue',
|
|
220
|
+
passed: !hasFailed,
|
|
221
|
+
detail: `${pending.length} pending, ${failed.length} failed out of ${items.length} total`,
|
|
222
|
+
repairGuidance: hasFailed ? 'Run "brainmcp capture purge-local" to clear failed items' : undefined,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
return {
|
|
227
|
+
name: 'capture queue',
|
|
228
|
+
passed: false,
|
|
229
|
+
detail: `error reading queue: ${err instanceof Error ? err.message : String(err)}`,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=doctor-checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-checks.js","sourceRoot":"","sources":["../../src/runtime/doctor-checks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CaptureTrigger } from '../lib/cli-contracts.js';
|
|
2
|
+
import type { ApiClient } from '../lib/api-client.js';
|
|
3
|
+
import type { StoredCredentials } from '../lib/credentials.js';
|
|
4
|
+
export declare const MAX_CAPTURE_QUEUE_ITEMS = 50;
|
|
5
|
+
export declare const MAX_CAPTURE_QUEUE_BYTES: number;
|
|
6
|
+
export declare const MAX_CAPTURE_ATTEMPTS = 8;
|
|
7
|
+
export type CaptureRetryClass = 'retryable' | 'non_retryable' | 'auth_required' | 'terminal';
|
|
8
|
+
export declare function apiCaptureTrigger(trigger: CaptureTrigger): 'manual' | 'precompact' | 'session_end';
|
|
9
|
+
export declare function classifyCaptureFailure(status: number | null, message?: string): CaptureRetryClass;
|
|
10
|
+
export declare function nextRetryAt(attempts: number, now?: number, jitterMs?: number): string;
|
|
11
|
+
export declare function queueByteSize(baseDir?: string): number;
|
|
12
|
+
export declare function canEnqueueCapture(baseDir?: string): boolean;
|
|
13
|
+
export interface FlushCaptureOptions {
|
|
14
|
+
apiClient: ApiClient;
|
|
15
|
+
baseDir?: string;
|
|
16
|
+
now?: number;
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
includeManual?: boolean;
|
|
19
|
+
/** Current hook project policy. Omit only for an explicit manual flush. */
|
|
20
|
+
allowAutomatic?: boolean;
|
|
21
|
+
credentials?: StoredCredentials | null;
|
|
22
|
+
digestId?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface FlushCaptureResult {
|
|
25
|
+
sent: number;
|
|
26
|
+
failed: number;
|
|
27
|
+
skipped: number;
|
|
28
|
+
unknown: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function flushCaptureQueue(options: FlushCaptureOptions): Promise<FlushCaptureResult>;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { listQueueItems, updateQueueItem, getQueueDir } from './queue.js';
|
|
2
|
+
import { logRuntimeEvent } from './diagnostics.js';
|
|
3
|
+
import { readPreferences, resolveStateDir } from './storage.js';
|
|
4
|
+
import { withSessionLock } from './lock.js';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
export const MAX_CAPTURE_QUEUE_ITEMS = 50;
|
|
9
|
+
export const MAX_CAPTURE_QUEUE_BYTES = 10 * 1024 * 1024;
|
|
10
|
+
export const MAX_CAPTURE_ATTEMPTS = 8;
|
|
11
|
+
const BACKOFF_MS = [1_000, 2_000, 4_000, 8_000, 16_000, 32_000, 32_000, 32_000];
|
|
12
|
+
export function apiCaptureTrigger(trigger) {
|
|
13
|
+
if (trigger === 'pre_compact')
|
|
14
|
+
return 'precompact';
|
|
15
|
+
return trigger;
|
|
16
|
+
}
|
|
17
|
+
export function classifyCaptureFailure(status, message) {
|
|
18
|
+
if (status === 401 || /unauthorized|invalid_token|auth_required/i.test(message ?? '')) {
|
|
19
|
+
return 'auth_required';
|
|
20
|
+
}
|
|
21
|
+
if (status === 403 || /missing required MCP OAuth scope/i.test(message ?? '')) {
|
|
22
|
+
return 'terminal';
|
|
23
|
+
}
|
|
24
|
+
if (status === 400 || (status !== null && status >= 400 && status < 500 && status !== 429)) {
|
|
25
|
+
return 'non_retryable';
|
|
26
|
+
}
|
|
27
|
+
if (status === 429 || (status !== null && status >= 500) || status === null) {
|
|
28
|
+
return 'retryable';
|
|
29
|
+
}
|
|
30
|
+
return 'retryable';
|
|
31
|
+
}
|
|
32
|
+
export function nextRetryAt(attempts, now = Date.now(), jitterMs = Math.floor(Math.random() * 500)) {
|
|
33
|
+
const base = BACKOFF_MS[Math.min(Math.max(attempts, 1), BACKOFF_MS.length) - 1] ?? 32_000;
|
|
34
|
+
return new Date(now + base + jitterMs).toISOString();
|
|
35
|
+
}
|
|
36
|
+
export function queueByteSize(baseDir) {
|
|
37
|
+
const dir = getQueueDir(baseDir);
|
|
38
|
+
let total = 0;
|
|
39
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
40
|
+
if (!entry.endsWith('.json'))
|
|
41
|
+
continue;
|
|
42
|
+
try {
|
|
43
|
+
total += fs.statSync(path.join(dir, entry)).size;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// ignore
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return total;
|
|
50
|
+
}
|
|
51
|
+
export function canEnqueueCapture(baseDir) {
|
|
52
|
+
const items = listQueueItems(baseDir).filter((item) => item.status === 'pending' || item.status === 'sending' || item.status === 'delivery_unknown');
|
|
53
|
+
if (items.length >= MAX_CAPTURE_QUEUE_ITEMS)
|
|
54
|
+
return false;
|
|
55
|
+
return queueByteSize(baseDir) < MAX_CAPTURE_QUEUE_BYTES;
|
|
56
|
+
}
|
|
57
|
+
async function postDigest(apiClient, item, signal, beforeSend) {
|
|
58
|
+
try {
|
|
59
|
+
await apiClient.request(`/api/workspaces/${item.workspaceId}/digests`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
body: {
|
|
62
|
+
clientDigestId: item.digestId,
|
|
63
|
+
trigger: apiCaptureTrigger(item.captureTrigger),
|
|
64
|
+
capturePolicyVersion: item.policyVersion,
|
|
65
|
+
digestText: item.digestText.trim() || 'Session capture with no assistant summary.',
|
|
66
|
+
source: 'mcp_tool',
|
|
67
|
+
},
|
|
68
|
+
signal,
|
|
69
|
+
expectedAuthorizationId: item.authorizationId,
|
|
70
|
+
beforeSend,
|
|
71
|
+
timeoutMs: 5_000,
|
|
72
|
+
});
|
|
73
|
+
return { ok: true, status: 200 };
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
const statusFromError = error && typeof error === 'object' && 'status' in error && typeof error.status === 'number'
|
|
78
|
+
? error.status
|
|
79
|
+
: null;
|
|
80
|
+
const statusMatch = message.match(/status (\d{3})/i);
|
|
81
|
+
const status = statusFromError ?? (statusMatch ? Number(statusMatch[1]) : null);
|
|
82
|
+
return { ok: false, status, message };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export async function flushCaptureQueue(options) {
|
|
86
|
+
// Pin this run to one physical account namespace, including completion after
|
|
87
|
+
// another process switches the active account.
|
|
88
|
+
options = { ...options, baseDir: resolveStateDir(options.baseDir) };
|
|
89
|
+
const result = { sent: 0, failed: 0, skipped: 0, unknown: 0 };
|
|
90
|
+
const now = options.now ?? Date.now();
|
|
91
|
+
const items = listQueueItems(options.baseDir);
|
|
92
|
+
const credentials = options.credentials === undefined
|
|
93
|
+
? await options.apiClient.getEffectiveCredentials()
|
|
94
|
+
: options.credentials;
|
|
95
|
+
for (const item of items) {
|
|
96
|
+
if (options.signal?.aborted)
|
|
97
|
+
break;
|
|
98
|
+
if (options.digestId && item.digestId !== options.digestId)
|
|
99
|
+
continue;
|
|
100
|
+
if (item.status === 'delivered' || item.status === 'cancelled' || item.status === 'failed') {
|
|
101
|
+
result.skipped += 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (item.nextRetryAt && Date.parse(item.nextRetryAt) > now) {
|
|
105
|
+
result.skipped += 1;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (!options.includeManual && item.captureTrigger === 'manual') {
|
|
109
|
+
// Manual items are sent by `capture now`, not background hooks.
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const isAutomatic = item.captureTrigger !== 'manual';
|
|
113
|
+
const consentStillMatches = () => {
|
|
114
|
+
const prefs = readPreferences(options.baseDir);
|
|
115
|
+
return prefs?.consent?.captureEnabled === true &&
|
|
116
|
+
(!isAutomatic || options.allowAutomatic === true) &&
|
|
117
|
+
item.consentRevision !== undefined && item.consentRevision === (prefs.consent.revision ?? 0) &&
|
|
118
|
+
(!isAutomatic || item.workspaceId === prefs.enabledWorkspaceId);
|
|
119
|
+
};
|
|
120
|
+
if (isAutomatic) {
|
|
121
|
+
if (!consentStillMatches()) {
|
|
122
|
+
if (item.status === 'pending') {
|
|
123
|
+
updateQueueItem(item.digestId, { status: 'cancelled', lastError: 'Capture consent or project policy changed before delivery' }, options.baseDir);
|
|
124
|
+
}
|
|
125
|
+
result.skipped += 1;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const currentAuthorizationId = credentials?.authType === 'oauth' ? credentials.authorizationId : undefined;
|
|
130
|
+
if (!item.authorizationId || !currentAuthorizationId || item.authorizationId !== currentAuthorizationId) {
|
|
131
|
+
updateQueueItem(item.digestId, { status: 'cancelled', lastError: 'Capture item belongs to a different or unknown authorization' }, options.baseDir);
|
|
132
|
+
result.skipped += 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (!credentials?.scopes?.includes('digests:write') || !credentials.workspaces?.includes(item.workspaceId)) {
|
|
136
|
+
result.skipped += 1;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const owner = randomUUID();
|
|
140
|
+
const claimed = await withSessionLock('capture-queue', () => {
|
|
141
|
+
const current = listQueueItems(options.baseDir).find((queued) => queued.digestId === item.digestId);
|
|
142
|
+
if (!current || ['delivered', 'cancelled', 'failed'].includes(current.status) ||
|
|
143
|
+
(current.deliveryLease && Date.parse(current.deliveryLease.expiresAt) > Date.now()) ||
|
|
144
|
+
(current.nextRetryAt && Date.parse(current.nextRetryAt) > now) ||
|
|
145
|
+
!consentStillMatches() || options.signal?.aborted)
|
|
146
|
+
return null;
|
|
147
|
+
return updateQueueItem(item.digestId, { status: 'sending',
|
|
148
|
+
deliveryLease: { owner, expiresAt: new Date(Date.now() + 10_000).toISOString() } }, options.baseDir);
|
|
149
|
+
}, { baseDir: options.baseDir, timeoutMs: 250 });
|
|
150
|
+
if (!claimed) {
|
|
151
|
+
result.skipped += 1;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const finish = (updates) => withSessionLock('capture-queue', () => {
|
|
155
|
+
const current = listQueueItems(options.baseDir).find((queued) => queued.digestId === item.digestId);
|
|
156
|
+
if (current?.deliveryLease?.owner !== owner)
|
|
157
|
+
return;
|
|
158
|
+
updateQueueItem(item.digestId, { ...updates, deliveryLease: undefined }, options.baseDir);
|
|
159
|
+
}, { baseDir: options.baseDir, timeoutMs: 250 });
|
|
160
|
+
const response = await postDigest(options.apiClient, claimed, options.signal, consentStillMatches);
|
|
161
|
+
if (response.ok) {
|
|
162
|
+
await finish({ status: 'delivered', deliveredAt: new Date().toISOString(), lastError: undefined, nextRetryAt: undefined });
|
|
163
|
+
result.sent += 1;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const klass = classifyCaptureFailure(response.status, response.message);
|
|
167
|
+
if (klass === 'auth_required' || klass === 'terminal' || klass === 'non_retryable') {
|
|
168
|
+
await finish({ status: 'failed', lastError: response.message ?? 'non-retryable capture failure', attempts: claimed.attempts + 1 });
|
|
169
|
+
result.failed += 1;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const attempts = claimed.attempts + 1;
|
|
173
|
+
if (attempts >= MAX_CAPTURE_ATTEMPTS) {
|
|
174
|
+
await finish({ status: 'failed', attempts, lastError: response.message ?? 'retry limit reached' });
|
|
175
|
+
result.failed += 1;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const unknown = response.status === null;
|
|
179
|
+
await finish({
|
|
180
|
+
status: unknown ? 'delivery_unknown' : 'pending',
|
|
181
|
+
attempts,
|
|
182
|
+
nextRetryAt: nextRetryAt(attempts, now),
|
|
183
|
+
lastError: response.message,
|
|
184
|
+
});
|
|
185
|
+
if (unknown)
|
|
186
|
+
result.unknown += 1;
|
|
187
|
+
else
|
|
188
|
+
result.failed += 1;
|
|
189
|
+
}
|
|
190
|
+
logRuntimeEvent('info', 'capture_flush', { ...result }, options.baseDir);
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=flush.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flush.js","sourceRoot":"","sources":["../../src/runtime/flush.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type HookEvent } from '../lib/cli-contracts.js';
|
|
2
|
+
import { ApiClient } from '../lib/api-client.js';
|
|
3
|
+
import { type StoredCredentials } from '../lib/credentials.js';
|
|
4
|
+
export declare const MAX_STDIN_BYTES: number;
|
|
5
|
+
export declare const DEFAULT_EVENT_TIMEOUT_MS = 1000;
|
|
6
|
+
export declare const SESSION_END_TIMEOUT_MS = 3000;
|
|
7
|
+
export interface HookDispatchResult {
|
|
8
|
+
handled: boolean;
|
|
9
|
+
emptyFallback: boolean;
|
|
10
|
+
outputPayload?: Record<string, unknown>;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Strict bounded stdin reader with length check.
|
|
15
|
+
*/
|
|
16
|
+
export declare function readBoundedStdin(stream?: NodeJS.ReadableStream, maxBytes?: number, signal?: AbortSignal): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Dispatches a normalized HookEvent under deadline/abort control (AHI-053).
|
|
19
|
+
* Always fails open with empty success on error or timeout.
|
|
20
|
+
*
|
|
21
|
+
* Stop/SessionStop never queues or flushes digests (AHI-D03).
|
|
22
|
+
* Capture queue writes require user-local consent (AHI-D01) and are further
|
|
23
|
+
* restrictable by project `.brainmcp.json` (AHI-D02).
|
|
24
|
+
*/
|
|
25
|
+
export declare function dispatchHookEvent(event: HookEvent, options?: {
|
|
26
|
+
baseDir?: string;
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
apiClient?: ApiClient;
|
|
30
|
+
credentials?: StoredCredentials | null;
|
|
31
|
+
}): Promise<HookDispatchResult>;
|