openvisio-agent 0.24.0 → 0.25.0

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.
@@ -0,0 +1,70 @@
1
+ // Embedded version of frontend/components/team/BotAvatar.tsx. Keep the seed,
2
+ // palettes and SVG geometry identical so an agent looks the same in both apps.
3
+ // Only generated numbers and fixed shapes enter the SVG; no remote images.
4
+ function hashSeed(seed) {
5
+ let h = 2166136261 >>> 0;
6
+ for (let i = 0; i < seed.length; i++) { h ^= seed.charCodeAt(i); h = Math.imul(h, 16777619); }
7
+ return h >>> 0;
8
+ }
9
+ function mulberry32(seed) {
10
+ let a = seed >>> 0;
11
+ return () => {
12
+ a = (a + 0x6d2b79f5) | 0;
13
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
14
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
15
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
16
+ };
17
+ }
18
+ const catalogHues = { sentinel: 0, forge: 40, pulse: 80, nova: 120, sage: 160, atlas: 200, vera: 240, relay: 280, quill: 320 };
19
+ function botSpec(seed) {
20
+ const h = Object.hasOwn(catalogHues, seed) ? catalogHues[seed] : hashSeed(seed + '#hue') % 360;
21
+ const v = hashSeed(seed + '#tone') % 97, saturation = 58 + v % 20, lightness = 50 + v % 9;
22
+ const rng = mulberry32(hashSeed(seed));
23
+ const pick = choices => choices[Math.floor(rng() * choices.length)];
24
+ return {
25
+ p: {
26
+ tile: `hsl(${h} ${Math.min(saturation + 6, 84)}% 90%)`,
27
+ body: `hsl(${h} ${saturation}% ${lightness}%)`,
28
+ dark: `hsl(${h} ${Math.min(saturation + 4, 82)}% ${lightness - 17}%)`,
29
+ face: `hsl(${h} 48% 97%)`,
30
+ accent: `hsl(${(h + 150 + v % 61) % 360} 82% 56%)`,
31
+ },
32
+ headRx: pick([14, 20, 26, 26]),
33
+ antenna: pick(['none', 'single', 'double', 'dish']),
34
+ eyes: pick(['round', 'square', 'visor', 'cyclops', 'happy']),
35
+ mouth: pick(['grid', 'smile', 'flat', 'dots', 'wave']),
36
+ };
37
+ }
38
+
39
+ export function botAvatarSvg(seed) {
40
+ const spec = botSpec(String(seed || 'bot')), { p } = spec;
41
+ const element = (tag, attributes, children = '') => `<${tag} ${Object.entries(attributes).map(([key, value]) => `${key}="${value}"`).join(' ')}>${children}</${tag}>`;
42
+ const circle = (cx, cy, r, fill, more = {}) => element('circle', { cx, cy, r, fill, ...more });
43
+ const rect = (x, y, width, height, rx, fill) => element('rect', { x, y, width, height, rx, fill });
44
+ const line = (x1, y1, x2, y2, more = {}) => element('line', { x1, y1, x2, y2, ...more });
45
+ const stroke = (color, width) => ({ stroke: color, 'stroke-width': width, 'stroke-linecap': 'round' });
46
+ const antenna = {
47
+ single: element('g', stroke(p.dark, 3), line(50, 26, 50, 15) + circle(50, 12, 4, p.accent, { stroke: 'none' })),
48
+ double: element('g', stroke(p.dark, 2.6), line(40, 26, 37, 16) + line(60, 26, 63, 16) + circle(36, 14, 3.2, p.accent, { stroke: 'none' }) + circle(64, 14, 3.2, p.accent, { stroke: 'none' })),
49
+ dish: element('g', {}, line(50, 26, 50, 16, stroke(p.dark, 3)) + element('ellipse', { cx: 50, cy: 14, rx: 10, ry: 3.4, fill: p.dark }) + circle(50, 12, 2.4, p.accent)),
50
+ }[spec.antenna] || '';
51
+ const eyes = {
52
+ round: circle(42, 48, 5, p.dark) + circle(58, 48, 5, p.dark) + circle(43.4, 46.6, 1.6, '#fff') + circle(59.4, 46.6, 1.6, '#fff'),
53
+ square: rect(37, 43, 9, 9, 2.5, p.dark) + rect(54, 43, 9, 9, 2.5, p.dark),
54
+ visor: element('g', {}, rect(36, 44, 28, 9, 4.5, p.dark) + circle(44, 48.5, 2.4, p.accent) + circle(56, 48.5, 2.4, p.accent)),
55
+ cyclops: circle(50, 48, 8, p.dark) + circle(50, 48, 3.4, p.accent),
56
+ happy: element('g', { ...stroke(p.dark, 3), fill: 'none' }, element('path', { d: 'M37 49 q5 -6 10 0' }) + element('path', { d: 'M53 49 q5 -6 10 0' })),
57
+ }[spec.eyes];
58
+ const mouth = {
59
+ grid: element('g', {}, rect(42, 58, 16, 6.5, 2, p.dark) + [47, 50, 53].map(x => line(x, 58, x, 64.5, { stroke: p.face, 'stroke-width': 1.2 })).join('')),
60
+ smile: element('path', { d: 'M42 59 q8 7 16 0', ...stroke(p.dark, 3), fill: 'none' }),
61
+ flat: rect(43, 60.5, 14, 3, 1.5, p.dark),
62
+ dots: element('g', { fill: p.dark }, [44, 50, 56].map(cx => element('circle', { cx, cy: 61, r: 1.8 })).join('')),
63
+ wave: element('path', { d: 'M42 61 q3 -4 6 0 t6 0', ...stroke(p.dark, 2.6), fill: 'none' }),
64
+ }[spec.mouth];
65
+ return element('svg', { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 100 100' }, rect(0, 0, 100, 100, 24, p.tile) + antenna + rect(18, 46, 5, 16, 2.5, p.dark) + rect(77, 46, 5, 16, 2.5, p.dark) + rect(24, 26, 52, 50, spec.headRx, p.body) + rect(31, 35, 38, 32, 9, p.face) + eyes + mouth);
66
+ }
67
+
68
+ export function botAvatarUrl(seed) {
69
+ return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(botAvatarSvg(seed));
70
+ }
package/studio/guide.html CHANGED
@@ -25,6 +25,7 @@
25
25
  <a href="#statuses">Understand statuses</a>
26
26
  <a href="#controls">Pause and follow</a>
27
27
  <a href="#models">Change models</a>
28
+ <a href="#remove">Remove a local agent</a>
28
29
  <a href="#access">Keyboard and reading</a>
29
30
  <a href="#troubleshooting">Troubleshooting</a>
30
31
  <a href="#history">History and privacy</a>
@@ -48,6 +49,11 @@
48
49
  <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>
49
50
  <p>Studio controls agents configured on this computer. An agent running on another teammate’s computer must be managed there.</p>
50
51
  </section>
52
+ <section id="remove">
53
+ <h2>Remove a local agent</h2>
54
+ <p>Select an agent, choose <strong>Remove from computer</strong>, review the local data, and type its local name to confirm. Studio stops its watcher and background service, then removes its local connection credentials, logs, memory, and cached context. This cannot be undone.</p>
55
+ <p>Repositories, branches, shared model-provider logins, and the team’s agent profile stay in place. Context shared with another local connection is retained. Removal is available on macOS and Linux for the default local agent storage; custom state directories and Windows require manual management.</p>
56
+ </section>
51
57
  <section id="watchers">
52
58
  <h2>Show your agents</h2>
53
59
  <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>
@@ -129,6 +135,7 @@ openvisio-agent watch --name ada</code></pre>
129
135
  </section>
130
136
  <section id="history">
131
137
  <h2>History and privacy</h2>
138
+ <p>Live views refresh at most every two seconds and reuse unchanged journal data. Pausing the view or hiding its browser tab disconnects live updates; returning reconnects automatically. Agents keep working while the view is paused.</p>
132
139
  <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>
133
140
  <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>
134
141
  <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>
package/studio/index.html CHANGED
@@ -61,7 +61,7 @@
61
61
  <div id="notice" class="notice" role="status" hidden></div>
62
62
  <section id="model-settings-summary" class="model-settings-summary" aria-label="Agent model settings" hidden>
63
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>
64
+ <div class="header-actions"><button id="open-model-settings" class="button" type="button">Model settings</button><button id="open-agent-removal" class="button" type="button">Remove from computer</button></div>
65
65
  </section>
66
66
  <dialog id="model-settings-dialog" aria-labelledby="model-settings-title">
67
67
  <form id="model-settings-form" class="model-settings-form">
@@ -78,6 +78,18 @@
78
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
79
  </form>
80
80
  </dialog>
81
+ <dialog id="agent-removal-dialog" aria-labelledby="agent-removal-title">
82
+ <form id="agent-removal-form" class="model-settings-form">
83
+ <div class="section-heading"><h2 id="agent-removal-title">Remove agent from this computer</h2><button id="close-agent-removal" class="button" type="button">Cancel</button></div>
84
+ <p>This stops the agent and removes its local connection credentials, activity history, memory, and cached context. This cannot be undone.</p>
85
+ <p>Your repositories, branches, shared model-provider logins, and the agent’s team profile stay in place.</p>
86
+ <p id="agent-removal-summary" role="status">Reviewing local data…</p>
87
+ <label id="agent-removal-label" for="agent-removal-confirmation">Type the local agent name to confirm</label>
88
+ <input id="agent-removal-confirmation" autocomplete="off" required maxlength="80">
89
+ <p id="agent-removal-error" role="alert" hidden></p>
90
+ <div class="model-settings-actions"><button id="confirm-agent-removal" class="button primary" type="submit" disabled>Remove agent and local data</button></div>
91
+ </form>
92
+ </dialog>
81
93
  <section class="metrics" aria-label="Workspace summary">
82
94
  <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
95
  <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>
package/studio/style.css CHANGED
@@ -82,8 +82,14 @@
82
82
  .agent-list { display: flex; flex-direction: column; gap: 5px; }
83
83
  .agent-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; border: 0; background: transparent; padding: 10px; border-radius: var(--radius-md); corner-shape: squircle; }
84
84
  .agent-item:hover { background: var(--hover); }
85
- .agent-item.is-selected { background: var(--stage); }
85
+ .agent-item.is-selected { background: color-mix(in oklab, var(--ink) 8%, transparent); box-shadow: inset 0 0 0 1px var(--border); }
86
+ .agent-selection { display: grid; place-items: center; width: 15px; flex-shrink: 0; visibility: hidden; }
87
+ .agent-selection .icon { width: 14px; height: 14px; }
88
+ .agent-item.is-selected .agent-selection { visibility: visible; }
86
89
  .agent-avatar { width: 31px; height: 31px; display: grid; place-items: center; position: relative; flex-shrink: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; corner-shape: squircle; color: var(--muted); font-size: 12px; font-weight: 500; }
90
+ .agent-avatar.has-image { border: 0; background: transparent; }
91
+ .bot-avatar { display: block; flex-shrink: 0; }
92
+ .fanout-identity { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-width: 0; max-width: 100%; }
87
93
  .avatar-status { position: absolute; width: 7px; height: 7px; right: -1px; bottom: -1px; border-radius: 50%; border: 2px solid var(--background); box-sizing: content-box; background: var(--subtle); }
88
94
  .avatar-status.online { background: var(--success); }
89
95
  .agent-text { flex: 1; min-width: 0; }
@@ -354,8 +360,8 @@
354
360
  .model-settings-summary:not([hidden]) { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 14px; padding: 18px; margin-bottom: 22px; border: 1px solid var(--border); border-radius: 20px; background: var(--surface, white); }
355
361
  .model-settings-summary h2 { margin: 0 0 6px; font-size: 13px; font-weight: 500; }
356
362
  .model-settings-summary p { margin: 0; color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }
357
- #model-settings-dialog { width: min(500px, calc(100vw - 32px)); max-height: calc(100dvh - 32px); margin: auto; padding: 24px; overflow-y: auto; border: 1px solid var(--border); border-radius: 24px; color: var(--ink); background: var(--surface, white); box-shadow: 0 24px 80px #0002; }
358
- #model-settings-dialog::backdrop { background: #0005; }
363
+ #model-settings-dialog, #agent-removal-dialog { width: min(500px, calc(100vw - 32px)); max-height: calc(100dvh - 32px); margin: auto; padding: 24px; overflow-y: auto; border: 1px solid var(--border); border-radius: 24px; color: var(--ink); background: var(--surface, white); box-shadow: 0 24px 80px #0002; }
364
+ #model-settings-dialog::backdrop, #agent-removal-dialog::backdrop { background: #0005; }
359
365
  .model-settings-form { display: grid; gap: 12px; }
360
366
  .model-settings-form h2 { font-size: 17px; font-weight: 500; }
361
367
  .model-settings-form p { margin: 0; font-size: 12px; line-height: 1.6; color: var(--muted); }
@@ -363,7 +369,7 @@
363
369
  .model-settings-form label span { font-size: 12px; color: var(--muted); }
364
370
  .model-settings-form input { width: 100%; min-width: 0; padding: 11px 14px; border: 1px solid var(--border); border-radius: 18px; background: transparent; color: var(--ink); font-size: 13px; }
365
371
  .model-settings-actions { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px; margin-top: 8px; }
366
- #model-settings-error { color: #9f3030; }
372
+ #model-settings-error, #agent-removal-error { color: #9f3030; }
367
373
  @media (max-width: 640px) {
368
374
  .page-header { flex-direction: column; }
369
375
  .header-actions { flex-direction: row; flex-wrap: wrap; align-items: center; padding: 0; gap: 8px; }