@volter/teams 0.1.48 → 0.1.50

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.
@@ -55,6 +55,12 @@ export function sendBrowserTurn(channel: BrowserChannel, text: string, options?:
55
55
  export interface MailHistoryEntry {
56
56
  id: string; from: string; fromName: string; to: string; text: string; at: number;
57
57
  direction: 'in' | 'out'; inReplyTo: string | null; delivery: string;
58
+ fromActor?: MailActor; toActor?: MailActor;
59
+ }
60
+ /** The existing native identity resolved by supercode, not the return mailbox. */
61
+ export interface MailActor {
62
+ id: string; name: string; kind: 'agent' | 'automation' | 'person'; self: boolean; via?: 'voice';
63
+ session?: { machine: string; harness: string; id: string };
58
64
  }
59
65
  /** Read mailbox history without marking messages read. Requires observe on the bound session. */
60
66
  export function readBrowserMail(channel: BrowserChannel): Promise<MailHistoryEntry[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/teams",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
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",
@@ -34,7 +34,7 @@ export function followTeamSession(options: FollowTeamSessionOptions): { stop():
34
34
  export function peerMessage(text: string): { name: string; address: string; body: string } | null;
35
35
 
36
36
  /** Overlay actual envelopes, deduplicating copies already delivered into the native transcript. */
37
- export function withMail<Entry extends { id: string; ts: number | null; mail?: { id: string } }>(transcript: Entry[], rows: import('./browser-channel.js').MailHistoryEntry[], options?: { agentName?: string }): Array<Entry | { id: string; role: 'notice'; text: string; ts: number; truncated: boolean; mail: { id: string; from: string; to: string; direction: 'in' | 'out'; inReplyTo?: string | null } }>;
37
+ export function withMail<Entry extends { id: string; ts: number | null; mail?: { id: string } }>(transcript: Entry[], rows: import('./browser-channel.js').MailHistoryEntry[], options?: { agentName?: string; agentId?: string; agentImage?(seed: string): string }): Array<Entry | { id: string; role: 'notice'; text: string; ts: number; truncated: boolean; mail: { id: string; from: string; to: string; direction: 'in' | 'out'; inReplyTo?: string | null; image?: string; via?: 'voice' } }>;
38
38
 
39
39
  export function personImage(seed?: string): string;
40
40
  export function agentImage(seed: string): string;
@@ -86,8 +86,8 @@ export function peerMessage(text) {
86
86
  return { id: own.id || outer.id || null, name: own['from-name'] || outer['from-name'] || own.from || outer.from || 'Another session', address: own.from || outer.from || '', body };
87
87
  }
88
88
 
89
- /** A person's picture: the brand's person placeholder, seeded; an empty seed is its one standard anonymous face. */
90
- export const personImage = (seed = '') => `https://brand.volter.ai/runner/svg?seed=${encodeURIComponent(seed)}&size=36&radius=18`;
89
+ /** A known person's picture; an unnamed sender gets the neutral brand user icon. */
90
+ export const personImage = (seed = '') => seed ? `https://brand.volter.ai/runner/svg?seed=${encodeURIComponent(seed)}&size=36&radius=18` : 'https://brand.volter.ai/icon/user-round/svg?size=24';
91
91
  /** An agent's picture (this session's own, a peer session's): the brand's agent mark, a rounded square, never a face. */
92
92
  export const agentImage = (seed) => `https://brand.volter.ai/agent/svg?seed=${encodeURIComponent(seed)}&size=36&radius=7`;
93
93
 
@@ -124,13 +124,15 @@ export function withPeople(transcript, messages, { agentName = null, sessionId =
124
124
  const metadata = byTime.get(entry.ts) ?? {};
125
125
  const name = metadata.source_name || null;
126
126
  const seed = name ? (metadata.source_principal_id || name) : '';
127
- return { ...entry, actor: { ...(seed ? { id: seed } : {}), name: name ?? 'Someone in the session', image: person(seed), kind: 'person' } };
127
+ const via = typeof metadata.source_via === 'string' ? metadata.source_via.trim() : '';
128
+ const source = ({ room: 'Room', app: 'App', terminal: 'Terminal', api: 'API' })[via] || via || 'Session';
129
+ return { ...entry, actor: { ...(seed ? { id: seed } : {}), name: name ?? 'Anonymous', anonymous: !name, source, image: person(seed), kind: 'person' } };
128
130
  });
129
131
  let last = null;
130
132
  return people.map((entry) => {
131
133
  if (!entry.actor) return entry;
132
- const author = `${entry.actor.kind ?? ''}:${entry.actor.name ?? entry.role}`;
133
- const continued = last !== null && last.author === author && Number.isFinite(entry.ts) && Number.isFinite(last.ts) && entry.ts - last.ts < 5 * 60_000;
134
+ const author = `${entry.actor.kind ?? ''}:${entry.actor.id ?? entry.actor.name ?? entry.role}:${entry.actor.source ?? ''}`;
135
+ const continued = !entry.actor.anonymous && last !== null && last.author === author && Number.isFinite(entry.ts) && Number.isFinite(last.ts) && entry.ts - last.ts < 5 * 60_000;
134
136
  last = { author, ts: entry.ts };
135
137
  const timeLabel = Number.isFinite(entry.ts) ? new Date(entry.ts).toLocaleTimeString(locale, { hour: 'numeric', minute: '2-digit' }) : undefined;
136
138
  return { ...entry, actor: { ...entry.actor, ...(continued ? { continued: true } : {}), ...(timeLabel ? { timeLabel } : {}) } };
@@ -140,14 +142,16 @@ export function withPeople(transcript, messages, { agentName = null, sessionId =
140
142
  /** Overlay real mail on the native transcript without rewriting native messages.
141
143
  * Stable envelope IDs deduplicate harness-delivered copies. Neither rendering
142
144
  * nor expanding an envelope changes its mailbox state. */
143
- export function withMail(transcript, rows, { agentName = 'This session' } = {}) {
145
+ export function withMail(transcript, rows, { agentName = 'This session', agentId = '', agentImage: picture = agentImage } = {}) {
144
146
  // An older mailbox must not jump ahead of the page of history being viewed.
145
147
  // Its envelopes become visible when the person loads that earlier history.
146
148
  const times = transcript.map(entry => entry.ts).filter(Number.isFinite);
147
149
  if (times.length) rows = rows.filter(row => row.at >= Math.min(...times));
150
+ const nameOf = actor => actor?.self ? agentName : actor?.name || 'Unknown sender';
148
151
  const mail = rows.map(row => ({ id: `mail:${row.id}`, role: 'notice', text: row.text, ts: row.at, truncated: false,
149
- mail: { id: row.id, from: row.fromName || row.from, to: row.direction === 'in' ? agentName : row.to,
150
- direction: row.direction, inReplyTo: row.inReplyTo } }));
152
+ mail: { id: row.id, from: nameOf(row.fromActor), to: row.toActor ? nameOf(row.toActor) : row.direction === 'in' ? agentName : 'Another session',
153
+ image: row.fromActor?.kind === 'agent' ? picture(row.fromActor.self ? agentId : row.fromActor.id) : `https://brand.volter.ai/bot/svg?seed=${encodeURIComponent(row.fromActor?.id || row.from)}&size=24&radius=4`,
154
+ via: row.fromActor?.via, direction: row.direction, inReplyTo: row.inReplyTo } }));
151
155
  const result = transcript.filter(entry => !entry.mail || !rows.some(row => row.id === entry.mail.id || row.id.startsWith(entry.mail.id)));
152
156
  // Keep native tool/result ordering intact (some native entries have no time).
153
157
  for (const entry of mail.sort((a, b) => a.ts - b.ts)) {