← Back to Files
Testing.cshtml
@page "/testing"
@{
ViewData["Title"] = "Testing - KaiChat";
}
<div class="page-header">
<a href="/" class="back-link">← Back to Chat</a>
<h1>Testing</h1>
</div>
<div class="split-page testing-page">
<nav class="split-nav" aria-label="Testing categories">
<button type="button" class="active" data-testing-tab="autonomy">Autonomy</button>
<button type="button" data-testing-tab="personas">Personas</button>
<button type="button" data-testing-tab="compaction">Compaction</button>
</nav>
<div class="split-content">
<section class="testing-panel active" data-testing-panel="autonomy">
<section class="timer-panel autonomy-hero">
<div class="timer-header">
<div>
<h1>Kai Autonomy</h1>
<p class="timer-subtitle">Step runner and state inspection</p>
</div>
<span id="autonomyStatus" class="timer-status">Stopped</span>
</div>
<div class="autonomy-stats">
<div>
<span>Next</span>
<strong id="autonomyNext">-</strong>
</div>
<div>
<span>Last</span>
<strong id="autonomyLast">-</strong>
</div>
<div>
<span>Today</span>
<strong id="autonomyCostToday">$0.000000</strong>
</div>
<div>
<span>Total</span>
<strong id="autonomyCostTotal">$0.000000</strong>
</div>
</div>
<div class="timer-actions">
<button class="btn btn-primary" onclick="forceAutonomyStep()">Run Step</button>
<button class="btn" onclick="rebuildAutonomyWorldCopy()">Rebuild World Copy</button>
<button class="btn" onclick="loadAutonomy()">Refresh</button>
</div>
<div id="autonomyMessage" class="timer-message"></div>
</section>
<div class="autonomy-grid testing-grid">
<section class="timer-panel">
<div class="timer-section-header">
<h2>Time Advance</h2>
</div>
<div class="autonomy-settings">
<label>
<span>Advance minutes</span>
<input id="autonomyFakeAdvance" class="form-input" type="number" step="1" value="15">
</label>
<label>
<span>Optional note</span>
<input id="autonomyReason" class="form-input" placeholder="Optional">
</label>
<div class="autonomy-meta">
<span>Effective now</span>
<strong id="autonomyFakeNow">-</strong>
</div>
<div class="autonomy-meta">
<span>World copy</span>
<strong id="autonomySandboxState">-</strong>
</div>
<div class="autonomy-meta">
<span>Conversation</span>
<strong id="autonomyConversation">-</strong>
</div>
</div>
</section>
<section class="timer-panel autonomy-wide" id="autonomyCurrentRunPanel" style="display:none">
<div class="timer-section-header">
<h2>Current Step</h2>
</div>
<div id="autonomyCurrentRun" class="autonomy-current-run"></div>
</section>
<section class="timer-panel autonomy-wide">
<div class="timer-section-header">
<h2>Intentions</h2>
</div>
<div id="autonomyIntentions" class="timer-list"></div>
</section>
<section class="timer-panel autonomy-wide">
<div class="timer-section-header">
<h2>Guidance</h2>
<div class="timer-actions">
<button class="btn" id="autonomyReflectButton" onclick="reflectAutonomyGuidance()">Reflect Now</button>
<button class="btn btn-primary" id="autonomyApproveGuidanceButton" onclick="approveAutonomyGuidance()">Approve</button>
<button class="btn" id="autonomyRejectGuidanceButton" onclick="rejectAutonomyGuidance()">Reject</button>
</div>
</div>
<div class="autonomy-guidance-grid">
<div>
<h3>Current</h3>
<pre id="autonomyGuidance" class="autonomy-world"></pre>
</div>
<div>
<h3>Pending</h3>
<div id="autonomyPendingGuidance" class="autonomy-pending-guidance"></div>
</div>
<div class="autonomy-wide">
<h3>Reflection Log</h3>
<pre id="autonomyReflectionLog" class="autonomy-world"></pre>
</div>
</div>
</section>
<section class="timer-panel autonomy-wide">
<div class="timer-section-header">
<h2>Kai World State</h2>
</div>
<pre id="autonomyWorld" class="autonomy-world"></pre>
</section>
<section class="timer-panel">
<div class="timer-section-header">
<h2>Continuity Files</h2>
</div>
<div id="autonomyFiles" class="timer-list"></div>
</section>
<section class="timer-panel autonomy-wide">
<div class="timer-section-header">
<h2>Activity Log</h2>
</div>
<pre id="autonomyActivityLog" class="autonomy-world"></pre>
</section>
<section class="timer-panel autonomy-wide">
<div class="timer-section-header">
<h2>Recent Steps</h2>
</div>
<div id="autonomyLogs" class="timer-list"></div>
</section>
</div>
</section>
<section class="testing-panel" data-testing-panel="personas">
<section class="timer-panel autonomy-hero">
<div class="timer-section-header">
<h2>Persona Autonomy Dashboard</h2>
<p class="timer-subtitle">Autonomy status discovered from each persona's state file.</p>
</div>
<div class="autonomy-stats">
<div>
<span>Visible personas</span>
<strong id="autonomyPersonaCount">-</strong>
</div>
<div>
<span>Enabled</span>
<strong id="autonomyPersonaEnabledCount">-</strong>
</div>
<div>
<span>Production</span>
<strong id="autonomyPersonaProductionCount">-</strong>
</div>
<div>
<span>Updated</span>
<strong id="autonomyPersonaUpdatedAt">-</strong>
</div>
</div>
<div class="timer-settings-row">
<label class="timer-check">
<input id="autonomyPersonasIncludeDisabled" type="checkbox">
<span>Include disabled personas</span>
</label>
<div class="timer-actions">
<button class="btn btn-primary" onclick="loadAutonomyPersonas()">Refresh</button>
</div>
</div>
<div id="autonomyPersonasMessage" class="timer-message"></div>
</section>
<section class="timer-panel autonomy-wide">
<div id="autonomyPersonas" class="autonomy-persona-grid"></div>
</section>
</section>
<section class="testing-panel" data-testing-panel="compaction">
<section class="compaction-panel">
<div class="compaction-controls">
<div class="form-group compaction-conversation-field">
<label for="compactionConversation">Conversation</label>
<select id="compactionConversation" class="form-input"></select>
</div>
<label class="checkbox-label compaction-checkbox">
<input type="checkbox" id="compactionForce" checked />
<span>Force plan</span>
</label>
<div class="compaction-actions">
<button type="button" class="btn" id="compactionRefreshBtn">Refresh</button>
<button type="button" class="btn btn-primary" id="compactionPreviewBtn">Preview</button>
<button type="button" class="btn btn-primary" id="compactionDryRunBtn">Dry Run</button>
<button type="button" class="btn btn-primary" id="compactionSummaryBtn">Dry Run + Summary</button>
</div>
</div>
<div class="compaction-status" id="compactionStatus">Loading conversations...</div>
</section>
<section class="compaction-results" id="compactionResults" hidden>
<div class="compaction-summary-row">
<div class="compaction-stat">
<span class="compaction-stat-label">Action</span>
<strong id="compactionAction">-</strong>
</div>
<div class="compaction-stat">
<span class="compaction-stat-label">API Tokens</span>
<strong id="compactionApiTokens">-</strong>
</div>
<div class="compaction-stat">
<span class="compaction-stat-label">After</span>
<strong id="compactionAfterTokens">-</strong>
</div>
<div class="compaction-stat">
<span class="compaction-stat-label">Chunks</span>
<strong id="compactionChunks">-</strong>
</div>
</div>
<div class="compaction-detail-grid">
<div>
<h2>Plan</h2>
<dl class="compaction-details">
<dt>Reason</dt>
<dd id="compactionReason">-</dd>
<dt>Messages</dt>
<dd id="compactionMessages">-</dd>
<dt>Trigger</dt>
<dd id="compactionTrigger">-</dd>
<dt>Limits</dt>
<dd id="compactionLimits">-</dd>
<dt>Archive</dt>
<dd id="compactionArchive">-</dd>
</dl>
</div>
<div>
<h2>Summary Preview</h2>
<pre class="compaction-summary-preview" id="compactionSummaryPreview">(none)</pre>
</div>
</div>
<h2>Chunks</h2>
<div class="compaction-table-wrap">
<table class="compaction-table">
<thead>
<tr>
<th>#</th>
<th>Units</th>
<th>Tokens</th>
<th>First Message</th>
<th>Last Message</th>
</tr>
</thead>
<tbody id="compactionChunkRows"></tbody>
</table>
</div>
<details class="compaction-json">
<summary>Raw Result</summary>
<pre id="compactionRawJson"></pre>
</details>
</section>
</section>
</div>
</div>
@section Scripts {
<script>
let autonomyStatus = null;
let autonomyPersonas = [];
let autonomyPersonasRefreshTimer = null;
function activateTestingPanel(name) {
const hasTab = Array.from(document.querySelectorAll('[data-testing-tab]'))
.some(function (button) { return button.dataset.testingTab === name; });
if (!hasTab) {
name = 'autonomy';
}
document.querySelectorAll('[data-testing-tab]').forEach(function (button) {
button.classList.toggle('active', button.dataset.testingTab === name);
});
document.querySelectorAll('[data-testing-panel]').forEach(function (panel) {
panel.classList.toggle('active', panel.dataset.testingPanel === name);
});
if (location.hash !== '#' + name) history.replaceState(null, '', '#' + name);
if (name === 'personas') {
loadAutonomyPersonas();
if (!autonomyPersonasRefreshTimer) {
autonomyPersonasRefreshTimer = setInterval(function () {
const active = document.querySelector('.testing-panel[data-testing-panel="personas"]')
.classList.contains('active');
if (active) loadAutonomyPersonas();
}, 10000);
}
}
else if (autonomyPersonasRefreshTimer) {
clearInterval(autonomyPersonasRefreshTimer);
autonomyPersonasRefreshTimer = null;
}
}
function setAutonomyMessage(message, isError) {
const el = document.getElementById('autonomyMessage');
el.textContent = message || '';
el.classList.toggle('error', !!isError);
}
function setAutonomyPersonasMessage(message, isError) {
const el = document.getElementById('autonomyPersonasMessage');
if (!el) return;
el.textContent = message || '';
el.classList.toggle('error', !!isError);
}
function formatAutonomyTime(value) {
if (!value) return '-';
const date = new Date(value);
return Number.isNaN(date.getTime()) ? '-' : date.toLocaleString();
}
function formatUsd(value) {
const num = Number(value || 0);
return '$' + num.toFixed(6);
}
function testingEscapeHtml(value) {
return String(value ?? '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
async function autonomyPost(path, body) {
const response = await fetch(path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body || {})
});
const data = await response.json().catch(() => null);
if (!response.ok) {
throw new Error((data && data.message) || 'Autonomy request failed.');
}
return data;
}
async function loadAutonomy() {
try {
const response = await fetch('/api/autonomy/status');
const data = await response.json();
if (!response.ok) throw new Error('Could not load autonomy status.');
renderAutonomy(data);
} catch (error) {
setAutonomyMessage(error.message || String(error), true);
}
}
async function loadAutonomyPersonas() {
const includeDisabled = document.getElementById('autonomyPersonasIncludeDisabled');
try {
const response = await fetch('/api/autonomy/personas?includeDisabled=' + encodeURIComponent((includeDisabled && includeDisabled.checked) ? 'true' : 'false'));
const data = await response.json();
if (!response.ok) throw new Error('Could not load persona autonomy status.');
renderAutonomyPersonas(Array.isArray(data) ? data : []);
} catch (error) {
setAutonomyPersonasMessage(error.message || String(error), true);
}
}
function renderAutonomyPersonas(personas) {
autonomyPersonas = personas || [];
const list = document.getElementById('autonomyPersonas');
const enabledCount = autonomyPersonas.filter(item => item.enabled).length;
const productionCount = autonomyPersonas.filter(item => item.productionMode).length;
const newest = autonomyPersonas
.map(item => item.updatedAtUtc)
.filter(Boolean)
.map(value => new Date(value).getTime())
.filter(value => !Number.isNaN(value))
.sort((a, b) => b - a)[0];
document.getElementById('autonomyPersonaCount').textContent = String(autonomyPersonas.length);
document.getElementById('autonomyPersonaEnabledCount').textContent = String(enabledCount);
document.getElementById('autonomyPersonaProductionCount').textContent = String(productionCount);
document.getElementById('autonomyPersonaUpdatedAt').textContent = newest ? formatAutonomyTime(new Date(newest).toISOString()) : '-';
if (!autonomyPersonas.length) {
list.innerHTML = '<div class="timer-empty">No autonomy states found.</div>';
return;
}
list.innerHTML = autonomyPersonas
.map(persona => {
const statusLabel = persona.enabled ? 'Enabled' : 'Disabled';
const statusClass = persona.enabled ? 'enabled' : 'disabled';
const productionLabel = persona.productionMode ? 'Production' : 'Testing';
const productionClass = persona.productionMode ? 'enabled' : 'testing';
const nextDue = formatAutonomyTime(persona.nextDueUtc);
const fakeNow = formatAutonomyTime(persona.fakeNowUtc);
const updated = formatAutonomyTime(persona.updatedAtUtc);
return '<article class="autonomy-persona-card ' + statusClass + '">' +
'<div class="autonomy-persona-header">' +
'<span class="autonomy-persona-name">' + testingEscapeHtml(persona.name || 'Unknown') + '</span>' +
'<span class="timer-pill ' + testingEscapeHtml(statusClass) + '">' + testingEscapeHtml(statusLabel) + '</span>' +
'</div>' +
'<div class="autonomy-persona-meta">' +
'<span><strong>Key:</strong> ' + testingEscapeHtml(persona.personaKey || '-') + '</span>' +
'<span><strong>Path:</strong> ' + testingEscapeHtml(persona.statePath || '-') + '</span>' +
'<span><strong>Sandbox:</strong> ' + testingEscapeHtml(persona.sandboxRoot || '-') + '</span>' +
'</div>' +
'<div class="autonomy-persona-meta">' +
'<span><strong>Next due:</strong> ' + nextDue + '</span>' +
'<span><strong>Fake now:</strong> ' + fakeNow + '</span>' +
'<span><strong>Updated:</strong> ' + updated + '</span>' +
'</div>' +
'<div class="autonomy-persona-meta">' +
'<span><strong>Interval:</strong> ' + testingEscapeHtml(persona.intervalMinutes || 15) + 'm</span>' +
'<span><strong>Daily budget:</strong> $' + Number(persona.dailyBudgetUsd || 0).toFixed(2) + '</span>' +
'<span><strong>Mode:</strong> <span class="timer-pill ' + productionClass + '">' + testingEscapeHtml(productionLabel) + '</span></span>' +
'<span><strong>Sandbox ready:</strong> ' + (persona.sandboxReady ? 'Yes' : 'No') + '</span>' +
'<span><strong>Cost today:</strong> $' + Number(persona.estimatedCostCacheMissTodayUsd || 0).toFixed(6) + '</span>' +
'</div>' +
'</article>';
})
.join('');
setAutonomyPersonasMessage('');
}
function renderAutonomy(status) {
autonomyStatus = status || {};
const settings = autonomyStatus.settings || {};
const statusEl = document.getElementById('autonomyStatus');
statusEl.textContent = autonomyStatus.isRunning ? 'Running' : (settings.enabled ? 'Scheduled' : 'Stopped');
statusEl.classList.toggle('running', !!autonomyStatus.isRunning || !!settings.enabled);
document.getElementById('autonomyNext').textContent = formatAutonomyTime(autonomyStatus.nextDueUtc);
document.getElementById('autonomyLast').textContent = formatAutonomyTime(autonomyStatus.lastStepUtc);
document.getElementById('autonomyFakeNow').textContent = formatAutonomyTime(autonomyStatus.fakeNowUtc);
document.getElementById('autonomyCostToday').textContent = formatUsd(autonomyStatus.estimatedCostCacheMissTodayUsd);
document.getElementById('autonomyCostTotal').textContent = formatUsd(autonomyStatus.estimatedCostCacheMissUsd);
document.getElementById('autonomySandboxState').textContent = autonomyStatus.sandboxReady ? 'Ready' : 'Not ready';
document.getElementById('autonomyConversation').textContent = autonomyStatus.targetConversationId || '-';
document.getElementById('autonomyWorld').textContent = autonomyStatus.worldStateMarkdown || '';
document.getElementById('autonomyActivityLog').textContent = autonomyStatus.activityLogMarkdown || '';
renderCurrentRun(autonomyStatus.currentRun);
renderAutonomyIntentions(autonomyStatus.intentions || []);
renderAutonomyGuidance(autonomyStatus);
renderAutonomyFiles(autonomyStatus.sandboxFiles || []);
renderAutonomyLogs(autonomyStatus.logs || []);
}
function renderCurrentRun(run) {
const panel = document.getElementById('autonomyCurrentRunPanel');
const body = document.getElementById('autonomyCurrentRun');
if (!run) {
panel.style.display = 'none';
body.innerHTML = '';
return;
}
panel.style.display = '';
const delivery = run.deliveryStatus
? '<p><strong>Delivery:</strong> ' + testingEscapeHtml(run.deliveryStatus) +
(run.deliveredMessageId ? ' (' + testingEscapeHtml(run.deliveredConversationId || '') + '/' + testingEscapeHtml(run.deliveredMessageId) + ')' : '') +
(run.deliveryTriggeredCompaction ? ' Compaction: ' + testingEscapeHtml(run.deliveryArchivePath || 'yes') : '') +
'</p>'
: '';
body.innerHTML =
'<div class="autonomy-stats autonomy-current-stats">' +
'<div><span>Phase</span><strong>' + testingEscapeHtml(run.phase || '-') + '</strong></div>' +
'<div><span>Started</span><strong>' + testingEscapeHtml(formatAutonomyTime(run.startedAtUtc)) + '</strong></div>' +
'<div><span>Effective</span><strong>' + testingEscapeHtml(formatAutonomyTime(run.effectiveNowUtc)) + '</strong></div>' +
'<div><span>Tokens</span><strong>' + testingEscapeHtml(run.inputTokens || 0) + ' / ' + testingEscapeHtml(run.outputTokens || 0) + '</strong></div>' +
'</div>' +
'<p><strong>Status:</strong> ' + testingEscapeHtml(run.message || '') + '</p>' +
'<p><strong>Intent actions:</strong> ' + testingEscapeHtml(run.appliedActionCount || 0) + ' applied / ' + testingEscapeHtml(run.actionCount || 0) + ' planned</p>' +
'<p><strong>Local file actions:</strong> ' + testingEscapeHtml(run.appliedFileOperationCount || 0) + ' applied / ' + testingEscapeHtml(run.fileOperationCount || 0) + ' planned</p>' +
(run.visibleMessage ? '<p><strong>Planned visible message:</strong> ' + testingEscapeHtml(run.visibleMessage) + '</p>' : '') +
(run.healthAwarenessNote ? '<p><strong>Health awareness:</strong> ' + testingEscapeHtml(run.healthAwarenessNote) + '</p>' : '') +
(run.guidanceProposalSummary ? '<p><strong>Guidance proposal:</strong> ' + testingEscapeHtml(run.guidanceProposalSummary) + (run.guidanceProposalId ? ' (' + testingEscapeHtml(run.guidanceProposalId) + ')' : '') + '</p>' : '') +
delivery;
}
function renderAutonomyIntentions(intentions) {
const list = document.getElementById('autonomyIntentions');
const active = (intentions || []).filter(item => (item.status || 'active') === 'active');
const closed = (intentions || []).filter(item => (item.status || 'active') !== 'active').slice(0, 10);
const items = active.concat(closed);
if (!items.length) {
list.innerHTML = '<div class="timer-empty">No intentions yet.</div>';
return;
}
list.innerHTML = items.map(item => {
const due = item.dueAtUtc ? 'Due ' + formatAutonomyTime(item.dueAtUtc) : 'No due time';
return '<div class="timer-row autonomy-intention ' + testingEscapeHtml(item.status || 'active') + '">' +
'<div>' +
'<strong>' + testingEscapeHtml(item.title || '(untitled)') + '</strong>' +
'<p>' + testingEscapeHtml(item.details || '') + '</p>' +
'<span>' + testingEscapeHtml((item.status || 'active') + ' | ' + (item.priority || 'normal') + ' | ' + due) + '</span>' +
'</div>' +
'<code>' + testingEscapeHtml(item.id || '') + '</code>' +
'</div>';
}).join('');
}
function renderAutonomyGuidance(status) {
const pending = status.pendingGuidanceProposal || null;
document.getElementById('autonomyGuidance').textContent = status.guidanceMarkdown || '';
document.getElementById('autonomyReflectionLog').textContent = status.reflectionLogMarkdown || '';
document.getElementById('autonomyApproveGuidanceButton').disabled = !pending || !!status.isRunning;
document.getElementById('autonomyRejectGuidanceButton').disabled = !pending || !!status.isRunning;
document.getElementById('autonomyReflectButton').disabled = !!pending || !!status.isRunning;
const target = document.getElementById('autonomyPendingGuidance');
if (!pending) {
target.innerHTML = '<div class="timer-empty">No pending proposal.</div>';
return;
}
target.innerHTML =
'<div class="autonomy-proposal-meta">' +
'<p><strong>ID:</strong> ' + testingEscapeHtml(pending.id || '') + '</p>' +
'<p><strong>Created:</strong> ' + testingEscapeHtml(formatAutonomyTime(pending.createdAtUtc)) + '</p>' +
'<p><strong>Summary:</strong> ' + testingEscapeHtml(pending.changeSummary || '') + '</p>' +
'<p><strong>Reason:</strong> ' + testingEscapeHtml(pending.reason || '') + '</p>' +
'</div>' +
'<pre class="autonomy-world">' + testingEscapeHtml(pending.proposedGuidanceMarkdown || '') + '</pre>';
}
function renderAutonomyFiles(files) {
const list = document.getElementById('autonomyFiles');
list.innerHTML = files.length
? files.map(path => '<div class="timer-row"><span class="autonomy-file">' + testingEscapeHtml(path) + '</span></div>').join('')
: '<div class="timer-empty">No continuity files.</div>';
}
function renderAutonomyLogs(logs) {
const list = document.getElementById('autonomyLogs');
const hasRendered = list.dataset.rendered === 'true';
const existingIds = new Set(Array.from(list.querySelectorAll('details.autonomy-log')).map(el => el.dataset.logId || ''));
const openIds = new Set(Array.from(list.querySelectorAll('details.autonomy-log[open]')).map(el => el.dataset.logId || ''));
if (!logs.length) {
list.innerHTML = '<div class="timer-empty">No steps yet.</div>';
list.dataset.rendered = 'true';
return;
}
list.innerHTML = logs.map(log => {
const logId = log.id || log.startedAtUtc || '';
const isNew = !existingIds.has(logId);
const shouldOpen = openIds.has(logId) || (!log.success && (!hasRendered || isNew));
const ops = (log.fileOperations || []).map(op => {
const state = op.applied ? 'applied' : 'skipped';
return '<li><strong>' + testingEscapeHtml(state) + '</strong> ' + testingEscapeHtml(op.action || '') + ' ' + testingEscapeHtml(op.path || '') + ' - ' + testingEscapeHtml(op.message || '') + '</li>';
}).join('');
const actions = (log.actionResults || []).map(action => {
const state = action.applied ? 'applied' : 'skipped';
return '<li><strong>' + testingEscapeHtml(state) + '</strong> ' + testingEscapeHtml(action.kind || '') + ' ' + testingEscapeHtml(action.id || '') + ' - ' + testingEscapeHtml(action.message || '') + '</li>';
}).join('');
return '<details class="autonomy-log" data-log-id="' + testingEscapeHtml(logId) + '" ' + (shouldOpen ? 'open' : '') + '>' +
'<summary>' +
'<span>' + testingEscapeHtml(formatAutonomyTime(log.startedAtUtc)) + '</span>' +
'<strong>' + testingEscapeHtml(log.message || log.type || '') + '</strong>' +
'<em>' + testingEscapeHtml(formatUsd(log.estimatedCostCacheMissUsd)) + '</em>' +
'</summary>' +
'<div class="autonomy-log-body">' +
'<p><strong>Effective:</strong> ' + testingEscapeHtml(formatAutonomyTime(log.effectiveNowUtc)) + ' | <strong>Elapsed:</strong> ' + testingEscapeHtml(log.elapsedMinutes || 0) + 'm | <strong>Tokens:</strong> ' + testingEscapeHtml(log.inputTokens || 0) + ' in / ' + testingEscapeHtml(log.outputTokens || 0) + ' out</p>' +
(log.journalEntry ? '<p><strong>Journal:</strong> ' + testingEscapeHtml(log.journalEntry) + '</p>' : '') +
(log.visibleMessage ? '<p><strong>Visible message:</strong> ' + testingEscapeHtml(log.visibleMessage) + '</p>' : '') +
(log.deliveryStatus ? '<p><strong>Delivery:</strong> ' + testingEscapeHtml(log.deliveryStatus) + (log.deliveredMessageId ? ' (' + testingEscapeHtml(log.deliveredConversationId || '') + '/' + testingEscapeHtml(log.deliveredMessageId) + ')' : '') + (log.deliveryTriggeredCompaction ? ' Compaction: ' + testingEscapeHtml(log.deliveryArchivePath || 'yes') : '') + '</p>' : '') +
(log.kaiContinuityNote ? '<p><strong>Kai continuity:</strong> ' + testingEscapeHtml(log.kaiContinuityNote) + '</p>' : '') +
(log.healthAwarenessNote ? '<p><strong>Health awareness:</strong> ' + testingEscapeHtml(log.healthAwarenessNote) + '</p>' : '') +
(log.guidanceProposalId ? '<p><strong>Guidance proposal:</strong> ' + testingEscapeHtml(log.guidanceProposalId) + '</p>' : '') +
(log.notesForRaymond ? '<p><strong>Legacy note:</strong> ' + testingEscapeHtml(log.notesForRaymond) + '</p>' : '') +
(actions ? '<p><strong>Actions:</strong></p><ul>' + actions + '</ul>' : '') +
(ops ? '<ul>' + ops + '</ul>' : '') +
(log.rawResponsePreview ? '<pre>' + testingEscapeHtml(log.rawResponsePreview) + '</pre>' : '') +
'</div>' +
'</details>';
}).join('');
list.dataset.rendered = 'true';
}
async function forceAutonomyStep() {
setAutonomyMessage('Running step...', false);
try {
const note = document.getElementById('autonomyReason').value.trim();
setTimeout(loadAutonomy, 500);
const result = await autonomyPost('/api/autonomy/step', {
fakeAdvanceMinutes: parseInt(document.getElementById('autonomyFakeAdvance').value || '0', 10),
reason: note || null
});
renderAutonomy(result.status);
setAutonomyMessage(result.message || 'Step complete.', !result.success);
} catch (error) {
setAutonomyMessage(error.message || String(error), true);
loadAutonomy();
}
}
async function reflectAutonomyGuidance() {
setAutonomyMessage('Reflecting...', false);
try {
setTimeout(loadAutonomy, 500);
const result = await autonomyPost('/api/autonomy/guidance/reflect', {});
renderAutonomy(result.status);
setAutonomyMessage(result.message || 'Reflection complete.', !result.success);
} catch (error) {
setAutonomyMessage(error.message || String(error), true);
loadAutonomy();
}
}
async function approveAutonomyGuidance() {
const pending = autonomyStatus && autonomyStatus.pendingGuidanceProposal;
if (!pending) return;
setAutonomyMessage('', false);
try {
const result = await autonomyPost('/api/autonomy/guidance/approve', { proposalId: pending.id });
renderAutonomy(result.status);
setAutonomyMessage(result.message || 'Guidance approved.', !result.success);
} catch (error) {
setAutonomyMessage(error.message || String(error), true);
loadAutonomy();
}
}
async function rejectAutonomyGuidance() {
const pending = autonomyStatus && autonomyStatus.pendingGuidanceProposal;
if (!pending) return;
setAutonomyMessage('', false);
try {
const result = await autonomyPost('/api/autonomy/guidance/reject', { proposalId: pending.id });
renderAutonomy(result.status);
setAutonomyMessage(result.message || 'Guidance rejected.', !result.success);
} catch (error) {
setAutonomyMessage(error.message || String(error), true);
loadAutonomy();
}
}
async function rebuildAutonomyWorldCopy() {
if (!confirm('Rebuild the autonomy world copy from the current source files? This will also clear the autonomy activity and reflection logs for this sandbox run.')) return;
setAutonomyMessage('', false);
try {
const status = await autonomyPost('/api/autonomy/sandbox/reset', {});
renderAutonomy(status);
setAutonomyMessage('World copy rebuilt and logs cleared.', false);
} catch (error) {
setAutonomyMessage(error.message || String(error), true);
}
}
function initCompactionTesting() {
var select = document.getElementById('compactionConversation');
var status = document.getElementById('compactionStatus');
var refreshBtn = document.getElementById('compactionRefreshBtn');
var previewBtn = document.getElementById('compactionPreviewBtn');
var dryRunBtn = document.getElementById('compactionDryRunBtn');
var summaryBtn = document.getElementById('compactionSummaryBtn');
var forceInput = document.getElementById('compactionForce');
function setBusy(isBusy, text) {
status.textContent = text || '';
refreshBtn.disabled = isBusy;
previewBtn.disabled = isBusy;
dryRunBtn.disabled = isBusy;
summaryBtn.disabled = isBusy;
select.disabled = isBusy;
forceInput.disabled = isBusy;
}
function selectedId() {
return select.value || '';
}
function formatNumber(value) {
if (value === null || value === undefined) return '-';
return Number(value).toLocaleString();
}
function setText(id, value) {
document.getElementById(id).textContent = value;
}
function renderResult(result, mode) {
document.getElementById('compactionResults').hidden = false;
var action = result.compacted ? 'Compacted' : result.shouldCompact ? 'Would compact' : 'Skip';
if (result.dryRun) action += ' (dry run)';
setText('compactionAction', action);
setText('compactionApiTokens', formatNumber(result.estimatedApiTokens));
setText('compactionAfterTokens', formatNumber(result.estimatedPostCompactionApiTokens));
setText('compactionChunks', formatNumber(result.chunkCount));
setText('compactionReason', result.reason || '-');
setText('compactionMessages',
formatNumber(result.messagesToCompact) + ' compacted / '
+ formatNumber(result.messagesToKeep) + ' kept / '
+ formatNumber(result.messagesBefore) + ' total');
setText('compactionTrigger',
formatNumber(result.estimatedApiTokens) + ' of '
+ formatNumber(result.triggerTokens) + ' trigger, '
+ formatNumber(result.contextTokenLimit) + ' context limit');
setText('compactionLimits',
formatNumber(result.modelInputTokens) + ' input / '
+ formatNumber(result.modelOutputTokens) + ' output, '
+ formatNumber(result.chunkTokenLimit) + ' chunk cap, '
+ formatNumber(result.summaryMaxTokens) + ' summary cap');
setText('compactionArchive', result.archivePath || '(none written)');
setText('compactionSummaryPreview', result.summaryPreview || '(none)');
setText('compactionRawJson', JSON.stringify(result, null, 2));
var rows = document.getElementById('compactionChunkRows');
rows.innerHTML = '';
(result.chunks || []).forEach(function (chunk) {
var tr = document.createElement('tr');
tr.innerHTML = '<td>' + formatNumber(chunk.index) + '</td>'
+ '<td>' + formatNumber(chunk.unitCount) + '</td>'
+ '<td>' + formatNumber(chunk.estimatedTokens) + '</td>'
+ '<td><code>' + testingEscapeHtml(chunk.firstMessageId || '-') + '</code></td>'
+ '<td><code>' + testingEscapeHtml(chunk.lastMessageId || '-') + '</code></td>';
rows.appendChild(tr);
});
if (!rows.children.length) {
var tr = document.createElement('tr');
tr.innerHTML = '<td colspan="5" class="compaction-empty-cell">No chunks</td>';
rows.appendChild(tr);
}
status.textContent = mode + ' complete.';
}
function loadCompactionConversations() {
setBusy(true, 'Loading conversations...');
return fetch('/api/conversations')
.then(function (r) {
if (!r.ok) throw new Error('Could not load conversations');
return r.json();
})
.then(function (convs) {
select.innerHTML = '';
convs.forEach(function (conv) {
var opt = document.createElement('option');
opt.value = conv.id;
opt.textContent = (conv.title || 'Chat') + ' (' + conv.messageCount + ' messages)';
select.appendChild(opt);
});
status.textContent = convs.length ? 'Ready.' : 'No conversations found.';
})
.catch(function (err) {
status.textContent = 'Error: ' + (err.message || err);
})
.finally(function () {
setBusy(false, status.textContent);
});
}
function previewCompaction() {
var id = selectedId();
if (!id) return;
setBusy(true, 'Previewing compaction...');
fetch('/api/compaction/preview/' + encodeURIComponent(id) + '?force=' + encodeURIComponent(forceInput.checked))
.then(function (r) {
if (!r.ok) throw new Error('Preview failed');
return r.json();
})
.then(function (result) { renderResult(result, 'Preview'); })
.catch(function (err) { status.textContent = 'Error: ' + (err.message || err); })
.finally(function () { setBusy(false, status.textContent); });
}
function dryRunCompaction(generateSummary) {
var id = selectedId();
if (!id) return;
setBusy(true, generateSummary ? 'Running dry-run summary...' : 'Running dry-run plan...');
fetch('/api/compaction/test/' + encodeURIComponent(id), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
force: forceInput.checked,
generateSummary: !!generateSummary
})
})
.then(function (r) {
if (!r.ok) throw new Error('Dry run failed');
return r.json();
})
.then(function (result) { renderResult(result, generateSummary ? 'Summary dry run' : 'Dry run'); })
.catch(function (err) { status.textContent = 'Error: ' + (err.message || err); })
.finally(function () { setBusy(false, status.textContent); });
}
refreshBtn.addEventListener('click', loadCompactionConversations);
previewBtn.addEventListener('click', previewCompaction);
dryRunBtn.addEventListener('click', function () { dryRunCompaction(false); });
summaryBtn.addEventListener('click', function () { dryRunCompaction(true); });
loadCompactionConversations();
}
window.forceAutonomyStep = forceAutonomyStep;
window.rebuildAutonomyWorldCopy = rebuildAutonomyWorldCopy;
window.loadAutonomy = loadAutonomy;
window.loadAutonomyPersonas = loadAutonomyPersonas;
window.reflectAutonomyGuidance = reflectAutonomyGuidance;
window.approveAutonomyGuidance = approveAutonomyGuidance;
window.rejectAutonomyGuidance = rejectAutonomyGuidance;
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('[data-testing-tab]').forEach(function (button) {
button.addEventListener('click', function () {
activateTestingPanel(button.dataset.testingTab);
});
});
const personaIncludeDisabled = document.getElementById('autonomyPersonasIncludeDisabled');
if (personaIncludeDisabled) {
personaIncludeDisabled.addEventListener('change', loadAutonomyPersonas);
}
const requested = (location.hash || '').replace('#', '');
activateTestingPanel(requested || 'autonomy');
loadAutonomy();
initCompactionTesting();
setInterval(loadAutonomy, 5000);
});
</script>
}