@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
package/dist/runtime/service.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { createServer } from 'node:http';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import { createHash, createCipheriv, createDecipheriv, randomBytes, randomUUID, scryptSync } from 'node:crypto';
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { dirname, join } from 'node:path';
|
|
9
9
|
|
|
@@ -14,6 +14,11 @@ import {
|
|
|
14
14
|
extractAnthropicUserMessage,
|
|
15
15
|
extractOpenAIUserMessage,
|
|
16
16
|
} from './provider-proxy.mjs';
|
|
17
|
+
import { recordTokenUsage, brandModel, getUsageSummary, getBillingSummary, getSubscriptionTier } from './metering.mjs';
|
|
18
|
+
import { deployFleet, loadFleet, saveFleet, getFleetStatus, buildAgentSystemPrompt } from './fleet-engine.mjs';
|
|
19
|
+
import { listPlugins, installPlugin, configurePlugin, dispatchHook } from './plugin-engine.mjs';
|
|
20
|
+
import { listWorkflowTemplates, configureWorkflow, getWorkflowStatus, startWorkflow, approveWorkflowStep } from './workflow-engine.mjs';
|
|
21
|
+
import { hqAuthMiddleware, generateApiKey, loginUser, registerUser, revokeSession, listAllTenants } from './auth-middleware.mjs';
|
|
17
22
|
import {
|
|
18
23
|
ARISTOTLE_28_MODULES,
|
|
19
24
|
NOOR_COGNITIVE_SUITE,
|
|
@@ -37,8 +42,13 @@ const { runFullChain } = require('./vendor/aria-gate-runtime/index.js');
|
|
|
37
42
|
const DEFAULT_HOST = process.env.ARIA_RUNTIME_HOST || '127.0.0.1';
|
|
38
43
|
const DEFAULT_PORT = Number(process.env.ARIA_RUNTIME_PORT || 4319);
|
|
39
44
|
const DEFAULT_HARNESS_URL =
|
|
45
|
+
process.env.ARIA_HARNESS_DAEMON_URL ||
|
|
46
|
+
process.env.ARIA_HIVE_RUNTIME_URL ||
|
|
40
47
|
process.env.ARIA_HARNESS_BASE_URL ||
|
|
41
48
|
process.env.ARIA_HARNESS_URL ||
|
|
49
|
+
process.env.ARIA_SOUL_URL ||
|
|
50
|
+
process.env.ARIAS_SOUL_URL ||
|
|
51
|
+
process.env.ARIA_SOUL_BASE_URL ||
|
|
42
52
|
'https://harness.ariasos.com';
|
|
43
53
|
const DEFAULT_RUNTIME_URL = process.env.ARIA_RUNTIME_URL || `http://${DEFAULT_HOST}:${DEFAULT_PORT}`;
|
|
44
54
|
const DEFAULT_QDRANT_URL = process.env.ARIA_QDRANT_URL || 'http://127.0.0.1:6333';
|
|
@@ -48,16 +58,23 @@ const DEFAULT_FORGE_SERVICE_URL =
|
|
|
48
58
|
process.env.FORGE_SERVICE_URL ||
|
|
49
59
|
`${DEFAULT_HARNESS_URL.replace(/\/$/, '')}/api/forge/psi`;
|
|
50
60
|
const DEFAULT_HEARTBEAT_GRACE_SECONDS = Number(process.env.ARIA_RUNTIME_HEARTBEAT_GRACE_SECONDS || 900);
|
|
61
|
+
const DEFAULT_OFFLINE_BUNDLE_SOFT_TTL_SECONDS = Number(process.env.ARIA_RUNTIME_OFFLINE_BUNDLE_SOFT_TTL_SECONDS || 86400);
|
|
62
|
+
const DEFAULT_OFFLINE_BUNDLE_HARD_TTL_SECONDS = Number(process.env.ARIA_RUNTIME_OFFLINE_BUNDLE_HARD_TTL_SECONDS || 259200);
|
|
51
63
|
const DEFAULT_JOB_CLAIM_SECONDS = Number(process.env.ARIA_RUNTIME_JOB_CLAIM_SECONDS || 120);
|
|
52
64
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
53
65
|
const STATE_DIR = join(__dirname, 'state');
|
|
54
66
|
const LEASE_PATH = join(STATE_DIR, 'lease.enc');
|
|
67
|
+
const OFFLINE_BUNDLE_PATH = join(STATE_DIR, 'offline-policy-bundle.enc');
|
|
55
68
|
const RUNTIME_META_PATH = join(STATE_DIR, 'runtime-meta.json');
|
|
56
69
|
const AUTONOMY_STATE_PATH = join(STATE_DIR, 'autonomy.json');
|
|
57
70
|
const COGNITION_STATE_PATH = join(STATE_DIR, 'cognition-state.enc');
|
|
58
71
|
const REVOCATION_LOCK_PATH = join(STATE_DIR, 'revoked.json');
|
|
59
72
|
const CONFIG_PATH = join(process.env.HOME || '', '.aria', 'config.json');
|
|
60
73
|
const CODEBASE_AWARENESS_STATE_PATH = join(process.env.HOME || '', '.aria', 'codebase-awareness-state.json');
|
|
74
|
+
const LEGACY_PACKET_CACHE_CANDIDATES = [
|
|
75
|
+
join(process.env.HOME || '', '.aria', '.aria-harness-last-packet.json'),
|
|
76
|
+
join(process.env.HOME || '', '.claude', '.aria-harness-last-packet.json'),
|
|
77
|
+
];
|
|
61
78
|
const leaseCache = new Map();
|
|
62
79
|
const TELEMETRY_LIMIT = 250;
|
|
63
80
|
const DECISION_LIMIT = 250;
|
|
@@ -88,10 +105,14 @@ const READABLE_LENS_SLOTS = [
|
|
|
88
105
|
const DOCTRINE_TRIGGER_MAP_CANDIDATES = [
|
|
89
106
|
join(__dirname, 'discipline', 'doctrine_trigger_map.json'),
|
|
90
107
|
join(__dirname, 'doctrine_trigger_map.json'),
|
|
108
|
+
join(process.env.HOME || '', '.aria', 'runtime', 'discipline', 'doctrine_trigger_map.json'),
|
|
109
|
+
join(process.env.HOME || '', '.aria', 'runtime', 'doctrine_trigger_map.json'),
|
|
91
110
|
join(process.env.HOME || '', '.claude', 'hooks', 'doctrine_trigger_map.json'),
|
|
92
111
|
join(process.env.HOME || '', '.claude', 'projects', '-home-hamzaibrahim1', 'memory', 'doctrine_trigger_map.json'),
|
|
93
112
|
join(process.env.HOME || '', '.codex', 'doctrine_trigger_map.json'),
|
|
113
|
+
join(process.env.HOME || '', '.opencode', 'doctrine_trigger_map.json'),
|
|
94
114
|
];
|
|
115
|
+
const DOCTRINE_TRIGGER_SYNC_INTERVAL_MS = Number(process.env.ARIA_DOCTRINE_TRIGGER_SYNC_INTERVAL_MS || 5000);
|
|
95
116
|
const TOOL_DEPLOY_PATTERNS = [
|
|
96
117
|
/\b(?:\.\/)?scripts\/deploy-/i,
|
|
97
118
|
/\bkubectl\s+apply\b/i,
|
|
@@ -115,12 +136,75 @@ const TOOL_DESTRUCTIVE_PATTERNS = [
|
|
|
115
136
|
/\b(?:DROP|TRUNCATE)\s+(?:TABLE|DATABASE|SCHEMA|INDEX)\b/i,
|
|
116
137
|
/\bkubectl\s+delete\b/i,
|
|
117
138
|
];
|
|
139
|
+
let doctrineTriggerMapCache = { sourcePath: null, map: { triggers: [] } };
|
|
140
|
+
let doctrineTriggerMapSyncedAt = 0;
|
|
118
141
|
|
|
119
142
|
function json(res, status, payload) {
|
|
120
143
|
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
|
|
121
144
|
res.end(JSON.stringify(payload, null, 2));
|
|
122
145
|
}
|
|
123
146
|
|
|
147
|
+
function buildWebSocketAccept(key) {
|
|
148
|
+
return createHash('sha1')
|
|
149
|
+
.update(`${String(key || '').trim()}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`)
|
|
150
|
+
.digest('base64');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function sendWebSocketCloseFrame(socket, code = 1008, reason = '') {
|
|
154
|
+
const reasonBuffer = Buffer.from(String(reason || ''), 'utf8');
|
|
155
|
+
const payload = Buffer.allocUnsafe(2 + reasonBuffer.length);
|
|
156
|
+
payload.writeUInt16BE(code, 0);
|
|
157
|
+
reasonBuffer.copy(payload, 2);
|
|
158
|
+
|
|
159
|
+
const header =
|
|
160
|
+
payload.length < 126
|
|
161
|
+
? Buffer.from([0x88, payload.length])
|
|
162
|
+
: Buffer.from([0x88, 126, (payload.length >> 8) & 0xff, payload.length & 0xff]);
|
|
163
|
+
|
|
164
|
+
socket.write(Buffer.concat([header, payload]));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function handleWebSocketUpgrade(req, socket) {
|
|
168
|
+
const url = new URL(req.url || '/', DEFAULT_RUNTIME_URL);
|
|
169
|
+
if (url.pathname !== '/v1/responses' && url.pathname !== '/responses') {
|
|
170
|
+
socket.write('HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n');
|
|
171
|
+
socket.destroy();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const upgrade = String(req.headers.upgrade || '').toLowerCase();
|
|
176
|
+
const connection = String(req.headers.connection || '').toLowerCase();
|
|
177
|
+
const websocketKey = req.headers['sec-websocket-key'];
|
|
178
|
+
if (upgrade !== 'websocket' || !connection.includes('upgrade') || !websocketKey) {
|
|
179
|
+
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
180
|
+
socket.destroy();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const accept = buildWebSocketAccept(websocketKey);
|
|
185
|
+
socket.write(
|
|
186
|
+
[
|
|
187
|
+
'HTTP/1.1 101 Switching Protocols',
|
|
188
|
+
'Upgrade: websocket',
|
|
189
|
+
'Connection: Upgrade',
|
|
190
|
+
`Sec-WebSocket-Accept: ${accept}`,
|
|
191
|
+
'\r\n',
|
|
192
|
+
].join('\r\n'),
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Codex can fall back to the HTTPS Responses path after a real websocket
|
|
196
|
+
// handshake closes. Until Aria owns the full Responses websocket protocol,
|
|
197
|
+
// keep this seam explicit instead of returning a misleading 404.
|
|
198
|
+
setTimeout(() => {
|
|
199
|
+
try {
|
|
200
|
+
sendWebSocketCloseFrame(socket, 1008, 'aria-runtime-http-fallback');
|
|
201
|
+
} catch {}
|
|
202
|
+
try {
|
|
203
|
+
socket.end();
|
|
204
|
+
} catch {}
|
|
205
|
+
}, 25).unref?.();
|
|
206
|
+
}
|
|
207
|
+
|
|
124
208
|
function isNonTrivialAssistantTurn(text, toolIntents = []) {
|
|
125
209
|
const body = String(text || '').trim();
|
|
126
210
|
if (toolIntents.length > 0) return true;
|
|
@@ -198,6 +282,17 @@ function inferToolAction(toolName, args) {
|
|
|
198
282
|
return 'tool';
|
|
199
283
|
}
|
|
200
284
|
|
|
285
|
+
function normalizeHarnessPacketPayload(payload) {
|
|
286
|
+
let current = payload;
|
|
287
|
+
for (let depth = 0; depth < 3; depth++) {
|
|
288
|
+
if (!current || typeof current !== 'object' || Array.isArray(current)) break;
|
|
289
|
+
if (!('packet' in current) || !current.packet || typeof current.packet !== 'object') break;
|
|
290
|
+
if (!('timestamp' in current) && !('version' in current)) break;
|
|
291
|
+
current = current.packet;
|
|
292
|
+
}
|
|
293
|
+
return current;
|
|
294
|
+
}
|
|
295
|
+
|
|
201
296
|
function summarizeToolTarget(toolName, args) {
|
|
202
297
|
if (typeof args?.command === 'string' && args.command.trim()) return args.command.trim();
|
|
203
298
|
if (typeof args?.file_path === 'string' && args.file_path.trim()) return args.file_path.trim();
|
|
@@ -244,11 +339,49 @@ function extractProviderToolIntents(providerStyle, providerMeta) {
|
|
|
244
339
|
}
|
|
245
340
|
|
|
246
341
|
function loadDoctrineTriggerMap() {
|
|
342
|
+
const now = Date.now();
|
|
343
|
+
if (now - doctrineTriggerMapSyncedAt < DOCTRINE_TRIGGER_SYNC_INTERVAL_MS) {
|
|
344
|
+
return doctrineTriggerMapCache.map;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const validCandidates = [];
|
|
247
348
|
for (const candidate of DOCTRINE_TRIGGER_MAP_CANDIDATES) {
|
|
248
349
|
const map = readJsonFile(candidate, null);
|
|
249
|
-
if (map
|
|
350
|
+
if (!map || !Array.isArray(map.triggers)) continue;
|
|
351
|
+
try {
|
|
352
|
+
validCandidates.push({
|
|
353
|
+
path: candidate,
|
|
354
|
+
mtimeMs: statSync(candidate).mtimeMs,
|
|
355
|
+
map,
|
|
356
|
+
});
|
|
357
|
+
} catch {}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (validCandidates.length === 0) {
|
|
361
|
+
doctrineTriggerMapCache = { sourcePath: null, map: { triggers: [] } };
|
|
362
|
+
doctrineTriggerMapSyncedAt = now;
|
|
363
|
+
return doctrineTriggerMapCache.map;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
validCandidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
367
|
+
const latest = validCandidates[0];
|
|
368
|
+
const normalized = JSON.stringify(latest.map, null, 2) + '\n';
|
|
369
|
+
for (const candidate of DOCTRINE_TRIGGER_MAP_CANDIDATES) {
|
|
370
|
+
try {
|
|
371
|
+
const dirPath = dirname(candidate);
|
|
372
|
+
if (!existsSync(dirPath)) {
|
|
373
|
+
mkdirSync(dirPath, { recursive: true, mode: 0o755 });
|
|
374
|
+
}
|
|
375
|
+
const current = existsSync(candidate) ? readFileSync(candidate, 'utf8') : null;
|
|
376
|
+
if (current !== normalized) {
|
|
377
|
+
writeFileSync(candidate, normalized, { mode: 0o644 });
|
|
378
|
+
}
|
|
379
|
+
} catch {}
|
|
250
380
|
}
|
|
251
|
-
|
|
381
|
+
|
|
382
|
+
doctrineTriggerMapCache = { sourcePath: latest.path, map: latest.map };
|
|
383
|
+
doctrineTriggerMapSyncedAt = now;
|
|
384
|
+
return latest.map;
|
|
252
385
|
}
|
|
253
386
|
|
|
254
387
|
function collectDoctrineTriggerHits(text) {
|
|
@@ -341,6 +474,26 @@ function synthesizeOwnerLease(apiKey, reason) {
|
|
|
341
474
|
};
|
|
342
475
|
}
|
|
343
476
|
|
|
477
|
+
function buildOfflineBundleFallbackPacket(bundle, error) {
|
|
478
|
+
const normalized = normalizeHarnessPacketPayload(bundle?.packet);
|
|
479
|
+
const packet = normalized && typeof normalized === 'object'
|
|
480
|
+
? JSON.parse(JSON.stringify(normalized))
|
|
481
|
+
: null;
|
|
482
|
+
if (!packet) return null;
|
|
483
|
+
packet.runtimeOfflineBundle = {
|
|
484
|
+
phase: error?.softExpired ? 'degraded' : 'fresh',
|
|
485
|
+
cachedAt: bundle.cachedAt || null,
|
|
486
|
+
lastUpstreamOkAt: bundle.lastUpstreamOkAt || null,
|
|
487
|
+
softExpiresAt: error?.softExpiresAt || null,
|
|
488
|
+
hardExpiresAt: error?.hardExpiresAt || null,
|
|
489
|
+
ageSeconds: error?.ageSeconds ?? null,
|
|
490
|
+
doctrineBundleHash: bundle.doctrineBundleHash || null,
|
|
491
|
+
source: bundle.source || null,
|
|
492
|
+
lastUpstreamError: bundle.lastUpstreamError || null,
|
|
493
|
+
};
|
|
494
|
+
return packet;
|
|
495
|
+
}
|
|
496
|
+
|
|
344
497
|
function deriveEncryptionKey(secret) {
|
|
345
498
|
return scryptSync(secret, 'aria-mounted-runtime', 32);
|
|
346
499
|
}
|
|
@@ -390,10 +543,130 @@ function saveEncryptedLease(lease, secret) {
|
|
|
390
543
|
writeFileSync(LEASE_PATH, encryptJson(lease, secret), { mode: 0o600 });
|
|
391
544
|
}
|
|
392
545
|
|
|
546
|
+
function loadEncryptedOfflineBundle(secret) {
|
|
547
|
+
try {
|
|
548
|
+
if (!existsSync(OFFLINE_BUNDLE_PATH)) return null;
|
|
549
|
+
return decryptJson(readFileSync(OFFLINE_BUNDLE_PATH, 'utf8'), secret);
|
|
550
|
+
} catch {
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function saveEncryptedOfflineBundle(bundle, secret) {
|
|
556
|
+
mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 });
|
|
557
|
+
writeFileSync(OFFLINE_BUNDLE_PATH, encryptJson(bundle, secret), { mode: 0o600 });
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function computeOfflineBundleStatus(bundle, now = Date.now()) {
|
|
561
|
+
if (!bundle || typeof bundle !== 'object') {
|
|
562
|
+
return {
|
|
563
|
+
present: false,
|
|
564
|
+
phase: 'missing',
|
|
565
|
+
usable: false,
|
|
566
|
+
softExpired: true,
|
|
567
|
+
hardExpired: true,
|
|
568
|
+
ageSeconds: null,
|
|
569
|
+
softTtlSeconds: DEFAULT_OFFLINE_BUNDLE_SOFT_TTL_SECONDS,
|
|
570
|
+
hardTtlSeconds: DEFAULT_OFFLINE_BUNDLE_HARD_TTL_SECONDS,
|
|
571
|
+
softExpiresAt: null,
|
|
572
|
+
hardExpiresAt: null,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const cachedAtMs = Date.parse(bundle.cachedAt || bundle.lastUpdatedAt || bundle.lastUpstreamOkAt || '');
|
|
577
|
+
const softExpiresAt = Number(bundle.softExpiresAt || 0);
|
|
578
|
+
const hardExpiresAt = Number(bundle.hardExpiresAt || 0);
|
|
579
|
+
const ageSeconds = Number.isFinite(cachedAtMs) ? Math.max(0, Math.round((now - cachedAtMs) / 1000)) : null;
|
|
580
|
+
const softExpired = !softExpiresAt || softExpiresAt <= now;
|
|
581
|
+
const hardExpired = !hardExpiresAt || hardExpiresAt <= now;
|
|
582
|
+
const phase = hardExpired ? 'expired' : (softExpired ? 'degraded' : 'fresh');
|
|
583
|
+
return {
|
|
584
|
+
present: true,
|
|
585
|
+
phase,
|
|
586
|
+
usable: !hardExpired,
|
|
587
|
+
softExpired,
|
|
588
|
+
hardExpired,
|
|
589
|
+
ageSeconds,
|
|
590
|
+
softTtlSeconds: Number(bundle.softTtlSeconds || DEFAULT_OFFLINE_BUNDLE_SOFT_TTL_SECONDS),
|
|
591
|
+
hardTtlSeconds: Number(bundle.hardTtlSeconds || DEFAULT_OFFLINE_BUNDLE_HARD_TTL_SECONDS),
|
|
592
|
+
softExpiresAt: softExpiresAt ? new Date(softExpiresAt).toISOString() : null,
|
|
593
|
+
hardExpiresAt: hardExpiresAt ? new Date(hardExpiresAt).toISOString() : null,
|
|
594
|
+
cachedAt: bundle.cachedAt || null,
|
|
595
|
+
lastUpstreamOkAt: bundle.lastUpstreamOkAt || null,
|
|
596
|
+
source: bundle.source || null,
|
|
597
|
+
doctrineBundleHash: bundle.doctrineBundleHash || null,
|
|
598
|
+
lastUpstreamError: bundle.lastUpstreamError || null,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function persistOfflineBundle(secret, payload) {
|
|
603
|
+
const existing = loadEncryptedOfflineBundle(secret);
|
|
604
|
+
const now = Date.now();
|
|
605
|
+
const base = existing && typeof existing === 'object' ? existing : {};
|
|
606
|
+
const cachedAt = typeof base.cachedAt === 'string' && base.cachedAt ? base.cachedAt : new Date(now).toISOString();
|
|
607
|
+
const softTtlSeconds = Math.max(60, Number(payload.softTtlSeconds || base.softTtlSeconds || DEFAULT_OFFLINE_BUNDLE_SOFT_TTL_SECONDS));
|
|
608
|
+
const hardTtlSeconds = Math.max(softTtlSeconds, Number(payload.hardTtlSeconds || base.hardTtlSeconds || DEFAULT_OFFLINE_BUNDLE_HARD_TTL_SECONDS));
|
|
609
|
+
const refreshedAt = new Date(now).toISOString();
|
|
610
|
+
const bundle = {
|
|
611
|
+
...base,
|
|
612
|
+
...payload,
|
|
613
|
+
cachedAt,
|
|
614
|
+
refreshedAt,
|
|
615
|
+
lastUpdatedAt: refreshedAt,
|
|
616
|
+
lastUpstreamOkAt: payload.lastUpstreamOkAt || refreshedAt,
|
|
617
|
+
softTtlSeconds,
|
|
618
|
+
hardTtlSeconds,
|
|
619
|
+
softExpiresAt: now + softTtlSeconds * 1000,
|
|
620
|
+
hardExpiresAt: now + hardTtlSeconds * 1000,
|
|
621
|
+
};
|
|
622
|
+
saveEncryptedOfflineBundle(bundle, secret);
|
|
623
|
+
return bundle;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function readLegacyPacketCache() {
|
|
627
|
+
const candidates = [];
|
|
628
|
+
for (const candidate of LEGACY_PACKET_CACHE_CANDIDATES) {
|
|
629
|
+
try {
|
|
630
|
+
if (!existsSync(candidate)) continue;
|
|
631
|
+
const raw = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
632
|
+
const packet = normalizeHarnessPacketPayload(raw.packet ?? raw);
|
|
633
|
+
const harness = typeof packet?.harness === 'string' ? packet.harness : '';
|
|
634
|
+
if (!harness.trim()) continue;
|
|
635
|
+
candidates.push({
|
|
636
|
+
path: candidate,
|
|
637
|
+
packet,
|
|
638
|
+
mtimeMs: statSync(candidate).mtimeMs,
|
|
639
|
+
});
|
|
640
|
+
} catch {}
|
|
641
|
+
}
|
|
642
|
+
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
643
|
+
return candidates[0] || null;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function ensureOfflineBundleSeeded(secret, lease = null) {
|
|
647
|
+
const existing = loadEncryptedOfflineBundle(secret);
|
|
648
|
+
const normalizedExisting = normalizeHarnessPacketPayload(existing?.packet);
|
|
649
|
+
if (normalizedExisting && typeof normalizedExisting.harness === 'string' && normalizedExisting.harness.trim()) {
|
|
650
|
+
return existing;
|
|
651
|
+
}
|
|
652
|
+
const legacy = readLegacyPacketCache();
|
|
653
|
+
if (!legacy) return existing;
|
|
654
|
+
return persistOfflineBundle(secret, {
|
|
655
|
+
...(existing && typeof existing === 'object' ? existing : {}),
|
|
656
|
+
keyHash: hashKey(secret),
|
|
657
|
+
packet: legacy.packet,
|
|
658
|
+
lease: lease || existing?.lease || loadEncryptedLease(secret),
|
|
659
|
+
source: `legacy-cache:${legacy.path}`,
|
|
660
|
+
doctrineBundleHash: lease?.claims?.doctrine_bundle_hash || existing?.doctrineBundleHash || null,
|
|
661
|
+
lastUpstreamError: existing?.lastUpstreamError || null,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
393
665
|
function defaultCognitionState() {
|
|
394
666
|
return {
|
|
395
667
|
telemetry: [],
|
|
396
668
|
decisions: [],
|
|
669
|
+
pendingDecisions: [],
|
|
397
670
|
evolutionPrinciples: [],
|
|
398
671
|
aegisPatterns: [],
|
|
399
672
|
heartbeats: [],
|
|
@@ -586,6 +859,7 @@ function summarizeCognitionState(state) {
|
|
|
586
859
|
return {
|
|
587
860
|
telemetryTurns: Array.isArray(state.telemetry) ? state.telemetry.length : 0,
|
|
588
861
|
decisions: Array.isArray(state.decisions) ? state.decisions.length : 0,
|
|
862
|
+
pendingDecisionUploads: Array.isArray(state.pendingDecisions) ? state.pendingDecisions.length : 0,
|
|
589
863
|
evolutionPrinciples: Array.isArray(state.evolutionPrinciples) ? state.evolutionPrinciples.length : 0,
|
|
590
864
|
aegisPatterns: Array.isArray(state.aegisPatterns) ? state.aegisPatterns.length : 0,
|
|
591
865
|
heartbeats: Array.isArray(state.heartbeats) ? state.heartbeats.length : 0,
|
|
@@ -827,6 +1101,15 @@ async function heartbeatUpstream(req, body, client, apiKey) {
|
|
|
827
1101
|
},
|
|
828
1102
|
};
|
|
829
1103
|
clearRevocationLock();
|
|
1104
|
+
const existingBundle = loadEncryptedOfflineBundle(apiKey);
|
|
1105
|
+
persistOfflineBundle(apiKey, {
|
|
1106
|
+
keyHash: lease.keyHash,
|
|
1107
|
+
lease,
|
|
1108
|
+
packet: existingBundle?.packet || null,
|
|
1109
|
+
source: existingBundle?.source || client.baseUrl || DEFAULT_HARNESS_URL,
|
|
1110
|
+
doctrineBundleHash: lease.claims?.doctrine_bundle_hash || existingBundle?.doctrineBundleHash || null,
|
|
1111
|
+
lastUpstreamError: null,
|
|
1112
|
+
});
|
|
830
1113
|
} catch (error) {
|
|
831
1114
|
if (!ownerBypassAllowed) {
|
|
832
1115
|
throw error;
|
|
@@ -883,14 +1166,45 @@ async function ensureLease(req, body, client) {
|
|
|
883
1166
|
|
|
884
1167
|
const keyHash = hashKey(apiKey);
|
|
885
1168
|
const cached = leaseCache.get(keyHash) || loadEncryptedLease(apiKey);
|
|
1169
|
+
ensureOfflineBundleSeeded(apiKey, cached);
|
|
886
1170
|
if (cached && cached.hardStopAt > Date.now()) {
|
|
887
1171
|
leaseCache.set(keyHash, cached);
|
|
888
1172
|
if (cached.nextRequiredAt > Date.now()) {
|
|
889
1173
|
return cached;
|
|
890
1174
|
}
|
|
891
1175
|
}
|
|
892
|
-
|
|
893
|
-
|
|
1176
|
+
try {
|
|
1177
|
+
return await heartbeatUpstream(req, body, client, apiKey);
|
|
1178
|
+
} catch (error) {
|
|
1179
|
+
const bundle = ensureOfflineBundleSeeded(apiKey, cached) || loadEncryptedOfflineBundle(apiKey);
|
|
1180
|
+
const bundleStatus = computeOfflineBundleStatus(bundle);
|
|
1181
|
+
if (bundle?.keyHash === keyHash && bundleStatus.usable) {
|
|
1182
|
+
const fallbackLease = cached || bundle.lease || null;
|
|
1183
|
+
if (fallbackLease) {
|
|
1184
|
+
const lease = {
|
|
1185
|
+
...fallbackLease,
|
|
1186
|
+
offlineBundle: {
|
|
1187
|
+
phase: bundleStatus.phase,
|
|
1188
|
+
ageSeconds: bundleStatus.ageSeconds,
|
|
1189
|
+
softExpiresAt: bundleStatus.softExpiresAt,
|
|
1190
|
+
hardExpiresAt: bundleStatus.hardExpiresAt,
|
|
1191
|
+
doctrineBundleHash: bundleStatus.doctrineBundleHash,
|
|
1192
|
+
source: bundleStatus.source,
|
|
1193
|
+
lastUpstreamError: error instanceof Error ? error.message : String(error),
|
|
1194
|
+
},
|
|
1195
|
+
};
|
|
1196
|
+
leaseCache.set(keyHash, lease);
|
|
1197
|
+
persistOfflineBundle(apiKey, {
|
|
1198
|
+
...bundle,
|
|
1199
|
+
lease,
|
|
1200
|
+
lastUpstreamError: error instanceof Error ? error.message : String(error),
|
|
1201
|
+
lastUpstreamOkAt: bundle.lastUpstreamOkAt || bundle.cachedAt || new Date().toISOString(),
|
|
1202
|
+
});
|
|
1203
|
+
return lease;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
throw error;
|
|
1207
|
+
}
|
|
894
1208
|
}
|
|
895
1209
|
|
|
896
1210
|
function deriveSessionId(req, body, prefix = 'runtime') {
|
|
@@ -919,6 +1233,47 @@ function findVerifiedState(text) {
|
|
|
919
1233
|
return /(?:verified|confirmed|observed|tested|health[- ]check|response code|exit code|pod image|digest)/i.test(String(text || ''));
|
|
920
1234
|
}
|
|
921
1235
|
|
|
1236
|
+
const APPLIED_COGNITION_BLOCK_RX = /<applied_cognition>([\s\S]*?)<\/applied_cognition>/i;
|
|
1237
|
+
|
|
1238
|
+
function validateAppliedCognitionContract(text) {
|
|
1239
|
+
const match = String(text || '').match(APPLIED_COGNITION_BLOCK_RX);
|
|
1240
|
+
if (!match) return { ok: false, violations: ['missing <applied_cognition> contract'] };
|
|
1241
|
+
const body = match[1] || '';
|
|
1242
|
+
const required = [
|
|
1243
|
+
['decision_delta', /\bdecision[_ -]?delta\s*:/i],
|
|
1244
|
+
['dominant_domain', /\bdominant[_ -]?domain\s*:/i],
|
|
1245
|
+
['binds_to', /\bbinds[_ -]?to\s*:/i],
|
|
1246
|
+
['expected_predicate', /\bexpected[_ -]?predicate\s*:/i],
|
|
1247
|
+
['artifact_change', /\bartifact[_ -]?change\s*:/i],
|
|
1248
|
+
];
|
|
1249
|
+
const violations = [];
|
|
1250
|
+
for (const [name, rx] of required) {
|
|
1251
|
+
if (!rx.test(body)) violations.push(`missing ${name}`);
|
|
1252
|
+
}
|
|
1253
|
+
if (/decision[_ -]?delta\s*:\s*(?:none|n\/a|no change|unchanged|same)/i.test(body)) {
|
|
1254
|
+
violations.push('decision_delta says cognition changed nothing');
|
|
1255
|
+
}
|
|
1256
|
+
return { ok: violations.length === 0, violations, contract: body.trim() };
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
function mergeAppliedCognitionValidation(validation, applied) {
|
|
1260
|
+
if (applied.ok) {
|
|
1261
|
+
return {
|
|
1262
|
+
...validation,
|
|
1263
|
+
appliedCognition: { ok: true, contract: applied.contract },
|
|
1264
|
+
gateTriggers: [...new Set([...(validation.gateTriggers || []), 'applied-cognition-contract'])],
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
return {
|
|
1268
|
+
...validation,
|
|
1269
|
+
passed: false,
|
|
1270
|
+
severity: 'block',
|
|
1271
|
+
violations: [...(validation.violations || []), ...applied.violations.map((v) => `applied_cognition: ${v}`)],
|
|
1272
|
+
gateTriggers: [...new Set([...(validation.gateTriggers || []), 'applied-cognition-contract-missing'])],
|
|
1273
|
+
appliedCognition: { ok: false, violations: applied.violations },
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
|
|
922
1277
|
function toTelemetryEvent(payload, source = 'aria-mounted-runtime') {
|
|
923
1278
|
return {
|
|
924
1279
|
event_type: payload.event_type || 'runtime.cognition.turn',
|
|
@@ -949,6 +1304,7 @@ async function pushTelemetryUpstream(client, apiKey, payload) {
|
|
|
949
1304
|
}
|
|
950
1305
|
|
|
951
1306
|
async function pushDecisionUpstream(client, apiKey, payload) {
|
|
1307
|
+
const normalizedPayload = normalizeDecisionPayload(payload);
|
|
952
1308
|
const url = `${client.baseUrl || DEFAULT_HARNESS_URL}/api/decisions/log`;
|
|
953
1309
|
const response = await fetch(url, {
|
|
954
1310
|
method: 'POST',
|
|
@@ -956,7 +1312,7 @@ async function pushDecisionUpstream(client, apiKey, payload) {
|
|
|
956
1312
|
Authorization: `Bearer ${apiKey}`,
|
|
957
1313
|
'Content-Type': 'application/json',
|
|
958
1314
|
},
|
|
959
|
-
body: JSON.stringify(
|
|
1315
|
+
body: JSON.stringify(normalizedPayload),
|
|
960
1316
|
});
|
|
961
1317
|
if (!response.ok) {
|
|
962
1318
|
const body = await response.text().catch(() => response.statusText);
|
|
@@ -965,6 +1321,88 @@ async function pushDecisionUpstream(client, apiKey, payload) {
|
|
|
965
1321
|
return response.json().catch(() => ({ logged: true }));
|
|
966
1322
|
}
|
|
967
1323
|
|
|
1324
|
+
function normalizeDecisionPayload(payload) {
|
|
1325
|
+
const sessionId = payload?.session_id || payload?.sessionId || null;
|
|
1326
|
+
const decisionType = payload?.decision_type || payload?.decisionType || 'operational';
|
|
1327
|
+
const category = payload?.category || payload?.decision_category || payload?.decisionCategory || payload?.surface || 'runtime';
|
|
1328
|
+
const decision = payload?.decision || payload?.summary || payload?.outcome || decisionType;
|
|
1329
|
+
const reasoning =
|
|
1330
|
+
payload?.reasoning ||
|
|
1331
|
+
payload?.summary ||
|
|
1332
|
+
(payload?.outcome ? `Outcome: ${payload.outcome}` : 'Runtime decision log');
|
|
1333
|
+
const context =
|
|
1334
|
+
payload?.context ||
|
|
1335
|
+
(sessionId ? `Session ${sessionId}` : 'Runtime decision log');
|
|
1336
|
+
const outcomeRaw = String(payload?.outcome || '').toLowerCase();
|
|
1337
|
+
const outcome =
|
|
1338
|
+
outcomeRaw === 'validated' ? 'success'
|
|
1339
|
+
: outcomeRaw === 'error' ? 'failure'
|
|
1340
|
+
: ['success', 'failure', 'neutral', 'pending'].includes(outcomeRaw) ? outcomeRaw
|
|
1341
|
+
: undefined;
|
|
1342
|
+
|
|
1343
|
+
return {
|
|
1344
|
+
...payload,
|
|
1345
|
+
session_id: sessionId,
|
|
1346
|
+
decision_type: decisionType,
|
|
1347
|
+
category,
|
|
1348
|
+
context,
|
|
1349
|
+
decision,
|
|
1350
|
+
reasoning,
|
|
1351
|
+
...(outcome ? { outcome } : {}),
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function delay(ms) {
|
|
1356
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
async function pushDecisionUpstreamWithRetry(client, apiKey, payload, attempts = 3) {
|
|
1360
|
+
let lastError = null;
|
|
1361
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
1362
|
+
try {
|
|
1363
|
+
return await pushDecisionUpstream(client, apiKey, payload);
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
lastError = error;
|
|
1366
|
+
if (attempt === attempts - 1) break;
|
|
1367
|
+
await delay(250 * (2 ** attempt));
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
throw lastError || new Error('decision upstream failed');
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
async function flushPendingDecisionUploads(client, apiKey) {
|
|
1374
|
+
const state = loadEncryptedCognitionState(apiKey);
|
|
1375
|
+
const pending = Array.isArray(state.pendingDecisions) ? state.pendingDecisions : [];
|
|
1376
|
+
if (!pending.length) {
|
|
1377
|
+
return { flushed: 0, retained: 0, lastError: null };
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
const remaining = [];
|
|
1381
|
+
let flushed = 0;
|
|
1382
|
+
let lastError = null;
|
|
1383
|
+
for (const entry of pending) {
|
|
1384
|
+
try {
|
|
1385
|
+
await pushDecisionUpstreamWithRetry(client, apiKey, entry.payload);
|
|
1386
|
+
flushed++;
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
1389
|
+
remaining.push({
|
|
1390
|
+
...entry,
|
|
1391
|
+
attempts: Number(entry.attempts || 0) + 1,
|
|
1392
|
+
lastError,
|
|
1393
|
+
lastTriedAt: new Date().toISOString(),
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
mutateCognitionState(apiKey, (current) => ({
|
|
1399
|
+
...current,
|
|
1400
|
+
pendingDecisions: remaining,
|
|
1401
|
+
}));
|
|
1402
|
+
|
|
1403
|
+
return { flushed, retained: remaining.length, lastError };
|
|
1404
|
+
}
|
|
1405
|
+
|
|
968
1406
|
function buildMinimalInjection(packet, task, aegisLearnings = null) {
|
|
969
1407
|
return {
|
|
970
1408
|
harness: packet,
|
|
@@ -1022,9 +1460,35 @@ function buildOwnerBypassPacket(message, reason = 'owner-local-bypass') {
|
|
|
1022
1460
|
|
|
1023
1461
|
async function loadRuntimePacket(req, body, client, packetRequest, message) {
|
|
1024
1462
|
if (body.packet) return body.packet;
|
|
1463
|
+
const apiKey = resolveApiKey(req, body);
|
|
1464
|
+
ensureOfflineBundleSeeded(apiKey, leaseCache.get(hashKey(apiKey)) || loadEncryptedLease(apiKey));
|
|
1025
1465
|
try {
|
|
1026
|
-
|
|
1466
|
+
const wrapped = await client.getHarnessPacket(packetRequest || {});
|
|
1467
|
+
const packet = normalizeHarnessPacketPayload(wrapped?.packet || wrapped);
|
|
1468
|
+
const lease = leaseCache.get(hashKey(apiKey)) || loadEncryptedLease(apiKey);
|
|
1469
|
+
persistOfflineBundle(apiKey, {
|
|
1470
|
+
keyHash: hashKey(apiKey),
|
|
1471
|
+
packet,
|
|
1472
|
+
lease,
|
|
1473
|
+
source: client.baseUrl || DEFAULT_HARNESS_URL,
|
|
1474
|
+
doctrineBundleHash: lease?.claims?.doctrine_bundle_hash || null,
|
|
1475
|
+
lastUpstreamError: null,
|
|
1476
|
+
});
|
|
1477
|
+
return packet;
|
|
1027
1478
|
} catch (error) {
|
|
1479
|
+
const bundle = ensureOfflineBundleSeeded(apiKey, leaseCache.get(hashKey(apiKey)) || loadEncryptedLease(apiKey)) || loadEncryptedOfflineBundle(apiKey);
|
|
1480
|
+
const bundleStatus = computeOfflineBundleStatus(bundle);
|
|
1481
|
+
if (bundle?.keyHash === hashKey(apiKey) && bundleStatus.usable) {
|
|
1482
|
+
const fallbackPacket = buildOfflineBundleFallbackPacket(bundle, bundleStatus);
|
|
1483
|
+
if (fallbackPacket) {
|
|
1484
|
+
persistOfflineBundle(apiKey, {
|
|
1485
|
+
...bundle,
|
|
1486
|
+
lastUpstreamError: error instanceof Error ? error.message : String(error),
|
|
1487
|
+
lastUpstreamOkAt: bundle.lastUpstreamOkAt || bundle.cachedAt || new Date().toISOString(),
|
|
1488
|
+
});
|
|
1489
|
+
return fallbackPacket;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1028
1492
|
if (!isOwnerBypassRequest(req, body)) {
|
|
1029
1493
|
throw error;
|
|
1030
1494
|
}
|
|
@@ -1266,6 +1730,174 @@ function anthropicResponseEnvelope(text, providerMeta, extra = {}, debug = false
|
|
|
1266
1730
|
};
|
|
1267
1731
|
}
|
|
1268
1732
|
|
|
1733
|
+
function flattenResponsesTextContent(content) {
|
|
1734
|
+
if (typeof content === 'string') return content;
|
|
1735
|
+
if (!Array.isArray(content)) {
|
|
1736
|
+
if (typeof content?.text === 'string') return content.text;
|
|
1737
|
+
if (typeof content?.content === 'string') return content.content;
|
|
1738
|
+
return '';
|
|
1739
|
+
}
|
|
1740
|
+
return content
|
|
1741
|
+
.map((part) => {
|
|
1742
|
+
if (typeof part === 'string') return part;
|
|
1743
|
+
if (typeof part?.text === 'string') return part.text;
|
|
1744
|
+
if (typeof part?.content === 'string') return part.content;
|
|
1745
|
+
if ((part?.type === 'input_text' || part?.type === 'output_text' || part?.type === 'text') && typeof part?.text === 'string') {
|
|
1746
|
+
return part.text;
|
|
1747
|
+
}
|
|
1748
|
+
return '';
|
|
1749
|
+
})
|
|
1750
|
+
.filter(Boolean)
|
|
1751
|
+
.join('\n');
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
function normalizeResponsesTool(tool) {
|
|
1755
|
+
if (!tool || typeof tool !== 'object') return null;
|
|
1756
|
+
if (tool.type === 'function' && tool.function && typeof tool.function === 'object') {
|
|
1757
|
+
return tool;
|
|
1758
|
+
}
|
|
1759
|
+
if (tool.type === 'function' || typeof tool.name === 'string') {
|
|
1760
|
+
return {
|
|
1761
|
+
type: 'function',
|
|
1762
|
+
function: {
|
|
1763
|
+
name: tool.name || tool.function?.name || 'tool',
|
|
1764
|
+
description: tool.description || tool.function?.description || '',
|
|
1765
|
+
parameters: tool.parameters || tool.function?.parameters || { type: 'object', properties: {} },
|
|
1766
|
+
},
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
return tool;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
function responsesInputToMessages(input) {
|
|
1773
|
+
if (typeof input === 'string' && input.trim()) {
|
|
1774
|
+
return [{ role: 'user', content: input.trim() }];
|
|
1775
|
+
}
|
|
1776
|
+
if (!Array.isArray(input)) return [];
|
|
1777
|
+
|
|
1778
|
+
const messages = [];
|
|
1779
|
+
for (const item of input) {
|
|
1780
|
+
if (!item || typeof item !== 'object') continue;
|
|
1781
|
+
if (item.type === 'message') {
|
|
1782
|
+
const role = typeof item.role === 'string' ? item.role : 'user';
|
|
1783
|
+
const text = flattenResponsesTextContent(item.content);
|
|
1784
|
+
if (text) messages.push({ role, content: text });
|
|
1785
|
+
continue;
|
|
1786
|
+
}
|
|
1787
|
+
if (item.type === 'input_text' || item.type === 'output_text' || item.type === 'text') {
|
|
1788
|
+
const role = item.role === 'assistant' ? 'assistant' : 'user';
|
|
1789
|
+
const text = flattenResponsesTextContent(item);
|
|
1790
|
+
if (text) messages.push({ role, content: text });
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
if (item.type === 'function_call_output') {
|
|
1794
|
+
const outputText =
|
|
1795
|
+
typeof item.output === 'string'
|
|
1796
|
+
? item.output
|
|
1797
|
+
: JSON.stringify(item.output || {});
|
|
1798
|
+
if (outputText) {
|
|
1799
|
+
messages.push({
|
|
1800
|
+
role: 'tool',
|
|
1801
|
+
tool_call_id: item.call_id || item.id || null,
|
|
1802
|
+
content: outputText,
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
continue;
|
|
1806
|
+
}
|
|
1807
|
+
if (item.type === 'function_call') {
|
|
1808
|
+
const args =
|
|
1809
|
+
typeof item.arguments === 'string'
|
|
1810
|
+
? item.arguments
|
|
1811
|
+
: JSON.stringify(item.arguments || item.input || {});
|
|
1812
|
+
messages.push({
|
|
1813
|
+
role: 'assistant',
|
|
1814
|
+
content: `${item.name || 'function_call'} ${args}`.trim(),
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
return messages;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
function responsesRequestToOpenAIBody(body = {}) {
|
|
1822
|
+
const messages = responsesInputToMessages(body.input);
|
|
1823
|
+
const instructions = typeof body.instructions === 'string' && body.instructions.trim()
|
|
1824
|
+
? [{ role: 'system', content: body.instructions.trim() }]
|
|
1825
|
+
: [];
|
|
1826
|
+
|
|
1827
|
+
return {
|
|
1828
|
+
...body,
|
|
1829
|
+
client: body.client || 'codex',
|
|
1830
|
+
surface: body.surface || 'codex',
|
|
1831
|
+
messages: [...instructions, ...messages],
|
|
1832
|
+
tools: Array.isArray(body.tools) ? body.tools.map(normalizeResponsesTool).filter(Boolean) : undefined,
|
|
1833
|
+
tool_choice: body.tool_choice,
|
|
1834
|
+
max_completion_tokens: body.max_output_tokens || body.max_completion_tokens,
|
|
1835
|
+
metadata: {
|
|
1836
|
+
...(body.metadata && typeof body.metadata === 'object' ? body.metadata : {}),
|
|
1837
|
+
response_api: true,
|
|
1838
|
+
client: body.client || 'codex',
|
|
1839
|
+
surface: body.surface || 'codex',
|
|
1840
|
+
},
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
function openAiCompletionToResponsesEnvelope(body, completion) {
|
|
1845
|
+
const message = completion?.choices?.[0]?.message || {};
|
|
1846
|
+
const text = typeof message.content === 'string' ? message.content : '';
|
|
1847
|
+
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
1848
|
+
const output = [];
|
|
1849
|
+
|
|
1850
|
+
if (text) {
|
|
1851
|
+
output.push({
|
|
1852
|
+
id: `msg_${randomUUID().replace(/-/g, '')}`,
|
|
1853
|
+
type: 'message',
|
|
1854
|
+
role: 'assistant',
|
|
1855
|
+
status: 'completed',
|
|
1856
|
+
content: [
|
|
1857
|
+
{
|
|
1858
|
+
type: 'output_text',
|
|
1859
|
+
text,
|
|
1860
|
+
},
|
|
1861
|
+
],
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
for (const toolCall of toolCalls) {
|
|
1866
|
+
const callId = toolCall?.id || `call_${randomUUID().replace(/-/g, '')}`;
|
|
1867
|
+
const functionInfo = toolCall?.function || {};
|
|
1868
|
+
output.push({
|
|
1869
|
+
id: callId,
|
|
1870
|
+
type: 'function_call',
|
|
1871
|
+
status: 'completed',
|
|
1872
|
+
call_id: callId,
|
|
1873
|
+
name: functionInfo.name || toolCall?.name || 'tool',
|
|
1874
|
+
arguments:
|
|
1875
|
+
typeof functionInfo.arguments === 'string'
|
|
1876
|
+
? functionInfo.arguments
|
|
1877
|
+
: JSON.stringify(functionInfo.arguments || toolCall?.arguments || {}),
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
return {
|
|
1882
|
+
id: `resp_${randomUUID().replace(/-/g, '')}`,
|
|
1883
|
+
object: 'response',
|
|
1884
|
+
created_at: new Date().toISOString(),
|
|
1885
|
+
status: 'completed',
|
|
1886
|
+
model: completion?.model || body?.model || 'aria-runtime',
|
|
1887
|
+
output,
|
|
1888
|
+
output_text: text,
|
|
1889
|
+
usage: completion?.usage
|
|
1890
|
+
? {
|
|
1891
|
+
input_tokens: completion.usage.prompt_tokens || 0,
|
|
1892
|
+
output_tokens: completion.usage.completion_tokens || 0,
|
|
1893
|
+
total_tokens: completion.usage.total_tokens || ((completion.usage.prompt_tokens || 0) + (completion.usage.completion_tokens || 0)),
|
|
1894
|
+
}
|
|
1895
|
+
: undefined,
|
|
1896
|
+
aria: completion?.aria || null,
|
|
1897
|
+
metadata: body?.metadata || null,
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1269
1901
|
function toReadableSignal(name) {
|
|
1270
1902
|
const map = {
|
|
1271
1903
|
fitrah_gate: 'truth boundary',
|
|
@@ -1644,6 +2276,10 @@ function computeForgeContractIssues(manifest, forgeResult) {
|
|
|
1644
2276
|
}
|
|
1645
2277
|
|
|
1646
2278
|
async function persistTurnArtifacts(req, body, client, apiKey, turn) {
|
|
2279
|
+
persistMizanBundle(apiKey, turn.preBundle, 'runtime/v1-pre');
|
|
2280
|
+
persistMizanBundle(apiKey, turn.midBundle, 'runtime/v1-mid');
|
|
2281
|
+
persistMizanBundle(apiKey, turn.postBundle, 'runtime/v1-post');
|
|
2282
|
+
|
|
1647
2283
|
const evolutionPrinciples = extractEvolutionPrinciples(turn.packet, [turn.preResult, turn.midResult, turn.postResult]);
|
|
1648
2284
|
const aegisPatterns = [
|
|
1649
2285
|
...extractAegisPatterns(turn.midResult || {}),
|
|
@@ -1783,39 +2419,93 @@ async function persistTurnArtifacts(req, body, client, apiKey, turn) {
|
|
|
1783
2419
|
await pushTelemetryUpstream(client, apiKey, telemetryPayload);
|
|
1784
2420
|
} catch {}
|
|
1785
2421
|
|
|
1786
|
-
|
|
2422
|
+
const isDecisionTurn =
|
|
2423
|
+
(turn.turnClass?.intensity && turn.turnClass.intensity !== 'light') ||
|
|
2424
|
+
isNonTrivialAssistantTurn(turn.userMessage || '', []) ||
|
|
2425
|
+
isNonTrivialAssistantTurn(turn.finalText || '', []);
|
|
2426
|
+
if (isDecisionTurn) {
|
|
2427
|
+
const surface =
|
|
2428
|
+
body?.surface ||
|
|
2429
|
+
body?.platform ||
|
|
2430
|
+
body?.client ||
|
|
2431
|
+
body?.metadata?.surface ||
|
|
2432
|
+
body?.metadata?.client ||
|
|
2433
|
+
'aria-mounted-runtime';
|
|
2434
|
+
const reasoning = [
|
|
2435
|
+
...(turn.preResult?.notes || []),
|
|
2436
|
+
...(turn.midResult?.notes || []),
|
|
2437
|
+
...(turn.postResult?.notes || []),
|
|
2438
|
+
...(Array.isArray(turn.validation?.violations) ? turn.validation.violations : []),
|
|
2439
|
+
...(Array.isArray(turn.layer3?.failures)
|
|
2440
|
+
? turn.layer3.failures.map((failure) => failure?.detail).filter(Boolean)
|
|
2441
|
+
: []),
|
|
2442
|
+
]
|
|
2443
|
+
.join(' | ')
|
|
2444
|
+
.slice(0, 4000) || 'Aria runtime cognition turn';
|
|
1787
2445
|
const decisionPayload = {
|
|
1788
2446
|
decision_type: body?.metadata?.decision_type || 'runtime-turn',
|
|
1789
2447
|
category: body?.metadata?.decision_category || 'cognition-control-plane',
|
|
1790
|
-
context:
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
...(turn.postResult?.notes || []),
|
|
1796
|
-
].join(' | ').slice(0, 4000) || 'Aria runtime cognition turn',
|
|
2448
|
+
context:
|
|
2449
|
+
(turn.userMessage && turn.userMessage.slice(0, 2000)) ||
|
|
2450
|
+
`${surface} runtime turn (${turn.providerMeta.provider || 'provider'})`,
|
|
2451
|
+
decision: turn.success ? 'turn completed' : 'turn blocked by runtime gate',
|
|
2452
|
+
reasoning,
|
|
1797
2453
|
outcome: 'pending',
|
|
1798
|
-
|
|
1799
|
-
|
|
2454
|
+
outcome_details: {
|
|
2455
|
+
expected: body?.metadata?.expected_outcome || null,
|
|
2456
|
+
immediate_actual: {
|
|
2457
|
+
success: turn.success,
|
|
2458
|
+
validation_severity: turn.validation?.severity || null,
|
|
2459
|
+
layer3_pass: turn.layer3?.pass ?? null,
|
|
2460
|
+
doctrine_hits: Array.isArray(turn.layer3?.doctrine?.hits)
|
|
2461
|
+
? turn.layer3.doctrine.hits.length
|
|
2462
|
+
: 0,
|
|
2463
|
+
},
|
|
2464
|
+
anchors: [
|
|
2465
|
+
turn.preReceipt?.receiptId ? `pre_receipt:${turn.preReceipt.receiptId}` : null,
|
|
2466
|
+
turn.midReceipt?.receiptId ? `mid_receipt:${turn.midReceipt.receiptId}` : null,
|
|
2467
|
+
turn.postReceipt?.receiptId ? `post_receipt:${turn.postReceipt.receiptId}` : null,
|
|
2468
|
+
].filter(Boolean),
|
|
2469
|
+
},
|
|
2470
|
+
expected_outcome: body?.metadata?.expected_outcome || {
|
|
2471
|
+
predicate: 'turn survives runtime validation and compounds into central telemetry with canonical receipts',
|
|
1800
2472
|
measurable_type: 'boolean',
|
|
1801
2473
|
threshold: true,
|
|
1802
2474
|
},
|
|
1803
|
-
|
|
2475
|
+
metadata: {
|
|
2476
|
+
session_id: turn.sessionId,
|
|
2477
|
+
surface,
|
|
2478
|
+
provider: turn.providerMeta.provider || null,
|
|
2479
|
+
finish_reason: turn.providerMeta.finishReason || null,
|
|
2480
|
+
pre_receipt_id: turn.preReceipt?.receiptId || null,
|
|
2481
|
+
mid_receipt_id: turn.midReceipt?.receiptId || null,
|
|
2482
|
+
post_receipt_id: turn.postReceipt?.receiptId || null,
|
|
2483
|
+
validation_severity: turn.validation?.severity || null,
|
|
2484
|
+
validation_passed: turn.validation?.passed ?? null,
|
|
2485
|
+
layer3_pass: turn.layer3?.pass ?? null,
|
|
2486
|
+
packet_bypassed: turn.packetBypassed === true,
|
|
2487
|
+
},
|
|
2488
|
+
source: body?.metadata?.decision_source || `${surface}-runtime`,
|
|
1804
2489
|
model_used: turn.providerMeta.model,
|
|
1805
2490
|
code_links: body?.metadata?.code_links || null,
|
|
1806
2491
|
};
|
|
2492
|
+
let decisionResult = null;
|
|
2493
|
+
let decisionError = null;
|
|
1807
2494
|
try {
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
2495
|
+
decisionResult = await pushDecisionUpstream(client, apiKey, decisionPayload);
|
|
2496
|
+
} catch (error) {
|
|
2497
|
+
decisionError = error instanceof Error ? error.message : String(error);
|
|
2498
|
+
}
|
|
2499
|
+
mutateCognitionState(apiKey, (state) => ({
|
|
2500
|
+
...state,
|
|
2501
|
+
decisions: appendBounded(state.decisions, {
|
|
2502
|
+
at: new Date().toISOString(),
|
|
2503
|
+
sessionId: turn.sessionId,
|
|
2504
|
+
decision: decisionPayload,
|
|
2505
|
+
result: decisionResult,
|
|
2506
|
+
error: decisionError,
|
|
2507
|
+
}, DECISION_LIMIT),
|
|
2508
|
+
}));
|
|
1819
2509
|
}
|
|
1820
2510
|
|
|
1821
2511
|
if (body.ariaGarden !== false && turn.userMessage && turn.finalText) {
|
|
@@ -1841,6 +2531,20 @@ async function handleProviderProxy(req, body, client, providerStyle) {
|
|
|
1841
2531
|
? await callProviderForAnthropic(body, turn.ariaSystemPrompt)
|
|
1842
2532
|
: await callProviderForOpenAI(body, turn.ariaSystemPrompt);
|
|
1843
2533
|
|
|
2534
|
+
try {
|
|
2535
|
+
recordTokenUsage({
|
|
2536
|
+
tenantId: body?.metadata?.jti || body?.jti || 'owner-local',
|
|
2537
|
+
agentId: body?.metadata?.agentId || body?.metadata?.roleProfile || null,
|
|
2538
|
+
agentName: body?.metadata?.agentName || null,
|
|
2539
|
+
department: body?.metadata?.department || null,
|
|
2540
|
+
sessionId: turn.sessionId,
|
|
2541
|
+
provider: providerMeta.provider,
|
|
2542
|
+
model: providerMeta.model,
|
|
2543
|
+
usage: providerMeta.usage,
|
|
2544
|
+
requestType: body?.metadata?.requestType || 'chat',
|
|
2545
|
+
});
|
|
2546
|
+
} catch {}
|
|
2547
|
+
|
|
1844
2548
|
let candidateText = providerMeta.text || '';
|
|
1845
2549
|
const toolIntents = extractProviderToolIntents(providerStyle, providerMeta);
|
|
1846
2550
|
const requiresReadableCognition = isNonTrivialAssistantTurn(candidateText, toolIntents);
|
|
@@ -1856,7 +2560,7 @@ async function handleProviderProxy(req, body, client, providerStyle) {
|
|
|
1856
2560
|
try {
|
|
1857
2561
|
validation = await client.validateOutput(candidateText, turn.sessionId);
|
|
1858
2562
|
} catch (error) {
|
|
1859
|
-
if (!turn.packetBypassed) throw error;
|
|
2563
|
+
if (!turn.packetBypassed && !isOwnerBypassRequest(req, body, apiKey)) throw error;
|
|
1860
2564
|
validation = {
|
|
1861
2565
|
passed: true,
|
|
1862
2566
|
severity: 'warn',
|
|
@@ -1871,7 +2575,7 @@ async function handleProviderProxy(req, body, client, providerStyle) {
|
|
|
1871
2575
|
try {
|
|
1872
2576
|
validation = await client.validateOutput(candidateText, turn.sessionId);
|
|
1873
2577
|
} catch (error) {
|
|
1874
|
-
if (!turn.packetBypassed) throw error;
|
|
2578
|
+
if (!turn.packetBypassed && !isOwnerBypassRequest(req, body, apiKey)) throw error;
|
|
1875
2579
|
validation = {
|
|
1876
2580
|
passed: true,
|
|
1877
2581
|
severity: 'warn',
|
|
@@ -1911,7 +2615,7 @@ async function handleProviderProxy(req, body, client, providerStyle) {
|
|
|
1911
2615
|
toolGateBlockers.push(`${intent.toolName}: ${runtimeCheck.reason || 'runtime action gate blocked this tool request'}`);
|
|
1912
2616
|
}
|
|
1913
2617
|
} catch (error) {
|
|
1914
|
-
if (!turn.packetBypassed) throw error;
|
|
2618
|
+
if (!turn.packetBypassed && !isOwnerBypassRequest(req, body, apiKey)) throw error;
|
|
1915
2619
|
toolGateBlockers.push(`${intent.toolName}: runtime action gate unavailable during owner-local-bypass`);
|
|
1916
2620
|
}
|
|
1917
2621
|
}
|
|
@@ -2260,6 +2964,8 @@ function packetToSubstrateSet(packet) {
|
|
|
2260
2964
|
function runtimeManifest() {
|
|
2261
2965
|
const runtimeMeta = ensureRuntimeMeta();
|
|
2262
2966
|
const state = sweepAutonomyState(loadAutonomyState());
|
|
2967
|
+
const ownerToken = readOwnerToken();
|
|
2968
|
+
const offlineBundleStatus = ownerToken ? computeOfflineBundleStatus(loadEncryptedOfflineBundle(ownerToken)) : computeOfflineBundleStatus(null);
|
|
2263
2969
|
return {
|
|
2264
2970
|
ok: true,
|
|
2265
2971
|
runtime: 'aria-mounted-runtime',
|
|
@@ -2306,6 +3012,8 @@ function runtimeManifest() {
|
|
|
2306
3012
|
'POST /forge/synthesize',
|
|
2307
3013
|
'POST /codebase/state',
|
|
2308
3014
|
'POST /v1/chat/completions',
|
|
3015
|
+
'POST /v1/responses',
|
|
3016
|
+
'POST /responses',
|
|
2309
3017
|
'POST /v1/messages',
|
|
2310
3018
|
],
|
|
2311
3019
|
mount: {
|
|
@@ -2323,9 +3031,13 @@ function runtimeManifest() {
|
|
|
2323
3031
|
},
|
|
2324
3032
|
security: {
|
|
2325
3033
|
encrypted_local_lease: LEASE_PATH,
|
|
3034
|
+
encrypted_offline_bundle: OFFLINE_BUNDLE_PATH,
|
|
2326
3035
|
encrypted_cognition_state: COGNITION_STATE_PATH,
|
|
2327
3036
|
revocation_lock: REVOCATION_LOCK_PATH,
|
|
2328
3037
|
upstream_heartbeat: '/api/license/heartbeat',
|
|
3038
|
+
offline_bundle_soft_ttl_seconds: DEFAULT_OFFLINE_BUNDLE_SOFT_TTL_SECONDS,
|
|
3039
|
+
offline_bundle_hard_ttl_seconds: DEFAULT_OFFLINE_BUNDLE_HARD_TTL_SECONDS,
|
|
3040
|
+
offline_bundle_status: offlineBundleStatus,
|
|
2329
3041
|
},
|
|
2330
3042
|
memory: {
|
|
2331
3043
|
qdrant_url: DEFAULT_QDRANT_URL,
|
|
@@ -2354,12 +3066,27 @@ async function runLayer3(req, body, client) {
|
|
|
2354
3066
|
substrate: packetToSubstrateSet(packet),
|
|
2355
3067
|
requireCognitionBlock: body.requireCognitionBlock ?? false,
|
|
2356
3068
|
});
|
|
3069
|
+
const doctrineHits = collectDoctrineTriggerHits(body.text);
|
|
3070
|
+
const doctrineFailures = doctrineHits.map((hit) => ({
|
|
3071
|
+
severity: String(hit.severity || 'block').toLowerCase() === 'block' ? 'block' : 'warn',
|
|
3072
|
+
kind: 'drift_trigger',
|
|
3073
|
+
detail: `${hit.trigger} (${hit.memory || 'doctrine_trigger_map.json'}): ${hit.message || hit.teaching || 'Doctrine trigger matched.'}`,
|
|
3074
|
+
}));
|
|
3075
|
+
const allFailures = [...result.failures, ...doctrineFailures];
|
|
3076
|
+
const hardFailures = allFailures.filter((failure) => failure.severity === 'block');
|
|
2357
3077
|
|
|
2358
3078
|
return {
|
|
2359
|
-
pass:
|
|
2360
|
-
summary:
|
|
2361
|
-
|
|
3079
|
+
pass: hardFailures.length === 0,
|
|
3080
|
+
summary:
|
|
3081
|
+
hardFailures.length === 0
|
|
3082
|
+
? `full_chain: pass (${allFailures.length} warns)`
|
|
3083
|
+
: `full_chain: ${hardFailures.length} hard failures across ${allFailures.length} total`,
|
|
3084
|
+
failures: allFailures,
|
|
2362
3085
|
packetTimestamp: packet.timestamp || null,
|
|
3086
|
+
doctrine: {
|
|
3087
|
+
sourcePath: doctrineTriggerMapCache.sourcePath,
|
|
3088
|
+
hits: doctrineHits,
|
|
3089
|
+
},
|
|
2363
3090
|
};
|
|
2364
3091
|
}
|
|
2365
3092
|
|
|
@@ -2602,7 +3329,10 @@ async function handleRoute(req, res) {
|
|
|
2602
3329
|
|
|
2603
3330
|
let client;
|
|
2604
3331
|
try {
|
|
2605
|
-
|
|
3332
|
+
// HQ routes use their own auth middleware — skip the global API key gate
|
|
3333
|
+
if (!url.pathname.startsWith('/hq/')) {
|
|
3334
|
+
client = createClient(req, body);
|
|
3335
|
+
}
|
|
2606
3336
|
} catch (error) {
|
|
2607
3337
|
return json(res, 401, { ok: false, error: error.message });
|
|
2608
3338
|
}
|
|
@@ -2698,6 +3428,12 @@ async function handleRoute(req, res) {
|
|
|
2698
3428
|
return json(res, 200, response);
|
|
2699
3429
|
}
|
|
2700
3430
|
|
|
3431
|
+
if (url.pathname === '/v1/responses' || url.pathname === '/responses') {
|
|
3432
|
+
const responseBody = responsesRequestToOpenAIBody(body);
|
|
3433
|
+
const completion = await handleProviderProxy(req, responseBody, client, 'openai');
|
|
3434
|
+
return json(res, 200, openAiCompletionToResponsesEnvelope(body, completion));
|
|
3435
|
+
}
|
|
3436
|
+
|
|
2701
3437
|
if (url.pathname === '/v1/messages') {
|
|
2702
3438
|
const response = await handleProviderProxy(req, body, client, 'anthropic');
|
|
2703
3439
|
return json(res, 200, response);
|
|
@@ -2728,16 +3464,55 @@ async function handleRoute(req, res) {
|
|
|
2728
3464
|
|
|
2729
3465
|
if (url.pathname === '/decision/log') {
|
|
2730
3466
|
const apiKey = resolveApiKey(req, body);
|
|
2731
|
-
const
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
3467
|
+
const normalizedBody = normalizeDecisionPayload(body);
|
|
3468
|
+
const flush = await flushPendingDecisionUploads(client, apiKey);
|
|
3469
|
+
try {
|
|
3470
|
+
const result = await pushDecisionUpstreamWithRetry(client, apiKey, normalizedBody);
|
|
3471
|
+
mutateCognitionState(apiKey, (state) => ({
|
|
3472
|
+
...state,
|
|
3473
|
+
decisions: appendBounded(state.decisions, {
|
|
3474
|
+
at: new Date().toISOString(),
|
|
3475
|
+
decision: normalizedBody,
|
|
3476
|
+
result,
|
|
3477
|
+
flushedPending: flush.flushed,
|
|
3478
|
+
}, DECISION_LIMIT),
|
|
3479
|
+
}));
|
|
3480
|
+
return json(res, 200, {
|
|
3481
|
+
ok: true,
|
|
2737
3482
|
result,
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
3483
|
+
flushedPending: flush.flushed,
|
|
3484
|
+
retainedPending: flush.retained,
|
|
3485
|
+
});
|
|
3486
|
+
} catch (error) {
|
|
3487
|
+
const upstreamError = error instanceof Error ? error.message : String(error);
|
|
3488
|
+
mutateCognitionState(apiKey, (state) => ({
|
|
3489
|
+
...state,
|
|
3490
|
+
decisions: appendBounded(state.decisions, {
|
|
3491
|
+
at: new Date().toISOString(),
|
|
3492
|
+
decision: normalizedBody,
|
|
3493
|
+
result: {
|
|
3494
|
+
logged: false,
|
|
3495
|
+
queuedUpstream: true,
|
|
3496
|
+
upstreamError,
|
|
3497
|
+
},
|
|
3498
|
+
flushedPending: flush.flushed,
|
|
3499
|
+
}, DECISION_LIMIT),
|
|
3500
|
+
pendingDecisions: appendBounded(state.pendingDecisions, {
|
|
3501
|
+
at: new Date().toISOString(),
|
|
3502
|
+
payload: normalizedBody,
|
|
3503
|
+
attempts: 0,
|
|
3504
|
+
lastError: upstreamError,
|
|
3505
|
+
queuedBy: 'decision/log',
|
|
3506
|
+
}, DECISION_LIMIT),
|
|
3507
|
+
}));
|
|
3508
|
+
return json(res, 200, {
|
|
3509
|
+
ok: true,
|
|
3510
|
+
queuedUpstream: true,
|
|
3511
|
+
upstreamError,
|
|
3512
|
+
flushedPending: flush.flushed,
|
|
3513
|
+
retainedPending: flush.retained + 1,
|
|
3514
|
+
});
|
|
3515
|
+
}
|
|
2741
3516
|
}
|
|
2742
3517
|
|
|
2743
3518
|
if (url.pathname === '/aegis/patterns') {
|
|
@@ -2799,12 +3574,12 @@ async function handleRoute(req, res) {
|
|
|
2799
3574
|
}
|
|
2800
3575
|
}
|
|
2801
3576
|
|
|
2802
|
-
if (url.pathname === '/packet') {
|
|
3577
|
+
if (url.pathname === '/packet' || url.pathname === '/api/harness/codex') {
|
|
2803
3578
|
const packet = await loadRuntimePacket(req, body, client, body.packetRequest || body, body.message || '');
|
|
2804
3579
|
return json(res, 200, { ok: true, packet });
|
|
2805
3580
|
}
|
|
2806
3581
|
|
|
2807
|
-
if (url.pathname === '/consult') {
|
|
3582
|
+
if (url.pathname === '/consult' || url.pathname === '/api/harness/delegate') {
|
|
2808
3583
|
const result = await client.consult(body);
|
|
2809
3584
|
return json(res, 200, { ok: true, ...result });
|
|
2810
3585
|
}
|
|
@@ -2824,7 +3599,7 @@ async function handleRoute(req, res) {
|
|
|
2824
3599
|
return json(res, 200, { ok: true, ...result });
|
|
2825
3600
|
}
|
|
2826
3601
|
|
|
2827
|
-
if (url.pathname === '/validate-output') {
|
|
3602
|
+
if (url.pathname === '/validate-output' || url.pathname === '/api/harness/validate') {
|
|
2828
3603
|
if (typeof body.text !== 'string' || typeof body.sessionId !== 'string') {
|
|
2829
3604
|
throw new Error('validate-output requires text and sessionId');
|
|
2830
3605
|
}
|
|
@@ -2843,6 +3618,7 @@ async function handleRoute(req, res) {
|
|
|
2843
3618
|
gateTriggers: ['owner-local-bypass'],
|
|
2844
3619
|
};
|
|
2845
3620
|
}
|
|
3621
|
+
validation = mergeAppliedCognitionValidation(validation, validateAppliedCognitionContract(body.text));
|
|
2846
3622
|
const response = { ok: true, validation };
|
|
2847
3623
|
|
|
2848
3624
|
if (body.runLayer3 !== false) {
|
|
@@ -3003,6 +3779,313 @@ async function handleRoute(req, res) {
|
|
|
3003
3779
|
return json(res, 200, { ok: true, ...result });
|
|
3004
3780
|
}
|
|
3005
3781
|
|
|
3782
|
+
// ── /hq/* routes: Agentic HQ API ──────────────────────────────
|
|
3783
|
+
|
|
3784
|
+
if (url.pathname.startsWith('/hq/')) {
|
|
3785
|
+
const auth = hqAuthMiddleware(url.pathname, req);
|
|
3786
|
+
if (!auth.authorized) {
|
|
3787
|
+
return json(res, 401, { ok: false, error: auth.error || 'Unauthorized' });
|
|
3788
|
+
}
|
|
3789
|
+
if (auth.tenantId && !body.tenantId) body.tenantId = auth.tenantId;
|
|
3790
|
+
req.hqAuth = auth;
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
// ── /hq/auth/* routes (public, handled before auth gate takes effect) ──
|
|
3794
|
+
if (url.pathname === '/hq/auth/login') {
|
|
3795
|
+
const { username, password } = body;
|
|
3796
|
+
if (!username || !password) return json(res, 400, { ok: false, error: 'username and password required' });
|
|
3797
|
+
const result = loginUser(username, password);
|
|
3798
|
+
if (!result.ok) return json(res, 401, result);
|
|
3799
|
+
return json(res, 200, { ok: true, token: result.session.token, role: result.session.role, tenantId: result.session.tenantId, email: result.session.email });
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
if (url.pathname === '/hq/auth/register') {
|
|
3803
|
+
const { email, password, tenantId } = body;
|
|
3804
|
+
if (!email || !password || !tenantId) return json(res, 400, { ok: false, error: 'email, password, and tenantId required' });
|
|
3805
|
+
const result = registerUser(email, password, tenantId);
|
|
3806
|
+
if (!result.ok) return json(res, 409, result);
|
|
3807
|
+
const loginResult = loginUser(email, password);
|
|
3808
|
+
return json(res, 200, { ok: true, token: loginResult.session.token, role: loginResult.session.role, tenantId: loginResult.session.tenantId, email: loginResult.session.email });
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
if (url.pathname === '/hq/auth/session') {
|
|
3812
|
+
const authHeader = req.headers['authorization'] || '';
|
|
3813
|
+
const token = authHeader.replace(/^Bearer\s+/i, '').trim();
|
|
3814
|
+
if (!token) return json(res, 401, { ok: false, error: 'Token required' });
|
|
3815
|
+
const { validateSession } = await import('./auth-middleware.mjs');
|
|
3816
|
+
const session = validateSession(token);
|
|
3817
|
+
if (!session.valid) return json(res, 401, { ok: false, error: 'Invalid or expired session' });
|
|
3818
|
+
return json(res, 200, { ok: true, role: session.role, tenantId: session.tenantId, email: session.email });
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
if (url.pathname === '/hq/auth/logout') {
|
|
3822
|
+
const authHeader = req.headers['authorization'] || '';
|
|
3823
|
+
const token = authHeader.replace(/^Bearer\s+/i, '').trim();
|
|
3824
|
+
if (token) revokeSession(token);
|
|
3825
|
+
return json(res, 200, { ok: true });
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3828
|
+
// ── /hq/owner/* routes (owner-only) ──
|
|
3829
|
+
if (url.pathname === '/hq/owner/tenants') {
|
|
3830
|
+
const auth = req.hqAuth;
|
|
3831
|
+
if (!auth || auth.role !== 'owner') return json(res, 403, { ok: false, error: 'Owner access required' });
|
|
3832
|
+
const tenants = listAllTenants();
|
|
3833
|
+
const enriched = tenants.map(t => {
|
|
3834
|
+
const fleet = loadFleet(t.tenantId);
|
|
3835
|
+
return { ...t, fleet: fleet ? { deployed: true, activeAgents: fleet.agents?.length || 0, industry: fleet.config?.industry } : { deployed: false } };
|
|
3836
|
+
});
|
|
3837
|
+
return json(res, 200, { ok: true, tenants: enriched });
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
if (url.pathname === '/hq/owner/tenant/dashboard') {
|
|
3841
|
+
const auth = req.hqAuth;
|
|
3842
|
+
if (!auth || auth.role !== 'owner') return json(res, 403, { ok: false, error: 'Owner access required' });
|
|
3843
|
+
const { tenantId: targetTenant } = body;
|
|
3844
|
+
if (!targetTenant) return json(res, 400, { ok: false, error: 'targetTenant required' });
|
|
3845
|
+
const fleet = loadFleet(targetTenant);
|
|
3846
|
+
if (!fleet) return json(res, 404, { ok: false, error: 'No fleet for that tenant' });
|
|
3847
|
+
return json(res, 200, { ok: true, ...getFleetStatus(targetTenant) });
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
if (url.pathname === '/hq/metering/usage') {
|
|
3851
|
+
const { tenantId, from, to } = body;
|
|
3852
|
+
if (!tenantId) return json(res, 400, { ok: false, error: 'tenantId required' });
|
|
3853
|
+
return json(res, 200, { ok: true, ...getUsageSummary(tenantId, { from, to }) });
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
if (url.pathname === '/hq/metering/billing') {
|
|
3857
|
+
const { tenantId, tier } = body;
|
|
3858
|
+
if (!tenantId) return json(res, 400, { ok: false, error: 'tenantId required' });
|
|
3859
|
+
return json(res, 200, { ok: true, ...getBillingSummary(tenantId, tier || 'starter') });
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
if (url.pathname === '/hq/onboarding/chat') {
|
|
3863
|
+
const { tenantId, message, state: clientState } = body;
|
|
3864
|
+
if (!tenantId) return json(res, 400, { ok: false, error: 'tenantId required' });
|
|
3865
|
+
const { createOnboardingSession, loadSession, saveSession, processResponse, getStepPrompt, advanceStep } = await import('./onboarding-engine.mjs');
|
|
3866
|
+
let session = loadSession(tenantId) || createOnboardingSession(tenantId);
|
|
3867
|
+
if (clientState) Object.assign(session.data, clientState);
|
|
3868
|
+
session.history.push({ role: 'user', content: message || '', step: session.step, timestamp: new Date().toISOString() });
|
|
3869
|
+
|
|
3870
|
+
let ariaText = '';
|
|
3871
|
+
|
|
3872
|
+
// Password collection: skip LLM, take user message directly
|
|
3873
|
+
if (session.step === 'credentials' && session.data.email && !session.data.password) {
|
|
3874
|
+
session.data.password = message;
|
|
3875
|
+
session.step = advanceStep(session.step);
|
|
3876
|
+
ariaText = `Great, I have your email as ${session.data.email}. Now please type a password for your dashboard login. Make it at least 8 characters.`;
|
|
3877
|
+
} else {
|
|
3878
|
+
const stepPrompt = getStepPrompt(session.step);
|
|
3879
|
+
try {
|
|
3880
|
+
const providerResult = await callProviderForOpenAI({
|
|
3881
|
+
...body,
|
|
3882
|
+
messages: [
|
|
3883
|
+
{ role: 'system', content: stepPrompt },
|
|
3884
|
+
{ role: 'user', content: message || 'Hello' },
|
|
3885
|
+
],
|
|
3886
|
+
});
|
|
3887
|
+
ariaText = providerResult.text || '';
|
|
3888
|
+
} catch (err) {
|
|
3889
|
+
ariaText = `I'd love to chat more about setting up your AI workforce! Unfortunately, I'm having trouble connecting right now. Could you try again? (Error: ${err.message})`;
|
|
3890
|
+
}
|
|
3891
|
+
session = processResponse(session, ariaText);
|
|
3892
|
+
// After email is extracted, append password prompt
|
|
3893
|
+
if (session.step === 'credentials' && session.data.email && !session.data.password) {
|
|
3894
|
+
ariaText += '\n\nNow please type a password for your dashboard login (at least 8 characters). Your next message will be saved as your password.';
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
saveSession(session);
|
|
3898
|
+
|
|
3899
|
+
if (session.step === 'complete' && session.data.confirmed) {
|
|
3900
|
+
const { buildFleetConfig } = await import('./onboarding-engine.mjs');
|
|
3901
|
+
const fleetConfig = buildFleetConfig(session);
|
|
3902
|
+
const enqueueJob = (jobDef) => {
|
|
3903
|
+
const autonomyState = loadAutonomyState();
|
|
3904
|
+
const job = { jobId: randomUUID(), kind: jobDef.kind, surface: 'fleet', sessionId: null, payload: jobDef.payload || {}, metadata: jobDef.metadata || {}, priority: jobDef.priority || 100, status: 'queued', attempts: 0, maxAttempts: 3, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), workerId: null, claimedAt: null, claimExpiresAt: null, progress: [], garden: null };
|
|
3905
|
+
autonomyState.jobs.push(job);
|
|
3906
|
+
saveAutonomyState(autonomyState);
|
|
3907
|
+
return job;
|
|
3908
|
+
};
|
|
3909
|
+
const { fleet, workerRegistrations, initialJobs } = deployFleet(tenantId, fleetConfig, enqueueJob);
|
|
3910
|
+
const autonomyState = loadAutonomyState();
|
|
3911
|
+
for (const reg of workerRegistrations) { ensureWorker(autonomyState, reg.workerId, reg); }
|
|
3912
|
+
for (const jobDef of initialJobs) {
|
|
3913
|
+
const job = { jobId: randomUUID(), kind: jobDef.kind, surface: 'fleet', sessionId: null, payload: jobDef.payload || {}, metadata: jobDef.metadata || {}, priority: jobDef.priority || 100, status: 'queued', attempts: 0, maxAttempts: 3, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), workerId: null, claimedAt: null, claimExpiresAt: null, progress: [], garden: null };
|
|
3914
|
+
autonomyState.jobs.push(job);
|
|
3915
|
+
}
|
|
3916
|
+
saveAutonomyState(autonomyState);
|
|
3917
|
+
const apiKey = generateApiKey(tenantId);
|
|
3918
|
+
let authToken = null;
|
|
3919
|
+
if (session.data.email && session.data.password) {
|
|
3920
|
+
const regResult = registerUser(session.data.email, session.data.password, tenantId);
|
|
3921
|
+
if (regResult.ok) {
|
|
3922
|
+
const loginResult = loginUser(session.data.email, session.data.password);
|
|
3923
|
+
authToken = loginResult.ok ? loginResult.session.token : null;
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
return json(res, 200, { ok: true, step: 'complete', data: session.data, ariaMessage: ariaText, fleet: { deployed: true, agents: fleet.agents.length }, apiKey, authToken });
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
return json(res, 200, { ok: true, step: session.step, data: session.data, ariaMessage: ariaText });
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
if (url.pathname === '/hq/onboarding/status') {
|
|
3933
|
+
const { tenantId } = body;
|
|
3934
|
+
if (!tenantId) return json(res, 400, { ok: false, error: 'tenantId required' });
|
|
3935
|
+
const { loadSession } = await import('./onboarding-engine.mjs');
|
|
3936
|
+
const session = loadSession(tenantId);
|
|
3937
|
+
if (!session) return json(res, 200, { ok: true, step: 'not-started', data: {} });
|
|
3938
|
+
return json(res, 200, { ok: true, step: session.step, data: session.data, updatedAt: session.updatedAt });
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
if (url.pathname === '/hq/fleet/deploy') {
|
|
3942
|
+
const { tenantId, config } = body;
|
|
3943
|
+
if (!tenantId || !config) return json(res, 400, { ok: false, error: 'tenantId and config required' });
|
|
3944
|
+
const enqueueJob = (jobDef) => {
|
|
3945
|
+
const autonomyState = loadAutonomyState();
|
|
3946
|
+
const job = { jobId: randomUUID(), kind: jobDef.kind, surface: jobDef.surface || 'fleet', sessionId: null, payload: jobDef.payload || {}, metadata: jobDef.metadata || {}, priority: jobDef.priority || 100, status: 'queued', attempts: 0, maxAttempts: 3, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), workerId: null, claimedAt: null, claimExpiresAt: null, progress: [], garden: null };
|
|
3947
|
+
autonomyState.jobs.push(job);
|
|
3948
|
+
saveAutonomyState(autonomyState);
|
|
3949
|
+
return job;
|
|
3950
|
+
};
|
|
3951
|
+
const { fleet, workerRegistrations, initialJobs } = deployFleet(tenantId, config, enqueueJob);
|
|
3952
|
+
const autonomyState = loadAutonomyState();
|
|
3953
|
+
for (const reg of workerRegistrations) {
|
|
3954
|
+
ensureWorker(autonomyState, reg.workerId, reg);
|
|
3955
|
+
}
|
|
3956
|
+
for (const jobDef of initialJobs) {
|
|
3957
|
+
const job = { jobId: randomUUID(), kind: jobDef.kind, surface: jobDef.surface || 'fleet', sessionId: null, payload: jobDef.payload || {}, metadata: jobDef.metadata || {}, priority: jobDef.priority || 100, status: 'queued', attempts: 0, maxAttempts: 3, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), workerId: null, claimedAt: null, claimExpiresAt: null, progress: [], garden: null };
|
|
3958
|
+
autonomyState.jobs.push(job);
|
|
3959
|
+
}
|
|
3960
|
+
saveAutonomyState(autonomyState);
|
|
3961
|
+
return json(res, 200, { ok: true, fleet, workersRegistered: workerRegistrations.length, jobsEnqueued: initialJobs.length });
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
if (url.pathname === '/hq/fleet/status') {
|
|
3965
|
+
const { tenantId } = body;
|
|
3966
|
+
if (!tenantId) return json(res, 400, { ok: false, error: 'tenantId required' });
|
|
3967
|
+
return json(res, 200, getFleetStatus(tenantId));
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3970
|
+
if (url.pathname === '/hq/fleet/agent/chat') {
|
|
3971
|
+
const { tenantId, agentId, message: agentMsg } = body;
|
|
3972
|
+
if (!tenantId || !agentId || !agentMsg) return json(res, 400, { ok: false, error: 'tenantId, agentId, and message required' });
|
|
3973
|
+
const fleet = loadFleet(tenantId);
|
|
3974
|
+
if (!fleet) return json(res, 404, { ok: false, error: 'No fleet deployed' });
|
|
3975
|
+
const agent = (fleet.agents || []).find(a => a.id === agentId || a.templateId === agentId);
|
|
3976
|
+
if (!agent) return json(res, 404, { ok: false, error: `Agent ${agentId} not found` });
|
|
3977
|
+
const systemPrompt = buildAgentSystemPrompt(agent, fleet.config);
|
|
3978
|
+
let reply = '';
|
|
3979
|
+
try {
|
|
3980
|
+
const providerResult = await callProviderForOpenAI({
|
|
3981
|
+
...body,
|
|
3982
|
+
messages: [
|
|
3983
|
+
{ role: 'system', content: systemPrompt },
|
|
3984
|
+
{ role: 'user', content: agentMsg },
|
|
3985
|
+
],
|
|
3986
|
+
metadata: { ...body.metadata, agentId: agent.id, agentName: agent.name, department: agent.department, requestType: 'fleet-chat' },
|
|
3987
|
+
});
|
|
3988
|
+
reply = providerResult.text || '';
|
|
3989
|
+
} catch (err) {
|
|
3990
|
+
reply = `I'm having trouble connecting right now. Please try again. (Error: ${err.message})`;
|
|
3991
|
+
}
|
|
3992
|
+
try {
|
|
3993
|
+
if (providerResult.usage) {
|
|
3994
|
+
recordTokenUsage({
|
|
3995
|
+
tenantId: body.tenantId || 'owner-local',
|
|
3996
|
+
agentId: agent.id,
|
|
3997
|
+
agentName: agent.name,
|
|
3998
|
+
department: agent.department,
|
|
3999
|
+
sessionId: null,
|
|
4000
|
+
provider: 'deepseek',
|
|
4001
|
+
model: body.model || 'deepseek-v4-flash',
|
|
4002
|
+
usage: providerResult.usage,
|
|
4003
|
+
requestType: 'fleet-chat',
|
|
4004
|
+
});
|
|
4005
|
+
}
|
|
4006
|
+
} catch {}
|
|
4007
|
+
agent.lastActivity = new Date().toISOString();
|
|
4008
|
+
agent.stats.messagesSent = (agent.stats.messagesSent || 0) + 1;
|
|
4009
|
+
saveFleet(fleet);
|
|
4010
|
+
return json(res, 200, { ok: true, agentId, agentName: agent.name, department: agent.department, reply, status: agent.status });
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
if (url.pathname === '/hq/fleet/agent/action') {
|
|
4014
|
+
const { tenantId, agentId, action, payload: actionPayload } = body;
|
|
4015
|
+
if (!tenantId || !agentId || !action) return json(res, 400, { ok: false, error: 'tenantId, agentId, and action required' });
|
|
4016
|
+
const fleet = loadFleet(tenantId);
|
|
4017
|
+
if (!fleet) return json(res, 404, { ok: false, error: 'No fleet deployed' });
|
|
4018
|
+
const agent = (fleet.agents || []).find(a => a.id === agentId || a.templateId === agentId);
|
|
4019
|
+
if (!agent) return json(res, 404, { ok: false, error: `Agent ${agentId} not found` });
|
|
4020
|
+
const autonomyState = loadAutonomyState();
|
|
4021
|
+
const job = { jobId: randomUUID(), kind: `fleet:${agent.id}:${action}`, surface: 'fleet', sessionId: null, payload: actionPayload || {}, metadata: { tenantId, agentId: agent.id, agentName: agent.name, department: agent.department, action, requestType: 'fleet-action' }, priority: 100, status: 'queued', attempts: 0, maxAttempts: 3, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), workerId: null, claimedAt: null, claimExpiresAt: null, progress: [], garden: null };
|
|
4022
|
+
autonomyState.jobs.push(job);
|
|
4023
|
+
saveAutonomyState(autonomyState);
|
|
4024
|
+
agent.lastActivity = new Date().toISOString();
|
|
4025
|
+
saveFleet(fleet);
|
|
4026
|
+
return json(res, 200, { ok: true, agentId, action, jobId: job.jobId, result: 'enqueued' });
|
|
4027
|
+
}
|
|
4028
|
+
|
|
4029
|
+
if (url.pathname === '/hq/plugins/list') {
|
|
4030
|
+
const { tenantId } = body;
|
|
4031
|
+
return json(res, 200, { ok: true, plugins: listPlugins(tenantId || 'owner-local') });
|
|
4032
|
+
}
|
|
4033
|
+
|
|
4034
|
+
if (url.pathname === '/hq/plugins/install') {
|
|
4035
|
+
const { tenantId, pluginId, config: pluginConfig } = body;
|
|
4036
|
+
if (!tenantId || !pluginId) return json(res, 400, { ok: false, error: 'tenantId and pluginId required' });
|
|
4037
|
+
const result = installPlugin(tenantId, pluginId, pluginConfig);
|
|
4038
|
+
if (!result.ok) return json(res, 400, result);
|
|
4039
|
+
return json(res, 200, result);
|
|
4040
|
+
}
|
|
4041
|
+
|
|
4042
|
+
if (url.pathname === '/hq/plugins/configure') {
|
|
4043
|
+
const { tenantId, pluginId, config: pluginConfig } = body;
|
|
4044
|
+
if (!tenantId || !pluginId) return json(res, 400, { ok: false, error: 'tenantId and pluginId required' });
|
|
4045
|
+
const result = configurePlugin(tenantId, pluginId, pluginConfig);
|
|
4046
|
+
if (!result.ok) return json(res, result.error.startsWith('No plugins') ? 404 : 400, result);
|
|
4047
|
+
return json(res, 200, result);
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
if (url.pathname === '/hq/workflows/list') {
|
|
4051
|
+
return json(res, 200, { ok: true, workflows: listWorkflowTemplates() });
|
|
4052
|
+
}
|
|
4053
|
+
|
|
4054
|
+
if (url.pathname === '/hq/workflows/configure') {
|
|
4055
|
+
const { tenantId, workflowId, config: workflowConfig } = body;
|
|
4056
|
+
if (!tenantId || !workflowId) return json(res, 400, { ok: false, error: 'tenantId and workflowId required' });
|
|
4057
|
+
const result = configureWorkflow(tenantId, workflowId, workflowConfig);
|
|
4058
|
+
if (!result.ok) return json(res, 400, result);
|
|
4059
|
+
return json(res, 200, result);
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
if (url.pathname === '/hq/workflows/start') {
|
|
4063
|
+
const { tenantId, workflowId, payload } = body;
|
|
4064
|
+
if (!tenantId || !workflowId) return json(res, 400, { ok: false, error: 'tenantId and workflowId required' });
|
|
4065
|
+
const result = startWorkflow(tenantId, workflowId, payload);
|
|
4066
|
+
if (!result.ok) return json(res, 400, result);
|
|
4067
|
+
return json(res, 200, result);
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
if (url.pathname === '/hq/workflows/approve') {
|
|
4071
|
+
const { tenantId, instanceId, approved } = body;
|
|
4072
|
+
if (!tenantId || !instanceId) return json(res, 400, { ok: false, error: 'tenantId and instanceId required' });
|
|
4073
|
+
const result = approveWorkflowStep(tenantId, instanceId, approved !== false);
|
|
4074
|
+
if (!result.ok) return json(res, 400, result);
|
|
4075
|
+
return json(res, 200, result);
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
if (url.pathname === '/hq/workflows/status') {
|
|
4079
|
+
const { tenantId } = body;
|
|
4080
|
+
if (!tenantId) return json(res, 400, { ok: false, error: 'tenantId required' });
|
|
4081
|
+
return json(res, 200, { ok: true, ...getWorkflowStatus(tenantId) });
|
|
4082
|
+
}
|
|
4083
|
+
|
|
4084
|
+
if (url.pathname === '/hq/subscription/tier') {
|
|
4085
|
+
const { tier } = body;
|
|
4086
|
+
return json(res, 200, { ok: true, tier: getSubscriptionTier(tier) });
|
|
4087
|
+
}
|
|
4088
|
+
|
|
3006
4089
|
return json(res, 404, { ok: false, error: `No route for POST ${url.pathname}` });
|
|
3007
4090
|
} catch (error) {
|
|
3008
4091
|
return json(res, 500, {
|
|
@@ -3028,6 +4111,15 @@ export async function startRuntimeServer(options = {}) {
|
|
|
3028
4111
|
});
|
|
3029
4112
|
});
|
|
3030
4113
|
});
|
|
4114
|
+
server.on('upgrade', (req, socket) => {
|
|
4115
|
+
try {
|
|
4116
|
+
handleWebSocketUpgrade(req, socket);
|
|
4117
|
+
} catch {
|
|
4118
|
+
try {
|
|
4119
|
+
socket.destroy();
|
|
4120
|
+
} catch {}
|
|
4121
|
+
}
|
|
4122
|
+
});
|
|
3031
4123
|
|
|
3032
4124
|
await new Promise((resolve, reject) => {
|
|
3033
4125
|
server.once('error', reject);
|