openvisio-agent 0.20.0 → 0.21.1
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/CHANGELOG.md +786 -0
- package/README.md +3 -1
- package/USER_GUIDE.md +13 -4
- package/package.json +2 -1
- package/scenarios/workspace.scenarios.mjs +3 -3
- package/scripts/certify.mjs +2 -2
- package/src/events.mjs +16 -2
- package/src/mastra-harness.mjs +33 -6
- package/src/model-settings.mjs +57 -0
- package/src/studio-cli.mjs +11 -0
- package/src/studio-server.mjs +43 -7
- package/src/watch.mjs +90 -44
- package/studio/app.mjs +229 -43
- package/studio/guide.html +13 -2
- package/studio/index.html +45 -9
- package/studio/openvisio.svg +1 -0
- package/studio/satoshi-400.woff2 +0 -0
- package/studio/satoshi-500.woff2 +0 -0
- package/studio/style.css +240 -70
package/studio/app.mjs
CHANGED
|
@@ -14,7 +14,7 @@ export function normalizeStatus(value, fallback = 'observed') {
|
|
|
14
14
|
if (['active', 'running', 'started', 'ready', 'in_progress', 'working', 'online', 'preflight'].includes(status)) return 'active';
|
|
15
15
|
if (['queued', 'pending', 'waiting'].includes(status)) return 'queued';
|
|
16
16
|
if (['completed', 'complete', 'finished', 'done', 'success', 'succeeded', 'ok'].includes(status)) return 'completed';
|
|
17
|
-
if (['failed', 'failure', 'error', 'rejected'].includes(status)) return 'failed';
|
|
17
|
+
if (['failed', 'failure', 'error', 'rejected', 'rate_limited'].includes(status)) return 'failed';
|
|
18
18
|
if (['cancelled', 'canceled', 'aborted', 'interrupted'].includes(status)) return 'cancelled';
|
|
19
19
|
if (['offline', 'stopped', 'disconnected', 'exited'].includes(status)) return 'offline';
|
|
20
20
|
if (['skipped', 'recovering', 'idle', 'blocked', 'timeout'].includes(status)) return status;
|
|
@@ -164,6 +164,22 @@ export function summarizeModel(model, selectedAgent = 'all') {
|
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
// Shared task identity groups recorded work; it does not imply delegation.
|
|
168
|
+
export function taskActivityCycles(model, selectedCycle) {
|
|
169
|
+
const ticket = record(selectedCycle.data.ticket);
|
|
170
|
+
if (ticket.projectId == null || ticket.ticketId == null) return [selectedCycle];
|
|
171
|
+
const latest = new Map();
|
|
172
|
+
for (const cycle of model.cycles) {
|
|
173
|
+
const candidate = record(cycle.data.ticket);
|
|
174
|
+
if (String(candidate.projectId) !== String(ticket.projectId) || String(candidate.ticketId) !== String(ticket.ticketId)) continue;
|
|
175
|
+
const id = agentId(cycle.agent);
|
|
176
|
+
if (!latest.has(id) || timeValue(cycle.lastSeen) > timeValue(latest.get(id).lastSeen)) latest.set(id, cycle);
|
|
177
|
+
}
|
|
178
|
+
// Keep the selected historical cycle visible even when this agent ran again.
|
|
179
|
+
latest.set(agentId(selectedCycle.agent), selectedCycle);
|
|
180
|
+
return [...latest.values()].sort((a, b) => agentId(a.agent).localeCompare(agentId(b.agent)));
|
|
181
|
+
}
|
|
182
|
+
|
|
167
183
|
export function eventTitle(event) {
|
|
168
184
|
const data = record(event?.data);
|
|
169
185
|
const titles = {
|
|
@@ -238,10 +254,13 @@ if (typeof document !== 'undefined') initializeStudio();
|
|
|
238
254
|
|
|
239
255
|
function initializeStudio() {
|
|
240
256
|
const $ = id => document.getElementById(id);
|
|
257
|
+
const launch = new URLSearchParams(location.hash.slice(1));
|
|
258
|
+
let launchSettings = launch.get('settings') === '1';
|
|
259
|
+
let editingAgent = null, editingSettings = null, settingsRequest = 0;
|
|
241
260
|
const state = {
|
|
242
261
|
snapshot: null, model: null, pending: null, paused: false, connected: false,
|
|
243
|
-
selectedAgent: 'all', agentSearch: '', search: '', kind: 'all', status: 'all',
|
|
244
|
-
view: 'activity', selectedCycle: null, selectedEvent: null, selectedProcess: null,
|
|
262
|
+
selectedAgent: launch.get('agent') || 'all', agentSearch: '', search: '', kind: 'all', status: 'all',
|
|
263
|
+
view: 'activity', detailView: 'plan', selectedCycle: null, selectedEvent: null, selectedProcess: null,
|
|
245
264
|
limit: PAGE_SIZE, follow: true, pendingUpdates: 0, error: null,
|
|
246
265
|
};
|
|
247
266
|
let scheduled = false;
|
|
@@ -331,7 +350,7 @@ function initializeStudio() {
|
|
|
331
350
|
button.type = 'button';
|
|
332
351
|
button.setAttribute('aria-pressed', String(state.selectedAgent === id));
|
|
333
352
|
const avatar = node('span', 'agent-avatar');
|
|
334
|
-
if (id === 'all') avatar.append(icon('
|
|
353
|
+
if (id === 'all') avatar.append(icon('agents'));
|
|
335
354
|
else { avatar.textContent = name.slice(0, 2).toUpperCase(); avatar.append(node('span', `avatar-status ${agent.status}`)); }
|
|
336
355
|
const body = node('span', 'agent-text');
|
|
337
356
|
body.append(node('span', 'agent-name', name), node('span', 'agent-caption', caption));
|
|
@@ -351,6 +370,64 @@ function initializeStudio() {
|
|
|
351
370
|
$('metric-processes-caption').textContent = state.snapshot.demo ? 'Simulated processes' : summary.unreportedProcesses ? `${summary.unreportedProcesses} runtime PID not reported` : `${online} ${online === 1 ? 'watcher' : 'watchers'} connected`;
|
|
352
371
|
$('metric-events-caption').textContent = 'In the available local history';
|
|
353
372
|
}
|
|
373
|
+
function renderModelSettings() {
|
|
374
|
+
const agent = state.model.agents.find(agent => agentId(agent) === state.selectedAgent);
|
|
375
|
+
const section = $('model-settings-summary');
|
|
376
|
+
section.hidden = !agent || state.snapshot.demo;
|
|
377
|
+
if (!agent || state.snapshot.demo) return;
|
|
378
|
+
$('model-settings-agent').textContent = `${agentName(agent)} · Models`;
|
|
379
|
+
$('open-model-settings').disabled = !agent.settingsEditable;
|
|
380
|
+
const settings = agent.modelSettings || {};
|
|
381
|
+
const pending = settings.revision && agent.appliedModelRevision !== settings.revision;
|
|
382
|
+
$('model-settings-state').textContent = !agent.settingsEditable ? 'Model controls are available for agents configured on this computer.'
|
|
383
|
+
: pending ? agent.modelControlsSupported && agent.status === 'online' ? 'Saved · waiting for the watcher to apply your choices.' : 'Saved for the next watcher connection. Older watchers need an update to apply changes live.'
|
|
384
|
+
: `Replies: ${settings.chatModel || settings.model || 'Runtime default'} · Coding: ${settings.model || 'Runtime default'}`;
|
|
385
|
+
if (launchSettings && agent.settingsEditable) { launchSettings = false; void openModelSettings(agent); }
|
|
386
|
+
}
|
|
387
|
+
async function settingsFetch(path, options) {
|
|
388
|
+
const response = await fetch(path, { cache: 'no-store', ...options });
|
|
389
|
+
const body = await response.json();
|
|
390
|
+
if (!response.ok) throw new Error(body.error || 'Could not load model settings. Try again.');
|
|
391
|
+
return body;
|
|
392
|
+
}
|
|
393
|
+
async function loadModels(agent, request) {
|
|
394
|
+
$('model-catalog-status').textContent = 'Loading model choices…';
|
|
395
|
+
try {
|
|
396
|
+
const { models } = await settingsFetch(`/api/agents/${encodeURIComponent(agent.slug)}/models`);
|
|
397
|
+
if (request !== settingsRequest) return;
|
|
398
|
+
const choices = [...new Set([editingSettings?.model, editingSettings?.chatModel, ...models].filter(Boolean))];
|
|
399
|
+
$('runtime-models').replaceChildren(...choices.map(model => { const option = node('option'); option.value = model; return option; }));
|
|
400
|
+
$('model-catalog-status').textContent = choices.length ? `${choices.length} models · select a field to search, or enter a model ID.` : 'Enter a model ID supported by your connected runtime.';
|
|
401
|
+
} catch (error) {
|
|
402
|
+
if (request === settingsRequest) $('model-catalog-status').textContent = `${error.message} You can still enter a model ID.`;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
async function openModelSettings(agent) {
|
|
406
|
+
const request = ++settingsRequest;
|
|
407
|
+
editingAgent = agent; editingSettings = null;
|
|
408
|
+
$('model-settings-title').textContent = `${agentName(agent)} · Model settings`;
|
|
409
|
+
$('model-settings-error').hidden = true;
|
|
410
|
+
$('reply-model').value = ''; $('coding-model').value = '';
|
|
411
|
+
$('runtime-models').replaceChildren();
|
|
412
|
+
$('save-model-settings').disabled = true;
|
|
413
|
+
$('reload-models').disabled = true;
|
|
414
|
+
$('model-catalog-status').textContent = 'Loading settings…';
|
|
415
|
+
if (!$('model-settings-dialog').open) $('model-settings-dialog').showModal();
|
|
416
|
+
try {
|
|
417
|
+
const settings = await settingsFetch(`/api/agents/${encodeURIComponent(agent.slug)}/settings`);
|
|
418
|
+
if (request !== settingsRequest) return;
|
|
419
|
+
editingSettings = settings;
|
|
420
|
+
$('reply-model').value = settings.chatModel || settings.model || '';
|
|
421
|
+
$('coding-model').value = settings.model || '';
|
|
422
|
+
$('save-model-settings').disabled = false;
|
|
423
|
+
$('reload-models').disabled = false;
|
|
424
|
+
void loadModels(agent, request);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
if (request !== settingsRequest) return;
|
|
427
|
+
$('model-settings-error').hidden = false; $('model-settings-error').textContent = error.message;
|
|
428
|
+
$('model-catalog-status').textContent = 'Close this panel and try again when the agent is available.';
|
|
429
|
+
}
|
|
430
|
+
}
|
|
354
431
|
function resetSelection() {
|
|
355
432
|
state.limit = PAGE_SIZE;
|
|
356
433
|
state.selectedEvent = null;
|
|
@@ -413,7 +490,12 @@ function initializeStudio() {
|
|
|
413
490
|
return baseRow({ id: event.id, title: eventTitle(event), detail: description(event), agent: event.agent, status: eventStatus(event), timestamp: event.timestamp,
|
|
414
491
|
iconName: kindIcons[event.type.split('.')[0]] || 'pulse', selected: state.selectedEvent === event.id,
|
|
415
492
|
extra: text(ticket.slug || (ticket.ticketId != null ? `Ticket ${ticket.ticketId}` : '')),
|
|
416
|
-
onClick: () => {
|
|
493
|
+
onClick: () => {
|
|
494
|
+
state.selectedEvent = event.id; state.selectedCycle = cycleKey(event); state.selectedProcess = null;
|
|
495
|
+
if (event.type.startsWith('tool.')) state.detailView = 'commands';
|
|
496
|
+
else if (event.type.startsWith('output.')) state.detailView = 'response';
|
|
497
|
+
else if (event.type === 'plan.updated') state.detailView = 'plan';
|
|
498
|
+
},
|
|
417
499
|
});
|
|
418
500
|
}
|
|
419
501
|
function cycleRow(cycle) {
|
|
@@ -455,7 +537,7 @@ function initializeStudio() {
|
|
|
455
537
|
return empty;
|
|
456
538
|
}
|
|
457
539
|
function detailSection(title, iconName) {
|
|
458
|
-
const section = node('section', 'detail-section');
|
|
540
|
+
const section = node('section', 'detail-section oa-card');
|
|
459
541
|
const heading = node('h3'); if (iconName) heading.append(icon(iconName)); heading.append(document.createTextNode(title));
|
|
460
542
|
section.append(heading);
|
|
461
543
|
return section;
|
|
@@ -479,6 +561,7 @@ function initializeStudio() {
|
|
|
479
561
|
const inspector = $('inspector');
|
|
480
562
|
// Preserve the user's expanded history and scroll across live snapshots.
|
|
481
563
|
const historyOpen = inspector.querySelector('.history-details')?.open;
|
|
564
|
+
const runtimeOpen = inspector.querySelector('.runtime-details')?.open;
|
|
482
565
|
const oldScroll = inspector.scrollTop;
|
|
483
566
|
const process = state.view === 'processes' ? state.model.processes.find(item => item.id === state.selectedProcess) : null;
|
|
484
567
|
const cycle = state.view !== 'processes' ? state.model.cycles.find(item => item.id === state.selectedCycle) : null;
|
|
@@ -489,15 +572,20 @@ function initializeStudio() {
|
|
|
489
572
|
inspector.replaceChildren(empty); return;
|
|
490
573
|
}
|
|
491
574
|
if (process) renderProcessDetail(inspector, process);
|
|
492
|
-
else if (cycle) renderCycleDetail(inspector, cycle
|
|
575
|
+
else if (cycle) renderCycleDetail(inspector, cycle);
|
|
493
576
|
else renderEventDetail(inspector, event);
|
|
494
577
|
const history = inspector.querySelector('.history-details');
|
|
495
578
|
if (history && historyOpen) history.open = true;
|
|
579
|
+
const runtime = inspector.querySelector('.runtime-details');
|
|
580
|
+
if (runtime && runtimeOpen) runtime.open = true;
|
|
496
581
|
if (!state.follow) inspector.scrollTop = oldScroll;
|
|
497
582
|
}
|
|
498
|
-
function renderCycleDetail(inspector, cycle
|
|
499
|
-
inspector.replaceChildren(
|
|
500
|
-
const overview =
|
|
583
|
+
function renderCycleDetail(inspector, cycle) {
|
|
584
|
+
inspector.replaceChildren(taskActivityCard(cycle));
|
|
585
|
+
const overview = node('details', 'detail-section runtime-details');
|
|
586
|
+
const runtimeSummary = node('summary', '', 'Runtime details');
|
|
587
|
+
runtimeSummary.dataset.focusKey = `runtime:${cycle.id}`;
|
|
588
|
+
overview.append(runtimeSummary);
|
|
501
589
|
const ticket = record(cycle.data.ticket); const thread = record(cycle.data.thread);
|
|
502
590
|
overview.append(detailGrid([
|
|
503
591
|
['Model', cycle.data.model], ['Lane', cycle.data.lane || cycle.data.kind],
|
|
@@ -508,62 +596,137 @@ function initializeStudio() {
|
|
|
508
596
|
['Thread', thread.threadId != null ? `${thread.channelId != null ? `Channel ${thread.channelId} · ` : ''}${thread.threadId}` : null],
|
|
509
597
|
['Workspace', cycle.data.workdir, true],
|
|
510
598
|
]));
|
|
511
|
-
inspector.append(overview);
|
|
512
599
|
if (cycle.status === 'offline') {
|
|
513
600
|
const note = detailSection('Activity unavailable', 'pulse');
|
|
514
601
|
note.append(node('p', 'detail-note', 'This watcher stopped reporting or restarted. The recorded history has no final outcome for this cycle. Check the ticket and watcher log before deciding what to do next.'));
|
|
515
602
|
inspector.append(note);
|
|
516
603
|
}
|
|
517
604
|
if (cycle.data.reason) { const reason = detailSection('Outcome', 'message'); reason.append(node('p', 'detail-note', cycle.data.reason)); inspector.append(reason); }
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const
|
|
522
|
-
|
|
605
|
+
const variants = node('div', 'detail-variants');
|
|
606
|
+
variants.setAttribute('role', 'group'); variants.setAttribute('aria-label', 'Cycle details view');
|
|
607
|
+
for (const [id, label, iconName] of [['plan', 'Plan', 'list'], ['commands', 'Commands', 'command'], ['response', 'Response', 'message']]) {
|
|
608
|
+
const button = node('button', `button variant${state.detailView === id ? ' is-selected' : ''}`);
|
|
609
|
+
button.type = 'button'; button.dataset.focusKey = `detail-view:${id}`;
|
|
610
|
+
button.setAttribute('aria-pressed', String(state.detailView === id));
|
|
611
|
+
button.append(icon(iconName), node('span', '', label));
|
|
612
|
+
button.addEventListener('click', () => { state.detailView = id; scheduleRender(); });
|
|
613
|
+
variants.append(button);
|
|
523
614
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
list
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
615
|
+
inspector.append(variants);
|
|
616
|
+
const detail = node('div', 'cycle-detail-content');
|
|
617
|
+
inspector.append(detail);
|
|
618
|
+
if (state.detailView === 'plan') {
|
|
619
|
+
const plan = detailSection('Plan', 'list');
|
|
620
|
+
if (cycle.plan?.length) {
|
|
621
|
+
plan.querySelector('h3').append(node('span', 'count', `${cycle.plan.filter(step => step.status === 'completed').length}/${cycle.plan.length}`));
|
|
622
|
+
const list = node('ol', 'plan-list');
|
|
623
|
+
cycle.plan.forEach((step, index) => {
|
|
624
|
+
const item = node('li', `plan-step ${step.status}`); const marker = node('span', 'plan-marker');
|
|
625
|
+
if (step.status === 'completed') marker.append(icon('check')); else marker.textContent = String(index + 1);
|
|
626
|
+
item.append(marker, node('span', 'plan-content', step.content), node('span', 'plan-status', statusLabels[step.status] || 'Pending'));
|
|
627
|
+
list.append(item);
|
|
628
|
+
});
|
|
629
|
+
plan.append(list);
|
|
630
|
+
} else plan.append(node('p', 'detail-note', 'No explicit plan recorded for this cycle.'));
|
|
631
|
+
detail.append(plan);
|
|
632
|
+
}
|
|
633
|
+
if (state.detailView === 'commands') {
|
|
634
|
+
const tools = detailSection('Commands & tools', 'command'); tools.querySelector('h3').append(node('span', 'count', cycle.tools.size));
|
|
538
635
|
const list = node('div', 'tools-list');
|
|
539
636
|
for (const tool of [...cycle.tools.values()].reverse()) {
|
|
540
637
|
const item = node('div', 'tool-item'); const heading = node('div', 'tool-heading');
|
|
541
638
|
heading.append(node('span', 'tool-name', tool.name || tool.kind || 'Tool'), badge(tool.status)); item.append(heading);
|
|
542
|
-
if (tool.title) item.append(node('pre', 'tool-title', tool.title));
|
|
639
|
+
if (tool.title && tool.title !== tool.name) item.append(node('pre', 'tool-title', tool.title));
|
|
543
640
|
for (const location of array(tool.locations)) {
|
|
544
641
|
if (location?.path) item.append(node('span', 'file-location', `${location.path}${location.line != null ? `:${location.line}` : ''}`));
|
|
545
642
|
}
|
|
546
643
|
list.append(item);
|
|
547
644
|
}
|
|
548
|
-
tools.
|
|
645
|
+
if (!cycle.tools.size) list.append(node('p', 'detail-note', 'Commands and tool calls will appear here as the agent works.'));
|
|
646
|
+
tools.append(list); detail.append(tools);
|
|
549
647
|
}
|
|
550
|
-
if (
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
648
|
+
if (state.detailView === 'response') {
|
|
649
|
+
if (cycle.progress.size) {
|
|
650
|
+
const progress = detailSection('Progress updates', 'message');
|
|
651
|
+
for (const entry of cycle.progress.values()) progress.append(node('pre', 'recorded-output output-entry', entry.text));
|
|
652
|
+
detail.append(progress);
|
|
653
|
+
}
|
|
654
|
+
if (cycle.final !== null) {
|
|
655
|
+
const final = detailSection('Final response', 'check'); final.append(node('pre', 'recorded-output', cycle.final || 'The recorded final response is empty.')); detail.append(final);
|
|
656
|
+
}
|
|
657
|
+
if (!cycle.progress.size && cycle.final === null) {
|
|
658
|
+
const output = detailSection('Recorded output', 'message'); output.append(node('p', 'detail-note', 'Visible progress and the final response will appear here when recorded.')); detail.append(output);
|
|
659
|
+
}
|
|
560
660
|
}
|
|
661
|
+
inspector.append(overview);
|
|
561
662
|
const history = node('details', 'detail-section history-details'); history.append(node('summary', '', `Cycle history · ${cycle.events.length} events`));
|
|
562
663
|
history.querySelector('summary').dataset.focusKey = `history:${cycle.id}`;
|
|
563
664
|
for (const event of cycle.events) { const row = node('div', 'mini-event'); row.append(node('time', '', shortTime(event.timestamp)), node('span', '', eventTitle(event))); history.append(row); }
|
|
564
665
|
history.append(detailGrid([['Cycle ID', cycle.cycleId, true], ['Run ID', cycle.runId, true]]));
|
|
565
666
|
inspector.append(history);
|
|
566
667
|
}
|
|
668
|
+
|
|
669
|
+
function taskActivityCard(cycle) {
|
|
670
|
+
const related = taskActivityCycles(state.model, cycle);
|
|
671
|
+
const displayed = related.slice(0, 4);
|
|
672
|
+
if (!displayed.some(item => item.id === cycle.id)) displayed[displayed.length - 1] = cycle;
|
|
673
|
+
const card = node('section', 'task-activity-card oa-card');
|
|
674
|
+
card.setAttribute('aria-label', 'Task activity');
|
|
675
|
+
const header = node('div', 'fanout-header');
|
|
676
|
+
const title = node('h2', '', 'Task activity');
|
|
677
|
+
const count = node('span', 'badge fanout-count');
|
|
678
|
+
const dots = node('span', 'agent-dots'); dots.setAttribute('aria-hidden', 'true');
|
|
679
|
+
displayed.forEach((_, index) => dots.append(node('span', `dot agent-color-${index}`)));
|
|
680
|
+
count.append(dots, node('span', '', `${related.filter(item => item.status === 'completed').length}/${related.length} complete`));
|
|
681
|
+
header.append(title, count);
|
|
682
|
+
const surface = node('div', 'fanout-surface oa-inset');
|
|
683
|
+
const task = node('div', 'fanout-task');
|
|
684
|
+
task.append(node('span', 'eyebrow', 'Task'), node('span', '', cycleTitle(cycle)));
|
|
685
|
+
surface.append(task);
|
|
686
|
+
const graph = node('div', 'fanout-graph');
|
|
687
|
+
const root = node('span', `fanout-root ${cycle.status}`); root.append(icon('stack'));
|
|
688
|
+
graph.append(root);
|
|
689
|
+
const lines = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
690
|
+
lines.setAttribute('viewBox', '0 0 400 80'); lines.setAttribute('preserveAspectRatio', 'none');
|
|
691
|
+
lines.setAttribute('class', 'fanout-lines'); lines.setAttribute('aria-hidden', 'true');
|
|
692
|
+
for (let index = 0; index < displayed.length; index++) {
|
|
693
|
+
const x = (index + .5) * 400 / displayed.length;
|
|
694
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
695
|
+
path.setAttribute('d', `M200 0 C200 38 ${x} 38 ${x} 80`); lines.append(path);
|
|
696
|
+
}
|
|
697
|
+
graph.append(lines);
|
|
698
|
+
const branches = node('div', 'fanout-branches');
|
|
699
|
+
displayed.forEach((item, index) => {
|
|
700
|
+
const branch = node('button', `fanout-branch agent-color-${index}${item.id === cycle.id ? ' is-selected' : ''}`);
|
|
701
|
+
branch.type = 'button'; branch.dataset.focusKey = `branch:${item.id}`;
|
|
702
|
+
branch.setAttribute('aria-label', `${agentName(item.agent)} · ${statusLabels[item.status] || item.status} · View cycle`);
|
|
703
|
+
branch.setAttribute('aria-pressed', String(item.id === cycle.id));
|
|
704
|
+
const marker = node('span', `fanout-marker ${item.status}`);
|
|
705
|
+
marker.append(icon(item.status === 'completed' ? 'check' : ['failed', 'cancelled', 'blocked', 'timeout'].includes(item.status) ? 'close' : ['active', 'recovering'].includes(item.status) ? 'loader' : 'clock'));
|
|
706
|
+
branch.append(marker, node('span', 'fanout-name', agentName(item.agent)));
|
|
707
|
+
branch.addEventListener('click', () => {
|
|
708
|
+
state.selectedCycle = item.id; state.selectedEvent = null; state.selectedAgent = 'all';
|
|
709
|
+
state.view = 'cycles'; state.search = ''; state.kind = 'all'; state.status = 'all';
|
|
710
|
+
$('event-search').value = ''; $('kind-filter').value = 'all'; $('status-filter').value = 'all';
|
|
711
|
+
state.follow = false; $('follow-latest').checked = false; scheduleRender();
|
|
712
|
+
});
|
|
713
|
+
branches.append(branch);
|
|
714
|
+
});
|
|
715
|
+
graph.append(branches); surface.append(graph);
|
|
716
|
+
const rows = node('div', 'fanout-results');
|
|
717
|
+
for (const item of displayed) {
|
|
718
|
+
const row = node('div', 'fanout-result');
|
|
719
|
+
const elapsed = Number.isFinite(item.data.durationMs) ? duration(item.data.durationMs) : statusLabels[item.status] || item.status;
|
|
720
|
+
const summary = item.final || [...item.progress.values()].at(-1)?.text || `${item.tools.size} tool calls recorded`;
|
|
721
|
+
row.append(node('span', 'fanout-result-name', agentName(item.agent)), node('span', 'fanout-result-summary', summary), node('span', 'fanout-duration', elapsed));
|
|
722
|
+
rows.append(row);
|
|
723
|
+
}
|
|
724
|
+
surface.append(rows);
|
|
725
|
+
const footer = node('div', 'fanout-footer');
|
|
726
|
+
footer.append(node('span', '', related.length > displayed.length ? `${displayed.length} of ${related.length} agents shown` : `${cycle.events.length} events in this cycle`), badge(cycle.status));
|
|
727
|
+
card.append(header, surface, footer);
|
|
728
|
+
return card;
|
|
729
|
+
}
|
|
567
730
|
function renderEventDetail(inspector, event) {
|
|
568
731
|
inspector.replaceChildren(inspectorHeader('Event details', eventTitle(event), agentName(event.agent), eventStatus(event)));
|
|
569
732
|
const section = detailSection('Recorded event', kindIcons[event.type.split('.')[0]] || 'pulse');
|
|
@@ -614,7 +777,7 @@ function initializeStudio() {
|
|
|
614
777
|
for (const button of document.querySelectorAll('[data-view]')) {
|
|
615
778
|
const selected = button.dataset.view === state.view; button.classList.toggle('is-selected', selected); button.setAttribute('aria-pressed', String(selected));
|
|
616
779
|
}
|
|
617
|
-
renderAgents(); renderMetrics(); renderActivity();
|
|
780
|
+
renderAgents(); renderMetrics(); renderActivity(); renderModelSettings();
|
|
618
781
|
$('history-label').textContent = `Local history · ${state.model.events.length} of up to ${Number(state.snapshot.limits?.maxEvents) || MAX_EVENTS} recent events`;
|
|
619
782
|
$('updated-at').textContent = `Snapshot at ${shortTime(state.snapshot.generatedAt)}`;
|
|
620
783
|
$('updated-at').title = fullTime(state.snapshot.generatedAt);
|
|
@@ -622,6 +785,29 @@ function initializeStudio() {
|
|
|
622
785
|
}
|
|
623
786
|
|
|
624
787
|
$('server-address').textContent = location.host;
|
|
788
|
+
$('open-model-settings').addEventListener('click', () => {
|
|
789
|
+
const agent = state.model.agents.find(agent => agentId(agent) === state.selectedAgent);
|
|
790
|
+
if (agent?.settingsEditable) void openModelSettings(agent);
|
|
791
|
+
});
|
|
792
|
+
$('close-model-settings').addEventListener('click', () => $('model-settings-dialog').close());
|
|
793
|
+
$('model-settings-dialog').addEventListener('close', () => { settingsRequest++; editingAgent = null; editingSettings = null; });
|
|
794
|
+
$('reload-models').addEventListener('click', () => { if (editingAgent) void loadModels(editingAgent, settingsRequest); });
|
|
795
|
+
$('model-settings-form').addEventListener('submit', async event => {
|
|
796
|
+
event.preventDefault();
|
|
797
|
+
if (!editingAgent || !editingSettings) return;
|
|
798
|
+
const request = settingsRequest;
|
|
799
|
+
$('save-model-settings').disabled = true; $('save-model-settings').textContent = 'Saving…';
|
|
800
|
+
$('model-settings-error').hidden = true;
|
|
801
|
+
try {
|
|
802
|
+
await settingsFetch(`/api/agents/${encodeURIComponent(editingAgent.slug)}/settings`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ version: editingSettings.version, model: $('coding-model').value.trim(), chatModel: $('reply-model').value.trim() }) });
|
|
803
|
+
if (request !== settingsRequest) return;
|
|
804
|
+
$('model-settings-dialog').close();
|
|
805
|
+
toast('Models saved. New requests will use your choices once the watcher applies them.');
|
|
806
|
+
receive(await settingsFetch('/api/snapshot'));
|
|
807
|
+
} catch (error) {
|
|
808
|
+
if (request === settingsRequest) { $('model-settings-error').hidden = false; $('model-settings-error').textContent = error.message; }
|
|
809
|
+
} finally { $('save-model-settings').disabled = false; $('save-model-settings').textContent = 'Save models'; }
|
|
810
|
+
});
|
|
625
811
|
$('pause-button').addEventListener('click', () => {
|
|
626
812
|
state.paused = !state.paused;
|
|
627
813
|
if (!state.paused && state.pending) { const pending = state.pending; state.pending = null; state.pendingUpdates = 0; receive(pending); }
|
package/studio/guide.html
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
<meta name="color-scheme" content="light">
|
|
7
7
|
<meta name="description" content="Get started with Agent Studio, understand agent activity, and troubleshoot your local viewer.">
|
|
8
8
|
<title>Agent Studio user guide · OpenVisio</title>
|
|
9
|
+
<link rel="icon" type="image/svg+xml" href="/openvisio.svg">
|
|
9
10
|
<link rel="stylesheet" href="/style.css">
|
|
10
11
|
</head>
|
|
11
12
|
<body>
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
<a href="#find-work">Find and inspect work</a>
|
|
23
24
|
<a href="#statuses">Understand statuses</a>
|
|
24
25
|
<a href="#controls">Pause and follow</a>
|
|
26
|
+
<a href="#models">Change models</a>
|
|
25
27
|
<a href="#access">Keyboard and reading</a>
|
|
26
28
|
<a href="#troubleshooting">Troubleshooting</a>
|
|
27
29
|
<a href="#history">History and privacy</a>
|
|
@@ -30,13 +32,21 @@
|
|
|
30
32
|
<section id="start">
|
|
31
33
|
<h2>Open Studio</h2>
|
|
32
34
|
<p>Agent Studio is a local viewer for bring-your-own (BYO) agents connected to an OpenVisio team. It shows recorded actions from Claude Code, Codex, and OpenCode watchers on this machine.</p>
|
|
33
|
-
<p>
|
|
35
|
+
<p>In the OpenVisio app, open <strong>Agents → Open Agent Studio</strong>. Agents can also include this button in chat replies. Updated backend watchers start Studio automatically on this computer.</p>
|
|
36
|
+
<p>You can also open Studio manually:</p>
|
|
34
37
|
<pre><code>openvisio-agent studio</code></pre>
|
|
35
38
|
<p>Your browser opens the local address printed in the terminal, usually <code>http://127.0.0.1:4317</code>. Keep that terminal running while you use Studio.</p>
|
|
36
39
|
<p>To explore without connecting an agent:</p>
|
|
37
40
|
<pre><code>openvisio-agent studio --demo</code></pre>
|
|
38
41
|
<p>The demo uses clearly labeled sample activity. It makes no model calls. To return to your agents, press <kbd>Ctrl+C</kbd> in the Studio terminal and run the command again without <code>--demo</code>.</p>
|
|
39
42
|
</section>
|
|
43
|
+
<section id="models">
|
|
44
|
+
<h2>Change models</h2>
|
|
45
|
+
<p>Select an agent in the sidebar, choose <strong>Model settings</strong>, select its reply and coding models, then choose <strong>Save models</strong>. Model fields let you search the runtime’s choices or enter a custom model ID.</p>
|
|
46
|
+
<p>Updated watchers apply saved choices to the next request without a restart. Work already running continues with its original model. Studio shows when a save is waiting for the watcher. Offline agents retain the selection for their next connection; older watchers need an update to apply changes live.</p>
|
|
47
|
+
<p>OpenCode choices come from its local model catalog. Listing a model does not guarantee credentials or available quota. If a model is rate-limited, choose one with available quota or retry after the provider’s limit resets.</p>
|
|
48
|
+
<p>Studio controls agents configured on this computer. An agent running on another teammate’s computer must be managed there.</p>
|
|
49
|
+
</section>
|
|
40
50
|
<section id="watchers">
|
|
41
51
|
<h2>Show your agents</h2>
|
|
42
52
|
<p>A <strong>watcher</strong> is the local program that listens for assignments and messages, then starts your coding agent when work arrives. Studio displays what the watcher records.</p>
|
|
@@ -57,6 +67,7 @@
|
|
|
57
67
|
<li>Select a row. Its details appear beside the list, or below it on a smaller screen.</li>
|
|
58
68
|
</ol>
|
|
59
69
|
<p>A <strong>cycle</strong> is one unit of agent work. It can include a plan, several tool calls, progress updates, and a final response. Expand <strong>Cycle history</strong> to review its recorded sequence.</p>
|
|
70
|
+
<p>Switch between <strong>Plan</strong>, <strong>Commands</strong>, and <strong>Response</strong> for steps, tool calls, and visible output. The <strong>Task activity</strong> card groups recorded work on the same ticket by agent; select an agent in the diagram to inspect its cycle. Expand <strong>Runtime details</strong> for model and timing information.</p>
|
|
60
71
|
<p><strong>No explicit plan recorded</strong> means the provider did not send a plan in the available history. A missing process ID means the provider did not report that operating-system identifier. Neither message alone means the work failed.</p>
|
|
61
72
|
<p>The process summary counts distinct reported process IDs associated with connected watchers. It is not a system-wide process monitor. The demo deliberately shows no real process count.</p>
|
|
62
73
|
</section>
|
|
@@ -112,7 +123,7 @@ openvisio-agent watch --name ada</code></pre>
|
|
|
112
123
|
<h2>History and privacy</h2>
|
|
113
124
|
<p>Studio reads local journals under <code>~/.openvisio/observability</code>. The viewer serves up to 500 recent events from a bounded set of journal files. Logs rotate, large text can be shortened, and bursts can drop records. This is a recent activity view, not a complete audit archive.</p>
|
|
114
125
|
<p>The journal records visible plans, tool metadata, and public output. Private reasoning and raw tool payloads are excluded. Known credentials and recognizable secret formats are redacted, but visible output can still include project text and file paths. Review it before sharing a screenshot or journal.</p>
|
|
115
|
-
<p>Studio’s server accepts local connections only
|
|
126
|
+
<p>Studio’s server accepts local connections only. Saving models changes only the selected agent’s local model settings; it does not start a model call or modify your team. Your running watchers continue to use their configured team and provider connections.</p>
|
|
116
127
|
<p><a href="/">Return to Agent Studio</a></p>
|
|
117
128
|
</section>
|
|
118
129
|
</main>
|
package/studio/index.html
CHANGED
|
@@ -6,11 +6,19 @@
|
|
|
6
6
|
<meta name="color-scheme" content="light">
|
|
7
7
|
<meta name="description" content="A local view of your BYO agents, their work, and recorded progress.">
|
|
8
8
|
<title>Agent studio · OpenVisio</title>
|
|
9
|
+
<link rel="icon" type="image/svg+xml" href="/openvisio.svg">
|
|
10
|
+
<link rel="preload" href="/satoshi-400.woff2" as="font" type="font/woff2" crossorigin>
|
|
9
11
|
<link rel="stylesheet" href="/style.css">
|
|
10
12
|
<script type="module" src="/app.mjs"></script>
|
|
11
13
|
</head>
|
|
12
14
|
<body>
|
|
13
15
|
<svg class="icon-definitions" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><defs>
|
|
16
|
+
<symbol id="i-agents" viewBox="0 0 24 24"><path d="M12 3v4m-7 5v-2h14v2"/><rect x="8" y="1" width="8" height="5" rx="2"/><rect x="1" y="13" width="8" height="8" rx="3"/><rect x="15" y="13" width="8" height="8" rx="3"/><path d="M4 17h2m12 0h2"/></symbol>
|
|
17
|
+
<symbol id="i-chevron" viewBox="0 0 24 24"><path d="m9 5 7 7-7 7"/></symbol>
|
|
18
|
+
<symbol id="i-book" viewBox="0 0 24 24"><path d="M12 5v16m0-16C9 3 5 3 2 4v15c4-1 7-1 10 2 3-3 6-3 10-2V4c-3-1-7-1-10 1Z"/></symbol>
|
|
19
|
+
<symbol id="i-command" viewBox="0 0 24 24"><path d="m5 7 5 5-5 5m9 0h6"/></symbol>
|
|
20
|
+
<symbol id="i-loader" viewBox="0 0 24 24"><path d="M12 3a9 9 0 1 1-9 9"/></symbol>
|
|
21
|
+
|
|
14
22
|
<symbol id="i-grid" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="2"/><rect x="14" y="3" width="7" height="7" rx="2"/><rect x="3" y="14" width="7" height="7" rx="2"/><rect x="14" y="14" width="7" height="7" rx="2"/></symbol>
|
|
15
23
|
<symbol id="i-pulse" viewBox="0 0 24 24"><path d="M3 12h4l3-7 4 14 3-7h4"/></symbol>
|
|
16
24
|
<symbol id="i-stack" viewBox="0 0 24 24"><path d="m3 7 9-4 9 4-9 4-9-4Zm0 5 9 4 9-4M3 17l9 4 9-4"/></symbol>
|
|
@@ -30,28 +38,56 @@
|
|
|
30
38
|
<a class="skip-link" href="#workspace">Skip to activity</a>
|
|
31
39
|
<div class="app-shell">
|
|
32
40
|
<aside class="sidebar" aria-label="Agent navigation">
|
|
33
|
-
<a class="brand" href="/" aria-label="OpenVisio Agent studio"><span class="brand-mark"><svg
|
|
41
|
+
<a class="brand" href="/" aria-label="OpenVisio Agent studio"><span class="brand-mark"><img src="/openvisio.svg" alt="" width="68" height="68"></span><span>OpenVisio<span class="brand-caption">Local workspace</span></span></a>
|
|
34
42
|
<div class="sidebar-title"><span>Agents</span><span class="count" id="agent-count">0</span></div>
|
|
35
43
|
<label class="agent-search"><svg class="icon" aria-hidden="true"><use href="#i-search"/></svg><input id="agent-search" type="search" placeholder="Find an agent" aria-label="Filter agent list" autocomplete="off"></label>
|
|
36
|
-
<nav id="agent-list" class="agent-list" aria-label="Select an agent"><button class="agent-item is-selected" type="button" aria-pressed="true"><span class="agent-avatar"><svg class="icon" aria-hidden="true"><use href="#i-
|
|
37
|
-
<div class="sidebar-
|
|
44
|
+
<nav id="agent-list" class="agent-list" aria-label="Select an agent"><button class="agent-item is-selected" type="button" aria-pressed="true"><span class="agent-avatar"><svg class="icon" aria-hidden="true"><use href="#i-agents"/></svg></span><span class="agent-text"><span class="agent-name">All agents</span><span class="agent-caption">Waiting for data</span></span></button></nav>
|
|
45
|
+
<div class="sidebar-resources">
|
|
46
|
+
<section class="documentation-card oa-card" aria-labelledby="documentation-title">
|
|
47
|
+
<h2 id="documentation-title"><svg class="icon" aria-hidden="true"><use href="#i-book"/></svg>Documentation</h2>
|
|
48
|
+
<div class="documentation-inset oa-inset">
|
|
49
|
+
<p>Set up your agents and get to know Studio.</p>
|
|
50
|
+
<a class="button documentation-link" href="/guide">Read the guide<svg class="icon" aria-hidden="true"><use href="#i-arrow"/></svg></a>
|
|
51
|
+
</div>
|
|
52
|
+
</section>
|
|
53
|
+
<div class="sidebar-note"><span class="local-indicator"><span class="dot"></span>On this machine</span><p>Recorded actions from your running BYO agents.</p><span class="mono" id="server-address">localhost:4317</span></div>
|
|
54
|
+
</div>
|
|
38
55
|
</aside>
|
|
39
56
|
<main id="workspace" class="workspace" tabindex="-1">
|
|
40
57
|
<header class="page-header">
|
|
41
58
|
<div><div class="eyebrow">Runtime overview <span id="demo-badge" class="badge demo-badge" hidden>Demo data</span></div><h1>Agent studio</h1><p class="page-description">See what your agents are doing, as it happens.</p></div>
|
|
42
|
-
<div class="header-actions"><a class="button" href="/guide">
|
|
59
|
+
<div class="header-actions"><a class="button header-documentation" href="/guide"><svg class="icon" aria-hidden="true"><use href="#i-book"/></svg>Documentation</a><span id="connection-status" class="connection-status" role="status"><span class="dot"></span><span>Connecting</span></span><button id="pause-button" class="button" type="button" aria-pressed="false"><svg class="icon" aria-hidden="true"><use href="#i-pause"/></svg><span>Pause view</span></button></div>
|
|
43
60
|
</header>
|
|
44
61
|
<div id="notice" class="notice" role="status" hidden></div>
|
|
62
|
+
<section id="model-settings-summary" class="model-settings-summary" aria-label="Agent model settings" hidden>
|
|
63
|
+
<div><h2 id="model-settings-agent">Model settings</h2><p id="model-settings-state" role="status"></p></div>
|
|
64
|
+
<button id="open-model-settings" class="button" type="button">Model settings</button>
|
|
65
|
+
</section>
|
|
66
|
+
<dialog id="model-settings-dialog" aria-labelledby="model-settings-title">
|
|
67
|
+
<form id="model-settings-form" class="model-settings-form">
|
|
68
|
+
<div class="section-heading"><h2 id="model-settings-title">Model settings</h2><button id="close-model-settings" class="button" type="button" aria-label="Close model settings">Close</button></div>
|
|
69
|
+
<p id="model-settings-description">Choose the models this agent uses. Changes apply to its next request; work already running continues.</p>
|
|
70
|
+
<label for="reply-model">Reply model<span>For chat, triage, and coordination</span></label>
|
|
71
|
+
<input id="reply-model" list="runtime-models" placeholder="Choose or search models" required maxlength="200" autocomplete="off">
|
|
72
|
+
<label for="coding-model">Coding model<span>For assigned coding work</span></label>
|
|
73
|
+
<input id="coding-model" list="runtime-models" placeholder="Choose or search models" required maxlength="200" autocomplete="off">
|
|
74
|
+
<datalist id="runtime-models"></datalist>
|
|
75
|
+
<p id="model-catalog-status" role="status">Loading model choices…</p>
|
|
76
|
+
<p class="detail-note">Model access and usage limits depend on the provider connected on this computer.</p>
|
|
77
|
+
<p id="model-settings-error" role="alert" hidden></p>
|
|
78
|
+
<div class="model-settings-actions"><button id="reload-models" class="button" type="button">Reload models</button><button id="save-model-settings" class="button primary" type="submit">Save models</button></div>
|
|
79
|
+
</form>
|
|
80
|
+
</dialog>
|
|
45
81
|
<section class="metrics" aria-label="Workspace summary">
|
|
46
|
-
<article class="metric"><div class="metric-label">Active cycles<svg class="icon" aria-hidden="true"><use href="#i-pulse"/></svg></div><div class="metric-value" id="metric-active">—</div><div class="metric-caption" id="metric-active-caption">Waiting for activity</div></article>
|
|
47
|
-
<article class="metric"><div class="metric-label">In the queue<svg class="icon" aria-hidden="true"><use href="#i-stack"/></svg></div><div class="metric-value" id="metric-queued">—</div><div class="metric-caption" id="metric-queued-caption">Cycles waiting to start</div></article>
|
|
48
|
-
<article class="metric"><div class="metric-label">Processes<svg class="icon" aria-hidden="true"><use href="#i-terminal"/></svg></div><div class="metric-value" id="metric-processes">—</div><div class="metric-caption" id="metric-processes-caption">Watchers and coding runtimes</div></article>
|
|
49
|
-
<article class="metric"><div class="metric-label">Recorded events<svg class="icon" aria-hidden="true"><use href="#i-list"/></svg></div><div class="metric-value" id="metric-events">—</div><div class="metric-caption" id="metric-events-caption">Recent local history</div></article>
|
|
82
|
+
<article class="metric oa-card"><div class="metric-label">Active cycles<svg class="icon" aria-hidden="true"><use href="#i-pulse"/></svg></div><div class="metric-inset oa-inset"><div class="metric-value" id="metric-active">—</div><div class="metric-caption" id="metric-active-caption">Waiting for activity</div></div></article>
|
|
83
|
+
<article class="metric oa-card"><div class="metric-label">In the queue<svg class="icon" aria-hidden="true"><use href="#i-stack"/></svg></div><div class="metric-inset oa-inset"><div class="metric-value" id="metric-queued">—</div><div class="metric-caption" id="metric-queued-caption">Cycles waiting to start</div></div></article>
|
|
84
|
+
<article class="metric oa-card"><div class="metric-label">Processes<svg class="icon" aria-hidden="true"><use href="#i-terminal"/></svg></div><div class="metric-inset oa-inset"><div class="metric-value" id="metric-processes">—</div><div class="metric-caption" id="metric-processes-caption">Watchers and coding runtimes</div></div></article>
|
|
85
|
+
<article class="metric oa-card"><div class="metric-label">Recorded events<svg class="icon" aria-hidden="true"><use href="#i-list"/></svg></div><div class="metric-inset oa-inset"><div class="metric-value" id="metric-events">—</div><div class="metric-caption" id="metric-events-caption">Recent local history</div></div></article>
|
|
50
86
|
</section>
|
|
51
87
|
<section class="work-panel" aria-label="Agent activity">
|
|
52
88
|
<div class="panel-toolbar"><div class="view-tabs" role="group" aria-label="Choose a view"><button class="tab is-selected" type="button" data-view="activity" aria-pressed="true"><svg class="icon" aria-hidden="true"><use href="#i-pulse"/></svg>Activity</button><button class="tab" type="button" data-view="cycles" aria-pressed="false"><svg class="icon" aria-hidden="true"><use href="#i-stack"/></svg>Cycles</button><button class="tab" type="button" data-view="processes" aria-pressed="false"><svg class="icon" aria-hidden="true"><use href="#i-terminal"/></svg>Processes</button></div><label class="follow-control"><input type="checkbox" id="follow-latest" checked>Follow latest</label></div>
|
|
53
89
|
<div class="filters"><label class="search-field"><svg class="icon" aria-hidden="true"><use href="#i-search"/></svg><input id="event-search" type="search" placeholder="Search actions, tickets, files…" aria-label="Search recorded activity" autocomplete="off"></label><label class="select-field"><span class="sr-only">Event kind</span><select id="kind-filter"><option value="all">All kinds</option><option value="cycle">Cycles</option><option value="tool">Tools</option><option value="plan">Plans</option><option value="output">Output</option><option value="process">Processes</option><option value="runtime">Watchers</option></select></label><label class="select-field"><span class="sr-only">Status</span><select id="status-filter"><option value="all">All statuses</option><option value="active">Active</option><option value="queued">Queued</option><option value="completed">Completed</option><option value="failed">Failed</option><option value="cancelled">Cancelled</option><option value="blocked">Blocked</option><option value="skipped">Skipped</option><option value="recovering">Recovering</option><option value="idle">Idle</option><option value="connecting">Connecting</option><option value="timeout">Timed out</option><option value="offline">Offline</option></select></label></div>
|
|
54
|
-
<div class="content-grid"><div class="activity-panel"><div class="section-heading"><h2 id="activity-heading">Latest activity</h2><span class="muted" id="result-count">Connecting…</span></div><div id="activity-list" class="activity-list" aria-label="Recorded activity"><div class="loading-state" role="status"><span class="skeleton skeleton-wide"></span><span class="skeleton"></span><span class="skeleton skeleton-wide"></span><span>Connecting to your local agents…</span></div></div><button id="load-more" class="button load-more" type="button" hidden>Show earlier activity</button></div><aside id="inspector" class="inspector" tabindex="-1" aria-label="Selected activity details"><div class="inspector-empty"><span class="empty-icon"><svg class="icon" aria-hidden="true"><use href="#i-terminal"/></svg></span><h2>The work, in view</h2><p>Select a cycle to see its tools, explicit plan, and recorded output.</p></div></aside></div>
|
|
90
|
+
<div class="content-grid"><div class="activity-panel oa-card"><div class="section-heading"><h2 id="activity-heading">Latest activity</h2><span class="muted" id="result-count">Connecting…</span></div><div id="activity-list" class="activity-list oa-inset" aria-label="Recorded activity"><div class="loading-state" role="status"><span class="skeleton skeleton-wide"></span><span class="skeleton"></span><span class="skeleton skeleton-wide"></span><span>Connecting to your local agents…</span></div></div><button id="load-more" class="button load-more" type="button" hidden>Show earlier activity</button></div><aside id="inspector" class="inspector" tabindex="-1" aria-label="Selected activity details"><div class="inspector-empty"><span class="empty-icon"><svg class="icon" aria-hidden="true"><use href="#i-terminal"/></svg></span><h2>The work, in view</h2><p>Select a cycle to see its tools, explicit plan, and recorded output.</p></div></aside></div>
|
|
55
91
|
</section>
|
|
56
92
|
<footer class="page-footer"><span id="history-label">Local history · no events yet</span><span id="updated-at">Waiting for the first snapshot</span></footer>
|
|
57
93
|
</main>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" width="240" height="240"><polygon points="90.6,111.0 120.0,128.0 90.6,145.0 61.1,128.0" fill="#A5B4FC"/><polygon points="61.1,128.0 90.6,145.0 90.6,179.0 61.1,162.0" fill="#6366F1"/><polygon points="120.0,128.0 120.0,162.0 90.6,179.0 90.6,145.0" fill="#4338CA"/><polygon points="149.4,111.0 178.9,128.0 149.4,145.0 120.0,128.0" fill="#A5B4FC"/><polygon points="120.0,128.0 149.4,145.0 149.4,179.0 120.0,162.0" fill="#6366F1"/><polygon points="178.9,128.0 178.9,162.0 149.4,179.0 149.4,145.0" fill="#4338CA"/><polygon points="120,60 149.4,77.0 120,94 90.6,77.0" fill="#FCD34D"/><polygon points="90.6,77.0 120,94 120,128 90.6,111.0" fill="#F59E0B"/><polygon points="149.4,77.0 149.4,111.0 120,128 120,94" fill="#B45309"/></svg>
|
|
Binary file
|
|
Binary file
|