@aria_asi/cli 0.2.30 → 0.2.32
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 +115 -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 +551 -11
- 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 +115 -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 +27 -9
- 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/assets/hooks/aria-agent-handoff.mjs +23 -0
- package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +121 -28
- package/dist/assets/hooks/aria-harness-via-sdk.mjs +126 -12
- package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +35 -0
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +383 -93
- package/dist/assets/hooks/aria-preprompt-consult.mjs +28 -2
- package/dist/assets/hooks/aria-preturn-memory-gate.mjs +93 -16
- package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +33 -1
- package/dist/assets/hooks/aria-stop-gate.mjs +346 -81
- package/dist/assets/hooks/doctrine_trigger_map.json +55 -0
- 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 +40 -5
- package/dist/assets/opencode-plugins/harness-stop/index.js +133 -10
- package/dist/runtime/auth-middleware.mjs +251 -0
- package/dist/runtime/codex-bridge.mjs +644 -0
- package/dist/runtime/discipline/CLAUDE.md +28 -0
- package/dist/runtime/discipline/doctrine_trigger_map.json +534 -0
- package/dist/runtime/doctrine_trigger_map.json +534 -0
- package/dist/runtime/fleet-engine.mjs +231 -0
- package/dist/runtime/harness-daemon.mjs +460 -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 +12 -0
- package/dist/runtime/sdk/index.js +120 -14
- package/dist/runtime/sdk/index.js.map +1 -1
- package/dist/runtime/service.mjs +1140 -48
- package/dist/runtime/workflow-engine.mjs +322 -0
- package/dist/sdk/BUNDLED.json +1 -1
- package/dist/sdk/index.d.ts +12 -0
- package/dist/sdk/index.js +120 -14
- package/dist/sdk/index.js.map +1 -1
- package/hooks/aria-agent-handoff.mjs +23 -0
- package/hooks/aria-cognition-substrate-binding.mjs +121 -28
- package/hooks/aria-harness-via-sdk.mjs +126 -12
- package/hooks/aria-pre-emit-dryrun.mjs +35 -0
- package/hooks/aria-pre-tool-gate.mjs +383 -93
- package/hooks/aria-preprompt-consult.mjs +28 -2
- package/hooks/aria-preturn-memory-gate.mjs +93 -16
- package/hooks/aria-repo-doctrine-gate.mjs +33 -1
- package/hooks/aria-stop-gate.mjs +346 -81
- package/hooks/doctrine_trigger_map.json +55 -0
- 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 +40 -5
- package/opencode-plugins/harness-stop/index.js +133 -10
- package/package.json +1 -1
- 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 +460 -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 +1140 -48
- package/runtime-src/workflow-engine.mjs +322 -0
- package/scripts/bundle-sdk.mjs +5 -0
- package/src/connectors/claude-code.ts +126 -20
- package/src/connectors/codex.ts +559 -10
- package/src/connectors/doctrine-trigger-map.ts +112 -0
- package/src/connectors/must-read.ts +117 -0
- package/src/connectors/opencode.ts +28 -9
- package/src/connectors/runtime.ts +241 -21
- package/src/connectors/shell.ts +78 -3
- package/dist/cli-0.2.0.tgz +0 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { delimiter, dirname, resolve } from 'node:path';
|
|
6
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const { WebSocketServer, WebSocket } = require('ws');
|
|
11
|
+
|
|
12
|
+
const HOME = homedir();
|
|
13
|
+
const LOG_PATH = `${HOME}/.aria/runtime/logs/codex-bridge.log`;
|
|
14
|
+
const RUNTIME_BASE_URL = (process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319').replace(/\/+$/, '');
|
|
15
|
+
const BRIDGE_PORT = Number(process.env.ARIA_CODEX_BRIDGE_PORT || '4320');
|
|
16
|
+
const DOWNSTREAM_PORT = Number(process.env.ARIA_CODEX_DOWNSTREAM_PORT || '4321');
|
|
17
|
+
const BRIDGE_HOST = process.env.ARIA_CODEX_BRIDGE_HOST || '127.0.0.1';
|
|
18
|
+
const DOWNSTREAM_URL = process.env.ARIA_CODEX_DOWNSTREAM_URL || `ws://${BRIDGE_HOST}:${DOWNSTREAM_PORT}`;
|
|
19
|
+
const DOWNSTREAM_STARTUP_GRACE_MS = Number(process.env.ARIA_CODEX_DOWNSTREAM_STARTUP_GRACE_MS || '5000');
|
|
20
|
+
const BRIDGE_WARNING_METHOD = 'guardianWarning';
|
|
21
|
+
|
|
22
|
+
let downstreamProcess = null;
|
|
23
|
+
let downstreamProcessStarting = null;
|
|
24
|
+
|
|
25
|
+
const turnState = new Map();
|
|
26
|
+
|
|
27
|
+
function resolveRealCodexBin() {
|
|
28
|
+
if (process.env.ARIA_CODEX_REAL_BIN && existsSync(process.env.ARIA_CODEX_REAL_BIN)) {
|
|
29
|
+
return process.env.ARIA_CODEX_REAL_BIN;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const wrapperDir = resolve(HOME, '.aria', 'wrappers');
|
|
33
|
+
const sanitizedPath = String(process.env.PATH || '')
|
|
34
|
+
.split(delimiter)
|
|
35
|
+
.filter((entry) => entry && resolve(entry) !== wrapperDir)
|
|
36
|
+
.join(delimiter);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const result = spawnSync('bash', ['-lc', 'which -a codex'], {
|
|
40
|
+
encoding: 'utf8',
|
|
41
|
+
env: {
|
|
42
|
+
...process.env,
|
|
43
|
+
PATH: sanitizedPath,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
const candidates = String(result.stdout || '')
|
|
47
|
+
.split('\n')
|
|
48
|
+
.map((line) => line.trim())
|
|
49
|
+
.filter(Boolean)
|
|
50
|
+
.filter((candidate) => !candidate.startsWith(`${wrapperDir}/`));
|
|
51
|
+
if (candidates.length > 0 && existsSync(candidates[0])) {
|
|
52
|
+
return candidates[0];
|
|
53
|
+
}
|
|
54
|
+
} catch {}
|
|
55
|
+
|
|
56
|
+
const fallbackCandidates = [
|
|
57
|
+
resolve(HOME, '.npm-global', 'bin', 'codex'),
|
|
58
|
+
resolve(HOME, '.local', 'bin', 'codex'),
|
|
59
|
+
'/usr/local/bin/codex',
|
|
60
|
+
'/usr/bin/codex',
|
|
61
|
+
];
|
|
62
|
+
return fallbackCandidates.find((candidate) => existsSync(candidate)) || resolve(HOME, '.npm-global', 'bin', 'codex');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const CODEx_REAL_BIN = resolveRealCodexBin();
|
|
66
|
+
|
|
67
|
+
function log(message) {
|
|
68
|
+
try {
|
|
69
|
+
if (!existsSync(dirname(LOG_PATH))) mkdirSync(dirname(LOG_PATH), { recursive: true });
|
|
70
|
+
appendFileSync(LOG_PATH, `${new Date().toISOString()} ${message}\n`);
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function sleep(ms) {
|
|
75
|
+
return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function readRuntimeToken() {
|
|
79
|
+
const envToken = process.env.ARIA_HARNESS_TOKEN || process.env.ARIA_API_KEY || process.env.OPENAI_API_KEY || process.env.ARIA_MASTER_TOKEN;
|
|
80
|
+
if (envToken) return envToken;
|
|
81
|
+
const ownerTokenPath = `${HOME}/.aria/owner-token`;
|
|
82
|
+
if (existsSync(ownerTokenPath)) {
|
|
83
|
+
try {
|
|
84
|
+
return readFileSync(ownerTokenPath, 'utf8').trim();
|
|
85
|
+
} catch {}
|
|
86
|
+
}
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function runtimeHeaders() {
|
|
91
|
+
const token = readRuntimeToken();
|
|
92
|
+
return {
|
|
93
|
+
'Content-Type': 'application/json',
|
|
94
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function postRuntime(pathname, body) {
|
|
99
|
+
const response = await fetch(`${RUNTIME_BASE_URL}${pathname}`, {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: runtimeHeaders(),
|
|
102
|
+
body: JSON.stringify(body),
|
|
103
|
+
});
|
|
104
|
+
const text = await response.text();
|
|
105
|
+
let parsed = null;
|
|
106
|
+
try {
|
|
107
|
+
parsed = text ? JSON.parse(text) : null;
|
|
108
|
+
} catch {}
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
const message = parsed?.error || parsed?.message || `${response.status} ${response.statusText}`;
|
|
111
|
+
throw new Error(`${pathname} failed: ${message}`);
|
|
112
|
+
}
|
|
113
|
+
return parsed;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function ensureTurnState(threadId, turnId) {
|
|
117
|
+
const key = `${threadId}:${turnId}`;
|
|
118
|
+
let state = turnState.get(key);
|
|
119
|
+
if (!state) {
|
|
120
|
+
state = {
|
|
121
|
+
threadId,
|
|
122
|
+
turnId,
|
|
123
|
+
userText: '',
|
|
124
|
+
preReceiptId: null,
|
|
125
|
+
agentText: '',
|
|
126
|
+
bufferedAgentNotifications: [],
|
|
127
|
+
firstAgentItemId: null,
|
|
128
|
+
};
|
|
129
|
+
turnState.set(key, state);
|
|
130
|
+
}
|
|
131
|
+
return state;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function extractInputText(input) {
|
|
135
|
+
if (!Array.isArray(input)) return '';
|
|
136
|
+
const parts = [];
|
|
137
|
+
for (const item of input) {
|
|
138
|
+
if (!item || typeof item !== 'object') continue;
|
|
139
|
+
if (typeof item.text === 'string' && item.text.trim()) {
|
|
140
|
+
parts.push(item.text.trim());
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (typeof item.message === 'string' && item.message.trim()) {
|
|
144
|
+
parts.push(item.message.trim());
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (Array.isArray(item.content)) {
|
|
148
|
+
for (const contentItem of item.content) {
|
|
149
|
+
if (contentItem && typeof contentItem.text === 'string' && contentItem.text.trim()) {
|
|
150
|
+
parts.push(contentItem.text.trim());
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return parts.join('\n\n').trim();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function deriveActionFromCommand(command = '') {
|
|
159
|
+
const text = String(command || '').trim();
|
|
160
|
+
if (!text) return 'write';
|
|
161
|
+
if (/\bkubectl\s+(apply|set\s+image|rollout\s+(restart|undo)|create|replace|delete)\b|\bdocker\s+(push|build\b.*--push)|\bdeploy-service\.sh\b/i.test(text)) {
|
|
162
|
+
return 'deploy';
|
|
163
|
+
}
|
|
164
|
+
if (/\brm\b|\bgit\s+reset\s+--hard\b|\bgit\s+checkout\s+--\b|\bDROP\s+(TABLE|DATABASE|SCHEMA|INDEX)\b/i.test(text)) {
|
|
165
|
+
return 'delete';
|
|
166
|
+
}
|
|
167
|
+
if (/\b(?:npm|pnpm|yarn)\s+run\s+(?:build|typecheck|test)\b|\btsc\b|\bmake\b|\bcargo\s+(?:build|test)\b/i.test(text)) {
|
|
168
|
+
return 'build';
|
|
169
|
+
}
|
|
170
|
+
return 'write';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function summarizeRuntimeFailures(result) {
|
|
174
|
+
const failures = Array.isArray(result?.layer3?.failures) ? result.layer3.failures : [];
|
|
175
|
+
const validationViolations = Array.isArray(result?.validation?.violations) ? result.validation.violations : [];
|
|
176
|
+
const messages = [
|
|
177
|
+
...validationViolations,
|
|
178
|
+
...failures.map((failure) => failure?.detail || failure?.message || JSON.stringify(failure)),
|
|
179
|
+
].filter(Boolean);
|
|
180
|
+
return messages.slice(0, 6).join(' | ');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function validateTurnText(threadId, turnId) {
|
|
184
|
+
const state = ensureTurnState(threadId, turnId);
|
|
185
|
+
const text = String(state.agentText || '').trim();
|
|
186
|
+
if (!text) {
|
|
187
|
+
return { ok: false, reason: 'No assistant text exists for this turn yet. Codex must emit readable cognition before action.' };
|
|
188
|
+
}
|
|
189
|
+
const result = await postRuntime('/validate-output', {
|
|
190
|
+
text,
|
|
191
|
+
sessionId: `codex:${threadId}:${turnId}`,
|
|
192
|
+
runLayer3: true,
|
|
193
|
+
requireCognitionBlock: true,
|
|
194
|
+
packetRequest: {
|
|
195
|
+
sessionId: `codex:${threadId}`,
|
|
196
|
+
platform: 'codex',
|
|
197
|
+
message: state.userText || text,
|
|
198
|
+
stage: 'codex-turn',
|
|
199
|
+
actor: 'codex-bridge',
|
|
200
|
+
system: 'codex-bridge',
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
const pass = result?.pass === true && result?.validation?.passed !== false;
|
|
204
|
+
return pass
|
|
205
|
+
? { ok: true, result }
|
|
206
|
+
: {
|
|
207
|
+
ok: false,
|
|
208
|
+
reason: summarizeRuntimeFailures(result) || 'Runtime validation failed for this turn before action/output release.',
|
|
209
|
+
result,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function checkActionAgainstRuntime(action, target, threadId, turnId) {
|
|
214
|
+
const result = await postRuntime('/check-action', {
|
|
215
|
+
action,
|
|
216
|
+
target,
|
|
217
|
+
sessionId: `codex:${threadId}:${turnId}`,
|
|
218
|
+
});
|
|
219
|
+
if (result?.allowed === false) {
|
|
220
|
+
return {
|
|
221
|
+
ok: false,
|
|
222
|
+
reason: result.reason || `Runtime denied ${action} on ${target}`,
|
|
223
|
+
result,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return { ok: true, result };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function recordMizanPre(threadId, turnId) {
|
|
230
|
+
const state = ensureTurnState(threadId, turnId);
|
|
231
|
+
try {
|
|
232
|
+
const result = await postRuntime('/mizan/pre', {
|
|
233
|
+
sessionId: `codex:${threadId}:${turnId}`,
|
|
234
|
+
packetRequest: {
|
|
235
|
+
sessionId: `codex:${threadId}`,
|
|
236
|
+
platform: 'codex',
|
|
237
|
+
message: state.userText || 'codex turn start',
|
|
238
|
+
stage: 'codex-pre',
|
|
239
|
+
actor: 'codex-bridge',
|
|
240
|
+
system: 'codex-bridge',
|
|
241
|
+
},
|
|
242
|
+
context: {
|
|
243
|
+
sessionId: `codex:${threadId}:${turnId}`,
|
|
244
|
+
surface: 'codex-bridge',
|
|
245
|
+
platform: 'codex',
|
|
246
|
+
userText: state.userText,
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
state.preReceiptId = result?.receipt?.receiptId || null;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
log(`warn mizan/pre thread=${threadId} turn=${turnId} error="${error instanceof Error ? error.message : String(error)}"`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function recordMizanPost(threadId, turnId, pass, summary) {
|
|
256
|
+
const state = ensureTurnState(threadId, turnId);
|
|
257
|
+
try {
|
|
258
|
+
await postRuntime('/mizan/post', {
|
|
259
|
+
sessionId: `codex:${threadId}:${turnId}`,
|
|
260
|
+
parentReceiptId: state.preReceiptId,
|
|
261
|
+
text: state.agentText || summary,
|
|
262
|
+
evidence: {
|
|
263
|
+
validated_output: pass,
|
|
264
|
+
bridge: 'codex',
|
|
265
|
+
},
|
|
266
|
+
context: {
|
|
267
|
+
sessionId: `codex:${threadId}:${turnId}`,
|
|
268
|
+
surface: 'codex-bridge',
|
|
269
|
+
platform: 'codex',
|
|
270
|
+
userText: state.userText,
|
|
271
|
+
summary,
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
} catch (error) {
|
|
275
|
+
log(`warn mizan/post thread=${threadId} turn=${turnId} error="${error instanceof Error ? error.message : String(error)}"`);
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
await postRuntime('/decision/log', {
|
|
279
|
+
session_id: `codex:${threadId}:${turnId}`,
|
|
280
|
+
decision_type: 'codex_turn',
|
|
281
|
+
decision_category: 'runtime-control-plane',
|
|
282
|
+
summary,
|
|
283
|
+
outcome: pass ? 'validated' : 'blocked',
|
|
284
|
+
surface: 'codex-bridge',
|
|
285
|
+
details: {
|
|
286
|
+
threadId,
|
|
287
|
+
turnId,
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
} catch (error) {
|
|
291
|
+
log(`warn decision/log thread=${threadId} turn=${turnId} error="${error instanceof Error ? error.message : String(error)}"`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function makeGuardianWarning(threadId, message) {
|
|
296
|
+
return {
|
|
297
|
+
method: BRIDGE_WARNING_METHOD,
|
|
298
|
+
params: {
|
|
299
|
+
threadId,
|
|
300
|
+
message,
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function handleApprovalRequest(upstream, downstream, message) {
|
|
306
|
+
const { id, method, params = {} } = message;
|
|
307
|
+
const threadId = params.threadId || params.conversationId || 'unknown-thread';
|
|
308
|
+
const turnId = params.turnId || 'legacy-turn';
|
|
309
|
+
const validation = await validateTurnText(threadId, turnId);
|
|
310
|
+
|
|
311
|
+
if (!validation.ok) {
|
|
312
|
+
const reason = `Aria runtime blocked action before tool approval: ${validation.reason}`;
|
|
313
|
+
upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));
|
|
314
|
+
const declineResult =
|
|
315
|
+
method === 'item/commandExecution/requestApproval'
|
|
316
|
+
? { decision: 'decline' }
|
|
317
|
+
: method === 'item/fileChange/requestApproval'
|
|
318
|
+
? { decision: 'decline' }
|
|
319
|
+
: method === 'item/permissions/requestApproval'
|
|
320
|
+
? { permissions: { fileSystem: { entries: [] }, network: { enabled: false } }, scope: 'turn', strictAutoReview: true }
|
|
321
|
+
: method === 'execCommandApproval'
|
|
322
|
+
? { decision: 'denied' }
|
|
323
|
+
: { decision: 'denied' };
|
|
324
|
+
downstream.send(JSON.stringify({ id, result: declineResult }));
|
|
325
|
+
log(`deny approval method=${method} thread=${threadId} turn=${turnId} reason="${validation.reason}"`);
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (method === 'item/commandExecution/requestApproval' || method === 'execCommandApproval') {
|
|
330
|
+
const command = method === 'item/commandExecution/requestApproval'
|
|
331
|
+
? params.command || ''
|
|
332
|
+
: Array.isArray(params.command) ? params.command.join(' ') : '';
|
|
333
|
+
const action = deriveActionFromCommand(command);
|
|
334
|
+
const target = JSON.stringify({
|
|
335
|
+
command,
|
|
336
|
+
cwd: params.cwd || null,
|
|
337
|
+
commandActions: params.commandActions || params.parsedCmd || null,
|
|
338
|
+
}).slice(0, 1500);
|
|
339
|
+
const actionCheck = await checkActionAgainstRuntime(action, target, threadId, turnId);
|
|
340
|
+
if (!actionCheck.ok) {
|
|
341
|
+
const reason = `Aria runtime denied ${action}: ${actionCheck.reason}`;
|
|
342
|
+
upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));
|
|
343
|
+
downstream.send(JSON.stringify({
|
|
344
|
+
id,
|
|
345
|
+
result: method === 'item/commandExecution/requestApproval'
|
|
346
|
+
? { decision: 'decline' }
|
|
347
|
+
: { decision: 'denied' },
|
|
348
|
+
}));
|
|
349
|
+
log(`deny command method=${method} action=${action} thread=${threadId} turn=${turnId} reason="${actionCheck.reason}"`);
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
downstream.send(JSON.stringify({
|
|
353
|
+
id,
|
|
354
|
+
result: method === 'item/commandExecution/requestApproval'
|
|
355
|
+
? { decision: 'accept' }
|
|
356
|
+
: { decision: 'approved' },
|
|
357
|
+
}));
|
|
358
|
+
log(`allow command method=${method} action=${action} thread=${threadId} turn=${turnId}`);
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (method === 'item/fileChange/requestApproval' || method === 'applyPatchApproval') {
|
|
363
|
+
const target = params.grantRoot || params.reason || params.itemId || 'file-change';
|
|
364
|
+
const actionCheck = await checkActionAgainstRuntime('write', String(target), threadId, turnId);
|
|
365
|
+
if (!actionCheck.ok) {
|
|
366
|
+
const reason = `Aria runtime denied file change: ${actionCheck.reason}`;
|
|
367
|
+
upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));
|
|
368
|
+
downstream.send(JSON.stringify({
|
|
369
|
+
id,
|
|
370
|
+
result: method === 'item/fileChange/requestApproval'
|
|
371
|
+
? { decision: 'decline' }
|
|
372
|
+
: { decision: 'denied' },
|
|
373
|
+
}));
|
|
374
|
+
log(`deny file-change method=${method} thread=${threadId} turn=${turnId} reason="${actionCheck.reason}"`);
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
downstream.send(JSON.stringify({
|
|
378
|
+
id,
|
|
379
|
+
result: method === 'item/fileChange/requestApproval'
|
|
380
|
+
? { decision: 'accept' }
|
|
381
|
+
: { decision: 'approved' },
|
|
382
|
+
}));
|
|
383
|
+
log(`allow file-change method=${method} thread=${threadId} turn=${turnId}`);
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (method === 'item/permissions/requestApproval') {
|
|
388
|
+
const target = JSON.stringify({
|
|
389
|
+
cwd: params.cwd || null,
|
|
390
|
+
permissions: params.permissions || null,
|
|
391
|
+
reason: params.reason || null,
|
|
392
|
+
}).slice(0, 1500);
|
|
393
|
+
const actionCheck = await checkActionAgainstRuntime('write', target, threadId, turnId);
|
|
394
|
+
if (!actionCheck.ok) {
|
|
395
|
+
const reason = `Aria runtime denied permissions request: ${actionCheck.reason}`;
|
|
396
|
+
upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));
|
|
397
|
+
downstream.send(JSON.stringify({
|
|
398
|
+
id,
|
|
399
|
+
result: {
|
|
400
|
+
permissions: {
|
|
401
|
+
fileSystem: {
|
|
402
|
+
entries: [],
|
|
403
|
+
},
|
|
404
|
+
network: {
|
|
405
|
+
enabled: false,
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
scope: 'turn',
|
|
409
|
+
strictAutoReview: true,
|
|
410
|
+
},
|
|
411
|
+
}));
|
|
412
|
+
log(`deny permissions thread=${threadId} turn=${turnId} reason="${actionCheck.reason}"`);
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function releaseTurnOutput(upstream, notification) {
|
|
421
|
+
const threadId = notification?.params?.threadId;
|
|
422
|
+
const turnId = notification?.params?.turn?.id || notification?.params?.turnId;
|
|
423
|
+
if (!threadId || !turnId) {
|
|
424
|
+
upstream.send(JSON.stringify(notification));
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const state = ensureTurnState(threadId, turnId);
|
|
429
|
+
if (state.bufferedAgentNotifications.length === 0) {
|
|
430
|
+
upstream.send(JSON.stringify(notification));
|
|
431
|
+
turnState.delete(`${threadId}:${turnId}`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const validation = await validateTurnText(threadId, turnId);
|
|
436
|
+
if (validation.ok) {
|
|
437
|
+
for (const buffered of state.bufferedAgentNotifications) {
|
|
438
|
+
upstream.send(JSON.stringify(buffered));
|
|
439
|
+
}
|
|
440
|
+
await recordMizanPost(threadId, turnId, true, 'codex turn validated and released');
|
|
441
|
+
log(`release turn thread=${threadId} turn=${turnId} chars=${state.agentText.length}`);
|
|
442
|
+
} else {
|
|
443
|
+
const refusalText = `Aria runtime blocked final output for this Codex turn.\n\n${validation.reason}`;
|
|
444
|
+
if (state.firstAgentItemId) {
|
|
445
|
+
upstream.send(JSON.stringify({
|
|
446
|
+
method: 'item/agentMessage/delta',
|
|
447
|
+
params: {
|
|
448
|
+
threadId,
|
|
449
|
+
turnId,
|
|
450
|
+
itemId: state.firstAgentItemId,
|
|
451
|
+
delta: refusalText,
|
|
452
|
+
},
|
|
453
|
+
}));
|
|
454
|
+
}
|
|
455
|
+
upstream.send(JSON.stringify(makeGuardianWarning(threadId, refusalText)));
|
|
456
|
+
await recordMizanPost(threadId, turnId, false, validation.reason);
|
|
457
|
+
log(`block turn thread=${threadId} turn=${turnId} reason="${validation.reason}"`);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
upstream.send(JSON.stringify(notification));
|
|
461
|
+
turnState.delete(`${threadId}:${turnId}`);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async function ensureDownstreamReady() {
|
|
465
|
+
if (downstreamProcess && !downstreamProcess.killed) return;
|
|
466
|
+
if (downstreamProcessStarting) {
|
|
467
|
+
await downstreamProcessStarting;
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
downstreamProcessStarting = (async () => {
|
|
472
|
+
log(`start downstream codex app-server bin=${CODEx_REAL_BIN} port=${DOWNSTREAM_PORT}`);
|
|
473
|
+
const env = {
|
|
474
|
+
...process.env,
|
|
475
|
+
PATH: (process.env.PATH || '')
|
|
476
|
+
.split(':')
|
|
477
|
+
.filter((entry) => entry && entry !== `${HOME}/.aria/wrappers`)
|
|
478
|
+
.join(':'),
|
|
479
|
+
};
|
|
480
|
+
delete env.ARIA_CODEX_BRIDGE_PORT;
|
|
481
|
+
delete env.ARIA_CODEX_DOWNSTREAM_PORT;
|
|
482
|
+
delete env.ARIA_CODEX_DOWNSTREAM_URL;
|
|
483
|
+
delete env.ARIA_CODEX_REAL_BIN;
|
|
484
|
+
|
|
485
|
+
downstreamProcess = spawn(CODEx_REAL_BIN, ['app-server', '--listen', `ws://${BRIDGE_HOST}:${DOWNSTREAM_PORT}`], {
|
|
486
|
+
env,
|
|
487
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
488
|
+
});
|
|
489
|
+
downstreamProcess.stdout.on('data', (chunk) => log(`[downstream stdout] ${String(chunk).trimEnd()}`));
|
|
490
|
+
downstreamProcess.stderr.on('data', (chunk) => log(`[downstream stderr] ${String(chunk).trimEnd()}`));
|
|
491
|
+
downstreamProcess.on('exit', (code, signal) => {
|
|
492
|
+
log(`downstream exit code=${code} signal=${signal || ''}`);
|
|
493
|
+
downstreamProcess = null;
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
const startedAt = Date.now();
|
|
497
|
+
while (Date.now() - startedAt < DOWNSTREAM_STARTUP_GRACE_MS) {
|
|
498
|
+
try {
|
|
499
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
500
|
+
const socket = new WebSocket(DOWNSTREAM_URL);
|
|
501
|
+
socket.once('open', () => {
|
|
502
|
+
socket.terminate();
|
|
503
|
+
resolvePromise();
|
|
504
|
+
});
|
|
505
|
+
socket.once('error', rejectPromise);
|
|
506
|
+
});
|
|
507
|
+
return;
|
|
508
|
+
} catch {
|
|
509
|
+
await sleep(100);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
throw new Error(`downstream codex app-server not reachable at ${DOWNSTREAM_URL}`);
|
|
513
|
+
})();
|
|
514
|
+
|
|
515
|
+
try {
|
|
516
|
+
await downstreamProcessStarting;
|
|
517
|
+
} finally {
|
|
518
|
+
downstreamProcessStarting = null;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function maybePrimeTurnState(message) {
|
|
523
|
+
if (!message || typeof message !== 'object') return;
|
|
524
|
+
const method = message.method;
|
|
525
|
+
const params = message.params || {};
|
|
526
|
+
if (method === 'turn/start' || method === 'turn/steer') {
|
|
527
|
+
const threadId = params.threadId;
|
|
528
|
+
if (!threadId) return;
|
|
529
|
+
const userText = extractInputText(params.input);
|
|
530
|
+
const state = ensureTurnState(threadId, `${Date.now()}`);
|
|
531
|
+
state.userText = userText;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
async function startBridge() {
|
|
536
|
+
await ensureDownstreamReady();
|
|
537
|
+
|
|
538
|
+
const wss = new WebSocketServer({
|
|
539
|
+
host: BRIDGE_HOST,
|
|
540
|
+
port: BRIDGE_PORT,
|
|
541
|
+
perMessageDeflate: false,
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
wss.on('connection', async (upstream) => {
|
|
545
|
+
await ensureDownstreamReady();
|
|
546
|
+
const downstream = new WebSocket(DOWNSTREAM_URL);
|
|
547
|
+
|
|
548
|
+
upstream.once('close', () => {
|
|
549
|
+
try { downstream.close(); } catch {}
|
|
550
|
+
});
|
|
551
|
+
downstream.once('close', () => {
|
|
552
|
+
try { upstream.close(); } catch {}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
upstream.on('message', async (data) => {
|
|
556
|
+
const raw = typeof data === 'string' ? data : data.toString();
|
|
557
|
+
let message;
|
|
558
|
+
try {
|
|
559
|
+
message = JSON.parse(raw);
|
|
560
|
+
} catch {
|
|
561
|
+
downstream.send(raw);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (message?.method === 'turn/start' && message?.params?.threadId) {
|
|
566
|
+
const state = ensureTurnState(message.params.threadId, String(Date.now()));
|
|
567
|
+
state.userText = extractInputText(message.params.input);
|
|
568
|
+
if (!message.params.approvalPolicy) {
|
|
569
|
+
message.params.approvalPolicy = 'untrusted';
|
|
570
|
+
}
|
|
571
|
+
void recordMizanPre(message.params.threadId, state.turnId);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
downstream.send(JSON.stringify(message));
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
downstream.on('message', async (data) => {
|
|
578
|
+
const raw = typeof data === 'string' ? data : data.toString();
|
|
579
|
+
let message;
|
|
580
|
+
try {
|
|
581
|
+
message = JSON.parse(raw);
|
|
582
|
+
} catch {
|
|
583
|
+
upstream.send(raw);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (message?.id != null && typeof message?.method === 'string') {
|
|
588
|
+
const intercepted = await handleApprovalRequest(upstream, downstream, message).catch((error) => {
|
|
589
|
+
const threadId = message?.params?.threadId || 'unknown-thread';
|
|
590
|
+
const reason = `Aria Codex bridge approval hook failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
591
|
+
upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));
|
|
592
|
+
log(`error approval method=${message.method} reason="${reason}"`);
|
|
593
|
+
return false;
|
|
594
|
+
});
|
|
595
|
+
if (intercepted) return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (message?.method === 'item/agentMessage/delta') {
|
|
599
|
+
const { threadId, turnId, itemId, delta } = message.params || {};
|
|
600
|
+
if (threadId && turnId && typeof delta === 'string') {
|
|
601
|
+
const state = ensureTurnState(threadId, turnId);
|
|
602
|
+
state.agentText += delta;
|
|
603
|
+
state.bufferedAgentNotifications.push(message);
|
|
604
|
+
if (!state.firstAgentItemId && itemId) state.firstAgentItemId = itemId;
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (message?.method === 'turn/completed') {
|
|
610
|
+
await releaseTurnOutput(upstream, message);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (message?.method === 'turn/started') {
|
|
615
|
+
const threadId = message?.params?.threadId;
|
|
616
|
+
const turnId = message?.params?.turn?.id || message?.params?.turnId;
|
|
617
|
+
if (threadId && turnId) {
|
|
618
|
+
const state = ensureTurnState(threadId, turnId);
|
|
619
|
+
if (!state.userText) {
|
|
620
|
+
state.userText = message?.params?.turn?.input?.map?.((entry) => entry?.text).filter(Boolean).join('\n\n') || '';
|
|
621
|
+
}
|
|
622
|
+
void recordMizanPre(threadId, turnId);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
upstream.send(JSON.stringify(message));
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
downstream.on('error', (error) => {
|
|
630
|
+
log(`downstream socket error ${error.message}`);
|
|
631
|
+
try {
|
|
632
|
+
upstream.send(JSON.stringify(makeGuardianWarning('codex-bridge', `Codex downstream socket error: ${error.message}`)));
|
|
633
|
+
} catch {}
|
|
634
|
+
});
|
|
635
|
+
upstream.on('error', (error) => log(`upstream socket error ${error.message}`));
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
log(`bridge listening ws://${BRIDGE_HOST}:${BRIDGE_PORT} downstream=${DOWNSTREAM_URL}`);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
startBridge().catch((error) => {
|
|
642
|
+
log(`fatal ${error instanceof Error ? error.stack || error.message : String(error)}`);
|
|
643
|
+
process.exit(1);
|
|
644
|
+
});
|
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
> the gates this surface installs. Following this saves multi-turn iteration
|
|
5
5
|
> loops on gate-blocks and substrate-binding violations.
|
|
6
6
|
|
|
7
|
+
## Must-read order for weaker or drifting models
|
|
8
|
+
|
|
9
|
+
Read these in order before the first non-trivial action:
|
|
10
|
+
|
|
11
|
+
1. `~/.claude/ARIA_MUST_READ.md` for Claude surfaces, `~/.codex/ARIA_MUST_READ.md` for Codex, or `~/.aria/ARIA_MUST_READ.md` for OpenCode.
|
|
12
|
+
2. This file.
|
|
13
|
+
3. `~/.aria/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md`
|
|
14
|
+
4. `~/.aria/runtime/discipline/skills/aria-harness/aria-harness-substrate-binding/SKILL.md`
|
|
15
|
+
5. The live doctrine trigger map for the current surface.
|
|
16
|
+
|
|
17
|
+
If compaction, gate deadlock, or repeated drift happens, restart from that order instead of guessing from stale memory.
|
|
18
|
+
|
|
7
19
|
## What this SDK is
|
|
8
20
|
|
|
9
21
|
`@aria/harness-http-client` is the Aria harness SDK. It binds a process
|
|
@@ -152,6 +164,22 @@ Every non-trivial assistant turn must satisfy all three.
|
|
|
152
164
|
|
|
153
165
|
**REJECTED (qualitative drift):** `better`, `improved`, `should work`, `more reliable`, `cleaner`, `nicer`. These are unmeasurable.
|
|
154
166
|
|
|
167
|
+
### `<applied_cognition>` — required with every non-trivial cognition block
|
|
168
|
+
|
|
169
|
+
The cognition block is not accepted as proof by itself. It must bind to a concrete action or output claim and state what changed because cognition ran.
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
<applied_cognition>
|
|
173
|
+
decision_delta: <what changed because cognition ran; not "none">
|
|
174
|
+
dominant_domain: <engineering_quality | trust | operations | security | product | ...>
|
|
175
|
+
binds_to: <the next tool/action/output claim this cognition constrains>
|
|
176
|
+
expected_predicate: <numeric, boolean, or state-string predicate proving success>
|
|
177
|
+
artifact_change: <how the produced artifact/action/output differs because cognition ran>
|
|
178
|
+
</applied_cognition>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Rejected:** missing fields, `decision_delta: none`, or a contract that does not bind to the next action/output. This is the structural enforcement for apply-lenses-don't-perform.
|
|
182
|
+
|
|
155
183
|
### `<verify>` — required for deploy commands
|
|
156
184
|
|
|
157
185
|
A deploy is anything matching `deploy-service.sh|kubectl apply -f|kubectl set image|docker push|trivial deploy|fast path deploy`. Without a verify block, the pre-tool-gate hard-blocks.
|