@aria_asi/cli 0.2.32 → 0.2.34

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.
Files changed (93) hide show
  1. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts +8 -1
  2. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts.map +1 -1
  3. package/dist/aria-connector/src/connectors/codebase-awareness.js +126 -71
  4. package/dist/aria-connector/src/connectors/codebase-awareness.js.map +1 -1
  5. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
  6. package/dist/aria-connector/src/connectors/codex.js +98 -0
  7. package/dist/aria-connector/src/connectors/codex.js.map +1 -1
  8. package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -1
  9. package/dist/aria-connector/src/setup-wizard.js +91 -24
  10. package/dist/aria-connector/src/setup-wizard.js.map +1 -1
  11. package/dist/assets/hooks/aria-harness-via-sdk.mjs +26 -8
  12. package/dist/assets/hooks/aria-pre-tool-gate.mjs +60 -1
  13. package/dist/assets/hooks/aria-stop-gate.mjs +69 -3
  14. package/dist/assets/hooks/doctrine_trigger_map.json +43 -0
  15. package/dist/assets/hooks/lib/domain-output-quality.mjs +103 -0
  16. package/dist/assets/hooks/lib/skill-autoload-gate.mjs +14 -0
  17. package/dist/assets/opencode-plugins/harness-context/index.js +1 -1
  18. package/dist/assets/opencode-plugins/harness-gate/index.js +114 -10
  19. package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +14 -0
  20. package/dist/assets/opencode-plugins/harness-outcome/index.js +39 -0
  21. package/dist/assets/opencode-plugins/harness-stop/index.js +234 -139
  22. package/dist/assets/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
  23. package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +14 -0
  24. package/dist/runtime/codex-bridge.mjs +71 -8
  25. package/dist/runtime/discipline/CLAUDE.md +2 -2
  26. package/dist/runtime/discipline/doctrine_trigger_map.json +43 -0
  27. package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +3 -3
  28. package/dist/runtime/doctrine_trigger_map.json +43 -0
  29. package/dist/runtime/harness-daemon.mjs +50 -2
  30. package/dist/runtime/hooks/aria-agent-handoff.mjs +247 -0
  31. package/dist/runtime/hooks/aria-agent-ledger-merge.mjs +164 -0
  32. package/dist/runtime/hooks/aria-architect-fallback.mjs +267 -0
  33. package/dist/runtime/hooks/aria-cognition-substrate-binding.mjs +761 -0
  34. package/dist/runtime/hooks/aria-discovery-record.mjs +101 -0
  35. package/dist/runtime/hooks/aria-harness-via-sdk.mjs +544 -0
  36. package/dist/runtime/hooks/aria-import-resolution-gate.mjs +330 -0
  37. package/dist/runtime/hooks/aria-outcome-record.mjs +84 -0
  38. package/dist/runtime/hooks/aria-pre-emit-dryrun.mjs +329 -0
  39. package/dist/runtime/hooks/aria-pre-text-gate.mjs +112 -0
  40. package/dist/runtime/hooks/aria-pre-tool-gate.mjs +2482 -0
  41. package/dist/runtime/hooks/aria-preprompt-consult.mjs +464 -0
  42. package/dist/runtime/hooks/aria-preturn-memory-gate.mjs +647 -0
  43. package/dist/runtime/hooks/aria-repo-doctrine-gate.mjs +429 -0
  44. package/dist/runtime/hooks/aria-stop-gate.mjs +1882 -0
  45. package/dist/runtime/hooks/aria-trigger-autolearn.mjs +229 -0
  46. package/dist/runtime/hooks/aria-userprompt-abandon-detect.mjs +192 -0
  47. package/dist/runtime/hooks/doctrine_trigger_map.json +577 -0
  48. package/dist/runtime/hooks/lib/canonical-lenses.mjs +65 -0
  49. package/dist/runtime/hooks/lib/domain-output-quality.mjs +103 -0
  50. package/dist/runtime/hooks/lib/gate-audit.mjs +43 -0
  51. package/dist/runtime/hooks/lib/gate-loop-state.mjs +50 -0
  52. package/dist/runtime/hooks/lib/hook-message-window.mjs +121 -0
  53. package/dist/runtime/hooks/lib/skill-autoload-gate.mjs +14 -0
  54. package/dist/runtime/hooks/test-aria-preturn-memory-gate.mjs +245 -0
  55. package/dist/runtime/hooks/test-tier-lens-labeling.mjs +367 -0
  56. package/dist/runtime/manifest.json +2 -2
  57. package/dist/runtime/sdk/BUNDLED.json +2 -2
  58. package/dist/runtime/sdk/index.d.ts +48 -0
  59. package/dist/runtime/sdk/index.js +140 -1
  60. package/dist/runtime/sdk/index.js.map +1 -1
  61. package/dist/runtime/sdk/runWithGovernance.d.ts +16 -0
  62. package/dist/runtime/sdk/runWithGovernance.js +54 -0
  63. package/dist/runtime/sdk/runWithGovernance.js.map +1 -0
  64. package/dist/runtime/service.mjs +339 -10
  65. package/dist/sdk/BUNDLED.json +2 -2
  66. package/dist/sdk/index.d.ts +48 -0
  67. package/dist/sdk/index.js +140 -1
  68. package/dist/sdk/index.js.map +1 -1
  69. package/dist/sdk/runWithGovernance.d.ts +16 -0
  70. package/dist/sdk/runWithGovernance.js +54 -0
  71. package/dist/sdk/runWithGovernance.js.map +1 -0
  72. package/hooks/aria-harness-via-sdk.mjs +26 -8
  73. package/hooks/aria-pre-tool-gate.mjs +60 -1
  74. package/hooks/aria-stop-gate.mjs +69 -3
  75. package/hooks/doctrine_trigger_map.json +43 -0
  76. package/hooks/lib/domain-output-quality.mjs +103 -0
  77. package/hooks/lib/skill-autoload-gate.mjs +14 -0
  78. package/opencode-plugins/harness-context/index.js +1 -1
  79. package/opencode-plugins/harness-gate/index.js +114 -10
  80. package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +14 -0
  81. package/opencode-plugins/harness-outcome/index.js +39 -0
  82. package/opencode-plugins/harness-stop/index.js +234 -139
  83. package/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
  84. package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +14 -0
  85. package/package.json +12 -5
  86. package/runtime-src/codex-bridge.mjs +71 -8
  87. package/runtime-src/harness-daemon.mjs +50 -2
  88. package/runtime-src/service.mjs +339 -10
  89. package/scripts/bundle-sdk.mjs +2 -0
  90. package/scripts/self-test-harness-gates.mjs +79 -0
  91. package/src/connectors/codebase-awareness.ts +141 -77
  92. package/src/connectors/codex.ts +98 -0
  93. package/src/setup-wizard.ts +105 -25
