@volter/teams 0.1.21 → 0.1.23
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/package.json +1 -1
- package/viewer/index.html +9 -5
- package/viewer/session-view-entry.mjs +28 -3
- package/viewer/session-view.mjs +23 -22
- package/viewer/viewer.mjs +34 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/teams",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Volter Teams, the neutral team plane: machine identity and daemon core, the local door and server link, admission from machine grants, product doors, and the Teams server's neutral routes (company RFC 0008).",
|
|
6
6
|
"license": "MIT",
|
package/viewer/index.html
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
<title>Viewer</title>
|
|
8
8
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.css">
|
|
9
9
|
<style>
|
|
10
|
-
|
|
10
|
+
/* The page's scheme is the host's when it sets one (`--scui-color-scheme` through the theme door), else dark. */
|
|
11
|
+
:root { --scui-color-scheme: dark; color-scheme: var(--scui-color-scheme); --bg: #0b0d10; --panel: #13161b; --line: #262b33; --text: #e6e8eb; --muted: #8b939e; --accent: #7aa2f7; }
|
|
11
12
|
* { box-sizing: border-box; }
|
|
12
13
|
html, body { height: 100%; margin: 0; background: var(--bg); color: var(--text); font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, sans-serif; }
|
|
13
14
|
body { display: flex; flex-direction: column; }
|
|
14
|
-
header { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-bottom: 1px solid var(--line); background: var(--panel); min-height: 32px; }
|
|
15
|
-
header .target { font-family: ui-monospace, Menlo, monospace; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
16
|
-
header .scope { margin-left: auto; font-size: 11px; padding: 1px 6px; border: 1px solid var(--line); border-radius: 4px; color: var(--muted); }
|
|
15
|
+
body > header { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-bottom: 1px solid var(--line); background: var(--panel); min-height: 32px; }
|
|
16
|
+
body > header .target { font-family: ui-monospace, Menlo, monospace; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
17
|
+
body > header .scope { margin-left: auto; font-size: 11px; padding: 1px 6px; border: 1px solid var(--line); border-radius: 4px; color: var(--muted); }
|
|
17
18
|
#status { padding: 4px 10px; color: var(--muted); font-size: 12px; border-bottom: 1px solid var(--line); }
|
|
18
19
|
#status:empty { display: none; }
|
|
19
20
|
main { flex: 1; min-height: 0; display: flex; }
|
|
@@ -34,11 +35,14 @@
|
|
|
34
35
|
button.action:disabled { opacity: .5; cursor: default; }
|
|
35
36
|
label.action input { display: none; }
|
|
36
37
|
/* supercode-ui follows this page's dark scheme; its composer spans the page instead of its 420px default. */
|
|
37
|
-
#session { flex: 1; min-width: 0; min-height: 0; display: flex;
|
|
38
|
+
#session { flex: 1; min-width: 0; min-height: 0; display: flex; }
|
|
38
39
|
#session .session-view { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
39
40
|
#session .session-view > section { flex: 1; min-height: 0; overflow: auto; }
|
|
40
41
|
#session .session-composer { border-top: 1px solid var(--line); padding: 8px; --scui-width: 100%; --scui-height: auto; }
|
|
41
42
|
#session .session-composer > * { flex: 1; min-width: 0; }
|
|
43
|
+
/* The host's header carries the session's details when it hides this one. */
|
|
44
|
+
#session.header-hidden .scui-team-transcript > header { display: none; }
|
|
45
|
+
body:has(#session:not([hidden])) { background: var(--scui-bg, var(--volter-surface-page, var(--bg))); color: var(--scui-fg, var(--text)); }
|
|
42
46
|
[hidden] { display: none !important; }
|
|
43
47
|
</style>
|
|
44
48
|
</head>
|
|
@@ -10,12 +10,31 @@ import { projectSubagentTranscript } from '@volter/supercode-ui/controller';
|
|
|
10
10
|
|
|
11
11
|
// A person's picture: the brand's person placeholder. An empty seed is its one standard anonymous face.
|
|
12
12
|
const personImage = (seed = '') => `https://brand.volter.ai/runner/svg?seed=${encodeURIComponent(seed)}&size=36&radius=18`;
|
|
13
|
+
// An agent's picture (this session's own, a peer session's): the brand's agent mark, a rounded square, never a face.
|
|
14
|
+
const agentImage = (seed) => `https://brand.volter.ai/agent/svg?seed=${encodeURIComponent(seed)}&size=36&radius=8`;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A message another session sent this one, as its harness delivers it: an envelope naming the sender (a
|
|
18
|
+
* `cross-session-message` tag, the innermost one when the harness wraps it in its own) around the body. Returns the
|
|
19
|
+
* sender and the body, or null for an ordinary turn.
|
|
20
|
+
*/
|
|
21
|
+
function peerMessage(text) {
|
|
22
|
+
const close = text.search(/<\\?\/cross-session-message>/);
|
|
23
|
+
if (close < 0) return null;
|
|
24
|
+
const opens = [...text.slice(0, close).matchAll(/<\\?cross-session-message\b([^>]*)>/g)];
|
|
25
|
+
if (!opens.length) return null;
|
|
26
|
+
const inner = opens[opens.length - 1];
|
|
27
|
+
const attributes = (tag) => Object.fromEntries([...tag.matchAll(/([\w-]+)="([^"]*)"/g)].map((m) => [m[1], m[2]]));
|
|
28
|
+
const own = attributes(inner[1]), outer = attributes(opens[0][1]);
|
|
29
|
+
const body = text.slice(inner.index + inner[0].length, close).trim();
|
|
30
|
+
return { name: own['from-name'] || outer['from-name'] || own.from || outer.from || 'Another session', address: own.from || outer.from || '', body };
|
|
31
|
+
}
|
|
13
32
|
|
|
14
33
|
/**
|
|
15
34
|
* Who wrote each user turn, from the message's own annotations (source_name / source_principal_id, recorded when a
|
|
16
35
|
* Room member typed it): their name and face, or, for anyone unnamed, the standard anonymous face. Never an agent mark.
|
|
17
36
|
*/
|
|
18
|
-
function withPeople(transcript, messages) {
|
|
37
|
+
function withPeople(transcript, messages, { agentName = null, sessionId = '' } = {}) {
|
|
19
38
|
const byTime = new Map();
|
|
20
39
|
for (const message of messages) {
|
|
21
40
|
if (message?.role !== 'user') continue;
|
|
@@ -23,7 +42,13 @@ function withPeople(transcript, messages) {
|
|
|
23
42
|
if (Number.isFinite(at)) byTime.set(at, message.metadata);
|
|
24
43
|
}
|
|
25
44
|
return transcript.map((entry) => {
|
|
45
|
+
// This session's own turns: named as the Room knows its agent, with the agent mark.
|
|
46
|
+
if (entry.role === 'assistant') return { ...entry, actor: { ...(agentName ? { name: agentName } : {}), image: agentImage(sessionId) } };
|
|
26
47
|
if (entry.role !== 'user') return entry;
|
|
48
|
+
// A harness's delivery notice about a message is a compact system line, not a person's turn.
|
|
49
|
+
if (/^\s*\[Cross-session [a-z ]*notice\]/i.test(entry.text)) return { ...entry, role: 'notice', text: entry.text.trim().split('\n')[0] };
|
|
50
|
+
const peer = peerMessage(entry.text);
|
|
51
|
+
if (peer) return { ...entry, text: peer.body, actor: { name: peer.name, image: agentImage(peer.address.startsWith('sc:') ? peer.address : peer.name) }, context: [{ id: `${entry.id}:envelope`, kind: 'envelope', label: 'Delivered by supercode', detail: entry.text }] };
|
|
27
52
|
const metadata = byTime.get(entry.ts) ?? {};
|
|
28
53
|
const name = metadata.source_name || null;
|
|
29
54
|
return { ...entry, actor: { name: name ?? 'Someone in the session', image: personImage(name ? (metadata.source_principal_id || name) : '') } };
|
|
@@ -55,7 +80,7 @@ function normalizedSession(descriptor, messages) {
|
|
|
55
80
|
* and machines); `control` (the token's scope) shows the composer; `onSend(text)` is called with a turn the
|
|
56
81
|
* member typed, and resolves once it was delivered or refused.
|
|
57
82
|
*/
|
|
58
|
-
export function mountSessionView(element, { onSend, control = false }) {
|
|
83
|
+
export function mountSessionView(element, { onSend, control = false, agentName = null }) {
|
|
59
84
|
// One badge, following the token's scope: fixed when mounted (the team view reads its labels once).
|
|
60
85
|
const labels = { readOnly: control ? 'Control' : 'Read-only' };
|
|
61
86
|
let current = { descriptor: null, messages: [], members: [], machines: [], live: false, status: 'loading', error: null, canSend: control, sending: false };
|
|
@@ -65,7 +90,7 @@ export function mountSessionView(element, { onSend, control = false }) {
|
|
|
65
90
|
};
|
|
66
91
|
const draw = () => {
|
|
67
92
|
const { descriptor, messages, members, machines, live, status, error, canSend, sending } = current;
|
|
68
|
-
const transcript = descriptor ? withPeople(projectSubagentTranscript(normalizedSession(descriptor, messages), { prefix: descriptor.id, maxEntries: 500 }), messages) : [];
|
|
93
|
+
const transcript = descriptor ? withPeople(projectSubagentTranscript(normalizedSession(descriptor, messages), { prefix: descriptor.id, maxEntries: 500 }), messages, { agentName, sessionId: descriptor.id }) : [];
|
|
69
94
|
const composerState = normalizeUiState({ ...EMPTY_UI_STATE, startup: 'ready', harness: descriptor?.harness ?? '', canSend: canSend && !sending, busy: sending, transcript });
|
|
70
95
|
render(h('div', { class: 'session-view' },
|
|
71
96
|
h(TeamTranscript, { session: descriptor, transcript, members, machines, live, status, error, adapter, labels }),
|