@volter/teams 0.1.22 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/teams",
3
- "version": "0.1.22",
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",
@@ -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 }),