@aria_asi/cli 0.2.29 → 0.2.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/claude-code.js +88 -20
- package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
- package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/codex.js +526 -2
- package/dist/aria-connector/src/connectors/codex.js.map +1 -1
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts +7 -0
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.js +87 -0
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.js.map +1 -0
- package/dist/aria-connector/src/connectors/must-read.d.ts +4 -0
- package/dist/aria-connector/src/connectors/must-read.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/must-read.js +111 -0
- package/dist/aria-connector/src/connectors/must-read.js.map +1 -0
- package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/opencode.js +2 -0
- package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
- package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/runtime.js +231 -19
- package/dist/aria-connector/src/connectors/runtime.js.map +1 -1
- package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/shell.js +76 -3
- package/dist/aria-connector/src/connectors/shell.js.map +1 -1
- package/dist/aria-connector/src/self-update.d.ts +2 -1
- package/dist/aria-connector/src/self-update.d.ts.map +1 -1
- package/dist/aria-connector/src/self-update.js +84 -8
- package/dist/aria-connector/src/self-update.js.map +1 -1
- package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +53 -34
- package/dist/assets/hooks/aria-harness-via-sdk.mjs +126 -12
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +185 -76
- package/dist/assets/hooks/aria-preturn-memory-gate.mjs +63 -14
- package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +2 -0
- package/dist/assets/hooks/aria-stop-gate.mjs +225 -52
- package/dist/assets/hooks/lib/canonical-lenses.mjs +6 -5
- package/dist/assets/hooks/lib/gate-loop-state.mjs +50 -0
- package/dist/assets/hooks/lib/hook-message-window.mjs +121 -0
- package/dist/assets/hooks/test-tier-lens-labeling.mjs +26 -58
- package/dist/assets/opencode-plugins/harness-gate/index.js +24 -2
- package/dist/assets/opencode-plugins/harness-stop/index.js +94 -5
- package/dist/runtime/auth-middleware.mjs +251 -0
- package/dist/runtime/codex-bridge.mjs +644 -0
- package/dist/runtime/discipline/CLAUDE.md +12 -0
- package/dist/runtime/discipline/doctrine_trigger_map.json +479 -0
- package/dist/runtime/doctrine_trigger_map.json +479 -0
- package/dist/runtime/fleet-engine.mjs +231 -0
- package/dist/runtime/harness-daemon.mjs +433 -0
- package/dist/runtime/local-phase.mjs +18 -0
- package/dist/runtime/manifest.json +1 -1
- package/dist/runtime/metering.mjs +100 -0
- package/dist/runtime/onboarding-engine.mjs +89 -0
- package/dist/runtime/plugin-engine.mjs +196 -0
- package/dist/runtime/sdk/BUNDLED.json +1 -1
- package/dist/runtime/sdk/index.d.ts +7 -0
- package/dist/runtime/sdk/index.js +120 -14
- package/dist/runtime/sdk/index.js.map +1 -1
- package/dist/runtime/service.mjs +1464 -67
- package/dist/runtime/vendor/aria-gate-runtime/index.d.ts +1 -1
- package/dist/runtime/vendor/aria-gate-runtime/index.d.ts.map +1 -1
- package/dist/runtime/vendor/aria-gate-runtime/index.js +16 -1
- package/dist/runtime/vendor/aria-gate-runtime/index.js.map +1 -1
- package/dist/runtime/workflow-engine.mjs +322 -0
- package/dist/sdk/BUNDLED.json +1 -1
- package/dist/sdk/index.d.ts +7 -0
- package/dist/sdk/index.js +120 -14
- package/dist/sdk/index.js.map +1 -1
- package/hooks/aria-cognition-substrate-binding.mjs +53 -34
- package/hooks/aria-harness-via-sdk.mjs +126 -12
- package/hooks/aria-pre-tool-gate.mjs +185 -76
- package/hooks/aria-preturn-memory-gate.mjs +63 -14
- package/hooks/aria-repo-doctrine-gate.mjs +2 -0
- package/hooks/aria-stop-gate.mjs +225 -52
- package/hooks/lib/canonical-lenses.mjs +6 -5
- package/hooks/lib/gate-loop-state.mjs +50 -0
- package/hooks/lib/hook-message-window.mjs +121 -0
- package/hooks/test-tier-lens-labeling.mjs +26 -58
- package/opencode-plugins/harness-gate/index.js +24 -2
- package/opencode-plugins/harness-stop/index.js +94 -5
- package/package.json +2 -2
- package/runtime-src/auth-middleware.mjs +251 -0
- package/runtime-src/codex-bridge.mjs +644 -0
- package/runtime-src/fleet-engine.mjs +231 -0
- package/runtime-src/harness-daemon.mjs +433 -0
- package/runtime-src/local-phase.mjs +18 -0
- package/runtime-src/metering.mjs +100 -0
- package/runtime-src/onboarding-engine.mjs +89 -0
- package/runtime-src/plugin-engine.mjs +196 -0
- package/runtime-src/service.mjs +1464 -67
- package/runtime-src/workflow-engine.mjs +322 -0
- package/scripts/bundle-sdk.mjs +5 -0
- package/src/connectors/claude-code.ts +98 -20
- package/src/connectors/codex.ts +534 -1
- package/src/connectors/doctrine-trigger-map.ts +112 -0
- package/src/connectors/must-read.ts +113 -0
- package/src/connectors/opencode.ts +3 -0
- package/src/connectors/runtime.ts +241 -21
- package/src/connectors/shell.ts +78 -3
- package/src/self-update.ts +89 -8
- package/dist/cli-0.2.0.tgz +0 -0
package/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;
|
|
@@ -65,12 +82,345 @@ const PRINCIPLE_LIMIT = 400;
|
|
|
65
82
|
const PATTERN_LIMIT = 400;
|
|
66
83
|
const RECEIPT_LIMIT = 500;
|
|
67
84
|
const OWNER_TOKEN_PATH = join(process.env.HOME || '', '.aria', 'owner-token');
|
|
85
|
+
const COGNITION_BLOCK_RX = /<cognition>([\s\S]*?)<\/cognition>/i;
|
|
86
|
+
const VERIFY_BLOCK_RX =
|
|
87
|
+
/<verify>[\s\S]*?target\s*:[\s\S]*?role\s*:[\s\S]*?verified\s*:[\s\S]*?rollback\s*:[\s\S]*?axiom\s*:[\s\S]*?<\/verify>/i;
|
|
88
|
+
const EXPECTED_BLOCK_RX = /<expected>([\s\S]*?)<\/expected>/i;
|
|
89
|
+
const MEASURABLE_PREDICATE_RX =
|
|
90
|
+
/\b(?:exit_code|exit|rc|status|state|count|latency|error_rate|healthy|exists|http|rows?|bytes?|sha|heartbeat|predicate)\b\s*(?:[:=]|==|>=|<=|>|<)\s*[^\n]+|\b(?:true|false)\b|\b\d+(?:\.\d+)?%?\b/i;
|
|
91
|
+
const QUALITATIVE_DRIFT_RX = /\b(?:better|improved|should work|more reliable|cleaner|enhanced)\b/i;
|
|
92
|
+
const DECISION_SIGNAL_RX = /(?:should|recommend|propose|suggest|let'?s|go with|i'd|i would|here'?s the plan|i'll|next step|action item|ship it|yes do|let me)/i;
|
|
93
|
+
const TRIVIAL_ACK_RX = /^(?:got it|on it|ok|sure|yes|no|done|ack)\b/i;
|
|
94
|
+
const NON_TRIVIAL_MIN_CHARS = 300;
|
|
95
|
+
const READABLE_LENS_SLOTS = [
|
|
96
|
+
{ key: 'truth', aliases: ['truth', 'nur', 'zahir'] },
|
|
97
|
+
{ key: 'harm', aliases: ['harm', 'mizan', 'batin'] },
|
|
98
|
+
{ key: 'trust', aliases: ['trust', 'hikma', 'hikmah'] },
|
|
99
|
+
{ key: 'power', aliases: ['power', 'tafakkur', 'sabab'] },
|
|
100
|
+
{ key: 'reflection', aliases: ['reflection', 'tadabbur', 'aqibah'] },
|
|
101
|
+
{ key: 'context', aliases: ['context', 'ilham'] },
|
|
102
|
+
{ key: 'impact', aliases: ['impact', 'wahi', 'meta'] },
|
|
103
|
+
{ key: 'beauty', aliases: ['beauty', 'firasah', 'fitrah'] },
|
|
104
|
+
];
|
|
105
|
+
const DOCTRINE_TRIGGER_MAP_CANDIDATES = [
|
|
106
|
+
join(__dirname, 'discipline', 'doctrine_trigger_map.json'),
|
|
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'),
|
|
110
|
+
join(process.env.HOME || '', '.claude', 'hooks', 'doctrine_trigger_map.json'),
|
|
111
|
+
join(process.env.HOME || '', '.claude', 'projects', '-home-hamzaibrahim1', 'memory', 'doctrine_trigger_map.json'),
|
|
112
|
+
join(process.env.HOME || '', '.codex', 'doctrine_trigger_map.json'),
|
|
113
|
+
join(process.env.HOME || '', '.opencode', 'doctrine_trigger_map.json'),
|
|
114
|
+
];
|
|
115
|
+
const DOCTRINE_TRIGGER_SYNC_INTERVAL_MS = Number(process.env.ARIA_DOCTRINE_TRIGGER_SYNC_INTERVAL_MS || 5000);
|
|
116
|
+
const TOOL_DEPLOY_PATTERNS = [
|
|
117
|
+
/\b(?:\.\/)?scripts\/deploy-/i,
|
|
118
|
+
/\bkubectl\s+apply\b/i,
|
|
119
|
+
/\bkubectl\s+set\s+image\b/i,
|
|
120
|
+
/\bkubectl\s+rollout\s+restart\b/i,
|
|
121
|
+
/\bkubectl\s+rollout\s+undo\b/i,
|
|
122
|
+
/\bdocker\s+push\b/i,
|
|
123
|
+
/\bdocker\s+build\b.*--push\b/i,
|
|
124
|
+
];
|
|
125
|
+
const TOOL_DESTRUCTIVE_PATTERNS = [
|
|
126
|
+
/(?:^|[;&|]\s*|\$\(\s*|`\s*)sudo\s+\S/i,
|
|
127
|
+
/systemctl\s+(?:disable|stop|mask|reset-failed|kill)\b/i,
|
|
128
|
+
/\brm\s+-[rRfF]+/i,
|
|
129
|
+
/\bgit\s+push\b.*\b--force\b/i,
|
|
130
|
+
/\bgit\s+reset\s+--hard\b/i,
|
|
131
|
+
/\bgit\s+checkout\s+--\b/i,
|
|
132
|
+
/\b--no-verify\b/i,
|
|
133
|
+
/\b--no-gpg-sign\b/i,
|
|
134
|
+
/\bkill\s+-(?:9|KILL|TERM|HUP|INT)\b/i,
|
|
135
|
+
/\bpkill\b/i,
|
|
136
|
+
/\b(?:DROP|TRUNCATE)\s+(?:TABLE|DATABASE|SCHEMA|INDEX)\b/i,
|
|
137
|
+
/\bkubectl\s+delete\b/i,
|
|
138
|
+
];
|
|
139
|
+
let doctrineTriggerMapCache = { sourcePath: null, map: { triggers: [] } };
|
|
140
|
+
let doctrineTriggerMapSyncedAt = 0;
|
|
68
141
|
|
|
69
142
|
function json(res, status, payload) {
|
|
70
143
|
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
|
|
71
144
|
res.end(JSON.stringify(payload, null, 2));
|
|
72
145
|
}
|
|
73
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
|
+
|
|
208
|
+
function isNonTrivialAssistantTurn(text, toolIntents = []) {
|
|
209
|
+
const body = String(text || '').trim();
|
|
210
|
+
if (toolIntents.length > 0) return true;
|
|
211
|
+
if (!body) return false;
|
|
212
|
+
if (TRIVIAL_ACK_RX.test(body)) return false;
|
|
213
|
+
return body.length >= NON_TRIVIAL_MIN_CHARS || DECISION_SIGNAL_RX.test(body);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function extractVisibleCognitionContract(text) {
|
|
217
|
+
const match = String(text || '').match(COGNITION_BLOCK_RX);
|
|
218
|
+
if (!match) {
|
|
219
|
+
return {
|
|
220
|
+
present: false,
|
|
221
|
+
readable: null,
|
|
222
|
+
rawBlock: null,
|
|
223
|
+
firstPrinciple: null,
|
|
224
|
+
labels: [],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const inner = match[1];
|
|
229
|
+
const readable = {};
|
|
230
|
+
const labels = [];
|
|
231
|
+
for (const slot of READABLE_LENS_SLOTS) {
|
|
232
|
+
const aliasPattern = slot.aliases.join('|');
|
|
233
|
+
const lensRx = new RegExp(
|
|
234
|
+
`\\b(?:${aliasPattern})\\s*:\\s*([^\\n]*(?:\\n(?!\\s*(?:${READABLE_LENS_SLOTS.flatMap((entry) => entry.aliases).join('|')})\\s*:|<\\/cognition>)[^\\n]*)*)`,
|
|
235
|
+
'i',
|
|
236
|
+
);
|
|
237
|
+
const lensMatch = inner.match(lensRx);
|
|
238
|
+
if (!lensMatch) continue;
|
|
239
|
+
readable[slot.key] = (lensMatch[1] || '').trim();
|
|
240
|
+
labels.push(slot.key);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const firstPrincipleMatch = inner.match(/\bfirst[_\s-]?principle\s*:\s*([^\n][\s\S]*?)(?=\n\s*[a-z_ -]+\s*:|$)/i);
|
|
244
|
+
return {
|
|
245
|
+
present: true,
|
|
246
|
+
readable,
|
|
247
|
+
rawBlock: match[0],
|
|
248
|
+
firstPrinciple: firstPrincipleMatch ? firstPrincipleMatch[1].trim() : null,
|
|
249
|
+
labels,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function hasMeasurableExpectedBlock(text) {
|
|
254
|
+
const match = String(text || '').match(EXPECTED_BLOCK_RX);
|
|
255
|
+
if (!match) return false;
|
|
256
|
+
const inner = match[1].trim();
|
|
257
|
+
if (!inner) return false;
|
|
258
|
+
if (QUALITATIVE_DRIFT_RX.test(inner) && !MEASURABLE_PREDICATE_RX.test(inner)) return false;
|
|
259
|
+
return MEASURABLE_PREDICATE_RX.test(inner);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function parseToolArgs(rawArgs) {
|
|
263
|
+
if (!rawArgs) return {};
|
|
264
|
+
if (typeof rawArgs === 'object') return rawArgs;
|
|
265
|
+
if (typeof rawArgs !== 'string') return {};
|
|
266
|
+
try {
|
|
267
|
+
return JSON.parse(rawArgs);
|
|
268
|
+
} catch {
|
|
269
|
+
return { raw: rawArgs };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function inferToolAction(toolName, args) {
|
|
274
|
+
const lower = String(toolName || '').toLowerCase();
|
|
275
|
+
if (args && typeof args.command === 'string') return 'bash';
|
|
276
|
+
if (args && (typeof args.file_path === 'string' || typeof args.notebook_path === 'string')) return 'edit';
|
|
277
|
+
if (/\b(?:bash|shell|terminal|command)\b/.test(lower)) return 'bash';
|
|
278
|
+
if (/\b(?:edit|write|notebook)\b/.test(lower)) return 'edit';
|
|
279
|
+
if (/\b(?:deploy|rollout|release)\b/.test(lower)) return 'deploy';
|
|
280
|
+
if (/\b(?:delete|destroy|drop|wipe|purge)\b/.test(lower)) return 'delete';
|
|
281
|
+
if (/\b(?:build|compile)\b/.test(lower)) return 'build';
|
|
282
|
+
return 'tool';
|
|
283
|
+
}
|
|
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
|
+
|
|
296
|
+
function summarizeToolTarget(toolName, args) {
|
|
297
|
+
if (typeof args?.command === 'string' && args.command.trim()) return args.command.trim();
|
|
298
|
+
if (typeof args?.file_path === 'string' && args.file_path.trim()) return args.file_path.trim();
|
|
299
|
+
if (typeof args?.notebook_path === 'string' && args.notebook_path.trim()) return args.notebook_path.trim();
|
|
300
|
+
if (typeof args?.path === 'string' && args.path.trim()) return args.path.trim();
|
|
301
|
+
if (typeof args?.target === 'string' && args.target.trim()) return args.target.trim();
|
|
302
|
+
return `${toolName}${Object.keys(args || {}).length ? ` ${JSON.stringify(args).slice(0, 200)}` : ''}`.trim();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function extractProviderToolIntents(providerStyle, providerMeta) {
|
|
306
|
+
if (providerStyle === 'anthropic') {
|
|
307
|
+
const content = Array.isArray(providerMeta?.raw?.content) ? providerMeta.raw.content : [];
|
|
308
|
+
return content
|
|
309
|
+
.filter((block) => block?.type === 'tool_use')
|
|
310
|
+
.map((block) => {
|
|
311
|
+
const args = parseToolArgs(block.input || {});
|
|
312
|
+
return {
|
|
313
|
+
provider: 'anthropic',
|
|
314
|
+
id: block.id || null,
|
|
315
|
+
toolName: block.name || 'tool_use',
|
|
316
|
+
args,
|
|
317
|
+
action: inferToolAction(block.name || 'tool_use', args),
|
|
318
|
+
target: summarizeToolTarget(block.name || 'tool_use', args),
|
|
319
|
+
raw: block,
|
|
320
|
+
};
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const rawMessage = providerMeta?.raw?.choices?.[0]?.message || {};
|
|
325
|
+
const toolCalls = Array.isArray(rawMessage.tool_calls) ? rawMessage.tool_calls : [];
|
|
326
|
+
return toolCalls.map((toolCall) => {
|
|
327
|
+
const name = toolCall?.function?.name || toolCall?.name || 'tool_call';
|
|
328
|
+
const args = parseToolArgs(toolCall?.function?.arguments || toolCall?.arguments || {});
|
|
329
|
+
return {
|
|
330
|
+
provider: 'openai',
|
|
331
|
+
id: toolCall?.id || null,
|
|
332
|
+
toolName: name,
|
|
333
|
+
args,
|
|
334
|
+
action: inferToolAction(name, args),
|
|
335
|
+
target: summarizeToolTarget(name, args),
|
|
336
|
+
raw: toolCall,
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
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 = [];
|
|
348
|
+
for (const candidate of DOCTRINE_TRIGGER_MAP_CANDIDATES) {
|
|
349
|
+
const map = readJsonFile(candidate, null);
|
|
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 {}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
doctrineTriggerMapCache = { sourcePath: latest.path, map: latest.map };
|
|
383
|
+
doctrineTriggerMapSyncedAt = now;
|
|
384
|
+
return latest.map;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function collectDoctrineTriggerHits(text) {
|
|
388
|
+
const triggerMap = loadDoctrineTriggerMap();
|
|
389
|
+
const source = String(text || '');
|
|
390
|
+
const lowerText = source.toLowerCase();
|
|
391
|
+
const hits = [];
|
|
392
|
+
for (const entry of triggerMap.triggers || []) {
|
|
393
|
+
try {
|
|
394
|
+
const rx = new RegExp(entry.trigger, 'ig');
|
|
395
|
+
const matched = [...source.matchAll(rx)][0];
|
|
396
|
+
if (!matched) continue;
|
|
397
|
+
const memoryName = typeof entry.memory === 'string' ? entry.memory.replace(/\.md$/, '') : '';
|
|
398
|
+
const memoryCited = memoryName && lowerText.includes(memoryName.toLowerCase());
|
|
399
|
+
if (memoryCited) continue;
|
|
400
|
+
hits.push({
|
|
401
|
+
trigger: entry.trigger,
|
|
402
|
+
memory: entry.memory || null,
|
|
403
|
+
teaching: entry.teaching || null,
|
|
404
|
+
message: entry.message || null,
|
|
405
|
+
severity: entry.severity || 'block',
|
|
406
|
+
});
|
|
407
|
+
} catch {}
|
|
408
|
+
}
|
|
409
|
+
return hits;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function buildToolGateBlockMessage(blockers) {
|
|
413
|
+
const reasons = blockers.map((blocker) => `- ${blocker}`).join('\n');
|
|
414
|
+
return [
|
|
415
|
+
'Aria runtime blocked the requested tool action.',
|
|
416
|
+
'',
|
|
417
|
+
reasons,
|
|
418
|
+
'',
|
|
419
|
+
'Re-draft with a readable <cognition> block before the tool request.',
|
|
420
|
+
'If the action is deploy, destructive, or state-mutating, include <verify> and <expected> blocks as well.',
|
|
421
|
+
].join('\n');
|
|
422
|
+
}
|
|
423
|
+
|
|
74
424
|
async function readJson(req) {
|
|
75
425
|
const chunks = [];
|
|
76
426
|
for await (const chunk of req) chunks.push(chunk);
|
|
@@ -124,6 +474,26 @@ function synthesizeOwnerLease(apiKey, reason) {
|
|
|
124
474
|
};
|
|
125
475
|
}
|
|
126
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
|
+
|
|
127
497
|
function deriveEncryptionKey(secret) {
|
|
128
498
|
return scryptSync(secret, 'aria-mounted-runtime', 32);
|
|
129
499
|
}
|
|
@@ -173,10 +543,130 @@ function saveEncryptedLease(lease, secret) {
|
|
|
173
543
|
writeFileSync(LEASE_PATH, encryptJson(lease, secret), { mode: 0o600 });
|
|
174
544
|
}
|
|
175
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
|
+
|
|
176
665
|
function defaultCognitionState() {
|
|
177
666
|
return {
|
|
178
667
|
telemetry: [],
|
|
179
668
|
decisions: [],
|
|
669
|
+
pendingDecisions: [],
|
|
180
670
|
evolutionPrinciples: [],
|
|
181
671
|
aegisPatterns: [],
|
|
182
672
|
heartbeats: [],
|
|
@@ -369,6 +859,7 @@ function summarizeCognitionState(state) {
|
|
|
369
859
|
return {
|
|
370
860
|
telemetryTurns: Array.isArray(state.telemetry) ? state.telemetry.length : 0,
|
|
371
861
|
decisions: Array.isArray(state.decisions) ? state.decisions.length : 0,
|
|
862
|
+
pendingDecisionUploads: Array.isArray(state.pendingDecisions) ? state.pendingDecisions.length : 0,
|
|
372
863
|
evolutionPrinciples: Array.isArray(state.evolutionPrinciples) ? state.evolutionPrinciples.length : 0,
|
|
373
864
|
aegisPatterns: Array.isArray(state.aegisPatterns) ? state.aegisPatterns.length : 0,
|
|
374
865
|
heartbeats: Array.isArray(state.heartbeats) ? state.heartbeats.length : 0,
|
|
@@ -610,6 +1101,15 @@ async function heartbeatUpstream(req, body, client, apiKey) {
|
|
|
610
1101
|
},
|
|
611
1102
|
};
|
|
612
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
|
+
});
|
|
613
1113
|
} catch (error) {
|
|
614
1114
|
if (!ownerBypassAllowed) {
|
|
615
1115
|
throw error;
|
|
@@ -666,14 +1166,45 @@ async function ensureLease(req, body, client) {
|
|
|
666
1166
|
|
|
667
1167
|
const keyHash = hashKey(apiKey);
|
|
668
1168
|
const cached = leaseCache.get(keyHash) || loadEncryptedLease(apiKey);
|
|
1169
|
+
ensureOfflineBundleSeeded(apiKey, cached);
|
|
669
1170
|
if (cached && cached.hardStopAt > Date.now()) {
|
|
670
1171
|
leaseCache.set(keyHash, cached);
|
|
671
1172
|
if (cached.nextRequiredAt > Date.now()) {
|
|
672
1173
|
return cached;
|
|
673
1174
|
}
|
|
674
1175
|
}
|
|
675
|
-
|
|
676
|
-
|
|
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
|
+
}
|
|
677
1208
|
}
|
|
678
1209
|
|
|
679
1210
|
function deriveSessionId(req, body, prefix = 'runtime') {
|
|
@@ -732,6 +1263,7 @@ async function pushTelemetryUpstream(client, apiKey, payload) {
|
|
|
732
1263
|
}
|
|
733
1264
|
|
|
734
1265
|
async function pushDecisionUpstream(client, apiKey, payload) {
|
|
1266
|
+
const normalizedPayload = normalizeDecisionPayload(payload);
|
|
735
1267
|
const url = `${client.baseUrl || DEFAULT_HARNESS_URL}/api/decisions/log`;
|
|
736
1268
|
const response = await fetch(url, {
|
|
737
1269
|
method: 'POST',
|
|
@@ -739,7 +1271,7 @@ async function pushDecisionUpstream(client, apiKey, payload) {
|
|
|
739
1271
|
Authorization: `Bearer ${apiKey}`,
|
|
740
1272
|
'Content-Type': 'application/json',
|
|
741
1273
|
},
|
|
742
|
-
body: JSON.stringify(
|
|
1274
|
+
body: JSON.stringify(normalizedPayload),
|
|
743
1275
|
});
|
|
744
1276
|
if (!response.ok) {
|
|
745
1277
|
const body = await response.text().catch(() => response.statusText);
|
|
@@ -748,6 +1280,88 @@ async function pushDecisionUpstream(client, apiKey, payload) {
|
|
|
748
1280
|
return response.json().catch(() => ({ logged: true }));
|
|
749
1281
|
}
|
|
750
1282
|
|
|
1283
|
+
function normalizeDecisionPayload(payload) {
|
|
1284
|
+
const sessionId = payload?.session_id || payload?.sessionId || null;
|
|
1285
|
+
const decisionType = payload?.decision_type || payload?.decisionType || 'operational';
|
|
1286
|
+
const category = payload?.category || payload?.decision_category || payload?.decisionCategory || payload?.surface || 'runtime';
|
|
1287
|
+
const decision = payload?.decision || payload?.summary || payload?.outcome || decisionType;
|
|
1288
|
+
const reasoning =
|
|
1289
|
+
payload?.reasoning ||
|
|
1290
|
+
payload?.summary ||
|
|
1291
|
+
(payload?.outcome ? `Outcome: ${payload.outcome}` : 'Runtime decision log');
|
|
1292
|
+
const context =
|
|
1293
|
+
payload?.context ||
|
|
1294
|
+
(sessionId ? `Session ${sessionId}` : 'Runtime decision log');
|
|
1295
|
+
const outcomeRaw = String(payload?.outcome || '').toLowerCase();
|
|
1296
|
+
const outcome =
|
|
1297
|
+
outcomeRaw === 'validated' ? 'success'
|
|
1298
|
+
: outcomeRaw === 'error' ? 'failure'
|
|
1299
|
+
: ['success', 'failure', 'neutral', 'pending'].includes(outcomeRaw) ? outcomeRaw
|
|
1300
|
+
: undefined;
|
|
1301
|
+
|
|
1302
|
+
return {
|
|
1303
|
+
...payload,
|
|
1304
|
+
session_id: sessionId,
|
|
1305
|
+
decision_type: decisionType,
|
|
1306
|
+
category,
|
|
1307
|
+
context,
|
|
1308
|
+
decision,
|
|
1309
|
+
reasoning,
|
|
1310
|
+
...(outcome ? { outcome } : {}),
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function delay(ms) {
|
|
1315
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
async function pushDecisionUpstreamWithRetry(client, apiKey, payload, attempts = 3) {
|
|
1319
|
+
let lastError = null;
|
|
1320
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
1321
|
+
try {
|
|
1322
|
+
return await pushDecisionUpstream(client, apiKey, payload);
|
|
1323
|
+
} catch (error) {
|
|
1324
|
+
lastError = error;
|
|
1325
|
+
if (attempt === attempts - 1) break;
|
|
1326
|
+
await delay(250 * (2 ** attempt));
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
throw lastError || new Error('decision upstream failed');
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
async function flushPendingDecisionUploads(client, apiKey) {
|
|
1333
|
+
const state = loadEncryptedCognitionState(apiKey);
|
|
1334
|
+
const pending = Array.isArray(state.pendingDecisions) ? state.pendingDecisions : [];
|
|
1335
|
+
if (!pending.length) {
|
|
1336
|
+
return { flushed: 0, retained: 0, lastError: null };
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
const remaining = [];
|
|
1340
|
+
let flushed = 0;
|
|
1341
|
+
let lastError = null;
|
|
1342
|
+
for (const entry of pending) {
|
|
1343
|
+
try {
|
|
1344
|
+
await pushDecisionUpstreamWithRetry(client, apiKey, entry.payload);
|
|
1345
|
+
flushed++;
|
|
1346
|
+
} catch (error) {
|
|
1347
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
1348
|
+
remaining.push({
|
|
1349
|
+
...entry,
|
|
1350
|
+
attempts: Number(entry.attempts || 0) + 1,
|
|
1351
|
+
lastError,
|
|
1352
|
+
lastTriedAt: new Date().toISOString(),
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
mutateCognitionState(apiKey, (current) => ({
|
|
1358
|
+
...current,
|
|
1359
|
+
pendingDecisions: remaining,
|
|
1360
|
+
}));
|
|
1361
|
+
|
|
1362
|
+
return { flushed, retained: remaining.length, lastError };
|
|
1363
|
+
}
|
|
1364
|
+
|
|
751
1365
|
function buildMinimalInjection(packet, task, aegisLearnings = null) {
|
|
752
1366
|
return {
|
|
753
1367
|
harness: packet,
|
|
@@ -805,9 +1419,35 @@ function buildOwnerBypassPacket(message, reason = 'owner-local-bypass') {
|
|
|
805
1419
|
|
|
806
1420
|
async function loadRuntimePacket(req, body, client, packetRequest, message) {
|
|
807
1421
|
if (body.packet) return body.packet;
|
|
1422
|
+
const apiKey = resolveApiKey(req, body);
|
|
1423
|
+
ensureOfflineBundleSeeded(apiKey, leaseCache.get(hashKey(apiKey)) || loadEncryptedLease(apiKey));
|
|
808
1424
|
try {
|
|
809
|
-
|
|
1425
|
+
const wrapped = await client.getHarnessPacket(packetRequest || {});
|
|
1426
|
+
const packet = normalizeHarnessPacketPayload(wrapped?.packet || wrapped);
|
|
1427
|
+
const lease = leaseCache.get(hashKey(apiKey)) || loadEncryptedLease(apiKey);
|
|
1428
|
+
persistOfflineBundle(apiKey, {
|
|
1429
|
+
keyHash: hashKey(apiKey),
|
|
1430
|
+
packet,
|
|
1431
|
+
lease,
|
|
1432
|
+
source: client.baseUrl || DEFAULT_HARNESS_URL,
|
|
1433
|
+
doctrineBundleHash: lease?.claims?.doctrine_bundle_hash || null,
|
|
1434
|
+
lastUpstreamError: null,
|
|
1435
|
+
});
|
|
1436
|
+
return packet;
|
|
810
1437
|
} catch (error) {
|
|
1438
|
+
const bundle = ensureOfflineBundleSeeded(apiKey, leaseCache.get(hashKey(apiKey)) || loadEncryptedLease(apiKey)) || loadEncryptedOfflineBundle(apiKey);
|
|
1439
|
+
const bundleStatus = computeOfflineBundleStatus(bundle);
|
|
1440
|
+
if (bundle?.keyHash === hashKey(apiKey) && bundleStatus.usable) {
|
|
1441
|
+
const fallbackPacket = buildOfflineBundleFallbackPacket(bundle, bundleStatus);
|
|
1442
|
+
if (fallbackPacket) {
|
|
1443
|
+
persistOfflineBundle(apiKey, {
|
|
1444
|
+
...bundle,
|
|
1445
|
+
lastUpstreamError: error instanceof Error ? error.message : String(error),
|
|
1446
|
+
lastUpstreamOkAt: bundle.lastUpstreamOkAt || bundle.cachedAt || new Date().toISOString(),
|
|
1447
|
+
});
|
|
1448
|
+
return fallbackPacket;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
811
1451
|
if (!isOwnerBypassRequest(req, body)) {
|
|
812
1452
|
throw error;
|
|
813
1453
|
}
|
|
@@ -982,6 +1622,9 @@ async function buildDirectTurnContext(req, body, client, options = {}) {
|
|
|
982
1622
|
|
|
983
1623
|
function openAiResponseEnvelope(body, text, providerMeta, extra = {}) {
|
|
984
1624
|
const debug = body?.ariaDebug === true;
|
|
1625
|
+
const toolCalls = !extra.blocked && Array.isArray(providerMeta?.raw?.choices?.[0]?.message?.tool_calls)
|
|
1626
|
+
? providerMeta.raw.choices[0].message.tool_calls
|
|
1627
|
+
: undefined;
|
|
985
1628
|
return {
|
|
986
1629
|
id: `chatcmpl_${randomUUID().replace(/-/g, '')}`,
|
|
987
1630
|
object: 'chat.completion',
|
|
@@ -990,10 +1633,11 @@ function openAiResponseEnvelope(body, text, providerMeta, extra = {}) {
|
|
|
990
1633
|
choices: [
|
|
991
1634
|
{
|
|
992
1635
|
index: 0,
|
|
993
|
-
finish_reason: providerMeta.finishReason || 'stop',
|
|
1636
|
+
finish_reason: toolCalls?.length ? (providerMeta.finishReason || 'tool_calls') : (providerMeta.finishReason || 'stop'),
|
|
994
1637
|
message: {
|
|
995
1638
|
role: 'assistant',
|
|
996
1639
|
content: text,
|
|
1640
|
+
...(toolCalls?.length ? { tool_calls: toolCalls } : {}),
|
|
997
1641
|
},
|
|
998
1642
|
},
|
|
999
1643
|
],
|
|
@@ -1009,13 +1653,32 @@ function openAiResponseEnvelope(body, text, providerMeta, extra = {}) {
|
|
|
1009
1653
|
}
|
|
1010
1654
|
|
|
1011
1655
|
function anthropicResponseEnvelope(text, providerMeta, extra = {}, debug = false) {
|
|
1656
|
+
const rawContent = Array.isArray(providerMeta?.raw?.content) ? providerMeta.raw.content : [];
|
|
1657
|
+
const content = !extra.blocked && rawContent.length
|
|
1658
|
+
? rawContent.map((block) => {
|
|
1659
|
+
if (block?.type === 'text') {
|
|
1660
|
+
return { type: 'text', text: typeof block.text === 'string' ? block.text : text };
|
|
1661
|
+
}
|
|
1662
|
+
if (block?.type === 'tool_use') {
|
|
1663
|
+
return {
|
|
1664
|
+
type: 'tool_use',
|
|
1665
|
+
id: block.id,
|
|
1666
|
+
name: block.name,
|
|
1667
|
+
input: block.input || {},
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
return block;
|
|
1671
|
+
})
|
|
1672
|
+
: [{ type: 'text', text }];
|
|
1012
1673
|
return {
|
|
1013
1674
|
id: `msg_${randomUUID().replace(/-/g, '')}`,
|
|
1014
1675
|
type: 'message',
|
|
1015
1676
|
role: 'assistant',
|
|
1016
1677
|
model: providerMeta.model,
|
|
1017
|
-
stop_reason:
|
|
1018
|
-
|
|
1678
|
+
stop_reason: rawContent.some((block) => block?.type === 'tool_use') && !extra.blocked
|
|
1679
|
+
? (providerMeta.finishReason || 'tool_use')
|
|
1680
|
+
: (providerMeta.finishReason || 'end_turn'),
|
|
1681
|
+
content,
|
|
1019
1682
|
usage: providerMeta.usage
|
|
1020
1683
|
? {
|
|
1021
1684
|
input_tokens: providerMeta.usage.promptTokens || 0,
|
|
@@ -1026,6 +1689,174 @@ function anthropicResponseEnvelope(text, providerMeta, extra = {}, debug = false
|
|
|
1026
1689
|
};
|
|
1027
1690
|
}
|
|
1028
1691
|
|
|
1692
|
+
function flattenResponsesTextContent(content) {
|
|
1693
|
+
if (typeof content === 'string') return content;
|
|
1694
|
+
if (!Array.isArray(content)) {
|
|
1695
|
+
if (typeof content?.text === 'string') return content.text;
|
|
1696
|
+
if (typeof content?.content === 'string') return content.content;
|
|
1697
|
+
return '';
|
|
1698
|
+
}
|
|
1699
|
+
return content
|
|
1700
|
+
.map((part) => {
|
|
1701
|
+
if (typeof part === 'string') return part;
|
|
1702
|
+
if (typeof part?.text === 'string') return part.text;
|
|
1703
|
+
if (typeof part?.content === 'string') return part.content;
|
|
1704
|
+
if ((part?.type === 'input_text' || part?.type === 'output_text' || part?.type === 'text') && typeof part?.text === 'string') {
|
|
1705
|
+
return part.text;
|
|
1706
|
+
}
|
|
1707
|
+
return '';
|
|
1708
|
+
})
|
|
1709
|
+
.filter(Boolean)
|
|
1710
|
+
.join('\n');
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
function normalizeResponsesTool(tool) {
|
|
1714
|
+
if (!tool || typeof tool !== 'object') return null;
|
|
1715
|
+
if (tool.type === 'function' && tool.function && typeof tool.function === 'object') {
|
|
1716
|
+
return tool;
|
|
1717
|
+
}
|
|
1718
|
+
if (tool.type === 'function' || typeof tool.name === 'string') {
|
|
1719
|
+
return {
|
|
1720
|
+
type: 'function',
|
|
1721
|
+
function: {
|
|
1722
|
+
name: tool.name || tool.function?.name || 'tool',
|
|
1723
|
+
description: tool.description || tool.function?.description || '',
|
|
1724
|
+
parameters: tool.parameters || tool.function?.parameters || { type: 'object', properties: {} },
|
|
1725
|
+
},
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
return tool;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
function responsesInputToMessages(input) {
|
|
1732
|
+
if (typeof input === 'string' && input.trim()) {
|
|
1733
|
+
return [{ role: 'user', content: input.trim() }];
|
|
1734
|
+
}
|
|
1735
|
+
if (!Array.isArray(input)) return [];
|
|
1736
|
+
|
|
1737
|
+
const messages = [];
|
|
1738
|
+
for (const item of input) {
|
|
1739
|
+
if (!item || typeof item !== 'object') continue;
|
|
1740
|
+
if (item.type === 'message') {
|
|
1741
|
+
const role = typeof item.role === 'string' ? item.role : 'user';
|
|
1742
|
+
const text = flattenResponsesTextContent(item.content);
|
|
1743
|
+
if (text) messages.push({ role, content: text });
|
|
1744
|
+
continue;
|
|
1745
|
+
}
|
|
1746
|
+
if (item.type === 'input_text' || item.type === 'output_text' || item.type === 'text') {
|
|
1747
|
+
const role = item.role === 'assistant' ? 'assistant' : 'user';
|
|
1748
|
+
const text = flattenResponsesTextContent(item);
|
|
1749
|
+
if (text) messages.push({ role, content: text });
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
if (item.type === 'function_call_output') {
|
|
1753
|
+
const outputText =
|
|
1754
|
+
typeof item.output === 'string'
|
|
1755
|
+
? item.output
|
|
1756
|
+
: JSON.stringify(item.output || {});
|
|
1757
|
+
if (outputText) {
|
|
1758
|
+
messages.push({
|
|
1759
|
+
role: 'tool',
|
|
1760
|
+
tool_call_id: item.call_id || item.id || null,
|
|
1761
|
+
content: outputText,
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
if (item.type === 'function_call') {
|
|
1767
|
+
const args =
|
|
1768
|
+
typeof item.arguments === 'string'
|
|
1769
|
+
? item.arguments
|
|
1770
|
+
: JSON.stringify(item.arguments || item.input || {});
|
|
1771
|
+
messages.push({
|
|
1772
|
+
role: 'assistant',
|
|
1773
|
+
content: `${item.name || 'function_call'} ${args}`.trim(),
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
return messages;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
function responsesRequestToOpenAIBody(body = {}) {
|
|
1781
|
+
const messages = responsesInputToMessages(body.input);
|
|
1782
|
+
const instructions = typeof body.instructions === 'string' && body.instructions.trim()
|
|
1783
|
+
? [{ role: 'system', content: body.instructions.trim() }]
|
|
1784
|
+
: [];
|
|
1785
|
+
|
|
1786
|
+
return {
|
|
1787
|
+
...body,
|
|
1788
|
+
client: body.client || 'codex',
|
|
1789
|
+
surface: body.surface || 'codex',
|
|
1790
|
+
messages: [...instructions, ...messages],
|
|
1791
|
+
tools: Array.isArray(body.tools) ? body.tools.map(normalizeResponsesTool).filter(Boolean) : undefined,
|
|
1792
|
+
tool_choice: body.tool_choice,
|
|
1793
|
+
max_completion_tokens: body.max_output_tokens || body.max_completion_tokens,
|
|
1794
|
+
metadata: {
|
|
1795
|
+
...(body.metadata && typeof body.metadata === 'object' ? body.metadata : {}),
|
|
1796
|
+
response_api: true,
|
|
1797
|
+
client: body.client || 'codex',
|
|
1798
|
+
surface: body.surface || 'codex',
|
|
1799
|
+
},
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
function openAiCompletionToResponsesEnvelope(body, completion) {
|
|
1804
|
+
const message = completion?.choices?.[0]?.message || {};
|
|
1805
|
+
const text = typeof message.content === 'string' ? message.content : '';
|
|
1806
|
+
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
1807
|
+
const output = [];
|
|
1808
|
+
|
|
1809
|
+
if (text) {
|
|
1810
|
+
output.push({
|
|
1811
|
+
id: `msg_${randomUUID().replace(/-/g, '')}`,
|
|
1812
|
+
type: 'message',
|
|
1813
|
+
role: 'assistant',
|
|
1814
|
+
status: 'completed',
|
|
1815
|
+
content: [
|
|
1816
|
+
{
|
|
1817
|
+
type: 'output_text',
|
|
1818
|
+
text,
|
|
1819
|
+
},
|
|
1820
|
+
],
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
for (const toolCall of toolCalls) {
|
|
1825
|
+
const callId = toolCall?.id || `call_${randomUUID().replace(/-/g, '')}`;
|
|
1826
|
+
const functionInfo = toolCall?.function || {};
|
|
1827
|
+
output.push({
|
|
1828
|
+
id: callId,
|
|
1829
|
+
type: 'function_call',
|
|
1830
|
+
status: 'completed',
|
|
1831
|
+
call_id: callId,
|
|
1832
|
+
name: functionInfo.name || toolCall?.name || 'tool',
|
|
1833
|
+
arguments:
|
|
1834
|
+
typeof functionInfo.arguments === 'string'
|
|
1835
|
+
? functionInfo.arguments
|
|
1836
|
+
: JSON.stringify(functionInfo.arguments || toolCall?.arguments || {}),
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
return {
|
|
1841
|
+
id: `resp_${randomUUID().replace(/-/g, '')}`,
|
|
1842
|
+
object: 'response',
|
|
1843
|
+
created_at: new Date().toISOString(),
|
|
1844
|
+
status: 'completed',
|
|
1845
|
+
model: completion?.model || body?.model || 'aria-runtime',
|
|
1846
|
+
output,
|
|
1847
|
+
output_text: text,
|
|
1848
|
+
usage: completion?.usage
|
|
1849
|
+
? {
|
|
1850
|
+
input_tokens: completion.usage.prompt_tokens || 0,
|
|
1851
|
+
output_tokens: completion.usage.completion_tokens || 0,
|
|
1852
|
+
total_tokens: completion.usage.total_tokens || ((completion.usage.prompt_tokens || 0) + (completion.usage.completion_tokens || 0)),
|
|
1853
|
+
}
|
|
1854
|
+
: undefined,
|
|
1855
|
+
aria: completion?.aria || null,
|
|
1856
|
+
metadata: body?.metadata || null,
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1029
1860
|
function toReadableSignal(name) {
|
|
1030
1861
|
const map = {
|
|
1031
1862
|
fitrah_gate: 'truth boundary',
|
|
@@ -1111,6 +1942,30 @@ function buildReadableAriaEnvelope(extra = {}, debug = false) {
|
|
|
1111
1942
|
: [],
|
|
1112
1943
|
} : null;
|
|
1113
1944
|
|
|
1945
|
+
const cognition = extra.cognitionContract ? {
|
|
1946
|
+
visible: Boolean(extra.cognitionContract.present),
|
|
1947
|
+
labels: Array.isArray(extra.cognitionContract.labels) ? extra.cognitionContract.labels : [],
|
|
1948
|
+
readable: extra.cognitionContract.readable || null,
|
|
1949
|
+
first_principle: extra.cognitionContract.firstPrinciple || null,
|
|
1950
|
+
} : null;
|
|
1951
|
+
|
|
1952
|
+
const doctrine = extra.doctrine ? {
|
|
1953
|
+
blocked: Boolean(extra.doctrine.blocked),
|
|
1954
|
+
hits: Array.isArray(extra.doctrine.hits) ? extra.doctrine.hits.slice(0, 3) : [],
|
|
1955
|
+
} : null;
|
|
1956
|
+
|
|
1957
|
+
const tool_gate = extra.toolGate ? {
|
|
1958
|
+
blocked: Boolean(extra.toolGate.blocked),
|
|
1959
|
+
blockers: Array.isArray(extra.toolGate.blockers) ? extra.toolGate.blockers.slice(0, 5) : [],
|
|
1960
|
+
intents: Array.isArray(extra.toolGate.intents)
|
|
1961
|
+
? extra.toolGate.intents.map((intent) => ({
|
|
1962
|
+
tool: intent.toolName,
|
|
1963
|
+
action: intent.action,
|
|
1964
|
+
target: intent.target,
|
|
1965
|
+
}))
|
|
1966
|
+
: [],
|
|
1967
|
+
} : null;
|
|
1968
|
+
|
|
1114
1969
|
const envelope = {
|
|
1115
1970
|
blocked: Boolean(extra.blocked),
|
|
1116
1971
|
control_plane: 'aria-mounted-runtime',
|
|
@@ -1128,6 +1983,9 @@ function buildReadableAriaEnvelope(extra = {}, debug = false) {
|
|
|
1128
1983
|
receipts,
|
|
1129
1984
|
validation,
|
|
1130
1985
|
layer3,
|
|
1986
|
+
cognition,
|
|
1987
|
+
doctrine,
|
|
1988
|
+
tool_gate,
|
|
1131
1989
|
};
|
|
1132
1990
|
|
|
1133
1991
|
if (debug) {
|
|
@@ -1141,6 +1999,34 @@ function coerceNonEmptyString(value) {
|
|
|
1141
1999
|
return typeof value === 'string' && value.trim() ? value.trim() : '';
|
|
1142
2000
|
}
|
|
1143
2001
|
|
|
2002
|
+
function analyzeToolIntentContract(intent, assistantText) {
|
|
2003
|
+
const target = String(intent?.target || '');
|
|
2004
|
+
const action = String(intent?.action || 'tool');
|
|
2005
|
+
const blockers = [];
|
|
2006
|
+
const isDeploy = action === 'deploy' || TOOL_DEPLOY_PATTERNS.some((rx) => rx.test(target));
|
|
2007
|
+
const isDestructive = action === 'delete' || TOOL_DESTRUCTIVE_PATTERNS.some((rx) => rx.test(target));
|
|
2008
|
+
const isMutation = isDeploy || isDestructive || action === 'edit' || action === 'write' || action === 'bash' || action === 'build';
|
|
2009
|
+
|
|
2010
|
+
if (!assistantText.match(COGNITION_BLOCK_RX)) {
|
|
2011
|
+
blockers.push(`${intent.toolName}: missing readable <cognition> block before tool request`);
|
|
2012
|
+
}
|
|
2013
|
+
if (isMutation && !assistantText.match(EXPECTED_BLOCK_RX)) {
|
|
2014
|
+
blockers.push(`${intent.toolName}: missing <expected> block before non-trivial tool request`);
|
|
2015
|
+
} else if (isMutation && !hasMeasurableExpectedBlock(assistantText)) {
|
|
2016
|
+
blockers.push(`${intent.toolName}: <expected> block lacks a measurable predicate`);
|
|
2017
|
+
}
|
|
2018
|
+
if ((isDeploy || isDestructive) && !VERIFY_BLOCK_RX.test(assistantText)) {
|
|
2019
|
+
blockers.push(`${intent.toolName}: missing <verify> block before deploy/destructive tool request`);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
return {
|
|
2023
|
+
isMutation,
|
|
2024
|
+
isDeploy,
|
|
2025
|
+
isDestructive,
|
|
2026
|
+
blockers,
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
|
|
1144
2030
|
function extractJsonObject(text) {
|
|
1145
2031
|
const raw = String(text || '').trim();
|
|
1146
2032
|
if (!raw) {
|
|
@@ -1349,6 +2235,10 @@ function computeForgeContractIssues(manifest, forgeResult) {
|
|
|
1349
2235
|
}
|
|
1350
2236
|
|
|
1351
2237
|
async function persistTurnArtifacts(req, body, client, apiKey, turn) {
|
|
2238
|
+
persistMizanBundle(apiKey, turn.preBundle, 'runtime/v1-pre');
|
|
2239
|
+
persistMizanBundle(apiKey, turn.midBundle, 'runtime/v1-mid');
|
|
2240
|
+
persistMizanBundle(apiKey, turn.postBundle, 'runtime/v1-post');
|
|
2241
|
+
|
|
1352
2242
|
const evolutionPrinciples = extractEvolutionPrinciples(turn.packet, [turn.preResult, turn.midResult, turn.postResult]);
|
|
1353
2243
|
const aegisPatterns = [
|
|
1354
2244
|
...extractAegisPatterns(turn.midResult || {}),
|
|
@@ -1488,39 +2378,93 @@ async function persistTurnArtifacts(req, body, client, apiKey, turn) {
|
|
|
1488
2378
|
await pushTelemetryUpstream(client, apiKey, telemetryPayload);
|
|
1489
2379
|
} catch {}
|
|
1490
2380
|
|
|
1491
|
-
|
|
2381
|
+
const isDecisionTurn =
|
|
2382
|
+
(turn.turnClass?.intensity && turn.turnClass.intensity !== 'light') ||
|
|
2383
|
+
isNonTrivialAssistantTurn(turn.userMessage || '', []) ||
|
|
2384
|
+
isNonTrivialAssistantTurn(turn.finalText || '', []);
|
|
2385
|
+
if (isDecisionTurn) {
|
|
2386
|
+
const surface =
|
|
2387
|
+
body?.surface ||
|
|
2388
|
+
body?.platform ||
|
|
2389
|
+
body?.client ||
|
|
2390
|
+
body?.metadata?.surface ||
|
|
2391
|
+
body?.metadata?.client ||
|
|
2392
|
+
'aria-mounted-runtime';
|
|
2393
|
+
const reasoning = [
|
|
2394
|
+
...(turn.preResult?.notes || []),
|
|
2395
|
+
...(turn.midResult?.notes || []),
|
|
2396
|
+
...(turn.postResult?.notes || []),
|
|
2397
|
+
...(Array.isArray(turn.validation?.violations) ? turn.validation.violations : []),
|
|
2398
|
+
...(Array.isArray(turn.layer3?.failures)
|
|
2399
|
+
? turn.layer3.failures.map((failure) => failure?.detail).filter(Boolean)
|
|
2400
|
+
: []),
|
|
2401
|
+
]
|
|
2402
|
+
.join(' | ')
|
|
2403
|
+
.slice(0, 4000) || 'Aria runtime cognition turn';
|
|
1492
2404
|
const decisionPayload = {
|
|
1493
2405
|
decision_type: body?.metadata?.decision_type || 'runtime-turn',
|
|
1494
2406
|
category: body?.metadata?.decision_category || 'cognition-control-plane',
|
|
1495
|
-
context:
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
...(turn.postResult?.notes || []),
|
|
1501
|
-
].join(' | ').slice(0, 4000) || 'Aria runtime cognition turn',
|
|
2407
|
+
context:
|
|
2408
|
+
(turn.userMessage && turn.userMessage.slice(0, 2000)) ||
|
|
2409
|
+
`${surface} runtime turn (${turn.providerMeta.provider || 'provider'})`,
|
|
2410
|
+
decision: turn.success ? 'turn completed' : 'turn blocked by runtime gate',
|
|
2411
|
+
reasoning,
|
|
1502
2412
|
outcome: 'pending',
|
|
1503
|
-
|
|
1504
|
-
|
|
2413
|
+
outcome_details: {
|
|
2414
|
+
expected: body?.metadata?.expected_outcome || null,
|
|
2415
|
+
immediate_actual: {
|
|
2416
|
+
success: turn.success,
|
|
2417
|
+
validation_severity: turn.validation?.severity || null,
|
|
2418
|
+
layer3_pass: turn.layer3?.pass ?? null,
|
|
2419
|
+
doctrine_hits: Array.isArray(turn.layer3?.doctrine?.hits)
|
|
2420
|
+
? turn.layer3.doctrine.hits.length
|
|
2421
|
+
: 0,
|
|
2422
|
+
},
|
|
2423
|
+
anchors: [
|
|
2424
|
+
turn.preReceipt?.receiptId ? `pre_receipt:${turn.preReceipt.receiptId}` : null,
|
|
2425
|
+
turn.midReceipt?.receiptId ? `mid_receipt:${turn.midReceipt.receiptId}` : null,
|
|
2426
|
+
turn.postReceipt?.receiptId ? `post_receipt:${turn.postReceipt.receiptId}` : null,
|
|
2427
|
+
].filter(Boolean),
|
|
2428
|
+
},
|
|
2429
|
+
expected_outcome: body?.metadata?.expected_outcome || {
|
|
2430
|
+
predicate: 'turn survives runtime validation and compounds into central telemetry with canonical receipts',
|
|
1505
2431
|
measurable_type: 'boolean',
|
|
1506
2432
|
threshold: true,
|
|
1507
2433
|
},
|
|
1508
|
-
|
|
2434
|
+
metadata: {
|
|
2435
|
+
session_id: turn.sessionId,
|
|
2436
|
+
surface,
|
|
2437
|
+
provider: turn.providerMeta.provider || null,
|
|
2438
|
+
finish_reason: turn.providerMeta.finishReason || null,
|
|
2439
|
+
pre_receipt_id: turn.preReceipt?.receiptId || null,
|
|
2440
|
+
mid_receipt_id: turn.midReceipt?.receiptId || null,
|
|
2441
|
+
post_receipt_id: turn.postReceipt?.receiptId || null,
|
|
2442
|
+
validation_severity: turn.validation?.severity || null,
|
|
2443
|
+
validation_passed: turn.validation?.passed ?? null,
|
|
2444
|
+
layer3_pass: turn.layer3?.pass ?? null,
|
|
2445
|
+
packet_bypassed: turn.packetBypassed === true,
|
|
2446
|
+
},
|
|
2447
|
+
source: body?.metadata?.decision_source || `${surface}-runtime`,
|
|
1509
2448
|
model_used: turn.providerMeta.model,
|
|
1510
2449
|
code_links: body?.metadata?.code_links || null,
|
|
1511
2450
|
};
|
|
2451
|
+
let decisionResult = null;
|
|
2452
|
+
let decisionError = null;
|
|
1512
2453
|
try {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
2454
|
+
decisionResult = await pushDecisionUpstream(client, apiKey, decisionPayload);
|
|
2455
|
+
} catch (error) {
|
|
2456
|
+
decisionError = error instanceof Error ? error.message : String(error);
|
|
2457
|
+
}
|
|
2458
|
+
mutateCognitionState(apiKey, (state) => ({
|
|
2459
|
+
...state,
|
|
2460
|
+
decisions: appendBounded(state.decisions, {
|
|
2461
|
+
at: new Date().toISOString(),
|
|
2462
|
+
sessionId: turn.sessionId,
|
|
2463
|
+
decision: decisionPayload,
|
|
2464
|
+
result: decisionResult,
|
|
2465
|
+
error: decisionError,
|
|
2466
|
+
}, DECISION_LIMIT),
|
|
2467
|
+
}));
|
|
1524
2468
|
}
|
|
1525
2469
|
|
|
1526
2470
|
if (body.ariaGarden !== false && turn.userMessage && turn.finalText) {
|
|
@@ -1546,44 +2490,94 @@ async function handleProviderProxy(req, body, client, providerStyle) {
|
|
|
1546
2490
|
? await callProviderForAnthropic(body, turn.ariaSystemPrompt)
|
|
1547
2491
|
: await callProviderForOpenAI(body, turn.ariaSystemPrompt);
|
|
1548
2492
|
|
|
1549
|
-
let candidateText = providerMeta.text || '';
|
|
1550
|
-
let validation;
|
|
1551
2493
|
try {
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
};
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
|
|
2494
|
+
recordTokenUsage({
|
|
2495
|
+
tenantId: body?.metadata?.jti || body?.jti || 'owner-local',
|
|
2496
|
+
agentId: body?.metadata?.agentId || body?.metadata?.roleProfile || null,
|
|
2497
|
+
agentName: body?.metadata?.agentName || null,
|
|
2498
|
+
department: body?.metadata?.department || null,
|
|
2499
|
+
sessionId: turn.sessionId,
|
|
2500
|
+
provider: providerMeta.provider,
|
|
2501
|
+
model: providerMeta.model,
|
|
2502
|
+
usage: providerMeta.usage,
|
|
2503
|
+
requestType: body?.metadata?.requestType || 'chat',
|
|
2504
|
+
});
|
|
2505
|
+
} catch {}
|
|
2506
|
+
|
|
2507
|
+
let candidateText = providerMeta.text || '';
|
|
2508
|
+
const toolIntents = extractProviderToolIntents(providerStyle, providerMeta);
|
|
2509
|
+
const requiresReadableCognition = isNonTrivialAssistantTurn(candidateText, toolIntents);
|
|
2510
|
+
const cognitionContract = extractVisibleCognitionContract(candidateText);
|
|
2511
|
+
|
|
2512
|
+
let validation = {
|
|
2513
|
+
passed: true,
|
|
2514
|
+
severity: 'pass',
|
|
2515
|
+
violations: [],
|
|
2516
|
+
gateTriggers: [],
|
|
2517
|
+
};
|
|
2518
|
+
if (candidateText.trim()) {
|
|
1566
2519
|
try {
|
|
1567
2520
|
validation = await client.validateOutput(candidateText, turn.sessionId);
|
|
1568
2521
|
} catch (error) {
|
|
1569
|
-
if (!turn.packetBypassed) throw error;
|
|
2522
|
+
if (!turn.packetBypassed && !isOwnerBypassRequest(req, body, apiKey)) throw error;
|
|
1570
2523
|
validation = {
|
|
1571
2524
|
passed: true,
|
|
1572
2525
|
severity: 'warn',
|
|
1573
2526
|
violations: [
|
|
1574
|
-
`remote validate unavailable
|
|
2527
|
+
`remote validate unavailable: ${error instanceof Error ? error.message : String(error)}`,
|
|
1575
2528
|
],
|
|
1576
2529
|
gateTriggers: ['owner-local-bypass'],
|
|
1577
2530
|
};
|
|
1578
2531
|
}
|
|
2532
|
+
if (validation?.severity === 'block' && validation?.rewritten) {
|
|
2533
|
+
candidateText = validation.rewritten;
|
|
2534
|
+
try {
|
|
2535
|
+
validation = await client.validateOutput(candidateText, turn.sessionId);
|
|
2536
|
+
} catch (error) {
|
|
2537
|
+
if (!turn.packetBypassed && !isOwnerBypassRequest(req, body, apiKey)) throw error;
|
|
2538
|
+
validation = {
|
|
2539
|
+
passed: true,
|
|
2540
|
+
severity: 'warn',
|
|
2541
|
+
violations: [
|
|
2542
|
+
`remote validate unavailable after rewrite: ${error instanceof Error ? error.message : String(error)}`,
|
|
2543
|
+
],
|
|
2544
|
+
gateTriggers: ['owner-local-bypass'],
|
|
2545
|
+
};
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
1579
2548
|
}
|
|
1580
2549
|
|
|
1581
2550
|
const layer3 = await runLayer3(req, {
|
|
1582
|
-
text: candidateText,
|
|
2551
|
+
text: candidateText || (toolIntents.length > 0 ? `<cognition>\n</cognition>` : ''),
|
|
1583
2552
|
packet: turn.packet,
|
|
1584
2553
|
fetchPacket: false,
|
|
1585
|
-
requireCognitionBlock: body.requireCognitionBlock ??
|
|
2554
|
+
requireCognitionBlock: body.requireCognitionBlock ?? requiresReadableCognition,
|
|
1586
2555
|
}, client);
|
|
2556
|
+
const doctrineHits = candidateText.trim() ? collectDoctrineTriggerHits(candidateText) : [];
|
|
2557
|
+
const doctrineBlockers = doctrineHits
|
|
2558
|
+
.filter((hit) => String(hit.severity || 'block').toLowerCase() === 'block')
|
|
2559
|
+
.map((hit) => hit.message || hit.teaching || hit.trigger);
|
|
2560
|
+
|
|
2561
|
+
const toolGateBlockers = [];
|
|
2562
|
+
for (const intent of toolIntents) {
|
|
2563
|
+
const contract = analyzeToolIntentContract(intent, candidateText);
|
|
2564
|
+
toolGateBlockers.push(...contract.blockers);
|
|
2565
|
+
try {
|
|
2566
|
+
const runtimeCheck = await client.checkAction(
|
|
2567
|
+
contract.isDeploy ? 'deploy'
|
|
2568
|
+
: contract.isDestructive ? 'delete'
|
|
2569
|
+
: intent.action === 'build' ? 'build'
|
|
2570
|
+
: 'write',
|
|
2571
|
+
intent.target || intent.toolName,
|
|
2572
|
+
);
|
|
2573
|
+
if (!runtimeCheck.allowed) {
|
|
2574
|
+
toolGateBlockers.push(`${intent.toolName}: ${runtimeCheck.reason || 'runtime action gate blocked this tool request'}`);
|
|
2575
|
+
}
|
|
2576
|
+
} catch (error) {
|
|
2577
|
+
if (!turn.packetBypassed && !isOwnerBypassRequest(req, body, apiKey)) throw error;
|
|
2578
|
+
toolGateBlockers.push(`${intent.toolName}: runtime action gate unavailable during owner-local-bypass`);
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
1587
2581
|
const postBundle = evaluateMizanPost(candidateText, {
|
|
1588
2582
|
hasVerifiedState: findVerifiedState(candidateText),
|
|
1589
2583
|
layer3Pass: layer3.pass,
|
|
@@ -1601,8 +2595,17 @@ async function handleProviderProxy(req, body, client, providerStyle) {
|
|
|
1601
2595
|
});
|
|
1602
2596
|
const postResult = postBundle.result;
|
|
1603
2597
|
|
|
1604
|
-
const blocked =
|
|
1605
|
-
|
|
2598
|
+
const blocked =
|
|
2599
|
+
validation.severity === 'block' ||
|
|
2600
|
+
!layer3.pass ||
|
|
2601
|
+
postResult.reAuthorSignal ||
|
|
2602
|
+
doctrineBlockers.length > 0 ||
|
|
2603
|
+
toolGateBlockers.length > 0;
|
|
2604
|
+
const finalText = toolGateBlockers.length > 0
|
|
2605
|
+
? buildToolGateBlockMessage(toolGateBlockers)
|
|
2606
|
+
: blocked
|
|
2607
|
+
? buildPhaseBlockMessage(postResult, 'post')
|
|
2608
|
+
: candidateText;
|
|
1606
2609
|
await persistTurnArtifacts(req, body, client, apiKey, {
|
|
1607
2610
|
...turn,
|
|
1608
2611
|
postResult,
|
|
@@ -1632,6 +2635,16 @@ async function handleProviderProxy(req, body, client, providerStyle) {
|
|
|
1632
2635
|
operatorPlan: turn.operatorPlan,
|
|
1633
2636
|
validation,
|
|
1634
2637
|
layer3,
|
|
2638
|
+
cognitionContract,
|
|
2639
|
+
doctrine: {
|
|
2640
|
+
blocked: doctrineBlockers.length > 0,
|
|
2641
|
+
hits: doctrineHits,
|
|
2642
|
+
},
|
|
2643
|
+
toolGate: {
|
|
2644
|
+
blocked: toolGateBlockers.length > 0,
|
|
2645
|
+
blockers: toolGateBlockers,
|
|
2646
|
+
intents: toolIntents,
|
|
2647
|
+
},
|
|
1635
2648
|
};
|
|
1636
2649
|
return providerStyle === 'anthropic'
|
|
1637
2650
|
? anthropicResponseEnvelope(finalText, providerMeta, extra, body?.ariaDebug === true)
|
|
@@ -1910,6 +2923,8 @@ function packetToSubstrateSet(packet) {
|
|
|
1910
2923
|
function runtimeManifest() {
|
|
1911
2924
|
const runtimeMeta = ensureRuntimeMeta();
|
|
1912
2925
|
const state = sweepAutonomyState(loadAutonomyState());
|
|
2926
|
+
const ownerToken = readOwnerToken();
|
|
2927
|
+
const offlineBundleStatus = ownerToken ? computeOfflineBundleStatus(loadEncryptedOfflineBundle(ownerToken)) : computeOfflineBundleStatus(null);
|
|
1913
2928
|
return {
|
|
1914
2929
|
ok: true,
|
|
1915
2930
|
runtime: 'aria-mounted-runtime',
|
|
@@ -1956,6 +2971,8 @@ function runtimeManifest() {
|
|
|
1956
2971
|
'POST /forge/synthesize',
|
|
1957
2972
|
'POST /codebase/state',
|
|
1958
2973
|
'POST /v1/chat/completions',
|
|
2974
|
+
'POST /v1/responses',
|
|
2975
|
+
'POST /responses',
|
|
1959
2976
|
'POST /v1/messages',
|
|
1960
2977
|
],
|
|
1961
2978
|
mount: {
|
|
@@ -1973,9 +2990,13 @@ function runtimeManifest() {
|
|
|
1973
2990
|
},
|
|
1974
2991
|
security: {
|
|
1975
2992
|
encrypted_local_lease: LEASE_PATH,
|
|
2993
|
+
encrypted_offline_bundle: OFFLINE_BUNDLE_PATH,
|
|
1976
2994
|
encrypted_cognition_state: COGNITION_STATE_PATH,
|
|
1977
2995
|
revocation_lock: REVOCATION_LOCK_PATH,
|
|
1978
2996
|
upstream_heartbeat: '/api/license/heartbeat',
|
|
2997
|
+
offline_bundle_soft_ttl_seconds: DEFAULT_OFFLINE_BUNDLE_SOFT_TTL_SECONDS,
|
|
2998
|
+
offline_bundle_hard_ttl_seconds: DEFAULT_OFFLINE_BUNDLE_HARD_TTL_SECONDS,
|
|
2999
|
+
offline_bundle_status: offlineBundleStatus,
|
|
1979
3000
|
},
|
|
1980
3001
|
memory: {
|
|
1981
3002
|
qdrant_url: DEFAULT_QDRANT_URL,
|
|
@@ -2004,12 +3025,27 @@ async function runLayer3(req, body, client) {
|
|
|
2004
3025
|
substrate: packetToSubstrateSet(packet),
|
|
2005
3026
|
requireCognitionBlock: body.requireCognitionBlock ?? false,
|
|
2006
3027
|
});
|
|
3028
|
+
const doctrineHits = collectDoctrineTriggerHits(body.text);
|
|
3029
|
+
const doctrineFailures = doctrineHits.map((hit) => ({
|
|
3030
|
+
severity: String(hit.severity || 'block').toLowerCase() === 'block' ? 'block' : 'warn',
|
|
3031
|
+
kind: 'drift_trigger',
|
|
3032
|
+
detail: `${hit.trigger} (${hit.memory || 'doctrine_trigger_map.json'}): ${hit.message || hit.teaching || 'Doctrine trigger matched.'}`,
|
|
3033
|
+
}));
|
|
3034
|
+
const allFailures = [...result.failures, ...doctrineFailures];
|
|
3035
|
+
const hardFailures = allFailures.filter((failure) => failure.severity === 'block');
|
|
2007
3036
|
|
|
2008
3037
|
return {
|
|
2009
|
-
pass:
|
|
2010
|
-
summary:
|
|
2011
|
-
|
|
3038
|
+
pass: hardFailures.length === 0,
|
|
3039
|
+
summary:
|
|
3040
|
+
hardFailures.length === 0
|
|
3041
|
+
? `full_chain: pass (${allFailures.length} warns)`
|
|
3042
|
+
: `full_chain: ${hardFailures.length} hard failures across ${allFailures.length} total`,
|
|
3043
|
+
failures: allFailures,
|
|
2012
3044
|
packetTimestamp: packet.timestamp || null,
|
|
3045
|
+
doctrine: {
|
|
3046
|
+
sourcePath: doctrineTriggerMapCache.sourcePath,
|
|
3047
|
+
hits: doctrineHits,
|
|
3048
|
+
},
|
|
2013
3049
|
};
|
|
2014
3050
|
}
|
|
2015
3051
|
|
|
@@ -2348,6 +3384,12 @@ async function handleRoute(req, res) {
|
|
|
2348
3384
|
return json(res, 200, response);
|
|
2349
3385
|
}
|
|
2350
3386
|
|
|
3387
|
+
if (url.pathname === '/v1/responses' || url.pathname === '/responses') {
|
|
3388
|
+
const responseBody = responsesRequestToOpenAIBody(body);
|
|
3389
|
+
const completion = await handleProviderProxy(req, responseBody, client, 'openai');
|
|
3390
|
+
return json(res, 200, openAiCompletionToResponsesEnvelope(body, completion));
|
|
3391
|
+
}
|
|
3392
|
+
|
|
2351
3393
|
if (url.pathname === '/v1/messages') {
|
|
2352
3394
|
const response = await handleProviderProxy(req, body, client, 'anthropic');
|
|
2353
3395
|
return json(res, 200, response);
|
|
@@ -2378,16 +3420,55 @@ async function handleRoute(req, res) {
|
|
|
2378
3420
|
|
|
2379
3421
|
if (url.pathname === '/decision/log') {
|
|
2380
3422
|
const apiKey = resolveApiKey(req, body);
|
|
2381
|
-
const
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
3423
|
+
const normalizedBody = normalizeDecisionPayload(body);
|
|
3424
|
+
const flush = await flushPendingDecisionUploads(client, apiKey);
|
|
3425
|
+
try {
|
|
3426
|
+
const result = await pushDecisionUpstreamWithRetry(client, apiKey, normalizedBody);
|
|
3427
|
+
mutateCognitionState(apiKey, (state) => ({
|
|
3428
|
+
...state,
|
|
3429
|
+
decisions: appendBounded(state.decisions, {
|
|
3430
|
+
at: new Date().toISOString(),
|
|
3431
|
+
decision: normalizedBody,
|
|
3432
|
+
result,
|
|
3433
|
+
flushedPending: flush.flushed,
|
|
3434
|
+
}, DECISION_LIMIT),
|
|
3435
|
+
}));
|
|
3436
|
+
return json(res, 200, {
|
|
3437
|
+
ok: true,
|
|
2387
3438
|
result,
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
3439
|
+
flushedPending: flush.flushed,
|
|
3440
|
+
retainedPending: flush.retained,
|
|
3441
|
+
});
|
|
3442
|
+
} catch (error) {
|
|
3443
|
+
const upstreamError = error instanceof Error ? error.message : String(error);
|
|
3444
|
+
mutateCognitionState(apiKey, (state) => ({
|
|
3445
|
+
...state,
|
|
3446
|
+
decisions: appendBounded(state.decisions, {
|
|
3447
|
+
at: new Date().toISOString(),
|
|
3448
|
+
decision: normalizedBody,
|
|
3449
|
+
result: {
|
|
3450
|
+
logged: false,
|
|
3451
|
+
queuedUpstream: true,
|
|
3452
|
+
upstreamError,
|
|
3453
|
+
},
|
|
3454
|
+
flushedPending: flush.flushed,
|
|
3455
|
+
}, DECISION_LIMIT),
|
|
3456
|
+
pendingDecisions: appendBounded(state.pendingDecisions, {
|
|
3457
|
+
at: new Date().toISOString(),
|
|
3458
|
+
payload: normalizedBody,
|
|
3459
|
+
attempts: 0,
|
|
3460
|
+
lastError: upstreamError,
|
|
3461
|
+
queuedBy: 'decision/log',
|
|
3462
|
+
}, DECISION_LIMIT),
|
|
3463
|
+
}));
|
|
3464
|
+
return json(res, 200, {
|
|
3465
|
+
ok: true,
|
|
3466
|
+
queuedUpstream: true,
|
|
3467
|
+
upstreamError,
|
|
3468
|
+
flushedPending: flush.flushed,
|
|
3469
|
+
retainedPending: flush.retained + 1,
|
|
3470
|
+
});
|
|
3471
|
+
}
|
|
2391
3472
|
}
|
|
2392
3473
|
|
|
2393
3474
|
if (url.pathname === '/aegis/patterns') {
|
|
@@ -2449,12 +3530,12 @@ async function handleRoute(req, res) {
|
|
|
2449
3530
|
}
|
|
2450
3531
|
}
|
|
2451
3532
|
|
|
2452
|
-
if (url.pathname === '/packet') {
|
|
3533
|
+
if (url.pathname === '/packet' || url.pathname === '/api/harness/codex') {
|
|
2453
3534
|
const packet = await loadRuntimePacket(req, body, client, body.packetRequest || body, body.message || '');
|
|
2454
3535
|
return json(res, 200, { ok: true, packet });
|
|
2455
3536
|
}
|
|
2456
3537
|
|
|
2457
|
-
if (url.pathname === '/consult') {
|
|
3538
|
+
if (url.pathname === '/consult' || url.pathname === '/api/harness/delegate') {
|
|
2458
3539
|
const result = await client.consult(body);
|
|
2459
3540
|
return json(res, 200, { ok: true, ...result });
|
|
2460
3541
|
}
|
|
@@ -2474,7 +3555,7 @@ async function handleRoute(req, res) {
|
|
|
2474
3555
|
return json(res, 200, { ok: true, ...result });
|
|
2475
3556
|
}
|
|
2476
3557
|
|
|
2477
|
-
if (url.pathname === '/validate-output') {
|
|
3558
|
+
if (url.pathname === '/validate-output' || url.pathname === '/api/harness/validate') {
|
|
2478
3559
|
if (typeof body.text !== 'string' || typeof body.sessionId !== 'string') {
|
|
2479
3560
|
throw new Error('validate-output requires text and sessionId');
|
|
2480
3561
|
}
|
|
@@ -2653,6 +3734,313 @@ async function handleRoute(req, res) {
|
|
|
2653
3734
|
return json(res, 200, { ok: true, ...result });
|
|
2654
3735
|
}
|
|
2655
3736
|
|
|
3737
|
+
// ── /hq/* routes: Agentic HQ API ──────────────────────────────
|
|
3738
|
+
|
|
3739
|
+
if (url.pathname.startsWith('/hq/')) {
|
|
3740
|
+
const auth = hqAuthMiddleware(url.pathname, req);
|
|
3741
|
+
if (!auth.authorized) {
|
|
3742
|
+
return json(res, 401, { ok: false, error: auth.error || 'Unauthorized' });
|
|
3743
|
+
}
|
|
3744
|
+
if (auth.tenantId && !body.tenantId) body.tenantId = auth.tenantId;
|
|
3745
|
+
req.hqAuth = auth;
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3748
|
+
// ── /hq/auth/* routes (public, handled before auth gate takes effect) ──
|
|
3749
|
+
if (url.pathname === '/hq/auth/login') {
|
|
3750
|
+
const { username, password } = body;
|
|
3751
|
+
if (!username || !password) return json(res, 400, { ok: false, error: 'username and password required' });
|
|
3752
|
+
const result = loginUser(username, password);
|
|
3753
|
+
if (!result.ok) return json(res, 401, result);
|
|
3754
|
+
return json(res, 200, { ok: true, token: result.session.token, role: result.session.role, tenantId: result.session.tenantId, email: result.session.email });
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3757
|
+
if (url.pathname === '/hq/auth/register') {
|
|
3758
|
+
const { email, password, tenantId } = body;
|
|
3759
|
+
if (!email || !password || !tenantId) return json(res, 400, { ok: false, error: 'email, password, and tenantId required' });
|
|
3760
|
+
const result = registerUser(email, password, tenantId);
|
|
3761
|
+
if (!result.ok) return json(res, 409, result);
|
|
3762
|
+
const loginResult = loginUser(email, password);
|
|
3763
|
+
return json(res, 200, { ok: true, token: loginResult.session.token, role: loginResult.session.role, tenantId: loginResult.session.tenantId, email: loginResult.session.email });
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
if (url.pathname === '/hq/auth/session') {
|
|
3767
|
+
const authHeader = req.headers['authorization'] || '';
|
|
3768
|
+
const token = authHeader.replace(/^Bearer\s+/i, '').trim();
|
|
3769
|
+
if (!token) return json(res, 401, { ok: false, error: 'Token required' });
|
|
3770
|
+
const { validateSession } = await import('./auth-middleware.mjs');
|
|
3771
|
+
const session = validateSession(token);
|
|
3772
|
+
if (!session.valid) return json(res, 401, { ok: false, error: 'Invalid or expired session' });
|
|
3773
|
+
return json(res, 200, { ok: true, role: session.role, tenantId: session.tenantId, email: session.email });
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
if (url.pathname === '/hq/auth/logout') {
|
|
3777
|
+
const authHeader = req.headers['authorization'] || '';
|
|
3778
|
+
const token = authHeader.replace(/^Bearer\s+/i, '').trim();
|
|
3779
|
+
if (token) revokeSession(token);
|
|
3780
|
+
return json(res, 200, { ok: true });
|
|
3781
|
+
}
|
|
3782
|
+
|
|
3783
|
+
// ── /hq/owner/* routes (owner-only) ──
|
|
3784
|
+
if (url.pathname === '/hq/owner/tenants') {
|
|
3785
|
+
const auth = req.hqAuth;
|
|
3786
|
+
if (!auth || auth.role !== 'owner') return json(res, 403, { ok: false, error: 'Owner access required' });
|
|
3787
|
+
const tenants = listAllTenants();
|
|
3788
|
+
const enriched = tenants.map(t => {
|
|
3789
|
+
const fleet = loadFleet(t.tenantId);
|
|
3790
|
+
return { ...t, fleet: fleet ? { deployed: true, activeAgents: fleet.agents?.length || 0, industry: fleet.config?.industry } : { deployed: false } };
|
|
3791
|
+
});
|
|
3792
|
+
return json(res, 200, { ok: true, tenants: enriched });
|
|
3793
|
+
}
|
|
3794
|
+
|
|
3795
|
+
if (url.pathname === '/hq/owner/tenant/dashboard') {
|
|
3796
|
+
const auth = req.hqAuth;
|
|
3797
|
+
if (!auth || auth.role !== 'owner') return json(res, 403, { ok: false, error: 'Owner access required' });
|
|
3798
|
+
const { tenantId: targetTenant } = body;
|
|
3799
|
+
if (!targetTenant) return json(res, 400, { ok: false, error: 'targetTenant required' });
|
|
3800
|
+
const fleet = loadFleet(targetTenant);
|
|
3801
|
+
if (!fleet) return json(res, 404, { ok: false, error: 'No fleet for that tenant' });
|
|
3802
|
+
return json(res, 200, { ok: true, ...getFleetStatus(targetTenant) });
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
if (url.pathname === '/hq/metering/usage') {
|
|
3806
|
+
const { tenantId, from, to } = body;
|
|
3807
|
+
if (!tenantId) return json(res, 400, { ok: false, error: 'tenantId required' });
|
|
3808
|
+
return json(res, 200, { ok: true, ...getUsageSummary(tenantId, { from, to }) });
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
if (url.pathname === '/hq/metering/billing') {
|
|
3812
|
+
const { tenantId, tier } = body;
|
|
3813
|
+
if (!tenantId) return json(res, 400, { ok: false, error: 'tenantId required' });
|
|
3814
|
+
return json(res, 200, { ok: true, ...getBillingSummary(tenantId, tier || 'starter') });
|
|
3815
|
+
}
|
|
3816
|
+
|
|
3817
|
+
if (url.pathname === '/hq/onboarding/chat') {
|
|
3818
|
+
const { tenantId, message, state: clientState } = body;
|
|
3819
|
+
if (!tenantId) return json(res, 400, { ok: false, error: 'tenantId required' });
|
|
3820
|
+
const { createOnboardingSession, loadSession, saveSession, processResponse, getStepPrompt, advanceStep } = await import('./onboarding-engine.mjs');
|
|
3821
|
+
let session = loadSession(tenantId) || createOnboardingSession(tenantId);
|
|
3822
|
+
if (clientState) Object.assign(session.data, clientState);
|
|
3823
|
+
session.history.push({ role: 'user', content: message || '', step: session.step, timestamp: new Date().toISOString() });
|
|
3824
|
+
|
|
3825
|
+
let ariaText = '';
|
|
3826
|
+
|
|
3827
|
+
// Password collection: skip LLM, take user message directly
|
|
3828
|
+
if (session.step === 'credentials' && session.data.email && !session.data.password) {
|
|
3829
|
+
session.data.password = message;
|
|
3830
|
+
session.step = advanceStep(session.step);
|
|
3831
|
+
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.`;
|
|
3832
|
+
} else {
|
|
3833
|
+
const stepPrompt = getStepPrompt(session.step);
|
|
3834
|
+
try {
|
|
3835
|
+
const providerResult = await callProviderForOpenAI({
|
|
3836
|
+
...body,
|
|
3837
|
+
messages: [
|
|
3838
|
+
{ role: 'system', content: stepPrompt },
|
|
3839
|
+
{ role: 'user', content: message || 'Hello' },
|
|
3840
|
+
],
|
|
3841
|
+
});
|
|
3842
|
+
ariaText = providerResult.text || '';
|
|
3843
|
+
} catch (err) {
|
|
3844
|
+
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})`;
|
|
3845
|
+
}
|
|
3846
|
+
session = processResponse(session, ariaText);
|
|
3847
|
+
// After email is extracted, append password prompt
|
|
3848
|
+
if (session.step === 'credentials' && session.data.email && !session.data.password) {
|
|
3849
|
+
ariaText += '\n\nNow please type a password for your dashboard login (at least 8 characters). Your next message will be saved as your password.';
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
saveSession(session);
|
|
3853
|
+
|
|
3854
|
+
if (session.step === 'complete' && session.data.confirmed) {
|
|
3855
|
+
const { buildFleetConfig } = await import('./onboarding-engine.mjs');
|
|
3856
|
+
const fleetConfig = buildFleetConfig(session);
|
|
3857
|
+
const enqueueJob = (jobDef) => {
|
|
3858
|
+
const autonomyState = loadAutonomyState();
|
|
3859
|
+
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 };
|
|
3860
|
+
autonomyState.jobs.push(job);
|
|
3861
|
+
saveAutonomyState(autonomyState);
|
|
3862
|
+
return job;
|
|
3863
|
+
};
|
|
3864
|
+
const { fleet, workerRegistrations, initialJobs } = deployFleet(tenantId, fleetConfig, enqueueJob);
|
|
3865
|
+
const autonomyState = loadAutonomyState();
|
|
3866
|
+
for (const reg of workerRegistrations) { ensureWorker(autonomyState, reg.workerId, reg); }
|
|
3867
|
+
for (const jobDef of initialJobs) {
|
|
3868
|
+
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 };
|
|
3869
|
+
autonomyState.jobs.push(job);
|
|
3870
|
+
}
|
|
3871
|
+
saveAutonomyState(autonomyState);
|
|
3872
|
+
const apiKey = generateApiKey(tenantId);
|
|
3873
|
+
let authToken = null;
|
|
3874
|
+
if (session.data.email && session.data.password) {
|
|
3875
|
+
const regResult = registerUser(session.data.email, session.data.password, tenantId);
|
|
3876
|
+
if (regResult.ok) {
|
|
3877
|
+
const loginResult = loginUser(session.data.email, session.data.password);
|
|
3878
|
+
authToken = loginResult.ok ? loginResult.session.token : null;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
return json(res, 200, { ok: true, step: 'complete', data: session.data, ariaMessage: ariaText, fleet: { deployed: true, agents: fleet.agents.length }, apiKey, authToken });
|
|
3882
|
+
}
|
|
3883
|
+
|
|
3884
|
+
return json(res, 200, { ok: true, step: session.step, data: session.data, ariaMessage: ariaText });
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
if (url.pathname === '/hq/onboarding/status') {
|
|
3888
|
+
const { tenantId } = body;
|
|
3889
|
+
if (!tenantId) return json(res, 400, { ok: false, error: 'tenantId required' });
|
|
3890
|
+
const { loadSession } = await import('./onboarding-engine.mjs');
|
|
3891
|
+
const session = loadSession(tenantId);
|
|
3892
|
+
if (!session) return json(res, 200, { ok: true, step: 'not-started', data: {} });
|
|
3893
|
+
return json(res, 200, { ok: true, step: session.step, data: session.data, updatedAt: session.updatedAt });
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3896
|
+
if (url.pathname === '/hq/fleet/deploy') {
|
|
3897
|
+
const { tenantId, config } = body;
|
|
3898
|
+
if (!tenantId || !config) return json(res, 400, { ok: false, error: 'tenantId and config required' });
|
|
3899
|
+
const enqueueJob = (jobDef) => {
|
|
3900
|
+
const autonomyState = loadAutonomyState();
|
|
3901
|
+
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 };
|
|
3902
|
+
autonomyState.jobs.push(job);
|
|
3903
|
+
saveAutonomyState(autonomyState);
|
|
3904
|
+
return job;
|
|
3905
|
+
};
|
|
3906
|
+
const { fleet, workerRegistrations, initialJobs } = deployFleet(tenantId, config, enqueueJob);
|
|
3907
|
+
const autonomyState = loadAutonomyState();
|
|
3908
|
+
for (const reg of workerRegistrations) {
|
|
3909
|
+
ensureWorker(autonomyState, reg.workerId, reg);
|
|
3910
|
+
}
|
|
3911
|
+
for (const jobDef of initialJobs) {
|
|
3912
|
+
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 };
|
|
3913
|
+
autonomyState.jobs.push(job);
|
|
3914
|
+
}
|
|
3915
|
+
saveAutonomyState(autonomyState);
|
|
3916
|
+
return json(res, 200, { ok: true, fleet, workersRegistered: workerRegistrations.length, jobsEnqueued: initialJobs.length });
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3919
|
+
if (url.pathname === '/hq/fleet/status') {
|
|
3920
|
+
const { tenantId } = body;
|
|
3921
|
+
if (!tenantId) return json(res, 400, { ok: false, error: 'tenantId required' });
|
|
3922
|
+
return json(res, 200, getFleetStatus(tenantId));
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
if (url.pathname === '/hq/fleet/agent/chat') {
|
|
3926
|
+
const { tenantId, agentId, message: agentMsg } = body;
|
|
3927
|
+
if (!tenantId || !agentId || !agentMsg) return json(res, 400, { ok: false, error: 'tenantId, agentId, and message required' });
|
|
3928
|
+
const fleet = loadFleet(tenantId);
|
|
3929
|
+
if (!fleet) return json(res, 404, { ok: false, error: 'No fleet deployed' });
|
|
3930
|
+
const agent = (fleet.agents || []).find(a => a.id === agentId || a.templateId === agentId);
|
|
3931
|
+
if (!agent) return json(res, 404, { ok: false, error: `Agent ${agentId} not found` });
|
|
3932
|
+
const systemPrompt = buildAgentSystemPrompt(agent, fleet.config);
|
|
3933
|
+
let reply = '';
|
|
3934
|
+
try {
|
|
3935
|
+
const providerResult = await callProviderForOpenAI({
|
|
3936
|
+
...body,
|
|
3937
|
+
messages: [
|
|
3938
|
+
{ role: 'system', content: systemPrompt },
|
|
3939
|
+
{ role: 'user', content: agentMsg },
|
|
3940
|
+
],
|
|
3941
|
+
metadata: { ...body.metadata, agentId: agent.id, agentName: agent.name, department: agent.department, requestType: 'fleet-chat' },
|
|
3942
|
+
});
|
|
3943
|
+
reply = providerResult.text || '';
|
|
3944
|
+
} catch (err) {
|
|
3945
|
+
reply = `I'm having trouble connecting right now. Please try again. (Error: ${err.message})`;
|
|
3946
|
+
}
|
|
3947
|
+
try {
|
|
3948
|
+
if (providerResult.usage) {
|
|
3949
|
+
recordTokenUsage({
|
|
3950
|
+
tenantId: body.tenantId || 'owner-local',
|
|
3951
|
+
agentId: agent.id,
|
|
3952
|
+
agentName: agent.name,
|
|
3953
|
+
department: agent.department,
|
|
3954
|
+
sessionId: null,
|
|
3955
|
+
provider: 'deepseek',
|
|
3956
|
+
model: body.model || 'deepseek-v4-flash',
|
|
3957
|
+
usage: providerResult.usage,
|
|
3958
|
+
requestType: 'fleet-chat',
|
|
3959
|
+
});
|
|
3960
|
+
}
|
|
3961
|
+
} catch {}
|
|
3962
|
+
agent.lastActivity = new Date().toISOString();
|
|
3963
|
+
agent.stats.messagesSent = (agent.stats.messagesSent || 0) + 1;
|
|
3964
|
+
saveFleet(fleet);
|
|
3965
|
+
return json(res, 200, { ok: true, agentId, agentName: agent.name, department: agent.department, reply, status: agent.status });
|
|
3966
|
+
}
|
|
3967
|
+
|
|
3968
|
+
if (url.pathname === '/hq/fleet/agent/action') {
|
|
3969
|
+
const { tenantId, agentId, action, payload: actionPayload } = body;
|
|
3970
|
+
if (!tenantId || !agentId || !action) return json(res, 400, { ok: false, error: 'tenantId, agentId, and action required' });
|
|
3971
|
+
const fleet = loadFleet(tenantId);
|
|
3972
|
+
if (!fleet) return json(res, 404, { ok: false, error: 'No fleet deployed' });
|
|
3973
|
+
const agent = (fleet.agents || []).find(a => a.id === agentId || a.templateId === agentId);
|
|
3974
|
+
if (!agent) return json(res, 404, { ok: false, error: `Agent ${agentId} not found` });
|
|
3975
|
+
const autonomyState = loadAutonomyState();
|
|
3976
|
+
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 };
|
|
3977
|
+
autonomyState.jobs.push(job);
|
|
3978
|
+
saveAutonomyState(autonomyState);
|
|
3979
|
+
agent.lastActivity = new Date().toISOString();
|
|
3980
|
+
saveFleet(fleet);
|
|
3981
|
+
return json(res, 200, { ok: true, agentId, action, jobId: job.jobId, result: 'enqueued' });
|
|
3982
|
+
}
|
|
3983
|
+
|
|
3984
|
+
if (url.pathname === '/hq/plugins/list') {
|
|
3985
|
+
const { tenantId } = body;
|
|
3986
|
+
return json(res, 200, { ok: true, plugins: listPlugins(tenantId || 'owner-local') });
|
|
3987
|
+
}
|
|
3988
|
+
|
|
3989
|
+
if (url.pathname === '/hq/plugins/install') {
|
|
3990
|
+
const { tenantId, pluginId, config: pluginConfig } = body;
|
|
3991
|
+
if (!tenantId || !pluginId) return json(res, 400, { ok: false, error: 'tenantId and pluginId required' });
|
|
3992
|
+
const result = installPlugin(tenantId, pluginId, pluginConfig);
|
|
3993
|
+
if (!result.ok) return json(res, 400, result);
|
|
3994
|
+
return json(res, 200, result);
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3997
|
+
if (url.pathname === '/hq/plugins/configure') {
|
|
3998
|
+
const { tenantId, pluginId, config: pluginConfig } = body;
|
|
3999
|
+
if (!tenantId || !pluginId) return json(res, 400, { ok: false, error: 'tenantId and pluginId required' });
|
|
4000
|
+
const result = configurePlugin(tenantId, pluginId, pluginConfig);
|
|
4001
|
+
if (!result.ok) return json(res, result.error.startsWith('No plugins') ? 404 : 400, result);
|
|
4002
|
+
return json(res, 200, result);
|
|
4003
|
+
}
|
|
4004
|
+
|
|
4005
|
+
if (url.pathname === '/hq/workflows/list') {
|
|
4006
|
+
return json(res, 200, { ok: true, workflows: listWorkflowTemplates() });
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
if (url.pathname === '/hq/workflows/configure') {
|
|
4010
|
+
const { tenantId, workflowId, config: workflowConfig } = body;
|
|
4011
|
+
if (!tenantId || !workflowId) return json(res, 400, { ok: false, error: 'tenantId and workflowId required' });
|
|
4012
|
+
const result = configureWorkflow(tenantId, workflowId, workflowConfig);
|
|
4013
|
+
if (!result.ok) return json(res, 400, result);
|
|
4014
|
+
return json(res, 200, result);
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
if (url.pathname === '/hq/workflows/start') {
|
|
4018
|
+
const { tenantId, workflowId, payload } = body;
|
|
4019
|
+
if (!tenantId || !workflowId) return json(res, 400, { ok: false, error: 'tenantId and workflowId required' });
|
|
4020
|
+
const result = startWorkflow(tenantId, workflowId, payload);
|
|
4021
|
+
if (!result.ok) return json(res, 400, result);
|
|
4022
|
+
return json(res, 200, result);
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
if (url.pathname === '/hq/workflows/approve') {
|
|
4026
|
+
const { tenantId, instanceId, approved } = body;
|
|
4027
|
+
if (!tenantId || !instanceId) return json(res, 400, { ok: false, error: 'tenantId and instanceId required' });
|
|
4028
|
+
const result = approveWorkflowStep(tenantId, instanceId, approved !== false);
|
|
4029
|
+
if (!result.ok) return json(res, 400, result);
|
|
4030
|
+
return json(res, 200, result);
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
if (url.pathname === '/hq/workflows/status') {
|
|
4034
|
+
const { tenantId } = body;
|
|
4035
|
+
if (!tenantId) return json(res, 400, { ok: false, error: 'tenantId required' });
|
|
4036
|
+
return json(res, 200, { ok: true, ...getWorkflowStatus(tenantId) });
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
if (url.pathname === '/hq/subscription/tier') {
|
|
4040
|
+
const { tier } = body;
|
|
4041
|
+
return json(res, 200, { ok: true, tier: getSubscriptionTier(tier) });
|
|
4042
|
+
}
|
|
4043
|
+
|
|
2656
4044
|
return json(res, 404, { ok: false, error: `No route for POST ${url.pathname}` });
|
|
2657
4045
|
} catch (error) {
|
|
2658
4046
|
return json(res, 500, {
|
|
@@ -2678,6 +4066,15 @@ export async function startRuntimeServer(options = {}) {
|
|
|
2678
4066
|
});
|
|
2679
4067
|
});
|
|
2680
4068
|
});
|
|
4069
|
+
server.on('upgrade', (req, socket) => {
|
|
4070
|
+
try {
|
|
4071
|
+
handleWebSocketUpgrade(req, socket);
|
|
4072
|
+
} catch {
|
|
4073
|
+
try {
|
|
4074
|
+
socket.destroy();
|
|
4075
|
+
} catch {}
|
|
4076
|
+
}
|
|
4077
|
+
});
|
|
2681
4078
|
|
|
2682
4079
|
await new Promise((resolve, reject) => {
|
|
2683
4080
|
server.once('error', reject);
|