@aria_asi/cli 0.2.36 → 0.2.37
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/CLIENT-ONBOARDING.md +4 -2
- package/bin/aria.js +11 -7
- package/dist/aria-connector/src/auth.d.ts +14 -0
- package/dist/aria-connector/src/auth.d.ts.map +1 -1
- package/dist/aria-connector/src/auth.js +103 -1
- package/dist/aria-connector/src/auth.js.map +1 -1
- package/dist/aria-connector/src/chat.d.ts.map +1 -1
- package/dist/aria-connector/src/chat.js +13 -8
- package/dist/aria-connector/src/chat.js.map +1 -1
- package/dist/aria-connector/src/config.d.ts +6 -1
- package/dist/aria-connector/src/config.d.ts.map +1 -1
- package/dist/aria-connector/src/config.js.map +1 -1
- package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/claude-code.js +50 -6
- 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 +310 -10
- package/dist/aria-connector/src/connectors/codex.js.map +1 -1
- package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/opencode.js +35 -11
- package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
- package/dist/aria-connector/src/connectors/repo-guard.d.ts +10 -0
- package/dist/aria-connector/src/connectors/repo-guard.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/repo-guard.js +110 -164
- package/dist/aria-connector/src/connectors/repo-guard.js.map +1 -1
- package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/runtime.js +17 -7
- 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 +12 -8
- package/dist/aria-connector/src/connectors/shell.js.map +1 -1
- package/dist/aria-connector/src/harness-client.d.ts +3 -1
- package/dist/aria-connector/src/harness-client.d.ts.map +1 -1
- package/dist/aria-connector/src/harness-client.js +7 -20
- package/dist/aria-connector/src/harness-client.js.map +1 -1
- package/dist/aria-connector/src/model-context.d.ts.map +1 -1
- package/dist/aria-connector/src/model-context.js +5 -0
- package/dist/aria-connector/src/model-context.js.map +1 -1
- package/dist/aria-connector/src/providers/types.d.ts +1 -1
- package/dist/aria-connector/src/providers/types.d.ts.map +1 -1
- package/dist/aria-connector/src/providers/xai.d.ts +3 -0
- package/dist/aria-connector/src/providers/xai.d.ts.map +1 -0
- package/dist/aria-connector/src/providers/xai.js +40 -0
- package/dist/aria-connector/src/providers/xai.js.map +1 -0
- package/dist/aria-connector/src/setup-wizard.js +1 -0
- package/dist/aria-connector/src/setup-wizard.js.map +1 -1
- package/dist/aria-connector/src/types.d.ts +2 -0
- package/dist/aria-connector/src/types.d.ts.map +1 -1
- package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +51 -9
- package/dist/assets/hooks/aria-first-class-coach.mjs +129 -0
- package/dist/assets/hooks/aria-harness-via-sdk.mjs +33 -6
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +33 -8
- package/dist/assets/hooks/aria-preprompt-consult.mjs +5 -6
- package/dist/assets/hooks/aria-preturn-memory-gate.mjs +5 -0
- package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +15 -0
- package/dist/assets/hooks/aria-stop-gate.mjs +125 -17
- package/dist/assets/hooks/doctrine_trigger_map.json +11 -0
- package/dist/assets/hooks/lib/emergency-gateoff-impl.mjs +39 -0
- package/dist/assets/hooks/lib/emergency-gateoff.mjs +6 -0
- package/dist/assets/hooks/lib/first-class-coach.mjs +755 -0
- package/dist/assets/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
- package/dist/assets/hooks/lib/skill-autoload-gate.mjs +1 -14
- package/dist/assets/opencode-plugins/harness-context/auth-token.mjs +126 -0
- package/dist/assets/opencode-plugins/harness-context/inject-context.mjs +62 -22
- package/dist/assets/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
- package/dist/assets/opencode-plugins/harness-gate/index.js +87 -27
- package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
- package/dist/assets/opencode-plugins/harness-outcome/index.js +29 -24
- package/dist/assets/opencode-plugins/harness-stop/index.js +229 -68
- package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
- package/dist/runtime/auth-token.mjs +121 -0
- package/dist/runtime/coach-kernel.mjs +371 -0
- package/dist/runtime/codex-bridge.mjs +440 -69
- package/dist/runtime/discipline/doctrine_trigger_map.json +11 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-essence/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/mizan/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/nadia/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/predictor/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/soul-domains/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-intra-phase/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-post-phase/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-pre-phase/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-deploy/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-no-stripping/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-output-discipline/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-substrate-binding/SKILL.md +18 -0
- package/dist/runtime/doctrine_trigger_map.json +11 -0
- package/dist/runtime/hooks/aria-cognition-substrate-binding.mjs +51 -9
- package/dist/runtime/hooks/aria-first-class-coach.mjs +129 -0
- package/dist/runtime/hooks/aria-harness-via-sdk.mjs +33 -6
- package/dist/runtime/hooks/aria-pre-tool-gate.mjs +33 -8
- package/dist/runtime/hooks/aria-preprompt-consult.mjs +5 -6
- package/dist/runtime/hooks/aria-preturn-memory-gate.mjs +5 -0
- package/dist/runtime/hooks/aria-repo-doctrine-gate.mjs +15 -0
- package/dist/runtime/hooks/aria-stop-gate.mjs +125 -17
- package/dist/runtime/hooks/doctrine_trigger_map.json +11 -0
- package/dist/runtime/hooks/lib/emergency-gateoff-impl.mjs +39 -0
- package/dist/runtime/hooks/lib/emergency-gateoff.mjs +6 -0
- package/dist/runtime/hooks/lib/first-class-coach.mjs +755 -0
- package/dist/runtime/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
- package/dist/runtime/hooks/lib/skill-autoload-gate.mjs +1 -14
- package/dist/runtime/local-phase.mjs +8 -0
- package/dist/runtime/manifest.json +2 -2
- package/dist/runtime/provider-proxy.mjs +136 -33
- package/dist/runtime/sdk/BUNDLED.json +2 -2
- package/dist/runtime/sdk/auth.d.ts +17 -0
- package/dist/runtime/sdk/auth.js +158 -0
- package/dist/runtime/sdk/auth.js.map +1 -0
- package/dist/runtime/sdk/index.d.ts +8 -1
- package/dist/runtime/sdk/index.js +15 -1
- package/dist/runtime/sdk/index.js.map +1 -1
- package/dist/runtime/service.mjs +1711 -74
- package/dist/runtime/task-project-ledger.mjs +290 -0
- package/dist/sdk/BUNDLED.json +2 -2
- package/dist/sdk/auth.d.ts +17 -0
- package/dist/sdk/auth.js +158 -0
- package/dist/sdk/auth.js.map +1 -0
- package/dist/sdk/index.d.ts +8 -1
- package/dist/sdk/index.js +15 -1
- package/dist/sdk/index.js.map +1 -1
- package/hooks/aria-cognition-substrate-binding.mjs +51 -9
- package/hooks/aria-first-class-coach.mjs +129 -0
- package/hooks/aria-harness-via-sdk.mjs +33 -6
- package/hooks/aria-pre-tool-gate.mjs +33 -8
- package/hooks/aria-preprompt-consult.mjs +5 -6
- package/hooks/aria-preturn-memory-gate.mjs +5 -0
- package/hooks/aria-repo-doctrine-gate.mjs +15 -0
- package/hooks/aria-stop-gate.mjs +125 -17
- package/hooks/doctrine_trigger_map.json +11 -0
- package/hooks/lib/emergency-gateoff-impl.mjs +39 -0
- package/hooks/lib/emergency-gateoff.mjs +6 -0
- package/hooks/lib/first-class-coach.mjs +755 -0
- package/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
- package/hooks/lib/skill-autoload-gate.mjs +1 -14
- package/opencode-plugins/harness-context/auth-token.mjs +126 -0
- package/opencode-plugins/harness-context/inject-context.mjs +62 -22
- package/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
- package/opencode-plugins/harness-gate/index.js +87 -27
- package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
- package/opencode-plugins/harness-outcome/index.js +29 -24
- package/opencode-plugins/harness-stop/index.js +229 -68
- package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
- package/package.json +8 -2
- package/runtime-src/auth-token.mjs +121 -0
- package/runtime-src/coach-kernel.mjs +371 -0
- package/runtime-src/codex-bridge.mjs +440 -69
- package/runtime-src/local-phase.mjs +8 -0
- package/runtime-src/provider-proxy.mjs +136 -33
- package/runtime-src/service.mjs +1711 -74
- package/scripts/bundle-sdk.mjs +8 -0
- package/scripts/check-client-compatibility.mjs +422 -0
- package/scripts/check-coach-kernel.mjs +204 -0
- package/scripts/check-managed-runtime-ledger.mjs +107 -0
- package/scripts/check-opencode-config-contract.mjs +78 -0
- package/scripts/check-quality-ledger.mjs +121 -0
- package/scripts/self-test-harness-gates.mjs +179 -11
- package/scripts/self-test-repo-guard.mjs +38 -0
- package/scripts/validate-skill-prompts.mjs +14 -1
- package/skills/aria-cognition/aria-essence/SKILL.md +18 -0
- package/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
- package/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
- package/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
- package/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
- package/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
- package/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
- package/skills/aria-cognition/mizan/SKILL.md +18 -0
- package/skills/aria-cognition/nadia/SKILL.md +18 -0
- package/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
- package/skills/aria-cognition/predictor/SKILL.md +18 -0
- package/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
- package/skills/aria-cognition/soul-domains/SKILL.md +18 -0
- package/src/auth.ts +136 -1
- package/src/chat.ts +13 -8
- package/src/config.ts +6 -1
- package/src/connectors/claude-code.ts +62 -18
- package/src/connectors/codex.ts +308 -10
- package/src/connectors/opencode.ts +35 -12
- package/src/connectors/repo-guard.ts +117 -172
- package/src/connectors/runtime.ts +19 -7
- package/src/connectors/shell.ts +12 -8
- package/src/harness-client.ts +8 -22
- package/src/model-context.ts +6 -0
- package/src/providers/types.ts +1 -1
- package/src/providers/xai.ts +55 -0
- package/src/setup-wizard.ts +1 -0
- package/src/types.ts +2 -0
|
@@ -7,6 +7,7 @@ import { delimiter, dirname, resolve } from 'node:path';
|
|
|
7
7
|
import { spawn, spawnSync } from 'node:child_process';
|
|
8
8
|
import { createRequire } from 'node:module';
|
|
9
9
|
import { evaluateSkillGate, formatSkillGateBlock } from './hooks/lib/skill-autoload-gate.mjs';
|
|
10
|
+
import { resolveAriaAuthToken } from './auth-token.mjs';
|
|
10
11
|
|
|
11
12
|
const require = createRequire(import.meta.url);
|
|
12
13
|
const { WebSocketServer, WebSocket } = require('ws');
|
|
@@ -96,15 +97,7 @@ function makeEvidenceRef(kind, value, metadata = {}) {
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
function readRuntimeToken() {
|
|
99
|
-
|
|
100
|
-
if (envToken) return envToken;
|
|
101
|
-
const ownerTokenPath = `${HOME}/.aria/owner-token`;
|
|
102
|
-
if (existsSync(ownerTokenPath)) {
|
|
103
|
-
try {
|
|
104
|
-
return readFileSync(ownerTokenPath, 'utf8').trim();
|
|
105
|
-
} catch {}
|
|
106
|
-
}
|
|
107
|
-
return '';
|
|
100
|
+
return resolveAriaAuthToken({ baseUrl: RUNTIME_BASE_URL }).token;
|
|
108
101
|
}
|
|
109
102
|
|
|
110
103
|
function runtimeHeaders() {
|
|
@@ -143,10 +136,11 @@ function ensureTurnState(threadId, turnId) {
|
|
|
143
136
|
userText: '',
|
|
144
137
|
preReceiptId: null,
|
|
145
138
|
agentText: '',
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
139
|
+
bufferedAgentNotifications: [],
|
|
140
|
+
firstAgentItemId: null,
|
|
141
|
+
preRecorded: false,
|
|
142
|
+
traceId: `trace_${randomUUID()}`,
|
|
143
|
+
};
|
|
150
144
|
turnState.set(key, state);
|
|
151
145
|
}
|
|
152
146
|
return state;
|
|
@@ -176,6 +170,180 @@ function extractInputText(input) {
|
|
|
176
170
|
return parts.join('\n\n').trim();
|
|
177
171
|
}
|
|
178
172
|
|
|
173
|
+
function inferThreadId(params = {}) {
|
|
174
|
+
return params.threadId || params.thread_id || params.turn?.threadId || params.turn?.thread_id || params.item?.threadId || params.item?.thread_id || null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function inferTurnId(params = {}) {
|
|
178
|
+
return params.turnId || params.turn_id || params.turn?.id || params.item?.turnId || params.item?.turn_id || null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function inferItemId(params = {}) {
|
|
182
|
+
return params.itemId || params.item_id || params.item?.id || null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function collectKnownText(value, out = [], depth = 0) {
|
|
186
|
+
if (depth > 8 || value == null) return out;
|
|
187
|
+
if (typeof value === 'string') {
|
|
188
|
+
if (value.trim()) out.push(value);
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
if (Array.isArray(value)) {
|
|
192
|
+
for (const item of value) collectKnownText(item, out, depth + 1);
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
195
|
+
if (typeof value !== 'object') return out;
|
|
196
|
+
|
|
197
|
+
const textKeys = ['delta', 'text', 'rawContent', 'raw_content', 'outputText', 'output_text'];
|
|
198
|
+
for (const key of textKeys) {
|
|
199
|
+
if (typeof value[key] === 'string' && value[key].trim()) out.push(value[key]);
|
|
200
|
+
}
|
|
201
|
+
const containerKeys = ['content', 'message', 'fragments', 'parts', 'summary'];
|
|
202
|
+
for (const key of containerKeys) {
|
|
203
|
+
if (value[key] != null) collectKnownText(value[key], out, depth + 1);
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function isAgentMessageItem(item = {}) {
|
|
209
|
+
const role = String(item.role || '').toLowerCase();
|
|
210
|
+
const type = String(item.type || item.kind || '').replace(/[\s-]/g, '_').toLowerCase();
|
|
211
|
+
return role === 'assistant' || type === 'agent_message' || type === 'agentmessage' || type === 'assistant_message';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function extractAssistantTextFromTurn(turn = {}) {
|
|
215
|
+
const items = Array.isArray(turn?.items) ? turn.items : [];
|
|
216
|
+
return items
|
|
217
|
+
.filter((item) => item && typeof item === 'object' && isAgentMessageItem(item))
|
|
218
|
+
.map((item) => (typeof item.text === 'string' ? item.text : collectKnownText(item).join('')))
|
|
219
|
+
.filter((text) => typeof text === 'string' && text.trim())
|
|
220
|
+
.join('\n\n');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function extractAssistantTextFromDownstream(message) {
|
|
224
|
+
const method = String(message?.method || '');
|
|
225
|
+
const params = message?.params || {};
|
|
226
|
+
if (method === 'item/agentMessage/delta') {
|
|
227
|
+
return typeof params.delta === 'string' ? params.delta : '';
|
|
228
|
+
}
|
|
229
|
+
if (/agentMessage|agent_message|assistantMessage|assistant_message/i.test(method)) {
|
|
230
|
+
return collectKnownText(params).join('');
|
|
231
|
+
}
|
|
232
|
+
if ((method === 'item/completed' || method === 'item.completed') && isAgentMessageItem(params.item || params)) {
|
|
233
|
+
return collectKnownText(params.item || params).join('');
|
|
234
|
+
}
|
|
235
|
+
return '';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function extractCompletionText(notification) {
|
|
239
|
+
const params = notification?.params || {};
|
|
240
|
+
const turnText = extractAssistantTextFromTurn(params.turn);
|
|
241
|
+
if (turnText) return turnText;
|
|
242
|
+
return collectKnownText(params.lastAgentMessage || params.last_agent_message || params.output || params.response || '').join('');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function isAssistantOutputNotification(message) {
|
|
246
|
+
const method = String(message?.method || '');
|
|
247
|
+
const params = message?.params || {};
|
|
248
|
+
if (method === 'item/agentMessage/delta') return typeof params.delta === 'string';
|
|
249
|
+
if ((method === 'item/completed' || method === 'item.completed') && isAgentMessageItem(params.item || params)) return true;
|
|
250
|
+
return /agentMessage|agent_message|assistantMessage|assistant_message/i.test(method);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function mergeAssistantText(state, text) {
|
|
254
|
+
const next = typeof text === 'string' ? text : '';
|
|
255
|
+
if (!next.trim()) return false;
|
|
256
|
+
const current = String(state.agentText || '');
|
|
257
|
+
if (!current) {
|
|
258
|
+
state.agentText = next;
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
if (current === next || current.includes(next)) return false;
|
|
262
|
+
if (next.includes(current)) {
|
|
263
|
+
state.agentText = next;
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
state.agentText = `${current}\n\n${next}`;
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function rememberAssistantNotification(state, message) {
|
|
271
|
+
const method = String(message?.method || '');
|
|
272
|
+
const text = extractAssistantTextFromDownstream(message);
|
|
273
|
+
if (method === 'item/agentMessage/delta' && typeof text === 'string') {
|
|
274
|
+
state.agentText += text;
|
|
275
|
+
} else {
|
|
276
|
+
mergeAssistantText(state, text);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const itemId = inferItemId(message?.params || {});
|
|
280
|
+
if (!state.firstAgentItemId && itemId) state.firstAgentItemId = itemId;
|
|
281
|
+
state.bufferedAgentNotifications.push(message);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function scrubAssistantText(value, replacement, originalText, depth = 0) {
|
|
285
|
+
if (depth > 10 || value == null) return value;
|
|
286
|
+
if (typeof value === 'string') {
|
|
287
|
+
if (!value.trim()) return value;
|
|
288
|
+
const original = String(originalText || '').trim();
|
|
289
|
+
if (original && (value === original || value.trim() === original)) return replacement;
|
|
290
|
+
if (original && value.includes(original)) return value.split(original).join(replacement);
|
|
291
|
+
return value;
|
|
292
|
+
}
|
|
293
|
+
if (Array.isArray(value)) return value.map((item) => scrubAssistantText(item, replacement, originalText, depth + 1));
|
|
294
|
+
if (typeof value !== 'object') return value;
|
|
295
|
+
const cloned = { ...value };
|
|
296
|
+
for (const [key, entry] of Object.entries(cloned)) {
|
|
297
|
+
if (/^(?:delta|text|rawContent|raw_content|outputText|output_text|lastAgentMessage|last_agent_message|response|output)$/i.test(key)) {
|
|
298
|
+
cloned[key] = scrubAssistantText(entry, replacement, originalText, depth + 1);
|
|
299
|
+
} else if (entry && typeof entry === 'object') {
|
|
300
|
+
cloned[key] = scrubAssistantText(entry, replacement, originalText, depth + 1);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return cloned;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function buildBlockedCompletionNotification(notification, refusalText, originalText) {
|
|
307
|
+
const blocked = scrubAssistantText(notification, refusalText, originalText);
|
|
308
|
+
const params = blocked.params || {};
|
|
309
|
+
const existingTurn = params.turn && typeof params.turn === 'object' ? params.turn : {};
|
|
310
|
+
const existingError = existingTurn.error && typeof existingTurn.error === 'object' ? existingTurn.error : {};
|
|
311
|
+
const turnId = existingTurn.id || params.turnId || params.turn_id || null;
|
|
312
|
+
blocked.params = {
|
|
313
|
+
...params,
|
|
314
|
+
ariaBlocked: true,
|
|
315
|
+
ariaBlockReason: refusalText,
|
|
316
|
+
turn: {
|
|
317
|
+
...existingTurn,
|
|
318
|
+
...(turnId ? { id: turnId } : {}),
|
|
319
|
+
status: 'failed',
|
|
320
|
+
error: {
|
|
321
|
+
...existingError,
|
|
322
|
+
message: refusalText,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
return blocked;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function safeSendJson(socket, message, label) {
|
|
330
|
+
if (socket.readyState !== WebSocket.OPEN) {
|
|
331
|
+
log(`skip send ${label || 'message'} because socket state=${socket.readyState}`);
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
socket.send(JSON.stringify(message));
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function safeSendRaw(socket, payload, label) {
|
|
339
|
+
if (socket.readyState !== WebSocket.OPEN) {
|
|
340
|
+
log(`skip send ${label || 'raw message'} because socket state=${socket.readyState}`);
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
socket.send(payload);
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
|
|
179
347
|
function deriveActionFromCommand(command = '') {
|
|
180
348
|
const text = String(command || '').trim();
|
|
181
349
|
if (!text) return 'write';
|
|
@@ -207,6 +375,18 @@ async function validateTurnText(threadId, turnId) {
|
|
|
207
375
|
if (!text) {
|
|
208
376
|
return { ok: false, reason: 'No assistant text exists for this turn yet. Codex must emit readable cognition before action.' };
|
|
209
377
|
}
|
|
378
|
+
const coach = await recordCoachPhaseForTurn(threadId, turnId, 'post_generation', {
|
|
379
|
+
text,
|
|
380
|
+
evidenceRefs: [makeEvidenceRef('assistant_output', text, { threadId, turnId, traceId: state.traceId })],
|
|
381
|
+
metadata: { requireCognitionBlock: true },
|
|
382
|
+
});
|
|
383
|
+
if (coach?.permitted === false) {
|
|
384
|
+
return {
|
|
385
|
+
ok: false,
|
|
386
|
+
reason: coach.clientMessage || 'Coach Kernel held this Codex output before release.',
|
|
387
|
+
result: { coach },
|
|
388
|
+
};
|
|
389
|
+
}
|
|
210
390
|
const skillGate = evaluateSkillGate({
|
|
211
391
|
sessionId: `codex:${threadId}:${turnId}`,
|
|
212
392
|
surface: 'codex-bridge-output',
|
|
@@ -232,6 +412,13 @@ async function validateTurnText(threadId, turnId) {
|
|
|
232
412
|
traceId: state.traceId,
|
|
233
413
|
},
|
|
234
414
|
});
|
|
415
|
+
await recordCoachPhaseForTurn(threadId, turnId, 'pre_output', {
|
|
416
|
+
text,
|
|
417
|
+
validation: result?.validation || null,
|
|
418
|
+
layer3: result?.layer3 || null,
|
|
419
|
+
evidenceRefs: [makeEvidenceRef('runtime_validation', result, { threadId, turnId, traceId: state.traceId })],
|
|
420
|
+
metadata: { validation_passed: result?.validation?.passed !== false, layer3_pass: result?.layer3?.pass !== false },
|
|
421
|
+
});
|
|
235
422
|
const pass = result?.pass === true && result?.validation?.passed !== false;
|
|
236
423
|
return pass
|
|
237
424
|
? { ok: true, result }
|
|
@@ -244,6 +431,20 @@ async function validateTurnText(threadId, turnId) {
|
|
|
244
431
|
|
|
245
432
|
async function checkActionAgainstRuntime(action, target, threadId, turnId, metadata = {}) {
|
|
246
433
|
const state = ensureTurnState(threadId, turnId);
|
|
434
|
+
const coach = await recordCoachPhaseForTurn(threadId, turnId, 'pre_tool', {
|
|
435
|
+
text: target,
|
|
436
|
+
action,
|
|
437
|
+
target,
|
|
438
|
+
evidenceRefs: [makeEvidenceRef('codex_tool_request', { action, target, metadata }, { threadId, turnId, traceId: state.traceId })],
|
|
439
|
+
metadata: { ...metadata, requireVerify: metadata.requireVerify === true },
|
|
440
|
+
});
|
|
441
|
+
if (coach?.permitted === false) {
|
|
442
|
+
return {
|
|
443
|
+
ok: false,
|
|
444
|
+
reason: coach.clientMessage || `Coach Kernel denied ${action} before tool approval`,
|
|
445
|
+
result: { coach },
|
|
446
|
+
};
|
|
447
|
+
}
|
|
247
448
|
const skillGate = evaluateSkillGate({
|
|
248
449
|
sessionId: `codex:${threadId}:${turnId}`,
|
|
249
450
|
surface: 'codex-bridge-action',
|
|
@@ -276,9 +477,46 @@ async function checkActionAgainstRuntime(action, target, threadId, turnId, metad
|
|
|
276
477
|
return { ok: true, result };
|
|
277
478
|
}
|
|
278
479
|
|
|
480
|
+
async function recordCoachPhaseForTurn(threadId, turnId, phase, patch = {}) {
|
|
481
|
+
const state = ensureTurnState(threadId, turnId);
|
|
482
|
+
try {
|
|
483
|
+
return await postRuntime('/coach/phase', {
|
|
484
|
+
phase,
|
|
485
|
+
requestId: state.traceId,
|
|
486
|
+
sessionId: `codex:${threadId}:${turnId}`,
|
|
487
|
+
surface: 'codex-bridge',
|
|
488
|
+
lane: 'codex_bridge',
|
|
489
|
+
text: typeof patch.text === 'string' ? patch.text : state.userText || state.agentText || '',
|
|
490
|
+
action: patch.action || '',
|
|
491
|
+
target: patch.target || '',
|
|
492
|
+
evidenceRefs: Array.isArray(patch.evidenceRefs) ? patch.evidenceRefs : [],
|
|
493
|
+
validation: patch.validation || null,
|
|
494
|
+
layer3: patch.layer3 || null,
|
|
495
|
+
metadata: {
|
|
496
|
+
threadId,
|
|
497
|
+
turnId,
|
|
498
|
+
traceId: state.traceId,
|
|
499
|
+
bridge: 'codex',
|
|
500
|
+
...(patch.metadata && typeof patch.metadata === 'object' ? patch.metadata : {}),
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
} catch (error) {
|
|
504
|
+
log(`warn coach/${phase} thread=${threadId} turn=${turnId} error="${error instanceof Error ? error.message : String(error)}"`);
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
279
509
|
async function recordMizanPre(threadId, turnId) {
|
|
280
510
|
const state = ensureTurnState(threadId, turnId);
|
|
281
511
|
try {
|
|
512
|
+
await recordCoachPhaseForTurn(threadId, turnId, 'pre_turn', {
|
|
513
|
+
text: state.userText || 'codex turn start',
|
|
514
|
+
evidenceRefs: [makeEvidenceRef('user_input', state.userText, { threadId, turnId, traceId: state.traceId })],
|
|
515
|
+
});
|
|
516
|
+
await recordCoachPhaseForTurn(threadId, turnId, 'pre_cognition', {
|
|
517
|
+
text: state.userText || 'codex pre-cognition',
|
|
518
|
+
evidenceRefs: [makeEvidenceRef('codex_pre_cognition_request', state.userText, { threadId, turnId, traceId: state.traceId })],
|
|
519
|
+
});
|
|
282
520
|
const result = await postRuntime('/mizan/pre', {
|
|
283
521
|
sessionId: `codex:${threadId}:${turnId}`,
|
|
284
522
|
packetRequest: {
|
|
@@ -299,11 +537,25 @@ async function recordMizanPre(threadId, turnId) {
|
|
|
299
537
|
},
|
|
300
538
|
});
|
|
301
539
|
state.preReceiptId = result?.receipt?.receiptId || null;
|
|
540
|
+
await recordCoachPhaseForTurn(threadId, turnId, 'post_cognition', {
|
|
541
|
+
text: state.userText || 'codex post-cognition',
|
|
542
|
+
evidenceRefs: [
|
|
543
|
+
makeEvidenceRef('mizan_pre_receipt', result?.receipt || null, { threadId, turnId, traceId: state.traceId }),
|
|
544
|
+
],
|
|
545
|
+
metadata: { pre_receipt_id: state.preReceiptId },
|
|
546
|
+
});
|
|
302
547
|
} catch (error) {
|
|
303
548
|
log(`warn mizan/pre thread=${threadId} turn=${turnId} error="${error instanceof Error ? error.message : String(error)}"`);
|
|
304
549
|
}
|
|
305
550
|
}
|
|
306
551
|
|
|
552
|
+
async function recordMizanPreOnce(threadId, turnId) {
|
|
553
|
+
const state = ensureTurnState(threadId, turnId);
|
|
554
|
+
if (state.preRecorded) return;
|
|
555
|
+
state.preRecorded = true;
|
|
556
|
+
await recordMizanPre(threadId, turnId);
|
|
557
|
+
}
|
|
558
|
+
|
|
307
559
|
async function recordMizanPost(threadId, turnId, pass, summary) {
|
|
308
560
|
const state = ensureTurnState(threadId, turnId);
|
|
309
561
|
try {
|
|
@@ -326,6 +578,11 @@ async function recordMizanPost(threadId, turnId, pass, summary) {
|
|
|
326
578
|
traceId: state.traceId,
|
|
327
579
|
},
|
|
328
580
|
});
|
|
581
|
+
await recordCoachPhaseForTurn(threadId, turnId, 'claim_or_release', {
|
|
582
|
+
text: state.agentText || summary,
|
|
583
|
+
evidenceRefs: [makeEvidenceRef('codex_release_decision', { pass, summary }, { threadId, turnId, traceId: state.traceId })],
|
|
584
|
+
metadata: { validated_output: pass, summary, pre_receipt_id: state.preReceiptId },
|
|
585
|
+
});
|
|
329
586
|
} catch (error) {
|
|
330
587
|
log(`warn mizan/post thread=${threadId} turn=${turnId} error="${error instanceof Error ? error.message : String(error)}"`);
|
|
331
588
|
}
|
|
@@ -359,6 +616,39 @@ function makeGuardianWarning(threadId, message) {
|
|
|
359
616
|
};
|
|
360
617
|
}
|
|
361
618
|
|
|
619
|
+
function normalizeBridgeIssue(issue) {
|
|
620
|
+
const raw = String(issue || '').replace(/\s+/g, ' ').trim();
|
|
621
|
+
if (!raw) return '';
|
|
622
|
+
if (/No <cognition>/i.test(raw)) return 'missing readable cognition block';
|
|
623
|
+
if (/missing\s+<applied_cognition>/i.test(raw)) return 'missing applied cognition contract';
|
|
624
|
+
if (/qualitative_drift/i.test(raw)) return 'qualitative drift language needs a measurable predicate';
|
|
625
|
+
if (/premature_task_closeout|feedback_no_premature_task_closeout|done\|complete\|completed\|ready\|verified\|fixed/i.test(raw)) {
|
|
626
|
+
return 'completion/readiness claim conflicts with unresolved blocker state';
|
|
627
|
+
}
|
|
628
|
+
if (/\(\?:/.test(raw)) return 'doctrine matcher triggered; re-author with the named doctrine and concrete evidence';
|
|
629
|
+
return raw.slice(0, 600);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function formatCodexRecoveryBlock(surface, reason, issues = []) {
|
|
633
|
+
const blockers = Array.from(new Set([reason, ...issues].map(normalizeBridgeIssue).filter(Boolean)));
|
|
634
|
+
return [
|
|
635
|
+
'ARIA CODEX RECOVERY CONTRACT',
|
|
636
|
+
`surface: ${surface}`,
|
|
637
|
+
'status: output held for re-authoring',
|
|
638
|
+
'',
|
|
639
|
+
'Observed blockers:',
|
|
640
|
+
...(blockers.length ? blockers.map((item) => `- ${item}`) : ['- Aria validation failed.']),
|
|
641
|
+
'',
|
|
642
|
+
'Recovery contract:',
|
|
643
|
+
'1. Do not retry the same blocked text.',
|
|
644
|
+
'2. Re-author the answer from the original user request, not from this block report.',
|
|
645
|
+
'3. Include <cognition> and <applied_cognition> when the answer is non-trivial.',
|
|
646
|
+
'4. Use bounded status language when evidence is missing; do not use completion/readiness claims without proof.',
|
|
647
|
+
'5. Name a measurable verification predicate, or explicitly state that verification has not run.',
|
|
648
|
+
'6. Re-submit the corrected answer; if the same blocker repeats twice, escalate with this full recovery contract.',
|
|
649
|
+
].join('\n');
|
|
650
|
+
}
|
|
651
|
+
|
|
362
652
|
async function handleApprovalRequest(upstream, downstream, message) {
|
|
363
653
|
const { id, method, params = {} } = message;
|
|
364
654
|
const threadId = params.threadId || params.conversationId || 'unknown-thread';
|
|
@@ -366,8 +656,8 @@ async function handleApprovalRequest(upstream, downstream, message) {
|
|
|
366
656
|
const validation = await validateTurnText(threadId, turnId);
|
|
367
657
|
|
|
368
658
|
if (!validation.ok) {
|
|
369
|
-
const reason =
|
|
370
|
-
upstream
|
|
659
|
+
const reason = formatCodexRecoveryBlock('codex-bridge-approval', validation.reason);
|
|
660
|
+
safeSendJson(upstream, makeGuardianWarning(threadId, reason), 'approval warning');
|
|
371
661
|
const declineResult =
|
|
372
662
|
method === 'item/commandExecution/requestApproval'
|
|
373
663
|
? { decision: 'decline' }
|
|
@@ -378,7 +668,7 @@ async function handleApprovalRequest(upstream, downstream, message) {
|
|
|
378
668
|
: method === 'execCommandApproval'
|
|
379
669
|
? { decision: 'denied' }
|
|
380
670
|
: { decision: 'denied' };
|
|
381
|
-
downstream
|
|
671
|
+
safeSendJson(downstream, { id, result: declineResult }, 'approval decline');
|
|
382
672
|
log(`deny approval method=${method} thread=${threadId} turn=${turnId} reason="${validation.reason}"`);
|
|
383
673
|
return true;
|
|
384
674
|
}
|
|
@@ -397,23 +687,23 @@ async function handleApprovalRequest(upstream, downstream, message) {
|
|
|
397
687
|
requireVerify: action === 'delete' || action === 'deploy',
|
|
398
688
|
});
|
|
399
689
|
if (!actionCheck.ok) {
|
|
400
|
-
const reason = `Aria
|
|
401
|
-
upstream
|
|
402
|
-
downstream
|
|
690
|
+
const reason = formatCodexRecoveryBlock('codex-bridge-command', actionCheck.reason || `Aria denied ${action}`);
|
|
691
|
+
safeSendJson(upstream, makeGuardianWarning(threadId, reason), 'command denial warning');
|
|
692
|
+
safeSendJson(downstream, {
|
|
403
693
|
id,
|
|
404
694
|
result: method === 'item/commandExecution/requestApproval'
|
|
405
695
|
? { decision: 'decline' }
|
|
406
696
|
: { decision: 'denied' },
|
|
407
|
-
})
|
|
697
|
+
}, 'command denial');
|
|
408
698
|
log(`deny command method=${method} action=${action} thread=${threadId} turn=${turnId} reason="${actionCheck.reason}"`);
|
|
409
699
|
return true;
|
|
410
700
|
}
|
|
411
|
-
downstream
|
|
701
|
+
safeSendJson(downstream, {
|
|
412
702
|
id,
|
|
413
703
|
result: method === 'item/commandExecution/requestApproval'
|
|
414
704
|
? { decision: 'accept' }
|
|
415
705
|
: { decision: 'approved' },
|
|
416
|
-
})
|
|
706
|
+
}, 'command approval');
|
|
417
707
|
log(`allow command method=${method} action=${action} thread=${threadId} turn=${turnId}`);
|
|
418
708
|
return true;
|
|
419
709
|
}
|
|
@@ -423,23 +713,23 @@ async function handleApprovalRequest(upstream, downstream, message) {
|
|
|
423
713
|
const files = [params.path, params.filePath, params.grantRoot].filter((value) => typeof value === 'string' && value.trim());
|
|
424
714
|
const actionCheck = await checkActionAgainstRuntime('write', String(target), threadId, turnId, { files });
|
|
425
715
|
if (!actionCheck.ok) {
|
|
426
|
-
const reason =
|
|
427
|
-
upstream
|
|
428
|
-
downstream
|
|
716
|
+
const reason = formatCodexRecoveryBlock('codex-bridge-file-change', actionCheck.reason || 'file change denied');
|
|
717
|
+
safeSendJson(upstream, makeGuardianWarning(threadId, reason), 'file-change denial warning');
|
|
718
|
+
safeSendJson(downstream, {
|
|
429
719
|
id,
|
|
430
720
|
result: method === 'item/fileChange/requestApproval'
|
|
431
721
|
? { decision: 'decline' }
|
|
432
722
|
: { decision: 'denied' },
|
|
433
|
-
})
|
|
723
|
+
}, 'file-change denial');
|
|
434
724
|
log(`deny file-change method=${method} thread=${threadId} turn=${turnId} reason="${actionCheck.reason}"`);
|
|
435
725
|
return true;
|
|
436
726
|
}
|
|
437
|
-
downstream
|
|
727
|
+
safeSendJson(downstream, {
|
|
438
728
|
id,
|
|
439
729
|
result: method === 'item/fileChange/requestApproval'
|
|
440
730
|
? { decision: 'accept' }
|
|
441
731
|
: { decision: 'approved' },
|
|
442
|
-
})
|
|
732
|
+
}, 'file-change approval');
|
|
443
733
|
log(`allow file-change method=${method} thread=${threadId} turn=${turnId}`);
|
|
444
734
|
return true;
|
|
445
735
|
}
|
|
@@ -455,9 +745,9 @@ async function handleApprovalRequest(upstream, downstream, message) {
|
|
|
455
745
|
: [];
|
|
456
746
|
const actionCheck = await checkActionAgainstRuntime('write', target, threadId, turnId, { files });
|
|
457
747
|
if (!actionCheck.ok) {
|
|
458
|
-
const reason =
|
|
459
|
-
upstream
|
|
460
|
-
downstream
|
|
748
|
+
const reason = formatCodexRecoveryBlock('codex-bridge-permissions', actionCheck.reason || 'permissions request denied');
|
|
749
|
+
safeSendJson(upstream, makeGuardianWarning(threadId, reason), 'permissions denial warning');
|
|
750
|
+
safeSendJson(downstream, {
|
|
461
751
|
id,
|
|
462
752
|
result: {
|
|
463
753
|
permissions: {
|
|
@@ -471,7 +761,7 @@ async function handleApprovalRequest(upstream, downstream, message) {
|
|
|
471
761
|
scope: 'turn',
|
|
472
762
|
strictAutoReview: true,
|
|
473
763
|
},
|
|
474
|
-
})
|
|
764
|
+
}, 'permissions denial');
|
|
475
765
|
log(`deny permissions thread=${threadId} turn=${turnId} reason="${actionCheck.reason}"`);
|
|
476
766
|
return true;
|
|
477
767
|
}
|
|
@@ -481,16 +771,22 @@ async function handleApprovalRequest(upstream, downstream, message) {
|
|
|
481
771
|
}
|
|
482
772
|
|
|
483
773
|
async function releaseTurnOutput(upstream, notification) {
|
|
484
|
-
const
|
|
485
|
-
const
|
|
774
|
+
const params = notification?.params || {};
|
|
775
|
+
const threadId = inferThreadId(params);
|
|
776
|
+
const turnId = inferTurnId(params);
|
|
486
777
|
if (!threadId || !turnId) {
|
|
487
|
-
upstream
|
|
778
|
+
safeSendJson(upstream, notification, 'turn completion without ids');
|
|
488
779
|
return;
|
|
489
780
|
}
|
|
490
781
|
|
|
491
782
|
const state = ensureTurnState(threadId, turnId);
|
|
492
|
-
|
|
493
|
-
|
|
783
|
+
const completionText = extractCompletionText(notification);
|
|
784
|
+
mergeAssistantText(state, completionText);
|
|
785
|
+
|
|
786
|
+
const turnStatus = String(params.turn?.status || params.status || '').toLowerCase();
|
|
787
|
+
const hasAssistantText = String(state.agentText || '').trim().length > 0;
|
|
788
|
+
if (!hasAssistantText && turnStatus && turnStatus !== 'completed') {
|
|
789
|
+
safeSendJson(upstream, notification, 'non-completed turn without assistant output');
|
|
494
790
|
turnState.delete(`${threadId}:${turnId}`);
|
|
495
791
|
return;
|
|
496
792
|
}
|
|
@@ -498,29 +794,32 @@ async function releaseTurnOutput(upstream, notification) {
|
|
|
498
794
|
const validation = await validateTurnText(threadId, turnId);
|
|
499
795
|
if (validation.ok) {
|
|
500
796
|
for (const buffered of state.bufferedAgentNotifications) {
|
|
501
|
-
upstream
|
|
797
|
+
safeSendJson(upstream, buffered, 'validated assistant notification');
|
|
502
798
|
}
|
|
503
799
|
await recordMizanPost(threadId, turnId, true, 'codex turn validated and released');
|
|
800
|
+
safeSendJson(upstream, notification, 'validated turn completion');
|
|
504
801
|
log(`release turn thread=${threadId} turn=${turnId} chars=${state.agentText.length}`);
|
|
505
802
|
} else {
|
|
506
|
-
const refusalText =
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
upstream.send(JSON.stringify(makeGuardianWarning(threadId, refusalText)));
|
|
803
|
+
const refusalText = formatCodexRecoveryBlock('codex-bridge-output', validation.reason);
|
|
804
|
+
safeSendJson(upstream, {
|
|
805
|
+
method: 'item/agentMessage/delta',
|
|
806
|
+
params: {
|
|
807
|
+
threadId,
|
|
808
|
+
turnId,
|
|
809
|
+
itemId: state.firstAgentItemId || 'aria-blocked-output',
|
|
810
|
+
delta: refusalText,
|
|
811
|
+
},
|
|
812
|
+
}, 'blocked assistant replacement');
|
|
813
|
+
safeSendJson(upstream, makeGuardianWarning(threadId, refusalText), 'blocked turn warning');
|
|
519
814
|
await recordMizanPost(threadId, turnId, false, validation.reason);
|
|
815
|
+
safeSendJson(
|
|
816
|
+
upstream,
|
|
817
|
+
buildBlockedCompletionNotification(notification, refusalText, state.agentText || completionText),
|
|
818
|
+
'blocked turn completion'
|
|
819
|
+
);
|
|
520
820
|
log(`block turn thread=${threadId} turn=${turnId} reason="${validation.reason}"`);
|
|
521
821
|
}
|
|
522
822
|
|
|
523
|
-
upstream.send(JSON.stringify(notification));
|
|
524
823
|
turnState.delete(`${threadId}:${turnId}`);
|
|
525
824
|
}
|
|
526
825
|
|
|
@@ -607,6 +906,65 @@ async function startBridge() {
|
|
|
607
906
|
wss.on('connection', async (upstream) => {
|
|
608
907
|
await ensureDownstreamReady();
|
|
609
908
|
const downstream = new WebSocket(DOWNSTREAM_URL);
|
|
909
|
+
const pendingDownstreamMessages = [];
|
|
910
|
+
const pendingTurnStarts = new Map();
|
|
911
|
+
|
|
912
|
+
function rememberTurnStart(message) {
|
|
913
|
+
const params = message?.params || {};
|
|
914
|
+
const threadId = params.threadId;
|
|
915
|
+
if (!threadId || message?.id == null) return;
|
|
916
|
+
pendingTurnStarts.set(String(message.id), {
|
|
917
|
+
threadId,
|
|
918
|
+
userText: extractInputText(params.input),
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function takePendingTurnStart(threadId) {
|
|
923
|
+
for (const [requestId, pending] of pendingTurnStarts.entries()) {
|
|
924
|
+
if (pending.threadId === threadId) {
|
|
925
|
+
pendingTurnStarts.delete(requestId);
|
|
926
|
+
return pending;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function hydrateTurnState(threadId, turnId, userText) {
|
|
933
|
+
const state = ensureTurnState(threadId, turnId);
|
|
934
|
+
if (!state.userText && userText) state.userText = userText;
|
|
935
|
+
void recordMizanPreOnce(threadId, turnId);
|
|
936
|
+
return state;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function hydrateTurnStartResponse(message) {
|
|
940
|
+
if (message?.id == null) return;
|
|
941
|
+
const pending = pendingTurnStarts.get(String(message.id));
|
|
942
|
+
if (!pending) return;
|
|
943
|
+
const result = message.result || {};
|
|
944
|
+
const turn = result.turn || result;
|
|
945
|
+
const turnId = turn?.id || result.turnId || result.turn_id;
|
|
946
|
+
if (!turnId) return;
|
|
947
|
+
pendingTurnStarts.delete(String(message.id));
|
|
948
|
+
hydrateTurnState(pending.threadId, turnId, pending.userText);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function sendDownstream(payload) {
|
|
952
|
+
if (downstream.readyState === WebSocket.OPEN) {
|
|
953
|
+
downstream.send(payload);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
if (downstream.readyState === WebSocket.CONNECTING) {
|
|
957
|
+
pendingDownstreamMessages.push(payload);
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
log(`drop upstream message because downstream state=${downstream.readyState}`);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
downstream.once('open', () => {
|
|
964
|
+
while (pendingDownstreamMessages.length > 0 && downstream.readyState === WebSocket.OPEN) {
|
|
965
|
+
downstream.send(pendingDownstreamMessages.shift());
|
|
966
|
+
}
|
|
967
|
+
});
|
|
610
968
|
|
|
611
969
|
upstream.once('close', () => {
|
|
612
970
|
try { downstream.close(); } catch {}
|
|
@@ -621,20 +979,18 @@ async function startBridge() {
|
|
|
621
979
|
try {
|
|
622
980
|
message = JSON.parse(raw);
|
|
623
981
|
} catch {
|
|
624
|
-
|
|
982
|
+
sendDownstream(raw);
|
|
625
983
|
return;
|
|
626
984
|
}
|
|
627
985
|
|
|
628
986
|
if (message?.method === 'turn/start' && message?.params?.threadId) {
|
|
629
|
-
|
|
630
|
-
state.userText = extractInputText(message.params.input);
|
|
987
|
+
rememberTurnStart(message);
|
|
631
988
|
if (!message.params.approvalPolicy) {
|
|
632
989
|
message.params.approvalPolicy = 'untrusted';
|
|
633
990
|
}
|
|
634
|
-
void recordMizanPre(message.params.threadId, state.turnId);
|
|
635
991
|
}
|
|
636
992
|
|
|
637
|
-
|
|
993
|
+
sendDownstream(JSON.stringify(message));
|
|
638
994
|
});
|
|
639
995
|
|
|
640
996
|
downstream.on('message', async (data) => {
|
|
@@ -643,33 +999,47 @@ async function startBridge() {
|
|
|
643
999
|
try {
|
|
644
1000
|
message = JSON.parse(raw);
|
|
645
1001
|
} catch {
|
|
646
|
-
upstream
|
|
1002
|
+
safeSendRaw(upstream, raw, 'downstream raw message');
|
|
647
1003
|
return;
|
|
648
1004
|
}
|
|
649
1005
|
|
|
1006
|
+
if (message?.id != null && typeof message?.method !== 'string') {
|
|
1007
|
+
hydrateTurnStartResponse(message);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
650
1010
|
if (message?.id != null && typeof message?.method === 'string') {
|
|
651
1011
|
const intercepted = await handleApprovalRequest(upstream, downstream, message).catch((error) => {
|
|
652
1012
|
const threadId = message?.params?.threadId || 'unknown-thread';
|
|
653
1013
|
const reason = `Aria Codex bridge approval hook failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
654
|
-
upstream
|
|
1014
|
+
safeSendJson(upstream, makeGuardianWarning(threadId, reason), 'approval hook failure warning');
|
|
655
1015
|
log(`error approval method=${message.method} reason="${reason}"`);
|
|
656
1016
|
return false;
|
|
657
1017
|
});
|
|
658
1018
|
if (intercepted) return;
|
|
659
1019
|
}
|
|
660
1020
|
|
|
661
|
-
if (message
|
|
662
|
-
const
|
|
663
|
-
|
|
1021
|
+
if (isAssistantOutputNotification(message)) {
|
|
1022
|
+
const params = message.params || {};
|
|
1023
|
+
const threadId = inferThreadId(params);
|
|
1024
|
+
const turnId = inferTurnId(params);
|
|
1025
|
+
if (threadId && turnId) {
|
|
664
1026
|
const state = ensureTurnState(threadId, turnId);
|
|
665
|
-
state.
|
|
666
|
-
state.
|
|
667
|
-
|
|
1027
|
+
const pending = !state.userText ? takePendingTurnStart(threadId) : null;
|
|
1028
|
+
if (pending?.userText) state.userText = pending.userText;
|
|
1029
|
+
rememberAssistantNotification(state, message);
|
|
668
1030
|
return;
|
|
669
1031
|
}
|
|
670
1032
|
}
|
|
671
1033
|
|
|
672
1034
|
if (message?.method === 'turn/completed') {
|
|
1035
|
+
const params = message.params || {};
|
|
1036
|
+
const threadId = inferThreadId(params);
|
|
1037
|
+
const turnId = inferTurnId(params);
|
|
1038
|
+
if (threadId && turnId) {
|
|
1039
|
+
const state = ensureTurnState(threadId, turnId);
|
|
1040
|
+
const pending = !state.userText ? takePendingTurnStart(threadId) : null;
|
|
1041
|
+
if (pending?.userText) state.userText = pending.userText;
|
|
1042
|
+
}
|
|
673
1043
|
await releaseTurnOutput(upstream, message);
|
|
674
1044
|
return;
|
|
675
1045
|
}
|
|
@@ -678,21 +1048,22 @@ async function startBridge() {
|
|
|
678
1048
|
const threadId = message?.params?.threadId;
|
|
679
1049
|
const turnId = message?.params?.turn?.id || message?.params?.turnId;
|
|
680
1050
|
if (threadId && turnId) {
|
|
1051
|
+
const pending = takePendingTurnStart(threadId);
|
|
681
1052
|
const state = ensureTurnState(threadId, turnId);
|
|
682
1053
|
if (!state.userText) {
|
|
683
|
-
state.userText = message?.params?.turn?.input?.map?.((entry) => entry?.text).filter(Boolean).join('\n\n') || '';
|
|
1054
|
+
state.userText = message?.params?.turn?.input?.map?.((entry) => entry?.text).filter(Boolean).join('\n\n') || pending?.userText || '';
|
|
684
1055
|
}
|
|
685
|
-
void
|
|
1056
|
+
void recordMizanPreOnce(threadId, turnId);
|
|
686
1057
|
}
|
|
687
1058
|
}
|
|
688
1059
|
|
|
689
|
-
upstream
|
|
1060
|
+
safeSendJson(upstream, message, 'downstream message');
|
|
690
1061
|
});
|
|
691
1062
|
|
|
692
1063
|
downstream.on('error', (error) => {
|
|
693
1064
|
log(`downstream socket error ${error.message}`);
|
|
694
1065
|
try {
|
|
695
|
-
upstream
|
|
1066
|
+
safeSendJson(upstream, makeGuardianWarning('codex-bridge', `Codex downstream socket error: ${error.message}`), 'downstream socket warning');
|
|
696
1067
|
} catch {}
|
|
697
1068
|
});
|
|
698
1069
|
upstream.on('error', (error) => log(`upstream socket error ${error.message}`));
|