@@ -14,7 +14,10 @@ const ENV_PATH = path.join(ARIA_DIR, 'codebase-awareness.env');
14
14
  const STATE_PATH = path.join(ARIA_DIR, 'codebase-awareness-state.json');
15
15
  const INDEX_PATH = path.join(ARIA_DIR, 'codebase-awareness-index.json');
16
16
 
17
- type AwarenessStatus = 'idle' | 'watching' | 'scanning' | 'error';
17
+ type AwarenessStatus = 'idle' | 'watching' | 'scanning' | 'degraded' | 'error';
18
+ const HEARTBEAT_INTERVAL_MS = Math.max(5_000, Number(process.env.ARIA_CODEBASE_AWARENESS_HEARTBEAT_MS || '15000'));
19
+ const REFRESH_INTERVAL_MS = Math.max(30_000, Number(process.env.ARIA_CODEBASE_AWARENESS_REFRESH_MS || '300000'));
20
+ const REPO_DISCOVERY_INTERVAL_MS = Math.max(30_000, Number(process.env.ARIA_CODEBASE_AWARENESS_RETRY_MS || '60000'));
18
21
 
19
22
  interface RepoSnapshot {
20
23
  path: string;
@@ -36,6 +39,13 @@ interface AwarenessState {
36
39
  repoSnapshots: RepoSnapshot[];
37
40
  lastError: string | null;
38
41
  updatedAt: string;
42
+ daemon?: {
43
+ pid: number;
44
+ startedAt: string;
45
+ heartbeatAt: string;
46
+ refreshIntervalMs: number;
47
+ watcherModes: Record<string, string>;
48
+ };
39
49
  }
40
50
 
41
51
  function connectorPackageRoot(): string {
@@ -82,12 +92,17 @@ function readState(): AwarenessState {
82
92
  repoSnapshots: Array.isArray(parsed.repoSnapshots) ? parsed.repoSnapshots as RepoSnapshot[] : [],
83
93
  lastError: parsed.lastError || null,
84
94
  updatedAt: parsed.updatedAt || new Date(0).toISOString(),
95
+ daemon: parsed.daemon,
85
96
  };
86
97
  } catch {
87
98
  return defaultState();
88
99
  }
89
100
  }
90
101
 
