@brunobracaioli/aibrain 0.4.0-beta.11 → 0.4.0-beta.13
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 +10 -3
- package/dist/claude-observation.mjs +1 -1
- package/dist/cli.mjs +93 -14
- package/dist/codex-hooks.mjs +411 -0
- package/dist/codex-permissions.mjs +67 -34
- package/dist/extensions-catalog.mjs +26 -8
- package/dist/extensions-cli.mjs +2 -2
- package/dist/guardrails/guards.mjs +64 -12
- package/dist/guardrails/patch.mjs +33 -0
- package/dist/guardrails/run.mjs +106 -31
- package/dist/guardrails-plugin.mjs +59 -1
- package/dist/launch.mjs +22 -1
- package/dist/monitor.mjs +43 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
|
@@ -6,11 +6,18 @@
|
|
|
6
6
|
* Node executable plus the dispatcher inside the installed package; nothing
|
|
7
7
|
* comes from the server (ADR-0048).
|
|
8
8
|
*/
|
|
9
|
-
import { access, mkdir, writeFile } from 'node:fs/promises';
|
|
9
|
+
import { access, mkdir, realpath, writeFile } from 'node:fs/promises';
|
|
10
10
|
import { isAbsolute, join } from 'node:path';
|
|
11
11
|
import { hookById } from './extensions-catalog.mjs';
|
|
12
12
|
import { enabledHookIds, preferencesPath, readPreferences } from './extensions-preferences.mjs';
|
|
13
13
|
import { readStoredLocale, resolvePackLocale } from './engineering-pack.mjs';
|
|
14
|
+
import {
|
|
15
|
+
codexHookConfigArgs,
|
|
16
|
+
codexHooksLine,
|
|
17
|
+
codexHooksValue,
|
|
18
|
+
summarizeCodexHooks,
|
|
19
|
+
} from './codex-hooks.mjs';
|
|
20
|
+
import { readCodexHookList } from './codex-permissions.mjs';
|
|
14
21
|
|
|
15
22
|
export const GUARDRAILS_PLUGIN_NAME = 'aisac-guardrails';
|
|
16
23
|
const PLUGIN_VERSION = '1.0.0';
|
|
@@ -147,3 +154,54 @@ export async function prepareGuardrails(input) {
|
|
|
147
154
|
line: `AISAC Engineering: ${enabled.length} guardrail${enabled.length === 1 ? '' : 's'} on (aisac hooks to change them).`,
|
|
148
155
|
};
|
|
149
156
|
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* What Codex reports for the AISAC hooks the user has on, read with Codex's own
|
|
160
|
+
* `hooks/list` for the exact flags the monitor passes (spec FR6, E6b). Null when
|
|
161
|
+
* no Codex hook is enabled or the package lacks the dispatcher; `summary` is null
|
|
162
|
+
* when the listing failed.
|
|
163
|
+
* @param {{ root: string, packageRoot: string, executable: string, cwd: string, profile: string | undefined, environment: NodeJS.ProcessEnv }} input
|
|
164
|
+
* @param {{ listHooks?: typeof readCodexHookList }} [dependencies]
|
|
165
|
+
* @returns {Promise<{ enabled: string[], summary: import('./codex-hooks.mjs').CodexHooksSummary | null } | null>}
|
|
166
|
+
*/
|
|
167
|
+
export async function readCodexGuardrails(input, dependencies = {}) {
|
|
168
|
+
const listHooks = dependencies.listHooks ?? readCodexHookList;
|
|
169
|
+
const enabled = enabledHookIds(await readPreferences(input.root)).filter(
|
|
170
|
+
(id) => hookById(id)?.clients.includes('codex') === true,
|
|
171
|
+
);
|
|
172
|
+
if (enabled.length === 0) return null;
|
|
173
|
+
// The monitor resolves the same file from its own folder; both use the real path.
|
|
174
|
+
const dispatcher = await realpath(join(input.packageRoot, DISPATCHER)).catch(() => null);
|
|
175
|
+
if (dispatcher === null || !safeAbsolute(dispatcher) || !safeAbsolute(process.execPath))
|
|
176
|
+
return null;
|
|
177
|
+
const paths = { node: process.execPath, dispatcher };
|
|
178
|
+
const summary = await listHooks({
|
|
179
|
+
executable: input.executable,
|
|
180
|
+
cwd: input.cwd,
|
|
181
|
+
profile: input.profile,
|
|
182
|
+
environment: input.environment,
|
|
183
|
+
configArgs: codexHookConfigArgs(paths),
|
|
184
|
+
}).then(
|
|
185
|
+
(listed) => summarizeCodexHooks(listed, paths, enabled),
|
|
186
|
+
() => null,
|
|
187
|
+
);
|
|
188
|
+
return { enabled, summary };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The guardrails for a licensed Codex launch: the value the launcher hands the
|
|
193
|
+
* monitor, and the launch line. The line comes from `hooks/list`, so a guardrail
|
|
194
|
+
* that waits for the user's review in `/hooks` is never called on.
|
|
195
|
+
* @param {{ root: string, packageRoot: string, executable: string, cwd: string, profile: string | undefined, environment: NodeJS.ProcessEnv }} input
|
|
196
|
+
* @param {{ listHooks?: typeof readCodexHookList }} [dependencies]
|
|
197
|
+
* @returns {Promise<{ value: string, line: string } | null>}
|
|
198
|
+
*/
|
|
199
|
+
export async function prepareCodexGuardrails(input, dependencies = {}) {
|
|
200
|
+
const read = await readCodexGuardrails(input, dependencies);
|
|
201
|
+
if (read === null) return null;
|
|
202
|
+
const locale = resolvePackLocale(await readStoredLocale(input.root));
|
|
203
|
+
return {
|
|
204
|
+
value: codexHooksValue(read.enabled, locale),
|
|
205
|
+
line: codexHooksLine(read.enabled.length, read.summary),
|
|
206
|
+
};
|
|
207
|
+
}
|
package/dist/launch.mjs
CHANGED
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
sweepStaleLaunches,
|
|
19
19
|
writeLaunchMarker,
|
|
20
20
|
} from './engineering-pack.mjs';
|
|
21
|
-
import { prepareGuardrails } from './guardrails-plugin.mjs';
|
|
21
|
+
import { prepareCodexGuardrails, prepareGuardrails } from './guardrails-plugin.mjs';
|
|
22
|
+
import { CODEX_HOOKS_ENV, GUARDRAILS_SESSION_ENV, supportsCodexHooks } from './codex-hooks.mjs';
|
|
22
23
|
import {
|
|
23
24
|
CODEX_MCP_ENV,
|
|
24
25
|
claudeDenyRules,
|
|
@@ -149,6 +150,8 @@ function childEnvironment(root) {
|
|
|
149
150
|
delete environment[CODEX_ENGINEERING_ENV];
|
|
150
151
|
delete environment[CODEX_ENGINEERING_SKILLS_ROOT_ENV];
|
|
151
152
|
delete environment[CODEX_MCP_ENV];
|
|
153
|
+
delete environment[CODEX_HOOKS_ENV];
|
|
154
|
+
delete environment[GUARDRAILS_SESSION_ENV];
|
|
152
155
|
environment.AIBRAIN_STATE_DIR = resolve(root);
|
|
153
156
|
return environment;
|
|
154
157
|
}
|
|
@@ -451,6 +454,7 @@ export async function readSessionServers(root, client, environment) {
|
|
|
451
454
|
* @property {typeof prepareEngineeringPack} [preparePack] AISAC Engineering pack loader (E4b)
|
|
452
455
|
* @property {typeof prepareGuardrails} [prepareGuardrails] Guardrail hooks plugin writer (E6a)
|
|
453
456
|
* @property {typeof readSessionServers} [readSessionServers] MCP catalog servers to load (E6c)
|
|
457
|
+
* @property {typeof prepareCodexGuardrails} [prepareCodexGuardrails] Codex guardrail hooks (E6b)
|
|
454
458
|
*/
|
|
455
459
|
|
|
456
460
|
/**
|
|
@@ -675,6 +679,7 @@ export async function launchClaude(config, root, args, dependencies = {}) {
|
|
|
675
679
|
export async function launchCodex(config, root, args, dependencies = {}) {
|
|
676
680
|
const preparePack = dependencies.preparePack ?? prepareEngineeringPack;
|
|
677
681
|
const readServers = dependencies.readSessionServers ?? readSessionServers;
|
|
682
|
+
const writeCodexGuardrails = dependencies.prepareCodexGuardrails ?? prepareCodexGuardrails;
|
|
678
683
|
const validated = assertConfig(config);
|
|
679
684
|
assertCodexArgs(args);
|
|
680
685
|
const captureLockPlatform = captureLockPlatformCheck();
|
|
@@ -744,6 +749,22 @@ export async function launchCodex(config, root, args, dependencies = {}) {
|
|
|
744
749
|
process.stderr.write(`${line}\n`);
|
|
745
750
|
if (servers !== null && servers.ready.length > 0)
|
|
746
751
|
environment[CODEX_MCP_ENV] = JSON.stringify(servers.ready.map((entry) => entry.id));
|
|
752
|
+
// Guardrail hooks need the same license and a Codex measured with them
|
|
753
|
+
// (0.166.0+); like the pack they never block the launch (spec extensions FR6).
|
|
754
|
+
const guardrails = supportsCodexHooks(compatibility.version)
|
|
755
|
+
? await writeCodexGuardrails({
|
|
756
|
+
root,
|
|
757
|
+
packageRoot,
|
|
758
|
+
executable,
|
|
759
|
+
cwd: launchPlan.effectiveCwd,
|
|
760
|
+
profile: launchPlan.profile,
|
|
761
|
+
environment,
|
|
762
|
+
}).catch(() => null)
|
|
763
|
+
: null;
|
|
764
|
+
if (guardrails !== null) {
|
|
765
|
+
environment[CODEX_HOOKS_ENV] = guardrails.value;
|
|
766
|
+
process.stderr.write(`${guardrails.line}\n`);
|
|
767
|
+
}
|
|
747
768
|
}
|
|
748
769
|
environment.AI_BRAIN_MCP_SERVER = MCP_ALIAS;
|
|
749
770
|
environment.CODEX_BIN = executable;
|
package/dist/monitor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-check
|
|
3
3
|
|
|
4
|
-
import { access } from 'node:fs/promises';
|
|
4
|
+
import { access, realpath } from 'node:fs/promises';
|
|
5
5
|
import { constants } from 'node:fs';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { dirname, join } from 'node:path';
|
|
@@ -19,6 +19,13 @@ import {
|
|
|
19
19
|
parseCodexEngineering,
|
|
20
20
|
} from './codex-engineering.mjs';
|
|
21
21
|
import { CODEX_MCP_ENV, codexMcpConfigArgs, parseCodexMcp } from './mcp-servers.mjs';
|
|
22
|
+
import {
|
|
23
|
+
CODEX_HOOKS_ENV,
|
|
24
|
+
GUARDRAILS_SESSION_ENV,
|
|
25
|
+
codexHookConfigArgs,
|
|
26
|
+
guardrailsSessionValue,
|
|
27
|
+
parseCodexHooks,
|
|
28
|
+
} from './codex-hooks.mjs';
|
|
22
29
|
|
|
23
30
|
const root = dirname(fileURLToPath(import.meta.url));
|
|
24
31
|
const endpoint = process.env.AIBRAIN_MCP_URL?.trim();
|
|
@@ -59,6 +66,35 @@ try {
|
|
|
59
66
|
} catch {
|
|
60
67
|
catalogServers = [];
|
|
61
68
|
}
|
|
69
|
+
// Guardrail hooks (E6b) follow the pack as well, and need the state root the pack
|
|
70
|
+
// was checked against: their session value points at this launch's folder. The
|
|
71
|
+
// flags are the whole catalog with byte-stable commands (codex-hooks.mjs). A
|
|
72
|
+
// malformed value, or a package without the dispatcher, starts the session without
|
|
73
|
+
// them.
|
|
74
|
+
/** @type {{ args: string[], session: string } | null} */
|
|
75
|
+
let guardrails = null;
|
|
76
|
+
try {
|
|
77
|
+
const stateRoot = process.env.AIBRAIN_STATE_DIR;
|
|
78
|
+
const hooks =
|
|
79
|
+
engineering === null || stateRoot === undefined
|
|
80
|
+
? null
|
|
81
|
+
: parseCodexHooks(process.env[CODEX_HOOKS_ENV]);
|
|
82
|
+
if (hooks !== null && engineering !== null && stateRoot !== undefined) {
|
|
83
|
+
// The real path, as the launcher's hooks/list probe uses (prepareCodexGuardrails).
|
|
84
|
+
const dispatcher = await realpath(join(root, 'guardrails', 'run.mjs'));
|
|
85
|
+
await access(dispatcher, constants.R_OK);
|
|
86
|
+
guardrails = {
|
|
87
|
+
args: codexHookConfigArgs({ node: process.execPath, dispatcher }),
|
|
88
|
+
session: guardrailsSessionValue(hooks, {
|
|
89
|
+
// The pack sits in <launch>/engineering/skills, inside <state>/runtime.
|
|
90
|
+
launchDirectory: dirname(dirname(engineering.skillsRoot)),
|
|
91
|
+
stateRoot,
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
guardrails = null;
|
|
97
|
+
}
|
|
62
98
|
const policyText = process.env.CODEX_CONTEXT_POLICY_JSON;
|
|
63
99
|
let policy;
|
|
64
100
|
if (policyText === undefined) {
|
|
@@ -119,6 +155,11 @@ delete env[CODEX_APP_SERVER_PROFILE];
|
|
|
119
155
|
delete env[CODEX_ENGINEERING_ENV];
|
|
120
156
|
delete env[CODEX_ENGINEERING_SKILLS_ROOT_ENV];
|
|
121
157
|
delete env[CODEX_MCP_ENV];
|
|
158
|
+
delete env[CODEX_HOOKS_ENV];
|
|
159
|
+
// Only this monitor sets the value the hook processes read; one inherited from an
|
|
160
|
+
// outer session never reaches the app-server.
|
|
161
|
+
delete env[GUARDRAILS_SESSION_ENV];
|
|
162
|
+
if (guardrails !== null) env[GUARDRAILS_SESSION_ENV] = guardrails.session;
|
|
122
163
|
if (engineering !== null) env[CODEX_ENGINEERING_SKILLS_ROOT_ENV] = engineering.skillsRoot;
|
|
123
164
|
if (launchEnvelope?.profile !== undefined) env[CODEX_APP_SERVER_PROFILE] = launchEnvelope.profile;
|
|
124
165
|
if (policy !== null) env.CODEX_CONTEXT_POLICY_JSON = JSON.stringify(policy);
|
|
@@ -142,6 +183,7 @@ const child = spawn(
|
|
|
142
183
|
...(launchEnvelope === null ? [] : ownedAppServerConfigArgs(launchEnvelope)),
|
|
143
184
|
...(engineering === null ? [] : codexEngineeringConfigArgs(engineering)),
|
|
144
185
|
...codexMcpConfigArgs(catalogServers),
|
|
186
|
+
...(guardrails === null ? [] : guardrails.args),
|
|
145
187
|
],
|
|
146
188
|
{ cwd: process.cwd(), env, stdio: 'inherit' },
|
|
147
189
|
);
|
package/dist/package.json
CHANGED
package/package.json
CHANGED