@aria_asi/cli 0.2.29 → 0.2.31
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/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/claude-code.js +88 -20
- package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
- package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/codex.js +526 -2
- package/dist/aria-connector/src/connectors/codex.js.map +1 -1
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts +7 -0
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.js +87 -0
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.js.map +1 -0
- package/dist/aria-connector/src/connectors/must-read.d.ts +4 -0
- package/dist/aria-connector/src/connectors/must-read.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/must-read.js +111 -0
- package/dist/aria-connector/src/connectors/must-read.js.map +1 -0
- package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/opencode.js +2 -0
- package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
- package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/runtime.js +231 -19
- package/dist/aria-connector/src/connectors/runtime.js.map +1 -1
- package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/shell.js +76 -3
- package/dist/aria-connector/src/connectors/shell.js.map +1 -1
- package/dist/aria-connector/src/self-update.d.ts +2 -1
- package/dist/aria-connector/src/self-update.d.ts.map +1 -1
- package/dist/aria-connector/src/self-update.js +84 -8
- package/dist/aria-connector/src/self-update.js.map +1 -1
- package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +53 -34
- package/dist/assets/hooks/aria-harness-via-sdk.mjs +126 -12
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +185 -76
- package/dist/assets/hooks/aria-preturn-memory-gate.mjs +63 -14
- package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +2 -0
- package/dist/assets/hooks/aria-stop-gate.mjs +225 -52
- package/dist/assets/hooks/lib/canonical-lenses.mjs +6 -5
- package/dist/assets/hooks/lib/gate-loop-state.mjs +50 -0
- package/dist/assets/hooks/lib/hook-message-window.mjs +121 -0
- package/dist/assets/hooks/test-tier-lens-labeling.mjs +26 -58
- package/dist/assets/opencode-plugins/harness-gate/index.js +24 -2
- package/dist/assets/opencode-plugins/harness-stop/index.js +94 -5
- package/dist/runtime/auth-middleware.mjs +251 -0
- package/dist/runtime/codex-bridge.mjs +644 -0
- package/dist/runtime/discipline/CLAUDE.md +12 -0
- package/dist/runtime/discipline/doctrine_trigger_map.json +479 -0
- package/dist/runtime/doctrine_trigger_map.json +479 -0
- package/dist/runtime/fleet-engine.mjs +231 -0
- package/dist/runtime/harness-daemon.mjs +433 -0
- package/dist/runtime/local-phase.mjs +18 -0
- package/dist/runtime/manifest.json +1 -1
- package/dist/runtime/metering.mjs +100 -0
- package/dist/runtime/onboarding-engine.mjs +89 -0
- package/dist/runtime/plugin-engine.mjs +196 -0
- package/dist/runtime/sdk/BUNDLED.json +1 -1
- package/dist/runtime/sdk/index.d.ts +7 -0
- package/dist/runtime/sdk/index.js +120 -14
- package/dist/runtime/sdk/index.js.map +1 -1
- package/dist/runtime/service.mjs +1464 -67
- package/dist/runtime/vendor/aria-gate-runtime/index.d.ts +1 -1
- package/dist/runtime/vendor/aria-gate-runtime/index.d.ts.map +1 -1
- package/dist/runtime/vendor/aria-gate-runtime/index.js +16 -1
- package/dist/runtime/vendor/aria-gate-runtime/index.js.map +1 -1
- package/dist/runtime/workflow-engine.mjs +322 -0
- package/dist/sdk/BUNDLED.json +1 -1
- package/dist/sdk/index.d.ts +7 -0
- package/dist/sdk/index.js +120 -14
- package/dist/sdk/index.js.map +1 -1
- package/hooks/aria-cognition-substrate-binding.mjs +53 -34
- package/hooks/aria-harness-via-sdk.mjs +126 -12
- package/hooks/aria-pre-tool-gate.mjs +185 -76
- package/hooks/aria-preturn-memory-gate.mjs +63 -14
- package/hooks/aria-repo-doctrine-gate.mjs +2 -0
- package/hooks/aria-stop-gate.mjs +225 -52
- package/hooks/lib/canonical-lenses.mjs +6 -5
- package/hooks/lib/gate-loop-state.mjs +50 -0
- package/hooks/lib/hook-message-window.mjs +121 -0
- package/hooks/test-tier-lens-labeling.mjs +26 -58
- package/opencode-plugins/harness-gate/index.js +24 -2
- package/opencode-plugins/harness-stop/index.js +94 -5
- package/package.json +2 -2
- package/runtime-src/auth-middleware.mjs +251 -0
- package/runtime-src/codex-bridge.mjs +644 -0
- package/runtime-src/fleet-engine.mjs +231 -0
- package/runtime-src/harness-daemon.mjs +433 -0
- package/runtime-src/local-phase.mjs +18 -0
- package/runtime-src/metering.mjs +100 -0
- package/runtime-src/onboarding-engine.mjs +89 -0
- package/runtime-src/plugin-engine.mjs +196 -0
- package/runtime-src/service.mjs +1464 -67
- package/runtime-src/workflow-engine.mjs +322 -0
- package/scripts/bundle-sdk.mjs +5 -0
- package/src/connectors/claude-code.ts +98 -20
- package/src/connectors/codex.ts +534 -1
- package/src/connectors/doctrine-trigger-map.ts +112 -0
- package/src/connectors/must-read.ts +113 -0
- package/src/connectors/opencode.ts +3 -0
- package/src/connectors/runtime.ts +241 -21
- package/src/connectors/shell.ts +78 -3
- package/src/self-update.ts +89 -8
- package/dist/cli-0.2.0.tgz +0 -0
package/src/connectors/codex.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
copyFileSync,
|
|
9
9
|
chmodSync,
|
|
10
10
|
writeFileSync,
|
|
11
|
+
readFileSync,
|
|
11
12
|
} from 'fs';
|
|
12
13
|
import { homedir } from 'os';
|
|
13
14
|
import * as path from 'path';
|
|
@@ -15,6 +16,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
15
16
|
import type { AriaConfig } from '../config.js';
|
|
16
17
|
import { connectShell } from './shell.js';
|
|
17
18
|
import { installAriaCognitionSkills } from './cognitive-skills.js';
|
|
19
|
+
import { syncDoctrineTriggerMap } from './doctrine-trigger-map.js';
|
|
20
|
+
import { buildMustReadGuide, mustReadIntro } from './must-read.js';
|
|
18
21
|
|
|
19
22
|
function packageSdkDir(): string {
|
|
20
23
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -81,6 +84,528 @@ function installNodePackage(codexDir: string, logs: string[]): void {
|
|
|
81
84
|
logs.push(`Installed Codex Node package → ${pkgRoot}`);
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
function tomlString(value: string): string {
|
|
88
|
+
return JSON.stringify(value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildCodexHookRuntimeClient(): string {
|
|
92
|
+
return `import { readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
93
|
+
import { homedir } from 'node:os';
|
|
94
|
+
import path from 'node:path';
|
|
95
|
+
import { HTTPHarnessClient } from '@aria_asi/harness-http-client';
|
|
96
|
+
|
|
97
|
+
const HOME = homedir();
|
|
98
|
+
const DEFAULT_RUNTIME_URL = (process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319').replace(/\\/+$/, '');
|
|
99
|
+
const TURN_STATE_DIR = path.join(HOME, '.codex', 'tmp', 'aria-hook-turn-state');
|
|
100
|
+
|
|
101
|
+
function readToken() {
|
|
102
|
+
const envToken = process.env.ARIA_API_KEY || process.env.ARIA_MASTER_TOKEN || process.env.OPENAI_API_KEY;
|
|
103
|
+
if (envToken) return envToken;
|
|
104
|
+
const ownerPath = path.join(HOME, '.aria', 'owner-token');
|
|
105
|
+
if (existsSync(ownerPath)) return readFileSync(ownerPath, 'utf8').trim();
|
|
106
|
+
const licensePath = path.join(HOME, '.aria', 'license.json');
|
|
107
|
+
if (existsSync(licensePath)) {
|
|
108
|
+
const text = readFileSync(licensePath, 'utf8');
|
|
109
|
+
const harnessToken = text.match(/"harnessToken"\\s*:\\s*"([^"]+)"/)?.[1];
|
|
110
|
+
if (harnessToken) return harnessToken;
|
|
111
|
+
const token = text.match(/"token"\\s*:\\s*"([^"]+)"/)?.[1];
|
|
112
|
+
if (token) return token;
|
|
113
|
+
}
|
|
114
|
+
return '';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let clientInstance = null;
|
|
118
|
+
|
|
119
|
+
export function getHarnessClient() {
|
|
120
|
+
if (clientInstance) return clientInstance;
|
|
121
|
+
clientInstance = new HTTPHarnessClient({
|
|
122
|
+
baseUrl: DEFAULT_RUNTIME_URL,
|
|
123
|
+
apiKey: readToken(),
|
|
124
|
+
workspaceRoot: process.cwd(),
|
|
125
|
+
});
|
|
126
|
+
return clientInstance;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function readEventFromStdin() {
|
|
130
|
+
try {
|
|
131
|
+
const raw = readFileSync(0, 'utf8');
|
|
132
|
+
return raw.trim() ? JSON.parse(raw) : {};
|
|
133
|
+
} catch {
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function extractFirst(value, keys) {
|
|
139
|
+
if (!value || typeof value !== 'object') return null;
|
|
140
|
+
for (const key of keys) {
|
|
141
|
+
const candidate = value[key];
|
|
142
|
+
if (typeof candidate === 'string' && candidate.trim()) return candidate.trim();
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function inferSessionId(event) {
|
|
148
|
+
const threadId =
|
|
149
|
+
extractFirst(event, ['thread_id', 'threadId', 'conversation_id', 'conversationId']) ||
|
|
150
|
+
'codex';
|
|
151
|
+
const turnId =
|
|
152
|
+
extractFirst(event, ['turn_id', 'turnId', 'session_id', 'sessionId']) ||
|
|
153
|
+
String(Date.now());
|
|
154
|
+
return \`codex:\${threadId}:\${turnId}\`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function inferUserId(event) {
|
|
158
|
+
return (
|
|
159
|
+
extractFirst(event, ['user_id', 'userId']) ||
|
|
160
|
+
extractFirst(event?.metadata, ['user_id', 'userId'])
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function extractText(value) {
|
|
165
|
+
if (!value) return '';
|
|
166
|
+
if (typeof value === 'string') return value.trim();
|
|
167
|
+
if (Array.isArray(value)) {
|
|
168
|
+
return value.map(extractText).filter(Boolean).join('\\n\\n').trim();
|
|
169
|
+
}
|
|
170
|
+
if (typeof value === 'object') {
|
|
171
|
+
const parts = [];
|
|
172
|
+
for (const key of ['text', 'message', 'content', 'input_text', 'inputText', 'reasoning_text', 'summary_text', 'delta']) {
|
|
173
|
+
if (key in value) {
|
|
174
|
+
const piece = extractText(value[key]);
|
|
175
|
+
if (piece) parts.push(piece);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return parts.join('\\n\\n').trim();
|
|
179
|
+
}
|
|
180
|
+
return '';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function ensureTurnStateDir() {
|
|
184
|
+
if (!existsSync(TURN_STATE_DIR)) {
|
|
185
|
+
mkdirSync(TURN_STATE_DIR, { recursive: true, mode: 0o700 });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function turnStatePath(sessionId) {
|
|
190
|
+
ensureTurnStateDir();
|
|
191
|
+
const safe = String(sessionId || 'codex').replace(/[^a-zA-Z0-9._-]+/g, '_');
|
|
192
|
+
return path.join(TURN_STATE_DIR, \`\${safe}.json\`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function loadTurnState(sessionId) {
|
|
196
|
+
const statePath = turnStatePath(sessionId);
|
|
197
|
+
if (!existsSync(statePath)) return {};
|
|
198
|
+
try {
|
|
199
|
+
return JSON.parse(readFileSync(statePath, 'utf8'));
|
|
200
|
+
} catch {
|
|
201
|
+
return {};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function saveTurnState(sessionId, patch) {
|
|
206
|
+
const next = {
|
|
207
|
+
...loadTurnState(sessionId),
|
|
208
|
+
...patch,
|
|
209
|
+
sessionId,
|
|
210
|
+
updatedAt: new Date().toISOString(),
|
|
211
|
+
};
|
|
212
|
+
writeFileSync(turnStatePath(sessionId), JSON.stringify(next, null, 2) + '\\n', { mode: 0o600 });
|
|
213
|
+
return next;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function clearTurnState(sessionId) {
|
|
217
|
+
const statePath = turnStatePath(sessionId);
|
|
218
|
+
if (!existsSync(statePath)) return;
|
|
219
|
+
try {
|
|
220
|
+
unlinkSync(statePath);
|
|
221
|
+
} catch {}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function runtimePost(route, body = {}) {
|
|
225
|
+
const token = readToken();
|
|
226
|
+
const headers = {
|
|
227
|
+
'Content-Type': 'application/json',
|
|
228
|
+
};
|
|
229
|
+
if (token) headers.Authorization = \`Bearer \${token}\`;
|
|
230
|
+
const response = await fetch(\`\${DEFAULT_RUNTIME_URL}\${route}\`, {
|
|
231
|
+
method: 'POST',
|
|
232
|
+
headers,
|
|
233
|
+
body: JSON.stringify(body),
|
|
234
|
+
});
|
|
235
|
+
if (!response.ok) {
|
|
236
|
+
const detail = await response.text().catch(() => response.statusText);
|
|
237
|
+
throw new Error(\`runtime \${route} failed (\${response.status}): \${detail}\`);
|
|
238
|
+
}
|
|
239
|
+
return response.json();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function classifyAction(event) {
|
|
243
|
+
const toolName = String(event?.tool_name || event?.toolName || '').trim();
|
|
244
|
+
const toolCommand = String(
|
|
245
|
+
event?.tool_input?.command ??
|
|
246
|
+
event?.toolInput?.command ??
|
|
247
|
+
''
|
|
248
|
+
).trim();
|
|
249
|
+
const haystack = \`\${toolName}\\n\${toolCommand}\\n\${JSON.stringify(event)}\`;
|
|
250
|
+
if (/deploy|kubectl|docker\\s+push/i.test(haystack)) return 'deploy';
|
|
251
|
+
if (/\\brm\\b|delete|unlink|drop\\s+table/i.test(haystack)) return 'delete';
|
|
252
|
+
if (/build|test|tsc|jest|vitest|npm run|pnpm|yarn/i.test(haystack)) return 'build';
|
|
253
|
+
return 'write';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function summarizeTarget(event) {
|
|
257
|
+
return JSON.stringify({
|
|
258
|
+
tool_name: event?.tool_name || event?.toolName || null,
|
|
259
|
+
tool_input: event?.tool_input || event?.toolInput || null,
|
|
260
|
+
}).slice(0, 4000);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function extractUserText(event) {
|
|
264
|
+
return extractText(
|
|
265
|
+
event?.input ??
|
|
266
|
+
event?.user_input ??
|
|
267
|
+
event?.userInput ??
|
|
268
|
+
event?.prompt ??
|
|
269
|
+
event?.message ??
|
|
270
|
+
event
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function extractAssistantText(event) {
|
|
275
|
+
return extractText(
|
|
276
|
+
event?.last_assistant_message ??
|
|
277
|
+
event?.lastAssistantMessage ??
|
|
278
|
+
event?.output ??
|
|
279
|
+
event?.response ??
|
|
280
|
+
event?.message ??
|
|
281
|
+
event
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function formatValidationFailure(result) {
|
|
286
|
+
const parts = [];
|
|
287
|
+
if (Array.isArray(result?.validation?.violations) && result.validation.violations.length) {
|
|
288
|
+
parts.push(...result.validation.violations);
|
|
289
|
+
}
|
|
290
|
+
if (Array.isArray(result?.layer3?.failures) && result.layer3.failures.length) {
|
|
291
|
+
parts.push(...result.layer3.failures.map((failure) => failure?.detail || failure?.kind || JSON.stringify(failure)));
|
|
292
|
+
}
|
|
293
|
+
if (!parts.length && typeof result?.summary === 'string' && result.summary.trim()) {
|
|
294
|
+
parts.push(result.summary.trim());
|
|
295
|
+
}
|
|
296
|
+
return parts.join(' | ') || 'Aria validation failed.';
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function emitJson(payload, code = 0) {
|
|
300
|
+
process.stdout.write(\`\${JSON.stringify(payload)}\\n\`);
|
|
301
|
+
process.exit(code);
|
|
302
|
+
}
|
|
303
|
+
`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function buildCodexUserPromptHook(): string {
|
|
307
|
+
return `#!/usr/bin/env node
|
|
308
|
+
import {
|
|
309
|
+
getHarnessClient,
|
|
310
|
+
inferSessionId,
|
|
311
|
+
inferUserId,
|
|
312
|
+
extractUserText,
|
|
313
|
+
readEventFromStdin,
|
|
314
|
+
runtimePost,
|
|
315
|
+
saveTurnState,
|
|
316
|
+
emitJson,
|
|
317
|
+
} from './lib/runtime-client.mjs';
|
|
318
|
+
|
|
319
|
+
const event = readEventFromStdin();
|
|
320
|
+
const client = getHarnessClient();
|
|
321
|
+
const userText = extractUserText(event);
|
|
322
|
+
const sessionId = inferSessionId(event);
|
|
323
|
+
const userId = inferUserId(event);
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
const packet = await client.getHarnessPacket({
|
|
327
|
+
sessionId,
|
|
328
|
+
platform: 'codex',
|
|
329
|
+
message: userText || 'codex turn start',
|
|
330
|
+
});
|
|
331
|
+
const result = await runtimePost('/mizan/pre', {
|
|
332
|
+
sessionId,
|
|
333
|
+
packet,
|
|
334
|
+
packetRequest: {
|
|
335
|
+
sessionId,
|
|
336
|
+
platform: 'codex',
|
|
337
|
+
message: userText || 'codex turn start',
|
|
338
|
+
stage: 'codex-userprompt',
|
|
339
|
+
actor: 'codex-hook',
|
|
340
|
+
system: 'codex-hook',
|
|
341
|
+
},
|
|
342
|
+
context: {
|
|
343
|
+
sessionId,
|
|
344
|
+
surface: 'codex-hooks',
|
|
345
|
+
platform: 'codex',
|
|
346
|
+
userText,
|
|
347
|
+
userId,
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
saveTurnState(sessionId, {
|
|
351
|
+
userId,
|
|
352
|
+
userText,
|
|
353
|
+
preReceiptId: result?.receipt?.receiptId || null,
|
|
354
|
+
packetTimestamp: packet?.timestamp || null,
|
|
355
|
+
lastEvent: 'UserPromptSubmit',
|
|
356
|
+
});
|
|
357
|
+
process.exit(0);
|
|
358
|
+
} catch (error) {
|
|
359
|
+
emitJson({
|
|
360
|
+
decision: 'block',
|
|
361
|
+
reason: \`Aria cognition gate failed before turn start: \${error instanceof Error ? error.message : String(error)}\`,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function buildCodexPreToolHook(): string {
|
|
368
|
+
return `#!/usr/bin/env node
|
|
369
|
+
import {
|
|
370
|
+
getHarnessClient,
|
|
371
|
+
inferSessionId,
|
|
372
|
+
classifyAction,
|
|
373
|
+
summarizeTarget,
|
|
374
|
+
readEventFromStdin,
|
|
375
|
+
loadTurnState,
|
|
376
|
+
saveTurnState,
|
|
377
|
+
emitJson,
|
|
378
|
+
} from './lib/runtime-client.mjs';
|
|
379
|
+
|
|
380
|
+
const event = readEventFromStdin();
|
|
381
|
+
const client = getHarnessClient();
|
|
382
|
+
const sessionId = inferSessionId(event);
|
|
383
|
+
const action = classifyAction(event);
|
|
384
|
+
const target = summarizeTarget(event);
|
|
385
|
+
const state = loadTurnState(sessionId);
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
if (!state?.preReceiptId && !state?.userText) {
|
|
389
|
+
emitJson({
|
|
390
|
+
decision: 'block',
|
|
391
|
+
reason: 'Aria pre-tool gate blocked action because this turn has no pre-turn Mizan receipt. Re-submit the prompt so cognition is established before tool use.',
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
const actionCheck = await client.checkAction(action, target);
|
|
395
|
+
if (actionCheck?.allowed === false) {
|
|
396
|
+
emitJson({
|
|
397
|
+
decision: 'block',
|
|
398
|
+
reason: actionCheck?.reason || \`Aria denied \${action}\`,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
const toolName = String(event?.tool_name || event?.toolName || '').trim() || null;
|
|
402
|
+
const tools = Array.isArray(state?.tools) ? state.tools.slice(-24) : [];
|
|
403
|
+
tools.push({
|
|
404
|
+
at: new Date().toISOString(),
|
|
405
|
+
action,
|
|
406
|
+
toolName,
|
|
407
|
+
target,
|
|
408
|
+
});
|
|
409
|
+
saveTurnState(sessionId, {
|
|
410
|
+
tools,
|
|
411
|
+
lastEvent: 'PreToolUse',
|
|
412
|
+
});
|
|
413
|
+
process.exit(0);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
emitJson({
|
|
416
|
+
decision: 'block',
|
|
417
|
+
reason: \`Aria pre-tool hook failed closed: \${error instanceof Error ? error.message : String(error)}\`,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
`;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function buildCodexPostToolHook(): string {
|
|
424
|
+
return `#!/usr/bin/env node
|
|
425
|
+
import {
|
|
426
|
+
inferSessionId,
|
|
427
|
+
readEventFromStdin,
|
|
428
|
+
loadTurnState,
|
|
429
|
+
saveTurnState,
|
|
430
|
+
} from './lib/runtime-client.mjs';
|
|
431
|
+
|
|
432
|
+
const event = readEventFromStdin();
|
|
433
|
+
const sessionId = inferSessionId(event);
|
|
434
|
+
const state = loadTurnState(sessionId);
|
|
435
|
+
|
|
436
|
+
try {
|
|
437
|
+
const toolResponse = JSON.stringify(event?.tool_response ?? event?.toolResponse ?? null).slice(0, 4000);
|
|
438
|
+
const toolOutputs = Array.isArray(state?.toolOutputs) ? state.toolOutputs.slice(-24) : [];
|
|
439
|
+
toolOutputs.push({
|
|
440
|
+
at: new Date().toISOString(),
|
|
441
|
+
toolName: event?.tool_name || event?.toolName || null,
|
|
442
|
+
toolResponse,
|
|
443
|
+
});
|
|
444
|
+
saveTurnState(sessionId, {
|
|
445
|
+
toolOutputs,
|
|
446
|
+
lastEvent: 'PostToolUse',
|
|
447
|
+
});
|
|
448
|
+
process.exit(0);
|
|
449
|
+
} catch {
|
|
450
|
+
process.exit(0);
|
|
451
|
+
}
|
|
452
|
+
`;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function buildCodexStopHook(): string {
|
|
456
|
+
return `#!/usr/bin/env node
|
|
457
|
+
import {
|
|
458
|
+
getHarnessClient,
|
|
459
|
+
inferSessionId,
|
|
460
|
+
extractAssistantText,
|
|
461
|
+
readEventFromStdin,
|
|
462
|
+
runtimePost,
|
|
463
|
+
loadTurnState,
|
|
464
|
+
clearTurnState,
|
|
465
|
+
formatValidationFailure,
|
|
466
|
+
emitJson,
|
|
467
|
+
} from './lib/runtime-client.mjs';
|
|
468
|
+
|
|
469
|
+
const event = readEventFromStdin();
|
|
470
|
+
const client = getHarnessClient();
|
|
471
|
+
const sessionId = inferSessionId(event);
|
|
472
|
+
const state = loadTurnState(sessionId);
|
|
473
|
+
const text = extractAssistantText(event);
|
|
474
|
+
|
|
475
|
+
try {
|
|
476
|
+
if (!text) {
|
|
477
|
+
emitJson({ continue: true });
|
|
478
|
+
}
|
|
479
|
+
const validation = await runtimePost('/validate-output', {
|
|
480
|
+
text,
|
|
481
|
+
sessionId,
|
|
482
|
+
packetRequest: {
|
|
483
|
+
sessionId,
|
|
484
|
+
platform: 'codex',
|
|
485
|
+
message: state?.userText || 'codex stop validation',
|
|
486
|
+
stage: 'codex-stop',
|
|
487
|
+
actor: 'codex-hook',
|
|
488
|
+
system: 'codex-hook',
|
|
489
|
+
},
|
|
490
|
+
requireCognitionBlock: false,
|
|
491
|
+
runLayer3: true,
|
|
492
|
+
});
|
|
493
|
+
if (validation?.pass === false || validation?.validation?.passed === false || validation?.layer3?.pass === false) {
|
|
494
|
+
emitJson({
|
|
495
|
+
decision: 'block',
|
|
496
|
+
reason: \`Aria stop gate blocked output: \${formatValidationFailure(validation)}\`,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
const post = await runtimePost('/mizan/post', {
|
|
500
|
+
sessionId,
|
|
501
|
+
text,
|
|
502
|
+
evidence: {
|
|
503
|
+
validated_output: true,
|
|
504
|
+
validation_severity: validation?.validation?.severity || 'pass',
|
|
505
|
+
layer3_pass: validation?.layer3?.pass !== false,
|
|
506
|
+
surface: 'codex-hooks',
|
|
507
|
+
tool_count: Array.isArray(state?.tools) ? state.tools.length : 0,
|
|
508
|
+
},
|
|
509
|
+
context: {
|
|
510
|
+
sessionId,
|
|
511
|
+
surface: 'codex-hooks',
|
|
512
|
+
platform: 'codex',
|
|
513
|
+
userText: state?.userText || null,
|
|
514
|
+
},
|
|
515
|
+
parentReceiptId: state?.preReceiptId || null,
|
|
516
|
+
});
|
|
517
|
+
await runtimePost('/decision/log', {
|
|
518
|
+
session_id: sessionId,
|
|
519
|
+
decision_type: 'operational',
|
|
520
|
+
category: 'codex-hooks',
|
|
521
|
+
context: \`Codex native stop hook for \${sessionId}\`,
|
|
522
|
+
decision: 'validated output release',
|
|
523
|
+
reasoning: 'Mounted Aria runtime validated the output, issued Mizan post, and preserved the turn in garden memory.',
|
|
524
|
+
summary: 'codex native stop hook validated output',
|
|
525
|
+
outcome: 'success',
|
|
526
|
+
surface: 'codex-hooks',
|
|
527
|
+
metadata: {
|
|
528
|
+
post_receipt_id: post?.receipt?.receiptId || null,
|
|
529
|
+
pre_receipt_id: state?.preReceiptId || null,
|
|
530
|
+
tool_count: Array.isArray(state?.tools) ? state.tools.length : 0,
|
|
531
|
+
validation_severity: validation?.validation?.severity || 'pass',
|
|
532
|
+
layer3_pass: validation?.layer3?.pass !== false,
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
if (typeof state?.userText === 'string' && state.userText.trim()) {
|
|
536
|
+
await client.gardenTurn(sessionId, state.userText, text, state?.userId || undefined);
|
|
537
|
+
}
|
|
538
|
+
clearTurnState(sessionId);
|
|
539
|
+
emitJson({ continue: true });
|
|
540
|
+
} catch (error) {
|
|
541
|
+
emitJson({
|
|
542
|
+
decision: 'block',
|
|
543
|
+
reason: \`Aria stop hook failed closed: \${error instanceof Error ? error.message : String(error)}\`,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
`;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function buildCodexHooksToml(codexDir: string): string {
|
|
550
|
+
const hooksDir = path.join(codexDir, 'hooks');
|
|
551
|
+
const command = (name: string) => tomlString(`node ${path.join(hooksDir, name)}`);
|
|
552
|
+
return `# BEGIN ARIA MANAGED HOOKS
|
|
553
|
+
[hooks]
|
|
554
|
+
managed_dir = ${tomlString(hooksDir)}
|
|
555
|
+
|
|
556
|
+
[[hooks.UserPromptSubmit]]
|
|
557
|
+
hooks = [{ type = "command", command = ${command('aria-userprompt-submit.mjs')} }]
|
|
558
|
+
|
|
559
|
+
[[hooks.PreToolUse]]
|
|
560
|
+
matcher = ".*"
|
|
561
|
+
hooks = [{ type = "command", command = ${command('aria-pre-tool-use.mjs')} }]
|
|
562
|
+
|
|
563
|
+
[[hooks.PostToolUse]]
|
|
564
|
+
matcher = ".*"
|
|
565
|
+
hooks = [{ type = "command", command = ${command('aria-post-tool-use.mjs')} }]
|
|
566
|
+
|
|
567
|
+
[[hooks.Stop]]
|
|
568
|
+
hooks = [{ type = "command", command = ${command('aria-stop.mjs')} }]
|
|
569
|
+
# END ARIA MANAGED HOOKS
|
|
570
|
+
`;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function installCodexHooksConfig(codexDir: string, logs: string[]): void {
|
|
574
|
+
const configPath = path.join(codexDir, 'config.toml');
|
|
575
|
+
const managedBlock = buildCodexHooksToml(codexDir);
|
|
576
|
+
const markerRegex = /# BEGIN ARIA MANAGED HOOKS[\s\S]*?# END ARIA MANAGED HOOKS\n?/m;
|
|
577
|
+
let text = existsSync(configPath) ? readFileSync(configPath, 'utf8') : '';
|
|
578
|
+
if (markerRegex.test(text)) {
|
|
579
|
+
text = text.replace(markerRegex, managedBlock);
|
|
580
|
+
} else {
|
|
581
|
+
if (text && !text.endsWith('\n')) text += '\n';
|
|
582
|
+
text += `\n${managedBlock}`;
|
|
583
|
+
}
|
|
584
|
+
writeFileSync(configPath, text, { mode: 0o600 });
|
|
585
|
+
logs.push(`Installed Codex managed hooks config → ${configPath}`);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function installCodexHooks(codexDir: string, logs: string[]): void {
|
|
589
|
+
const hooksDir = path.join(codexDir, 'hooks');
|
|
590
|
+
mkdirSync(path.join(hooksDir, 'lib'), { recursive: true, mode: 0o755 });
|
|
591
|
+
|
|
592
|
+
const files: Array<[string, string]> = [
|
|
593
|
+
[path.join(hooksDir, 'lib', 'runtime-client.mjs'), buildCodexHookRuntimeClient()],
|
|
594
|
+
[path.join(hooksDir, 'aria-userprompt-submit.mjs'), buildCodexUserPromptHook()],
|
|
595
|
+
[path.join(hooksDir, 'aria-pre-tool-use.mjs'), buildCodexPreToolHook()],
|
|
596
|
+
[path.join(hooksDir, 'aria-post-tool-use.mjs'), buildCodexPostToolHook()],
|
|
597
|
+
[path.join(hooksDir, 'aria-stop.mjs'), buildCodexStopHook()],
|
|
598
|
+
];
|
|
599
|
+
|
|
600
|
+
for (const [filePath, content] of files) {
|
|
601
|
+
writeFileSync(filePath, content, { mode: 0o755 });
|
|
602
|
+
try { chmodSync(filePath, 0o755); } catch {}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
logs.push(`Installed Codex native hooks → ${hooksDir}`);
|
|
606
|
+
installCodexHooksConfig(codexDir, logs);
|
|
607
|
+
}
|
|
608
|
+
|
|
84
609
|
function buildCodexSkill(_config: AriaConfig): string {
|
|
85
610
|
return `---
|
|
86
611
|
name: aria-http-harness-client
|
|
@@ -111,7 +636,7 @@ That means:
|
|
|
111
636
|
|
|
112
637
|
- the mounted runtime is the universal control plane
|
|
113
638
|
- the shared SDK at \`~/.aria/sdk\` is the universal client substrate
|
|
114
|
-
- Codex
|
|
639
|
+
- Codex native hooks plus the runtime are the hard-gate path for this platform
|
|
115
640
|
|
|
116
641
|
## Preferred Call Paths
|
|
117
642
|
|
|
@@ -153,6 +678,8 @@ function buildCodexAgentsMd(config: AriaConfig): string {
|
|
|
153
678
|
|
|
154
679
|
Automatically injected by @aria_asi/cli. These instructions are always on for Codex sessions.
|
|
155
680
|
|
|
681
|
+
${mustReadIntro('codex')}
|
|
682
|
+
|
|
156
683
|
## Connected Repositories
|
|
157
684
|
${repoList || '(none linked yet)'}
|
|
158
685
|
|
|
@@ -253,6 +780,10 @@ function installAgentsMd(codexDir: string, config: AriaConfig, logs: string[]):
|
|
|
253
780
|
const agentsPath = path.join(codexDir, 'AGENTS.md');
|
|
254
781
|
writeFileSync(agentsPath, buildCodexAgentsMd(config), { mode: 0o644 });
|
|
255
782
|
logs.push(`Installed Codex AGENTS.md → ${agentsPath}`);
|
|
783
|
+
|
|
784
|
+
const mustReadPath = path.join(codexDir, 'ARIA_MUST_READ.md');
|
|
785
|
+
writeFileSync(mustReadPath, buildMustReadGuide('codex'), { mode: 0o644 });
|
|
786
|
+
logs.push(`Installed Codex must-read guide → ${mustReadPath}`);
|
|
256
787
|
}
|
|
257
788
|
|
|
258
789
|
export async function connectCodex(config: AriaConfig): Promise<string[]> {
|
|
@@ -268,7 +799,9 @@ export async function connectCodex(config: AriaConfig): Promise<string[]> {
|
|
|
268
799
|
installNodePackage(codexDir, logs);
|
|
269
800
|
installAgentsMd(codexDir, config, logs);
|
|
270
801
|
installSkill(codexDir, config, logs);
|
|
802
|
+
installCodexHooks(codexDir, logs);
|
|
271
803
|
installAriaCognitionSkills(codexDir, logs);
|
|
804
|
+
syncDoctrineTriggerMap(logs);
|
|
272
805
|
logs.push(...await connectShell('codex', config));
|
|
273
806
|
return logs;
|
|
274
807
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
statSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from 'fs';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
export interface DoctrineMapSyncResult {
|
|
13
|
+
sourcePath: string | null;
|
|
14
|
+
targetCount: number;
|
|
15
|
+
updatedCount: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface DoctrineMapCandidate {
|
|
19
|
+
path: string;
|
|
20
|
+
mtimeMs: number;
|
|
21
|
+
data: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function bundledDoctrineTriggerMapCandidates(): string[] {
|
|
25
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
return [
|
|
27
|
+
path.resolve(here, '..', '..', '..', '..', 'hooks', 'doctrine_trigger_map.json'),
|
|
28
|
+
path.resolve(here, '..', '..', '..', 'assets', 'hooks', 'doctrine_trigger_map.json'),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function installedDoctrineTriggerMapTargets(): string[] {
|
|
33
|
+
const home = homedir();
|
|
34
|
+
return [
|
|
35
|
+
path.join(home, '.aria', 'runtime', 'discipline', 'doctrine_trigger_map.json'),
|
|
36
|
+
path.join(home, '.aria', 'runtime', 'doctrine_trigger_map.json'),
|
|
37
|
+
path.join(home, '.claude', 'hooks', 'doctrine_trigger_map.json'),
|
|
38
|
+
path.join(home, '.claude', 'projects', '-home-hamzaibrahim1', 'memory', 'doctrine_trigger_map.json'),
|
|
39
|
+
path.join(home, '.codex', 'doctrine_trigger_map.json'),
|
|
40
|
+
path.join(home, '.opencode', 'doctrine_trigger_map.json'),
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readCandidate(targetPath: string): DoctrineMapCandidate | null {
|
|
45
|
+
if (!existsSync(targetPath)) return null;
|
|
46
|
+
try {
|
|
47
|
+
const data = JSON.parse(readFileSync(targetPath, 'utf8')) as Record<string, unknown>;
|
|
48
|
+
if (!Array.isArray(data.triggers)) return null;
|
|
49
|
+
return {
|
|
50
|
+
path: targetPath,
|
|
51
|
+
mtimeMs: statSync(targetPath).mtimeMs,
|
|
52
|
+
data,
|
|
53
|
+
};
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeDoctrineMap(data: Record<string, unknown>): string {
|
|
60
|
+
return JSON.stringify(data, null, 2) + '\n';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function syncDoctrineTriggerMap(logs: string[] = []): DoctrineMapSyncResult {
|
|
64
|
+
const targets = installedDoctrineTriggerMapTargets();
|
|
65
|
+
const readableCandidates = [
|
|
66
|
+
...bundledDoctrineTriggerMapCandidates(),
|
|
67
|
+
...targets,
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const candidates = readableCandidates
|
|
71
|
+
.map((targetPath) => readCandidate(targetPath))
|
|
72
|
+
.filter((candidate): candidate is DoctrineMapCandidate => candidate !== null)
|
|
73
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
74
|
+
|
|
75
|
+
if (candidates.length === 0) {
|
|
76
|
+
logs.push('⚠ doctrine trigger map missing from bundle and all installed targets');
|
|
77
|
+
return {
|
|
78
|
+
sourcePath: null,
|
|
79
|
+
targetCount: targets.length,
|
|
80
|
+
updatedCount: 0,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const source = candidates[0];
|
|
85
|
+
const normalized = normalizeDoctrineMap(source.data);
|
|
86
|
+
let updatedCount = 0;
|
|
87
|
+
|
|
88
|
+
for (const targetPath of targets) {
|
|
89
|
+
try {
|
|
90
|
+
const dirPath = path.dirname(targetPath);
|
|
91
|
+
if (!existsSync(dirPath)) {
|
|
92
|
+
mkdirSync(dirPath, { recursive: true, mode: 0o755 });
|
|
93
|
+
}
|
|
94
|
+
const current = existsSync(targetPath) ? readFileSync(targetPath, 'utf8') : null;
|
|
95
|
+
if (current === normalized) continue;
|
|
96
|
+
writeFileSync(targetPath, normalized, { mode: 0o644 });
|
|
97
|
+
updatedCount++;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
logs.push(`⚠ doctrine trigger map sync failed at ${targetPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
logs.push(
|
|
104
|
+
`Synced doctrine trigger map from ${source.path} to ${targets.length} installed target(s)`,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
sourcePath: source.path,
|
|
109
|
+
targetCount: targets.length,
|
|
110
|
+
updatedCount,
|
|
111
|
+
};
|
|
112
|
+
}
|