102
+ function compactError(error: unknown): string {
103
+ return error instanceof Error ? error.message : String(error);
104
+ }
105
+
91
106
  function writeState(update: Partial<AwarenessState>): AwarenessState {
92
107
  const current = readState();
93
108
  const next: AwarenessState = {
@@ -254,86 +269,135 @@ export async function startCodebaseAwarenessDaemon(options: {
254
269
  repos?: string[];
255
270
  fallbackRepoPath?: string;
256
271
  } = {}): Promise<void> {
257
- const repos = resolveRepos(undefined, options.repos, options.fallbackRepoPath);
258
- if (!repos.length) {
259
- writeState({ status: 'error', watchedRepos: [], lastError: 'No git repositories configured for codebase awareness.' });
260
- throw new Error('No git repositories configured for codebase awareness.');
261
- }
272
+ const startedAt = new Date().toISOString();
273
+ const handles = new Map<string, { stop: () => void; readonly mode: string }>();
274
+ const watcherModes: Record<string, string> = {};
275
+ let repos = resolveRepos(undefined, options.repos, options.fallbackRepoPath);
276
+
277
+ const writeDaemonState = (status: AwarenessStatus, update: Partial<AwarenessState> = {}): void => {
278
+ writeState({
279
+ status,
280
+ watchedRepos: repos,
281
+ ...update,
282
+ daemon: {
283
+ pid: process.pid,
284
+ startedAt,
285
+ heartbeatAt: new Date().toISOString(),
286
+ refreshIntervalMs: REFRESH_INTERVAL_MS,
287
+ watcherModes,
288
+ },
289
+ });
290
+ };
262
291
 
263
- await runCodebaseAwarenessScan({ repos });
264
- writeState({ status: 'watching', watchedRepos: repos, lastError: null });
292
+ const scanAll = async (reason: string): Promise<void> => {
293
+ repos = resolveRepos(undefined, options.repos, options.fallbackRepoPath);
294
+ if (!repos.length) {
295
+ writeDaemonState('degraded', { lastError: 'No git repositories configured for codebase awareness.' });
296
+ return;
297
+ }
265
298
 
266
- for (const repo of repos) {
267
- watchCodebase(
268
- repo,
269
- (image) => {
270
- const schemaText = schemaImageToText(image);
271
- const scanHash = hashSchema(schemaText);
272
- const lastScan = new Date().toISOString();
273
- const config = loadConfig();
274
- const repositories = [
275
- ...(config.repositories || []).filter((entry) => path.resolve(entry.path) !== repo),
276
- {
277
- path: repo,
278
- name: image.projectName,
279
- scanHash,
280
- lastScan,
299
+ writeDaemonState('scanning', { lastError: null });
300
+ const snapshots: RepoSnapshot[] = [];
301
+ const failures: string[] = [];
302
+ for (const repo of repos) {
303
+ try {
304
+ snapshots.push(await applyRepoScan(repo));
305
+ } catch (error) {
306
+ failures.push(`${repo}: ${compactError(error)}`);
307
+ }
308
+ }
309
+
310
+ const current = readState();
311
+ const repoSnapshots = [
312
+ ...snapshots,
313
+ ...current.repoSnapshots.filter((entry) => !snapshots.some((snapshot) => path.resolve(snapshot.path) === path.resolve(entry.path))),
314
+ ];
315
+ writeDaemonState(failures.length ? 'degraded' : 'watching', {
316
+ repoSnapshots,
317
+ lastError: failures.length ? `${reason}: ${failures.join(' | ')}` : null,
318
+ });
319
+ writeIndex({
320
+ generatedAt: new Date().toISOString(),
321
+ reason,
322
+ watchedRepos: repos,
323
+ repoSnapshots,
324
+ schemaImages: loadConfig().schemaImages || {},
325
+ });
326
+ };
327
+
328
+ const ensureWatchers = (): void => {
329
+ repos = resolveRepos(undefined, options.repos, options.fallbackRepoPath);
330
+ for (const [repo, handle] of handles.entries()) {
331
+ if (!repos.includes(repo)) {
332
+ handle.stop();
333
+ handles.delete(repo);
334
+ delete watcherModes[repo];
335
+ }
336
+ }
337
+ for (const repo of repos) {
338
+ if (handles.has(repo)) continue;
339
+ try {
340
+ const handle = watchCodebase(
341
+ repo,
342
+ (image) => {
343
+ const schemaText = schemaImageToText(image);
344
+ const scanHash = hashSchema(schemaText);
345
+ const lastScan = new Date().toISOString();
346
+ const config = loadConfig();
347
+ const repositories = [
348
+ ...(config.repositories || []).filter((entry) => path.resolve(entry.path) !== repo),
349
+ { path: repo, name: image.projectName, scanHash, lastScan },
350
+ ];
351
+ const nextConfig = {
352
+ ...config,
353
+ repositories,
354
+ schemaImages: { ...(config.schemaImages || {}), [image.projectName]: schemaText },
355
+ };
356
+ saveConfig(nextConfig);
357
+
358
+ const current = readState();
359
+ const snapshot: RepoSnapshot = {
360
+ path: repo,
361
+ name: image.projectName,
362
+ scanHash,
363
+ lastScan,
364
+ fileCount: image.fileCount,
365
+ language: image.language,
366
+ framework: image.framework || null,
367
+ packageManager: image.packageManager || null,
368
+ database: image.database || null,
369
+ orm: image.orm || null,
370
+ architecture: architectureSummary(image),
371
+ };
372
+ const repoSnapshots = [snapshot, ...current.repoSnapshots.filter((entry) => path.resolve(entry.path) !== repo)];
373
+ writeDaemonState('watching', { repoSnapshots, lastError: null });
374
+ writeIndex({ generatedAt: new Date().toISOString(), reason: 'watch-change', watchedRepos: repos, repoSnapshots, schemaImages: nextConfig.schemaImages || {} });
281
375
  },
282
- ];
283
- const nextConfig = {
284
- ...config,
285
- repositories,
286
- schemaImages: {
287
- ...(config.schemaImages || {}),
288
- [image.projectName]: schemaText,
376
+ {
377
+ debounceMs: 800,
378
+ pollIntervalMs: 4000,
379
+ onReady: (mode) => {
380
+ watcherModes[repo] = mode;
381
+ writeDaemonState('watching', { lastError: null });
382
+ },
383
+ onError: ({ phase, error }) => {
384
+ writeDaemonState('degraded', { lastError: `${repo} ${phase}: ${error.message}` });
385
+ },
289
386
  },
290
- };
291
- saveConfig(nextConfig);
292
-
293
- const current = readState();
294
- const snapshot: RepoSnapshot = {
295
- path: repo,
296
- name: image.projectName,
297
- scanHash,
298
- lastScan,
299
- fileCount: image.fileCount,
300
- language: image.language,
301
- framework: image.framework || null,
302
- packageManager: image.packageManager || null,
303
- database: image.database || null,
304
- orm: image.orm || null,
305
- architecture: architectureSummary(image),
306
- };
307
- const repoSnapshots = [
308
- snapshot,
309
- ...current.repoSnapshots.filter((entry) => path.resolve(entry.path) !== repo),
310
- ];
311
- writeState({
312
- status: 'watching',
313
- watchedRepos: repos,
314
- repoSnapshots,
315
- lastError: null,
316
- });
317
- writeIndex({
318
- generatedAt: new Date().toISOString(),
319
- watchedRepos: repos,
320
- repoSnapshots,
321
- schemaImages: nextConfig.schemaImages || {},
322
- });
323
- },
324
- {
325
- debounceMs: 800,
326
- pollIntervalMs: 4000,
327
- onError: ({ error }) => {
328
- writeState({
329
- status: 'error',
330
- watchedRepos: repos,
331
- lastError: error.message,
332
- });
333
- },
334
- },
335
- );
336
- }
387
+ );
388
+ handles.set(repo, handle);
389
+ watcherModes[repo] = handle.mode;
390
+ } catch (error) {
391
+ writeDaemonState('degraded', { lastError: `${repo} watcher init: ${compactError(error)}` });
392
+ }
393
+ }
394
+ };
395
+
396
+ await scanAll('daemon-start');
397
+ ensureWatchers();
398
+ setInterval(() => writeDaemonState(repos.length ? 'watching' : 'degraded'), HEARTBEAT_INTERVAL_MS).unref();
399
+ setInterval(() => { ensureWatchers(); void scanAll('periodic-refresh'); }, REFRESH_INTERVAL_MS).unref();
400
+ setInterval(() => { ensureWatchers(); }, REPO_DISCOVERY_INTERVAL_MS).unref();
337
401
 
338
402
  await new Promise(() => {});
339
403
  }
@@ -90,6 +90,8 @@ function tomlString(value: string): string {
90
90
 
91
91
  function buildCodexHookRuntimeClient(): string {
92
92
  return `import { readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync } from 'node:fs';
93
+ import { spawnSync } from 'node:child_process';
94
+ import { createHash, randomUUID } from 'node:crypto';
93
95
  import { homedir } from 'node:os';
94
96
  import path from 'node:path';
95
97
  import { HTTPHarnessClient } from '@aria_asi/harness-http-client';
@@ -97,6 +99,7 @@ import { HTTPHarnessClient } from '@aria_asi/harness-http-client';
97
99
  const HOME = homedir();
98
100
  const DEFAULT_RUNTIME_URL = (process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319').replace(/\\/+$/, '');
99
101
  const TURN_STATE_DIR = path.join(HOME, '.codex', 'tmp', 'aria-hook-turn-state');
102
+ const GOVERNANCE_GATE_PATH = path.join(HOME, '.aria', 'bin', 'aria-governance-gate');
100
103
 
101
104
  function readToken() {
102
105
  const envToken = process.env.ARIA_API_KEY || process.env.ARIA_MASTER_TOKEN || process.env.OPENAI_API_KEY;
@@ -154,6 +157,28 @@ export function inferSessionId(event) {
154
157
  return \`codex:\${threadId}:\${turnId}\`;
155
158
  }
156
159
 
160
+ export function ensureTraceId(state = {}) {
161
+ return typeof state.traceId === 'string' && state.traceId ? state.traceId : \`trace_\${randomUUID()}\`;
162
+ }
163
+
164
+ function stableEvidenceString(value) {
165
+ if (typeof value === 'string') return value;
166
+ try { return JSON.stringify(value); } catch { return String(value); }
167
+ }
168
+
169
+ export function makeEvidenceRef(kind, value, metadata = {}) {
170
+ const raw = stableEvidenceString(value);
171
+ const sha256 = createHash('sha256').update(raw).digest('hex');
172
+ return {
173
+ evidenceId: \`ev_\${sha256.slice(0, 16)}\`,
174
+ kind,
175
+ at: new Date().toISOString(),
176
+ sha256,
177
+ preview: raw.slice(0, 500),
178
+ metadata,
179
+ };
180
+ }
181
+
157
182
  export function inferUserId(event) {
158
183
  return (
159
184
  extractFirst(event, ['user_id', 'userId']) ||
@@ -300,6 +325,22 @@ export function emitJson(payload, code = 0) {
300
325
  process.stdout.write(\`\${JSON.stringify(payload)}\\n\`);
301
326
  process.exit(code);
302
327
  }
328
+
329
+ export function runGovernanceGate(payload = {}) {
330
+ if (!existsSync(GOVERNANCE_GATE_PATH)) return null;
331
+ const child = spawnSync(GOVERNANCE_GATE_PATH, {
332
+ input: \`\${JSON.stringify(payload)}\\n\`,
333
+ encoding: 'utf8',
334
+ maxBuffer: 1024 * 1024,
335
+ });
336
+ const stdout = String(child.stdout || '').trim();
337
+ let result = null;
338
+ try { result = stdout ? JSON.parse(stdout) : null; } catch {}
339
+ if (child.status !== 0 || result?.ok === false || result?.decision === 'block') {
340
+ throw new Error(stdout || child.stderr || 'aria-governance-gate blocked this Codex hook.');
341
+ }
342
+ return result;
343
+ }
303
344
  `;
304
345
  }
305
346
 
@@ -309,10 +350,14 @@ import {
309
350
  getHarnessClient,
310
351
  inferSessionId,
311
352
  inferUserId,
353
+ ensureTraceId,
312
354
  extractUserText,
355
+ makeEvidenceRef,
313
356
  readEventFromStdin,
314
357
  runtimePost,
358
+ loadTurnState,
315
359
  saveTurnState,
360
+ runGovernanceGate,
316
361
  emitJson,
317
362
  } from './lib/runtime-client.mjs';
318
363
 
@@ -321,6 +366,8 @@ const client = getHarnessClient();
321
366
  const userText = extractUserText(event);
322
367
  const sessionId = inferSessionId(event);
323
368
  const userId = inferUserId(event);
369
+ const priorState = loadTurnState(sessionId);
370
+ const traceId = ensureTraceId(priorState);
324
371
 
325
372
  try {
326
373
  const packet = await client.getHarnessPacket({
@@ -328,6 +375,14 @@ try {
328
375
  platform: 'codex',
329
376
  message: userText || 'codex turn start',
330
377
  });
378
+ const packetRef = makeEvidenceRef('harness_packet', packet, { sessionId, platform: 'codex' });
379
+ runGovernanceGate({
380
+ sessionId,
381
+ sourceRuntime: 'codex',
382
+ surface: 'codex-userprompt-submit',
383
+ text: userText.slice(0, 8000),
384
+ evidence: packetRef,
385
+ });
331
386
  const result = await runtimePost('/mizan/pre', {
332
387
  sessionId,
333
388
  packet,
@@ -341,17 +396,21 @@ try {
341
396
  },
342
397
  context: {
343
398
  sessionId,
399
+ traceId,
344
400
  surface: 'codex-hooks',
345
401
  platform: 'codex',
346
402
  userText,
347
403
  userId,
404
+ evidenceRefs: [packetRef],
348
405
  },
349
406
  });
350
407
  saveTurnState(sessionId, {
408
+ traceId,
351
409
  userId,
352
410
  userText,
353
411
  preReceiptId: result?.receipt?.receiptId || null,
354
412
  packetTimestamp: packet?.timestamp || null,
413
+ packetRef,
355
414
  lastEvent: 'UserPromptSubmit',
356
415
  });
357
416
  process.exit(0);
@@ -373,7 +432,9 @@ import {
373
432
  summarizeTarget,
374
433
  readEventFromStdin,
375
434
  loadTurnState,
435
+ makeEvidenceRef,
376
436
  saveTurnState,
437
+ runGovernanceGate,
377
438
  emitJson,
378
439
  } from './lib/runtime-client.mjs';
379
440
 
@@ -399,12 +460,24 @@ try {
399
460
  });
400
461
  }
401
462
  const toolName = String(event?.tool_name || event?.toolName || '').trim() || null;
463
+ runGovernanceGate({
464
+ sessionId,
465
+ sourceRuntime: 'codex',
466
+ surface: 'codex-pre-tool-use',
467
+ text: JSON.stringify(event).slice(0, 8000),
468
+ action,
469
+ toolName,
470
+ isDeploy: action === 'deploy',
471
+ isMutation: action === 'write' || action === 'delete',
472
+ evidence: makeEvidenceRef('codex_tool_request', { action, toolName, target }, { sessionId }),
473
+ });
402
474
  const tools = Array.isArray(state?.tools) ? state.tools.slice(-24) : [];
403
475
  tools.push({
404
476
  at: new Date().toISOString(),
405
477
  action,
406
478
  toolName,
407
479
  target,
480
+ evidenceRef: makeEvidenceRef('tool_request', { action, toolName, target }, { sessionId }),
408
481
  });
409
482
  saveTurnState(sessionId, {
410
483
  tools,
@@ -426,6 +499,7 @@ import {
426
499
  inferSessionId,
427
500
  readEventFromStdin,
428
501
  loadTurnState,
502
+ makeEvidenceRef,
429
503
  saveTurnState,
430
504
  } from './lib/runtime-client.mjs';
431
505
 
@@ -435,11 +509,16 @@ const state = loadTurnState(sessionId);
435
509
 
436
510
  try {
437
511
  const toolResponse = JSON.stringify(event?.tool_response ?? event?.toolResponse ?? null).slice(0, 4000);
512
+ const evidenceRef = makeEvidenceRef('tool_response', event?.tool_response ?? event?.toolResponse ?? null, {
513
+ sessionId,
514
+ toolName: event?.tool_name || event?.toolName || null,
515
+ });
438
516
  const toolOutputs = Array.isArray(state?.toolOutputs) ? state.toolOutputs.slice(-24) : [];
439
517
  toolOutputs.push({
440
518
  at: new Date().toISOString(),
441
519
  toolName: event?.tool_name || event?.toolName || null,
442
520
  toolResponse,
521
+ evidenceRef,
443
522
  });
444
523
  saveTurnState(sessionId, {
445
524
  toolOutputs,
@@ -461,8 +540,10 @@ import {
461
540
  readEventFromStdin,
462
541
  runtimePost,
463
542
  loadTurnState,
543
+ makeEvidenceRef,
464
544
  clearTurnState,
465
545
  formatValidationFailure,
546
+ runGovernanceGate,
466
547
  emitJson,
467
548
  } from './lib/runtime-client.mjs';
468
549
 
@@ -471,11 +552,21 @@ const client = getHarnessClient();
471
552
  const sessionId = inferSessionId(event);
472
553
  const state = loadTurnState(sessionId);
473
554
  const text = extractAssistantText(event);
555
+ const outputRef = makeEvidenceRef('assistant_output', text, { sessionId, traceId: state?.traceId || null });
556
+ const toolRefs = Array.isArray(state?.toolOutputs) ? state.toolOutputs.map((entry) => entry.evidenceRef).filter(Boolean) : [];
474
557
 
475
558
  try {
476
559
  if (!text) {
477
560
  emitJson({ continue: true });
478
561
  }
562
+ runGovernanceGate({
563
+ sessionId,
564
+ sourceRuntime: 'codex',
565
+ surface: 'codex-stop',
566
+ text: text.slice(0, 8000),
567
+ isOutputCloseout: true,
568
+ evidence: outputRef,
569
+ });
479
570
  const validation = await runtimePost('/validate-output', {
480
571
  text,
481
572
  sessionId,
@@ -505,12 +596,16 @@ try {
505
596
  layer3_pass: validation?.layer3?.pass !== false,
506
597
  surface: 'codex-hooks',
507
598
  tool_count: Array.isArray(state?.tools) ? state.tools.length : 0,
599
+ trace_id: state?.traceId || null,
600
+ output_ref: outputRef,
601
+ tool_refs: toolRefs,
508
602
  },
509
603
  context: {
510
604
  sessionId,
511
605
  surface: 'codex-hooks',
512
606
  platform: 'codex',
513
607
  userText: state?.userText || null,
608
+ traceId: state?.traceId || null,
514
609
  },
515
610
  parentReceiptId: state?.preReceiptId || null,
516
611
  });
@@ -527,6 +622,9 @@ try {
527
622
  metadata: {
528
623
  post_receipt_id: post?.receipt?.receiptId || null,
529
624
  pre_receipt_id: state?.preReceiptId || null,
625
+ trace_id: state?.traceId || null,
626
+ output_ref: outputRef,
627
+ tool_refs: toolRefs,
530
628
  tool_count: Array.isArray(state?.tools) ? state.tools.length : 0,
531
629
  validation_severity: validation?.validation?.severity || 'pass',
532
630
  layer3_pass: validation?.layer3?.pass !== false,
@@ -33,6 +33,22 @@ const HARNESS_URL =
33
33
  const HARNESS_TOKEN = process.env.ARIA_HARNESS_TOKEN || '';
34
34
  const ARIA_DIR = join(homedir(), '.aria');
35
35
  const LICENSE_PATH = join(ARIA_DIR, 'license.json');
36
+ const ONBOARDING_FALLBACK_URLS = [
37
+ process.env.ARIA_ONBOARDING_URL || '',
38
+ HARNESS_URL,
39
+ process.env.ARIA_SOUL_URL || '',
40
+ process.env.ARIAS_SOUL_URL || '',
41
+ process.env.ARIA_SOUL_BASE_URL || '',
42
+ 'https://api.ariasos.com',
43
+ 'https://arias-soul-6zp3gtk2ca-uc.a.run.app',
44
+ 'https://harness.ariasos.com',
45
+ ]
46
+ .flatMap((entry) => String(entry || '').split(','))
47
+ .map((entry) => entry.trim().replace(/\/+$/, ''))
48
+ .filter(Boolean)
49
+ .filter((entry, index, list) => list.indexOf(entry) === index);
50
+
51
+ let activeOnboardingBaseUrl = ONBOARDING_FALLBACK_URLS[0] || HARNESS_URL.replace(/\/+$/, '');
36
52
 
37
53
  type ConfigWrite =
38
54
  | { kind: 'persona_update'; updates: Record<string, unknown> }
@@ -57,7 +73,7 @@ interface DepthOption {
57
73
  interface ConverseResponse {
58
74
  ok: boolean;
59
75
  sessionId: string;
60
- topic: 'identity' | 'codebase' | 'persona' | 'done';
76
+ topic: 'identity' | 'codebase' | 'persona' | 'workforce' | 'harness_setup' | 'done';
61
77
  ariaPrompt: string;
62
78
  depthOptions: DepthOption[];
63
79
  freeTextAllowed: boolean;
@@ -69,17 +85,32 @@ interface ConverseResponse {
69
85
 
70
86
  export async function runSetupWizard(): Promise<void> {
71
87
  const rl = createInterface({ input: process.stdin, output: process.stdout });
88
+ let readlineClosed = false;
89
+ rl.on('close', () => { readlineClosed = true; });
72
90
  const ask = (q: string): Promise<string> =>
73
- new Promise((resolve) => rl.question(q, (a) => resolve(a)));
91
+ new Promise((resolve) => {
92
+ if (readlineClosed) {
93
+ resolve('__ARIA_ONBOARDING_EOF__');
94
+ return;
95
+ }
96
+ rl.question(q, (a) => resolve(a));
97
+ });
74
98
 
75
99
  const sessionId = `onboard_${Date.now()}_${randomBytes(4).toString('hex')}`;
76
100
  let userMessage: string | undefined;
77
101
  let action: 'start' | 'answer' = 'start';
102
+ let turnCount = 0;
78
103
 
79
104
  console.log('\n💬 Aria: starting onboarding…\n');
80
105
 
81
106
  try {
82
107
  while (true) {
108
+ turnCount += 1;
109
+ if (turnCount > 40) {
110
+ console.error('\n❌ Onboarding stopped after too many turns. Please run `aria` again to resume.');
111
+ return;
112
+ }
113
+
83
114
  const resp = await callConverse({ sessionId, action, userMessage });
84
115
  if (!resp.ok) {
85
116
  console.error(`\n❌ Onboarding error: ${resp.error || 'unknown'}`);
@@ -100,6 +131,10 @@ export async function runSetupWizard(): Promise<void> {
100
131
  }
101
132
 
102
133
  userMessage = (await ask('> ')).trim();
134
+ if (userMessage === '__ARIA_ONBOARDING_EOF__') {
135
+ console.error('\n❌ Onboarding input ended before setup completed. Run `aria` again to resume.');
136
+ return;
137
+ }
103
138
  action = 'answer';
104
139
  if (!userMessage) userMessage = resp.depthOptions[0]?.signal ?? 'next';
105
140
  }
@@ -117,18 +152,8 @@ async function callConverse(body: {
117
152
  action: 'start' | 'answer';
118
153
  userMessage?: string;
119
154
  }): Promise<ConverseResponse> {
120
- try {
121
- const res = await fetch(`${HARNESS_URL}/api/onboarding/converse`, {
122
- method: 'POST',
123
- headers: {
124
- 'Content-Type': 'application/json',
125
- ...(HARNESS_TOKEN ? { Authorization: `Bearer ${HARNESS_TOKEN}` } : {}),
126
- },
127
- body: JSON.stringify(body),
128
- });
129
- return (await res.json()) as ConverseResponse;
130
- } catch (err) {
131
- return {
155
+ return requestOnboardingJson<ConverseResponse>('/api/onboarding/converse', body, {
156
+ fallback: {
132
157
  ok: false,
133
158
  sessionId: body.sessionId,
134
159
  topic: 'identity',
@@ -137,9 +162,69 @@ async function callConverse(body: {
137
162
  freeTextAllowed: false,
138
163
  isComplete: false,
139
164
  progressPct: 0,
140
- error: err instanceof Error ? err.message : String(err),
141
- };
165
+ },
166
+ });
167
+ }
168
+
169
+ async function requestOnboardingJson<T extends { ok?: boolean; error?: string }>(
170
+ pathname: string,
171
+ body: Record<string, unknown>,
172
+ options: { fallback: T },
173
+ ): Promise<T> {
174
+ const failures: string[] = [];
175
+ const baseUrls = [
176
+ activeOnboardingBaseUrl,
177
+ ...ONBOARDING_FALLBACK_URLS,
178
+ ].filter((entry, index, list) => entry && list.indexOf(entry) === index);
179
+
180
+ for (const baseUrl of baseUrls) {
181
+ try {
182
+ const res = await fetch(`${baseUrl}${pathname}`, {
183
+ method: 'POST',
184
+ headers: {
185
+ 'Content-Type': 'application/json',
186
+ Accept: 'application/json',
187
+ ...(HARNESS_TOKEN ? { Authorization: `Bearer ${HARNESS_TOKEN}` } : {}),
188
+ },
189
+ body: JSON.stringify(body),
190
+ });
191
+ const text = await res.text();
192
+ const contentType = res.headers.get('content-type') || '';
193
+ const looksJson = /^\s*[\[{]/.test(text);
194
+
195
+ if (!contentType.includes('application/json') && !looksJson) {
196
+ failures.push(`${baseUrl}${pathname} returned HTTP ${res.status} ${contentType || 'without content-type'}`);
197
+ continue;
198
+ }
199
+
200
+ let data: T;
201
+ try {
202
+ data = JSON.parse(text || '{}') as T;
203
+ } catch (err) {
204
+ failures.push(`${baseUrl}${pathname} returned invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
205
+ continue;
206
+ }
207
+
208
+ if (!res.ok) {
209
+ failures.push(`${baseUrl}${pathname} returned HTTP ${res.status}: ${data.error || 'no error message'}`);
210
+ continue;
211
+ }
212
+
213
+ activeOnboardingBaseUrl = baseUrl;
214
+ return data;
215
+ } catch (err) {
216
+ failures.push(`${baseUrl}${pathname} transport failed: ${err instanceof Error ? err.message : String(err)}`);
217
+ }
142
218
  }
219
+
220
+ return {
221
+ ...options.fallback,
222
+ ok: false,
223
+ error:
224
+ 'Onboarding API is unavailable or not returning JSON. ' +
225
+ failures.slice(0, 4).join(' | ') +
226
+ (failures.length > 4 ? ` | ${failures.length - 4} more endpoint(s) failed` : ''),
227
+ };
143
228
  }
144
229
 
145
230
  async function maybeOfferGitHubConnect(ask: (q: string) => Promise<string>): Promise<void> {
@@ -207,19 +292,14 @@ async function applyConfigWrites(writes: ConfigWrite[]): Promise<void> {
207
292
  const provider = claims.provider || '';
208
293
  const apiKey = claims.apiKey || '';
209
294
  try {
210
- const resp = await fetch(`${HARNESS_URL}/api/onboarding/self-issue`, {
211
- method: 'POST',
212
- headers: { 'Content-Type': 'application/json' },
213
- body: JSON.stringify({ email, provider, llm_key: apiKey }),
214
- });
215
- const data = await resp.json() as {
295
+ const data = await requestOnboardingJson<{
216
296
  ok?: boolean;
217
297
  license?: { token: string; jti: string; tier: string; expires_at: string };
218
298
  claims?: Record<string, unknown>;
219
299
  error?: string;
220
- };
221
- if (!resp.ok || !data.ok || !data.license) {
222
- console.warn(` ⚠ I couldn't issue your license: ${data.error || `HTTP ${resp.status}`}`);
300
+ }>('/api/onboarding/self-issue', { email, provider, llm_key: apiKey }, { fallback: { ok: false } });
301
+ if (!data.ok || !data.license) {
302
+ console.warn(` ⚠ I couldn't issue your license: ${data.error || 'license endpoint did not return a license'}`);
223
303
  continue;
224
304
  }
225
305
  if (!existsSync(ARIA_DIR)) mkdirSync(ARIA_DIR, { recursive: true, mode: 0o700 });