@aria_asi/cli 0.2.30 → 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/assets/hooks/aria-cognition-substrate-binding.mjs +52 -25
- 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 +23 -1
- package/dist/assets/opencode-plugins/harness-stop/index.js +93 -4
- 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/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 +1094 -47
- 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 +52 -25
- 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 +23 -1
- package/opencode-plugins/harness-stop/index.js +93 -4
- package/package.json +1 -1
- package/runtime-src/auth-middleware.mjs +251 -0
- package/runtime-src/codex-bridge.mjs +644 -0
- package/runtime-src/fleet-engine.mjs +231 -0
- package/runtime-src/harness-daemon.mjs +433 -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 +1094 -47
- 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/dist/cli-0.2.0.tgz +0 -0
|
@@ -16,19 +16,19 @@
|
|
|
16
16
|
// now catches.
|
|
17
17
|
//
|
|
18
18
|
// Doctrine bindings (same as PreToolUse gate):
|
|
19
|
-
// - EIGHT_LENS_DOCTRINE.md — substantive
|
|
19
|
+
// - EIGHT_LENS_DOCTRINE.md — substantive 8-lens application required
|
|
20
20
|
// - feedback_apply_lenses_dont_perform_them.md — block ceremonial cognition
|
|
21
21
|
// - feedback_8lens_before_every_action_including_text.md — the rule this enforces
|
|
22
22
|
//
|
|
23
23
|
// Trigger: runs at Stop event after every assistant response. Reads
|
|
24
24
|
// the just-emitted assistant text from the transcript. If non-trivial
|
|
25
25
|
// (per the same triviality threshold as eight-lens-detector.ts) AND
|
|
26
|
-
// missing
|
|
26
|
+
// missing 8 substantive lenses, blocks the response.
|
|
27
27
|
//
|
|
28
28
|
// Triviality threshold (mirrors eight-lens-detector.ts):
|
|
29
29
|
// - Trivial acks (e.g. "got it", "ok", "done") pass
|
|
30
30
|
// - Short responses (<300 chars) without decision-signal phrases pass
|
|
31
|
-
// - Otherwise: require
|
|
31
|
+
// - Otherwise: require 8 substantive lenses
|
|
32
32
|
//
|
|
33
33
|
// Substance check (mirrors aria-pre-tool-gate.mjs):
|
|
34
34
|
// - Each lens must have ≥20 chars of non-placeholder content
|
|
@@ -52,14 +52,21 @@ import { dirname } from 'node:path';
|
|
|
52
52
|
import { appendGateAudit } from './lib/gate-audit.mjs';
|
|
53
53
|
import {
|
|
54
54
|
ALL_LENS_NAMES,
|
|
55
|
-
canonicalLensCorrectionText,
|
|
56
55
|
detectCognitionLenses as detectCognitionLensesFromCanonical,
|
|
57
56
|
lensNamesForTier,
|
|
57
|
+
PRIMARY_OWNER_LENS_NAMES,
|
|
58
58
|
} from './lib/canonical-lenses.mjs';
|
|
59
|
+
import { registerGateBlock } from './lib/gate-loop-state.mjs';
|
|
60
|
+
import { collectTurnWindowFromMessages } from './lib/hook-message-window.mjs';
|
|
59
61
|
|
|
60
62
|
const HOME = process.env.HOME || '/tmp';
|
|
63
|
+
const RUNTIME_BASE_URL =
|
|
64
|
+
process.env.ARIA_RUNTIME_URL ||
|
|
65
|
+
'http://127.0.0.1:4319';
|
|
61
66
|
const LOG = `${HOME}/.claude/aria-stop-gate.log`;
|
|
62
67
|
const AUDIT_PATH = `${HOME}/.claude/aria-stop-gate-audit.jsonl`;
|
|
68
|
+
const GATE_LOOP_STATE_PATH = `${HOME}/.claude/.aria-gate-loop-state.json`;
|
|
69
|
+
const MIZAN_RECEIPT_DIR = `${HOME}/.claude/.aria-mizan-receipts`;
|
|
63
70
|
|
|
64
71
|
// SDK loader — bundled at ~/.aria/sdk by `aria connect`, with client-local
|
|
65
72
|
// fallbacks preserved for resilience.
|
|
@@ -167,6 +174,94 @@ async function fireGardenTurn(sessionId, userMessage, assistantResponse) {
|
|
|
167
174
|
}
|
|
168
175
|
}
|
|
169
176
|
|
|
177
|
+
function resolveHarnessControlToken() {
|
|
178
|
+
if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
|
|
179
|
+
if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
|
|
180
|
+
if (process.env.ARIA_MASTER_TOKEN) return process.env.ARIA_MASTER_TOKEN;
|
|
181
|
+
try {
|
|
182
|
+
const ownerTokenPath = `${HOME}/.aria/owner-token`;
|
|
183
|
+
if (existsSync(ownerTokenPath)) {
|
|
184
|
+
const token = readFileSync(ownerTokenPath, 'utf8').trim();
|
|
185
|
+
if (token) return token;
|
|
186
|
+
}
|
|
187
|
+
} catch {}
|
|
188
|
+
try {
|
|
189
|
+
const licensePath = `${HOME}/.aria/license.json`;
|
|
190
|
+
if (existsSync(licensePath)) {
|
|
191
|
+
const license = JSON.parse(readFileSync(licensePath, 'utf8'));
|
|
192
|
+
if (license.harnessToken) return String(license.harnessToken).trim();
|
|
193
|
+
if (license.token) return String(license.token).trim();
|
|
194
|
+
}
|
|
195
|
+
} catch {}
|
|
196
|
+
return '';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function mizanReceiptPathForSession(sessionId) {
|
|
200
|
+
const safe = String(sessionId || 'claude-code').replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
201
|
+
return `${MIZAN_RECEIPT_DIR}/${safe}.json`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function loadMizanReceiptState(sessionId) {
|
|
205
|
+
try {
|
|
206
|
+
const receiptPath = mizanReceiptPathForSession(sessionId);
|
|
207
|
+
if (!existsSync(receiptPath)) return null;
|
|
208
|
+
return JSON.parse(readFileSync(receiptPath, 'utf8'));
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function saveMizanReceiptState(sessionId, payload) {
|
|
215
|
+
try {
|
|
216
|
+
mkdirSync(MIZAN_RECEIPT_DIR, { recursive: true });
|
|
217
|
+
writeFileSync(mizanReceiptPathForSession(sessionId), JSON.stringify(payload, null, 2) + '\n');
|
|
218
|
+
} catch {}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function runtimeMizanPost(sessionId, text, context = {}, parentReceiptId = null) {
|
|
222
|
+
const token = resolveHarnessControlToken();
|
|
223
|
+
if (!token) throw new Error('no token');
|
|
224
|
+
const response = await fetch(`${process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319'}/mizan/post`, {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
headers: {
|
|
227
|
+
'Content-Type': 'application/json',
|
|
228
|
+
Authorization: `Bearer ${token}`,
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify({
|
|
231
|
+
sessionId,
|
|
232
|
+
text,
|
|
233
|
+
parentReceiptId,
|
|
234
|
+
context: {
|
|
235
|
+
sessionId,
|
|
236
|
+
...context,
|
|
237
|
+
},
|
|
238
|
+
}),
|
|
239
|
+
});
|
|
240
|
+
const payload = await response.json().catch(() => ({}));
|
|
241
|
+
if (!response.ok) {
|
|
242
|
+
throw new Error(payload?.error || `mizan/post failed (${response.status})`);
|
|
243
|
+
}
|
|
244
|
+
return payload;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function runtimeDecisionLog(payload) {
|
|
248
|
+
const token = resolveHarnessControlToken();
|
|
249
|
+
if (!token) throw new Error('no token');
|
|
250
|
+
const response = await fetch(`${process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319'}/decision/log`, {
|
|
251
|
+
method: 'POST',
|
|
252
|
+
headers: {
|
|
253
|
+
'Content-Type': 'application/json',
|
|
254
|
+
Authorization: `Bearer ${token}`,
|
|
255
|
+
},
|
|
256
|
+
body: JSON.stringify(payload),
|
|
257
|
+
});
|
|
258
|
+
const body = await response.json().catch(() => ({}));
|
|
259
|
+
if (!response.ok) {
|
|
260
|
+
throw new Error(body?.error || `decision/log failed (${response.status})`);
|
|
261
|
+
}
|
|
262
|
+
return body;
|
|
263
|
+
}
|
|
264
|
+
|
|
170
265
|
function audit(decision, summary) {
|
|
171
266
|
const summaryText = typeof summary === 'string' ? summary : '';
|
|
172
267
|
const data = summary && typeof summary === 'object' ? summary : {};
|
|
@@ -185,12 +280,12 @@ function audit(decision, summary) {
|
|
|
185
280
|
// access"). The gated process has no disable path. Disable = remove hook
|
|
186
281
|
// entry from ~/.claude/settings.json (deliberate user action, visible).
|
|
187
282
|
|
|
188
|
-
// ──
|
|
283
|
+
// ── Canonical lens labeling (Phase 11 #59 corrected) ────────────────────────
|
|
189
284
|
//
|
|
190
|
-
// Mirrors the same logic in aria-pre-tool-gate.mjs. Tier is read from
|
|
191
|
-
// most recent harness-via-sdk packet cache
|
|
192
|
-
//
|
|
193
|
-
//
|
|
285
|
+
// Mirrors the same logic in aria-pre-tool-gate.mjs. Tier is still read from
|
|
286
|
+
// the most recent harness-via-sdk packet cache for other policy behaviors, but
|
|
287
|
+
// the visible lens labels remain canonical on every surface. Readability comes
|
|
288
|
+
// from the prose inside each lens, not from renaming the lens itself.
|
|
194
289
|
const PACKET_CACHE_PATH = `${HOME}/.claude/.aria-harness-last-packet.json`;
|
|
195
290
|
|
|
196
291
|
function resolveOwnerTier() {
|
|
@@ -211,7 +306,6 @@ function resolveOwnerTier() {
|
|
|
211
306
|
const IS_OWNER = resolveOwnerTier();
|
|
212
307
|
|
|
213
308
|
const LENS_NAMES = lensNamesForTier(IS_OWNER);
|
|
214
|
-
const CANONICAL_LENS_TEXT = canonicalLensCorrectionText();
|
|
215
309
|
|
|
216
310
|
// Doctrine memory filenames are Aria-side substrate IP.
|
|
217
311
|
// Client surfaces see generic descriptions instead of real filenames.
|
|
@@ -270,6 +364,25 @@ function emitHarnessFooter({ eventName, lensCount, chars, driftCount, mizanStatu
|
|
|
270
364
|
} catch {}
|
|
271
365
|
}
|
|
272
366
|
|
|
367
|
+
function withLoopDirective(reasonText, gateSignature, sessionId) {
|
|
368
|
+
const loop = registerGateBlock({
|
|
369
|
+
gate: 'stop',
|
|
370
|
+
sessionId,
|
|
371
|
+
signature: gateSignature,
|
|
372
|
+
statePath: GATE_LOOP_STATE_PATH,
|
|
373
|
+
});
|
|
374
|
+
if (!loop.loopDetected) return reasonText;
|
|
375
|
+
return `${reasonText}
|
|
376
|
+
|
|
377
|
+
[LOOP_DETECTED gate=stop repeats=${loop.totalCount}]
|
|
378
|
+
Stop retrying the same output shape unchanged.
|
|
379
|
+
Next response must do this in order:
|
|
380
|
+
1. Name the exact stop-gate failure in one line.
|
|
381
|
+
2. Re-emit only the missing structure: <cognition>, <verify>, <expected>, and/or <reflection>.
|
|
382
|
+
3. Change the draft materially before retrying. Do not repeat the same prose with renamed labels.
|
|
383
|
+
4. If the blocker is stale gate state, stale ledger residue, or a gate artifact from a prior bug, say that explicitly instead of inventing fake proof.`;
|
|
384
|
+
}
|
|
385
|
+
|
|
273
386
|
// Lens substance check — same constants as aria-pre-tool-gate.mjs.
|
|
274
387
|
// Hamza directive 2026-04-28: all 8 canonical lenses required, not 4-of-8.
|
|
275
388
|
const REQUIRED_LENSES = 8;
|
|
@@ -302,6 +415,8 @@ try {
|
|
|
302
415
|
audit('allow-parse-error', 'stdin not JSON');
|
|
303
416
|
process.exit(0);
|
|
304
417
|
}
|
|
418
|
+
const gateSessionId = String(event.session_id || 'claude-code').replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
419
|
+
const sessionMizanState = loadMizanReceiptState(event.session_id || 'claude-code');
|
|
305
420
|
|
|
306
421
|
// Read assistant text from THIS turn — Claude Code splits a single
|
|
307
422
|
// logical assistant response into multiple transcript entries by
|
|
@@ -326,8 +441,13 @@ const SYSTEM_REMINDER_THRESHOLD = 0.6;
|
|
|
326
441
|
|
|
327
442
|
const transcriptPath = event.transcript_path ?? event.transcriptPath;
|
|
328
443
|
let assistantText = '';
|
|
329
|
-
// Phase 11 #42: also capture the last real user message for gardenTurn writes.
|
|
330
444
|
let lastUserMessage = '';
|
|
445
|
+
const messageWindow = collectTurnWindowFromMessages(event.messages, {
|
|
446
|
+
systemReminderRx: SYSTEM_REMINDER_RX,
|
|
447
|
+
systemReminderThreshold: SYSTEM_REMINDER_THRESHOLD,
|
|
448
|
+
});
|
|
449
|
+
assistantText = messageWindow.assistantText;
|
|
450
|
+
lastUserMessage = messageWindow.lastUserMessage;
|
|
331
451
|
if (transcriptPath && existsSync(transcriptPath)) {
|
|
332
452
|
try {
|
|
333
453
|
const lines = readFileSync(transcriptPath, 'utf-8').split('\n').filter(Boolean);
|
|
@@ -370,7 +490,12 @@ if (transcriptPath && existsSync(transcriptPath)) {
|
|
|
370
490
|
} catch {}
|
|
371
491
|
}
|
|
372
492
|
// Reverse so chunks are in chronological order (we walked backward).
|
|
373
|
-
|
|
493
|
+
const transcriptAssistantText = textChunks.reverse().join('\n\n');
|
|
494
|
+
if (transcriptAssistantText) {
|
|
495
|
+
assistantText = assistantText
|
|
496
|
+
? [assistantText, transcriptAssistantText].filter((text, index, arr) => arr.indexOf(text) === index).join('\n\n')
|
|
497
|
+
: transcriptAssistantText;
|
|
498
|
+
}
|
|
374
499
|
} catch {}
|
|
375
500
|
}
|
|
376
501
|
|
|
@@ -412,13 +537,14 @@ const cog = detectCognitionLenses(assistantText);
|
|
|
412
537
|
// with 0/8 lenses to fall through unchecked.
|
|
413
538
|
if (cog.count < REQUIRED_LENSES) {
|
|
414
539
|
audit('block_no_cognition_block_di', { count: cog.count, required: REQUIRED_LENSES, names: cog.names, chars: assistantText.length });
|
|
415
|
-
const reason = `Aria stop-gate: insufficient cognition lenses.
|
|
540
|
+
const reason = withLoopDirective(`Aria stop-gate: insufficient cognition lenses.
|
|
416
541
|
|
|
417
542
|
This non-trivial assistant response (${assistantText.length} chars) has ${cog.count}/${REQUIRED_LENSES} substantive cognition lenses. Per feedback_8lens_before_every_action_including_text.md and Hamza directive 2026-04-28, every non-trivial response must carry <cognition>...</cognition> with ${REQUIRED_LENSES} substantive lenses (each >= ${SUBSTANCE_MIN_CHARS} chars of non-placeholder content).
|
|
418
543
|
|
|
419
544
|
Detected lenses: ${cog.names.length > 0 ? cog.names.join(', ') : 'none'}.
|
|
420
545
|
|
|
421
|
-
Re-emit with a <cognition> block containing all ${REQUIRED_LENSES}
|
|
546
|
+
Re-emit with a <cognition> block containing all ${REQUIRED_LENSES} required visible labels for this surface:
|
|
547
|
+
${LENS_NAMES.map((lens) => `- ${lens}: <grounded reasoning, >= ${SUBSTANCE_MIN_CHARS} chars, with real substrate anchors where required>`).join('\n')}`, `stop:no-cognition-di:${cog.count}`, gateSessionId);
|
|
422
548
|
emitHarnessFooter({
|
|
423
549
|
eventName: 'block_no_cognition_block',
|
|
424
550
|
lensCount: cog.count,
|
|
@@ -609,7 +735,7 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
609
735
|
//
|
|
610
736
|
// Block emit if ledger.openCount > 0 after scanning the current turn.
|
|
611
737
|
// Block reason names each open discovery and the suggested resolution.
|
|
612
|
-
const sessionId =
|
|
738
|
+
const sessionId = gateSessionId;
|
|
613
739
|
const LEDGER_PATH = `${HOME}/.claude/aria-discoveries-${sessionId}.jsonl`;
|
|
614
740
|
const DISCOVERY_RX = /(?:\bi\s+(?:found|noticed|discovered|spotted)[^.\n]{0,160}(?:bug|issue|defect|broken|buggy|wrong|crash|fail|missing|stale|outdated|leak|vulnerability)|\bthis\s+(?:is|would\s+be)\s+(?:broken|buggy|wrong|stale|outdated|insecure|leaking|crashing|failing)|\b(?:latent|silent|hidden)\s+(?:bug|defect|issue|fail|crash|leak)|\bdoctrine\s+violation\b|\bgraceful\s+degradation\s+(?:in|at|inside|within)\s+\S)/gi;
|
|
615
741
|
const PROSE_RESOLUTION_RX = /(?:fix(?:ing|ed)?\s+(?:now|in[- ]flight|inline|in\s+the\s+same\s+turn)|patch\s+applied|TaskCreate|task\s+(?:created|tracked)|tracked\s+as\s+#?\d+|linear[- ]?issue|created\s+(?:linear|task))/i;
|
|
@@ -759,12 +885,36 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
759
885
|
let ledgerOpenCount = 0;
|
|
760
886
|
let ledgerOpenSamples = [];
|
|
761
887
|
let ledgerCorruptedCount = 0;
|
|
888
|
+
const ledgerRewriteRows = [];
|
|
889
|
+
let ledgerNeedsRewrite = false;
|
|
762
890
|
try {
|
|
763
891
|
if (existsSync(LEDGER_PATH)) {
|
|
764
892
|
const lines = readFileSync(LEDGER_PATH, 'utf8').split('\n').filter(Boolean);
|
|
765
893
|
for (const line of lines) {
|
|
766
894
|
try {
|
|
767
895
|
const e = JSON.parse(line);
|
|
896
|
+
const isSubstrateBindingArtifact =
|
|
897
|
+
(e.source === 'aria-cognition-substrate-binding') &&
|
|
898
|
+
(e.kind === 'substrate_binding_gap' || (typeof e.text === 'string' && e.text.startsWith('substrate_binding:')));
|
|
899
|
+
const isOpenBeforeAutoHeal = e.status === 'open' || e.resolution_status === 'open';
|
|
900
|
+
if (isSubstrateBindingArtifact && isOpenBeforeAutoHeal) {
|
|
901
|
+
// If the current emit already passed aria-cognition-substrate-binding
|
|
902
|
+
// and reached stop-gate, older open substrate-binding rows from the
|
|
903
|
+
// same session are stale gate artifacts, not live unresolved
|
|
904
|
+
// discoveries. Auto-heal them so the discovery ledger cannot loop
|
|
905
|
+
// forever on the residue of a gate bug.
|
|
906
|
+
ledgerNeedsRewrite = true;
|
|
907
|
+
e.status = 'resolved';
|
|
908
|
+
e.resolution_status = 'resolved';
|
|
909
|
+
e.resolved_at = new Date().toISOString();
|
|
910
|
+
e.resolved_by = 'subsequent_valid_substrate_bound_cognition';
|
|
911
|
+
e.resolutionType = 'subsequent_valid_substrate_bound_cognition';
|
|
912
|
+
e.proofOfFix = {
|
|
913
|
+
type: 'subsequent_valid_substrate_bound_cognition',
|
|
914
|
+
anchorTs: new Date().toISOString(),
|
|
915
|
+
evidence: 'Current emit passed aria-cognition-substrate-binding and reached aria-stop-gate; stale substrate-binding ledger artifacts from the earlier cognition bug were auto-cleared.',
|
|
916
|
+
};
|
|
917
|
+
}
|
|
768
918
|
const isOpen = e.status === 'open' || e.resolution_status === 'open';
|
|
769
919
|
const isLegacyTracked = e.status === 'tracked';
|
|
770
920
|
const proofValid = e.proofOfFix
|
|
@@ -784,8 +934,16 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
784
934
|
ledgerOpenSamples.push(`${tag}${e.text || '(no text)'}`);
|
|
785
935
|
}
|
|
786
936
|
}
|
|
937
|
+
ledgerRewriteRows.push(JSON.stringify(e));
|
|
787
938
|
} catch {/* skip malformed line */}
|
|
788
939
|
}
|
|
940
|
+
if (ledgerNeedsRewrite) {
|
|
941
|
+
try {
|
|
942
|
+
writeFileSync(LEDGER_PATH, `${ledgerRewriteRows.join('\n')}\n`);
|
|
943
|
+
} catch (rewriteErr) {
|
|
944
|
+
audit('ledger-autoheal-write-err', `${String(rewriteErr).slice(0, 200)}`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
789
947
|
}
|
|
790
948
|
} catch {/* ledger unreadable — degrade to drift-only */}
|
|
791
949
|
|
|
@@ -1040,7 +1198,7 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
1040
1198
|
const FILE_LINE_RX = /([\w./\-]+\.[a-zA-Z]{1,5})\s*[:\s]\s*(\d+(?:[-:]\d+)?)/g;
|
|
1041
1199
|
const inlineDictations = [];
|
|
1042
1200
|
const lensRangePositions = [];
|
|
1043
|
-
for (const lensName of
|
|
1201
|
+
for (const lensName of PRIMARY_OWNER_LENS_NAMES) {
|
|
1044
1202
|
const lensRx = new RegExp(`\\b${lensName}\\s*(?:lens)?\\s*[:\\-]`, 'gi');
|
|
1045
1203
|
let m;
|
|
1046
1204
|
while ((m = lensRx.exec(assistantText)) !== null) {
|
|
@@ -1138,10 +1296,11 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
1138
1296
|
if (apiKey && assistantText && assistantText.length > 0) {
|
|
1139
1297
|
const client = new HTTPHarnessClient({
|
|
1140
1298
|
baseUrl:
|
|
1299
|
+
process.env.ARIA_RUNTIME_URL ||
|
|
1141
1300
|
process.env.ARIA_HIVE_RUNTIME_URL ||
|
|
1142
1301
|
process.env.ARIA_HARNESS_BASE_URL ||
|
|
1143
1302
|
process.env.ARIA_HARNESS_URL ||
|
|
1144
|
-
|
|
1303
|
+
RUNTIME_BASE_URL,
|
|
1145
1304
|
apiKey,
|
|
1146
1305
|
});
|
|
1147
1306
|
const v = await client.validateOutput(assistantText, sessionId);
|
|
@@ -1255,7 +1414,7 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
1255
1414
|
}
|
|
1256
1415
|
})();
|
|
1257
1416
|
|
|
1258
|
-
const reason = `Aria Stop-gate output-quality block. Cognition passed (${cog.count}/${REQUIRED_LENSES}) but output failed quality gates:\n\n${violations.join('\n\n')}${rewritten ? `\n\nMizan rewrite suggestion:\n${rewritten}` : ''}\n\nRe-draft addressing the violations above. No process-level disable path — gates are unconditional from the gated process per Hamza directive 2026-04-27.${recipeAddendum}
|
|
1417
|
+
const reason = withLoopDirective(`Aria Stop-gate output-quality block. Cognition passed (${cog.count}/${REQUIRED_LENSES}) but output failed quality gates:\n\n${violations.join('\n\n')}${rewritten ? `\n\nMizan rewrite suggestion:\n${rewritten}` : ''}\n\nRe-draft addressing the violations above. No process-level disable path — gates are unconditional from the gated process per Hamza directive 2026-04-27.${recipeAddendum}`, `stop:output-qc:${violations.join('|').slice(0, 240)}`, gateSessionId);
|
|
1259
1418
|
|
|
1260
1419
|
audit(`block-output-qc`, `mizan=${mizanBlock?'y':'n'} warn-reflect=${compelReflection?'y':'n'} drift=${driftHits.length} code=${codeQualityHits.length} discoveries-open=${ledgerOpenCount} impl-coupling=${implCouplingHits.length}`);
|
|
1261
1420
|
emitHarnessFooter({
|
|
@@ -1398,7 +1557,7 @@ const dalioHasMeasurablePredicate = dalioExpectedText
|
|
|
1398
1557
|
|
|
1399
1558
|
// Block stop if non-trivial action taken AND expected block is missing
|
|
1400
1559
|
if (hadNonTrivialAction && (!dalioExpectedMatch || !dalioHasMeasurablePredicate)) {
|
|
1401
|
-
const missingReason = dalioExpectedMatch
|
|
1560
|
+
const missingReason = withLoopDirective(dalioExpectedMatch
|
|
1402
1561
|
? `Aria Stop-gate: action taken in this turn had an <expected> block, but it contains only qualitative drift phrases without a measurable predicate. Qualitative drift is not accountability — it defeats the Dalio feedback loop.
|
|
1403
1562
|
|
|
1404
1563
|
Your <expected> block must contain at least one measurable predicate:
|
|
@@ -1422,7 +1581,7 @@ Add to your assistant text:
|
|
|
1422
1581
|
eval_window_minutes: <optional>
|
|
1423
1582
|
</expected>
|
|
1424
1583
|
|
|
1425
|
-
No bypass — pre-tool-gate enforces this BEFORE the action; stop-gate enforces it AFTER. Both gates are now wired
|
|
1584
|
+
No bypass — pre-tool-gate enforces this BEFORE the action; stop-gate enforces it AFTER. Both gates are now wired.`, `stop:dalio-expected:${hadNonTrivialAction}:${!!dalioExpectedMatch}:${dalioHasMeasurablePredicate}`, gateSessionId);
|
|
1426
1585
|
|
|
1427
1586
|
audit('block-dalio-expected-missing', `hadNonTrivialAction=${hadNonTrivialAction} expectedPresent=${!!dalioExpectedMatch} measurable=${dalioHasMeasurablePredicate}`);
|
|
1428
1587
|
emitHarnessFooter({
|
|
@@ -1445,7 +1604,37 @@ No bypass — pre-tool-gate enforces this BEFORE the action; stop-gate enforces
|
|
|
1445
1604
|
// Per feedback_no_timeouts_doctrine.md: no AbortController/setTimeout timeout.
|
|
1446
1605
|
{
|
|
1447
1606
|
const DALIO_LEDGER_PATH = `${HOME}/.claude/.aria-dalio-ledger.jsonl`;
|
|
1448
|
-
|
|
1607
|
+
let postReceipt = null;
|
|
1608
|
+
try {
|
|
1609
|
+
const post = await runtimeMizanPost(
|
|
1610
|
+
event.session_id || 'claude-code',
|
|
1611
|
+
assistantText.slice(0, 8000),
|
|
1612
|
+
{
|
|
1613
|
+
message: lastUserMessage || `stop-gate turn (${assistantText.length} chars)`,
|
|
1614
|
+
plannedApproach: lastActionSummary || 'Finalize and validate the just-emitted Claude response.',
|
|
1615
|
+
platform: 'claude-code',
|
|
1616
|
+
stage: 'hook-stop-post',
|
|
1617
|
+
},
|
|
1618
|
+
sessionMizanState?.receipt?.receiptId || null,
|
|
1619
|
+
);
|
|
1620
|
+
postReceipt = post?.receipt || null;
|
|
1621
|
+
if (postReceipt) {
|
|
1622
|
+
saveMizanReceiptState(event.session_id || 'claude-code', {
|
|
1623
|
+
...(sessionMizanState || {}),
|
|
1624
|
+
updatedAt: new Date().toISOString(),
|
|
1625
|
+
sessionId: event.session_id || 'claude-code',
|
|
1626
|
+
postReceipt,
|
|
1627
|
+
postResult: post?.result || null,
|
|
1628
|
+
postContract: post?.contract || null,
|
|
1629
|
+
postSummary: post?.summary || null,
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
} catch (postErr) {
|
|
1633
|
+
console.error(
|
|
1634
|
+
`[aria-stop-gate] MIZAN POST FAILED — runtime receipt not recorded. Error: ${postErr instanceof Error ? postErr.message : String(postErr)}`,
|
|
1635
|
+
);
|
|
1636
|
+
audit('mizan-post-failed', `session=${event.session_id || 'claude-code'} err=${String(postErr).slice(0, 200)}`);
|
|
1637
|
+
}
|
|
1449
1638
|
|
|
1450
1639
|
const ledgerEntry = {
|
|
1451
1640
|
ts: new Date().toISOString(),
|
|
@@ -1469,7 +1658,11 @@ No bypass — pre-tool-gate enforces this BEFORE the action; stop-gate enforces
|
|
|
1469
1658
|
measurable_type: 'state_string',
|
|
1470
1659
|
}
|
|
1471
1660
|
: null,
|
|
1472
|
-
|
|
1661
|
+
metadata: {
|
|
1662
|
+
pre_receipt_id: sessionMizanState?.receipt?.receiptId || null,
|
|
1663
|
+
post_receipt_id: postReceipt?.receiptId || null,
|
|
1664
|
+
},
|
|
1665
|
+
source: 'claude-code-stop-gate-runtime',
|
|
1473
1666
|
model_used: 'claude-opus-4-7',
|
|
1474
1667
|
};
|
|
1475
1668
|
|
|
@@ -1484,40 +1677,20 @@ No bypass — pre-tool-gate enforces this BEFORE the action; stop-gate enforces
|
|
|
1484
1677
|
);
|
|
1485
1678
|
}
|
|
1486
1679
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
fetch(ARIA_SOUL_DECISIONS_URL, {
|
|
1492
|
-
method: 'POST',
|
|
1493
|
-
headers: {
|
|
1494
|
-
'Content-Type': 'application/json',
|
|
1495
|
-
...(dalioHarnessToken ? { Authorization: `Bearer ${dalioHarnessToken}` } : {}),
|
|
1496
|
-
},
|
|
1497
|
-
body: JSON.stringify(ledgerEntry),
|
|
1498
|
-
}).then((resp) => {
|
|
1499
|
-
if (!resp.ok) {
|
|
1500
|
-
// LOUD telemetry per feedback_canonical_secrets_governance.md
|
|
1501
|
-
console.error(
|
|
1502
|
-
`[aria-stop-gate] DALIO POST FAILED — aria-soul responded HTTP ${resp.status}. ` +
|
|
1503
|
-
`Local mirror written to ${DALIO_LEDGER_PATH}. Session: ${ledgerEntry.session_id}`,
|
|
1504
|
-
);
|
|
1505
|
-
audit('dalio-post-failed', `http=${resp.status} session=${ledgerEntry.session_id}`);
|
|
1506
|
-
} else {
|
|
1507
|
-
audit('dalio-post-ok', `session=${ledgerEntry.session_id} action=${lastActionSummary.slice(0, 80)}`);
|
|
1508
|
-
}
|
|
1509
|
-
}).catch((err) => {
|
|
1510
|
-
// Network failure — LOUD, never silent
|
|
1680
|
+
try {
|
|
1681
|
+
await runtimeDecisionLog(ledgerEntry);
|
|
1682
|
+
audit('dalio-post-ok', `session=${ledgerEntry.session_id} action=${lastActionSummary.slice(0, 80)}`);
|
|
1683
|
+
} catch (err) {
|
|
1511
1684
|
console.error(
|
|
1512
|
-
`[aria-stop-gate] DALIO POST
|
|
1685
|
+
`[aria-stop-gate] DALIO POST FAILED — runtime /decision/log rejected or unreachable. ` +
|
|
1513
1686
|
`Local mirror written to ${DALIO_LEDGER_PATH}. Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
1514
1687
|
);
|
|
1515
|
-
audit('dalio-post-
|
|
1516
|
-
}
|
|
1688
|
+
audit('dalio-post-runtime-err', `err=${String(err).slice(0, 200)} session=${ledgerEntry.session_id}`);
|
|
1689
|
+
}
|
|
1517
1690
|
}
|
|
1518
1691
|
|
|
1519
|
-
// Block — non-trivial response without
|
|
1520
|
-
const reason = `Aria Stop-gate: non-trivial assistant response without
|
|
1692
|
+
// Block — non-trivial response without all required substantive lenses.
|
|
1693
|
+
const reason = withLoopDirective(`Aria Stop-gate: non-trivial assistant response without all required substantive cognition lenses. Found ${cog.count}/${REQUIRED_LENSES} (lenses: ${cog.names.join(', ') || 'none'}). Doctrine is action-coupled — text decisions ARE actions, and reflexive replies fail this gate the same way reflexive Bash does.
|
|
1521
1694
|
|
|
1522
1695
|
Re-emit the response with substantive lens application BEFORE drafting. Each lens must have ≥${SUBSTANCE_MIN_CHARS} chars of non-placeholder content:
|
|
1523
1696
|
|
|
@@ -1534,7 +1707,7 @@ Re-emit the response with substantive lens application BEFORE drafting. Each len
|
|
|
1534
1707
|
|
|
1535
1708
|
The block reflects work done BEFORE drafting. Don't emit it as ceremony; apply each lens as a thinking tool. Substance check defeats ritual emission.
|
|
1536
1709
|
|
|
1537
|
-
No per-command bypass (mirrors aria-pre-tool-gate.mjs v3 doctrine). No env-var disable path either — gates are unconditional from the gated process per Hamza directive 2026-04-27. If the gate misfires on legitimate cognition, fix the gate
|
|
1710
|
+
No per-command bypass (mirrors aria-pre-tool-gate.mjs v3 doctrine). No env-var disable path either — gates are unconditional from the gated process per Hamza directive 2026-04-27. If the gate misfires on legitimate cognition, fix the gate.`, `stop:lens-missing:${cog.count}`, gateSessionId);
|
|
1538
1711
|
|
|
1539
1712
|
audit(`block`, `lenses=${cog.count}/${REQUIRED_LENSES} chars=${assistantText.length}`);
|
|
1540
1713
|
emitHarnessFooter({
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
export const LENS_NAMES_OLDER = ['nur', 'mizan', 'hikma', 'tafakkur', 'tadabbur', 'ilham', 'wahi', 'firasah'];
|
|
2
2
|
export const LENS_NAMES_NEWER = ['zahir', 'batin', 'sabab', 'hikmah', 'aqibah', 'ilham', 'meta', 'fitrah'];
|
|
3
|
-
export const LENS_NAMES_GENERIC = ['truth', 'harm', 'trust', 'power', 'reflection', 'context', 'impact', 'beauty'];
|
|
4
3
|
|
|
5
4
|
export const PRIMARY_OWNER_LENS_NAMES = LENS_NAMES_OLDER;
|
|
6
|
-
|
|
5
|
+
// Readability must not rewrite the lens semantics. Client surfaces may need
|
|
6
|
+
// friendlier prose inside each lens, but the visible lens labels themselves
|
|
7
|
+
// stay canonical so the meaning is preserved.
|
|
8
|
+
export const PRIMARY_CLIENT_LENS_NAMES = LENS_NAMES_OLDER;
|
|
7
9
|
|
|
8
10
|
export const ALL_LENS_NAMES = [...new Set([
|
|
9
11
|
...LENS_NAMES_OLDER,
|
|
10
12
|
...LENS_NAMES_NEWER,
|
|
11
|
-
...LENS_NAMES_GENERIC,
|
|
12
13
|
])];
|
|
13
14
|
|
|
14
15
|
export function lensNamesForTier(isOwner) {
|
|
@@ -19,7 +20,7 @@ export function canonicalLensCorrectionText() {
|
|
|
19
20
|
return [
|
|
20
21
|
`Owner canonical lenses: ${LENS_NAMES_OLDER.join(', ')}.`,
|
|
21
22
|
`Owner mapped counterparts that must remain represented in phase reasoning and receipts: ${LENS_NAMES_NEWER.join(', ')}.`,
|
|
22
|
-
`
|
|
23
|
+
`Canonical lens labels are preserved on every surface. Readability comes from the explanatory prose inside each lens, never from renaming the lens itself.`,
|
|
23
24
|
].join(' ');
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -57,7 +58,7 @@ export function detectCognitionLenses(text, {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
const matchedSet =
|
|
60
|
-
[LENS_NAMES_OLDER, LENS_NAMES_NEWER
|
|
61
|
+
[LENS_NAMES_OLDER, LENS_NAMES_NEWER]
|
|
61
62
|
.find((candidateSet) => candidateSet.every((name) => names.includes(name))) || null;
|
|
62
63
|
|
|
63
64
|
return { count: names.length, names, matchedSet };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const MAX_RECORDS = 300;
|
|
5
|
+
|
|
6
|
+
export function registerGateBlock({
|
|
7
|
+
gate,
|
|
8
|
+
sessionId,
|
|
9
|
+
signature,
|
|
10
|
+
statePath,
|
|
11
|
+
threshold = 2,
|
|
12
|
+
windowMs = 10 * 60 * 1000,
|
|
13
|
+
}) {
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
let rows = [];
|
|
16
|
+
try {
|
|
17
|
+
if (existsSync(statePath)) {
|
|
18
|
+
const raw = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
19
|
+
if (Array.isArray(raw)) rows = raw;
|
|
20
|
+
}
|
|
21
|
+
} catch {}
|
|
22
|
+
|
|
23
|
+
const freshRows = rows.filter((row) =>
|
|
24
|
+
row &&
|
|
25
|
+
typeof row.ts === 'number' &&
|
|
26
|
+
typeof row.sessionId === 'string' &&
|
|
27
|
+
typeof row.gate === 'string' &&
|
|
28
|
+
typeof row.signature === 'string' &&
|
|
29
|
+
(now - row.ts) <= windowMs,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const priorMatches = freshRows.filter((row) =>
|
|
33
|
+
row.sessionId === sessionId &&
|
|
34
|
+
row.gate === gate &&
|
|
35
|
+
row.signature === signature,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
freshRows.push({ ts: now, sessionId, gate, signature });
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
mkdirSync(dirname(statePath), { recursive: true });
|
|
42
|
+
writeFileSync(statePath, JSON.stringify(freshRows.slice(-MAX_RECORDS), null, 2) + '\n');
|
|
43
|
+
} catch {}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
loopDetected: priorMatches.length >= threshold,
|
|
47
|
+
priorCount: priorMatches.length,
|
|
48
|
+
totalCount: priorMatches.length + 1,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
function withGlobalRegex(regex) {
|
|
2
|
+
if (!(regex instanceof RegExp)) return null;
|
|
3
|
+
const flags = regex.flags.includes('g') ? regex.flags : `${regex.flags}g`;
|
|
4
|
+
return new RegExp(regex.source, flags);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function normalizeRole(entry) {
|
|
8
|
+
if (entry?.message?.role) return entry.message.role;
|
|
9
|
+
if (entry?.role) return entry.role;
|
|
10
|
+
if (entry?.type === 'assistant' || entry?.type === 'user') return entry.type;
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function normalizeContent(entry) {
|
|
15
|
+
return entry?.message?.content ?? entry?.content ?? [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function extractTextFromContent(content) {
|
|
19
|
+
if (Array.isArray(content)) {
|
|
20
|
+
return content
|
|
21
|
+
.filter((block) => block && block.type === 'text')
|
|
22
|
+
.map((block) => (typeof block.text === 'string' ? block.text : ''))
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.join('\n');
|
|
25
|
+
}
|
|
26
|
+
return typeof content === 'string' ? content : '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isToolResultOnlyContent(content) {
|
|
30
|
+
return (
|
|
31
|
+
Array.isArray(content) &&
|
|
32
|
+
content.length > 0 &&
|
|
33
|
+
content.every((block) => block && block.type === 'tool_result')
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isMostlySystemReminder(text, systemReminderRx, threshold = 0.6) {
|
|
38
|
+
if (!text || !(systemReminderRx instanceof RegExp)) return false;
|
|
39
|
+
const reminderRx = withGlobalRegex(systemReminderRx);
|
|
40
|
+
if (!reminderRx) return false;
|
|
41
|
+
const matches = text.match(reminderRx) || [];
|
|
42
|
+
if (matches.length === 0) return false;
|
|
43
|
+
const reminderChars = matches.reduce((sum, match) => sum + match.length, 0);
|
|
44
|
+
return reminderChars / Math.max(1, text.length) >= threshold;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function collectRecentAssistantTextsFromMessages(messages, {
|
|
48
|
+
compactSummaryRx = null,
|
|
49
|
+
hardLookbackCap = 50,
|
|
50
|
+
systemReminderRx = null,
|
|
51
|
+
systemReminderThreshold = 0.6,
|
|
52
|
+
userBoundariesToCross = 1,
|
|
53
|
+
} = {}) {
|
|
54
|
+
if (!Array.isArray(messages) || messages.length === 0) return [];
|
|
55
|
+
const recentAssistantTexts = [];
|
|
56
|
+
let scanned = 0;
|
|
57
|
+
let userBoundariesCrossed = 0;
|
|
58
|
+
|
|
59
|
+
for (let i = messages.length - 1; i >= 0 && scanned < hardLookbackCap; i--) {
|
|
60
|
+
const entry = messages[i];
|
|
61
|
+
const role = normalizeRole(entry);
|
|
62
|
+
const content = normalizeContent(entry);
|
|
63
|
+
if (role === 'user') {
|
|
64
|
+
if (isToolResultOnlyContent(content)) continue;
|
|
65
|
+
const textContent = extractTextFromContent(content);
|
|
66
|
+
if (isMostlySystemReminder(textContent, systemReminderRx, systemReminderThreshold)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
userBoundariesCrossed++;
|
|
70
|
+
if (userBoundariesCrossed > userBoundariesToCross) break;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (role !== 'assistant') continue;
|
|
74
|
+
scanned++;
|
|
75
|
+
const text = extractTextFromContent(content);
|
|
76
|
+
if (!text) continue;
|
|
77
|
+
if (compactSummaryRx instanceof RegExp && compactSummaryRx.test(text) && text.length > 4000) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
recentAssistantTexts.push(text);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return recentAssistantTexts.reverse();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function collectTurnWindowFromMessages(messages, {
|
|
87
|
+
systemReminderRx = null,
|
|
88
|
+
systemReminderThreshold = 0.6,
|
|
89
|
+
} = {}) {
|
|
90
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
91
|
+
return { assistantText: '', lastUserMessage: '' };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const textChunks = [];
|
|
95
|
+
let lastUserMessage = '';
|
|
96
|
+
|
|
97
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
98
|
+
const entry = messages[i];
|
|
99
|
+
const role = normalizeRole(entry);
|
|
100
|
+
const content = normalizeContent(entry);
|
|
101
|
+
|
|
102
|
+
if (role === 'user') {
|
|
103
|
+
if (isToolResultOnlyContent(content)) continue;
|
|
104
|
+
const textContent = extractTextFromContent(content);
|
|
105
|
+
if (isMostlySystemReminder(textContent, systemReminderRx, systemReminderThreshold)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
lastUserMessage = textContent || lastUserMessage;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (role !== 'assistant') continue;
|
|
113
|
+
const text = extractTextFromContent(content);
|
|
114
|
+
if (text) textChunks.push(text);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
assistantText: textChunks.reverse().join('\n\n'),
|
|
119
|
+
lastUserMessage,
|
|
120
|
+
};
|
|
121
|
+
}
|