@volter/supercode-teams 0.2.52 → 0.2.54

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/fleet.mjs CHANGED
@@ -57,6 +57,9 @@ export async function fleetDiscover({ context = null, machines = null, params =
57
57
  : listed;
58
58
  const results = await Promise.all(targets.map(async (machine) => {
59
59
  if (!machine.id) return { name: machine.name, error: 'not an accessible machine of this team' };
60
+ // A machine the server reports offline is not asked (asking only waits for a link that is not coming); it is
61
+ // named as offline, not as a failure, and only when it was named or seen in the last day.
62
+ if (machine.connection === 'offline') return { name: machine.name, offline: true, lastSeenAt: machine.last_seen_at ?? null };
60
63
  let channel = null;
61
64
  try {
62
65
  channel = await openRemoteMachine({ origin: selected.origin, teamId: selected.team_id, machineId: machine.id, token });
@@ -70,9 +73,13 @@ export async function fleetDiscover({ context = null, machines = null, params =
70
73
  }));
71
74
  const sessions = [];
72
75
  const unreachable = [];
76
+ const offline = [];
77
+ const named = new Set(machines ?? []);
73
78
  for (const result of results) {
74
- if (result.error) unreachable.push({ machine: result.name, error: result.error });
79
+ if (result.offline) {
80
+ if (named.has(result.name) || (result.lastSeenAt && Date.now() - result.lastSeenAt < 86_400_000)) offline.push({ machine: result.name, last_seen_at: result.lastSeenAt });
81
+ } else if (result.error) unreachable.push({ machine: result.name, error: result.error });
75
82
  else for (const session of result.sessions) sessions.push({ machine: result.name, ...session });
76
83
  }
77
- return { sessions, unreachable };
84
+ return { sessions, unreachable, ...(offline.length ? { offline } : {}) };
78
85
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.2.52",
3
+ "version": "0.2.54",
4
4
  "type": "module",
5
5
  "description": "A machine daemon serving this machine's doors, and a local or remote Teams server for shared session discovery, OpenTelemetry ingestion and access to enrolled machines.",
6
6
  "license": "MIT",
@@ -66,6 +66,21 @@ const sessionArgs = (api, auth, ref) => ({
66
66
  credentialId: auth.credential_id, ref, now: api.clock.nowMs(), bound: auth.bound,
67
67
  });
68
68
 
69
+ /**
70
+ * The people and machines a session's reader sees named: its owner's display name and the names of the machines
71
+ * it ran on. A Room's session token reads no member or machine list, so its view would show raw ids otherwise.
72
+ */
73
+ export async function sessionNames(api, teamId, descriptor) {
74
+ const owner = descriptor?.user?.principal_id;
75
+ const ids = [...new Set(descriptor?.machine_ids ?? [])].slice(0, 20);
76
+ const person = owner ? await api.store.one('SELECT id, kind, display_name FROM principals WHERE id=?', [owner]) : null;
77
+ const machines = ids.length ? await api.store.query(`SELECT id, name, custodian_id, last_seen_at FROM machines WHERE team_id=? AND id IN (${ids.map(() => '?').join(',')})`, [teamId, ...ids]) : [];
78
+ return {
79
+ members: person ? [{ principal: { id: person.id, kind: person.kind, display_name: person.display_name }, role: 'member' }] : [],
80
+ machines: machines.map((row) => ({ id: row.id, name: row.name, custodian_id: row.custodian_id, connection: 'offline', last_seen_at: row.last_seen_at })),
81
+ };
82
+ }
83
+
69
84
  async function readForEvent(api, auth, ref, tail) {
70
85
  const nextCursorToken = cursorToken();
71
86
  const snapshot = await api.store.readSessionSnapshot({
@@ -76,10 +91,11 @@ async function readForEvent(api, auth, ref, tail) {
76
91
  id: item.id, source_id: item.source_id, native_key: item.native_key,
77
92
  recorded_at: item.recorded_at, value: JSON.parse(item.value_json),
78
93
  }));
94
+ const descriptor = JSON.parse(snapshot.descriptor_json);
79
95
  return {
80
96
  version: 1,
81
97
  context: { server_id: api.serverId, team_id: auth.team_id, principal_id: auth.principal_id },
82
- descriptor: JSON.parse(snapshot.descriptor_json), messages,
98
+ descriptor, messages, names: await sessionNames(api, auth.team_id, descriptor),
83
99
  before_cursor: snapshot.beforeCursor, event_cursor: snapshot.eventCursor,
84
100
  revision: snapshot.revision,
85
101
  display: { returned: messages.length, total: snapshot.total, truncated_fields: 0 },
@@ -10,7 +10,7 @@ import { bearer, hashToken, canRead, canManage, isAdmin } from '@volter/teams/se
10
10
  import { validateListQuery, validateSearchQuery, validateReadQuery, validateCaptureBegin, validateCapturePart, canonicalJson } from '../contracts/index.mjs';
11
11
  import { normalizeSearch, textFromContent, cursorHash, cursorToken } from './catalog.mjs';
12
12
  import { captureDigest, captureMessages, handleOtlpRoute, sha256, validateArtifactName } from './ingest.mjs';
13
- import { handleCatalogEvents, handleSessionEvents } from './event-routes.mjs';
13
+ import { handleCatalogEvents, handleSessionEvents, sessionNames } from './event-routes.mjs';
14
14
  import { exact } from '@volter/teams/server/request-json';
15
15
  import { delegatedSessionOf, looksDelegated } from '@volter/teams/server/delegated';
16
16
  import { renderSourceCollectorConfig } from './otlp/collector-config.mjs';
@@ -208,7 +208,7 @@ async function readSession(api,res,auth,id,url,requestId){
208
208
  const {rows,beforeCursor,revision}=snapshot;
209
209
  const messages=rows.map(row=>({id:row.id,source_id:row.source_id,native_key:row.native_key,recorded_at:row.recorded_at,value:JSON.parse(row.value_json)}));
210
210
  const descriptor=JSON.parse(snapshot.descriptor_json);
211
- send(res,200,{version:1,context:{server_id:api.serverId,team_id:auth.team_id,principal_id:auth.principal_id},descriptor,working:await workingOf(api,auth.team_id,descriptor.id),messages,before_cursor:beforeCursor,event_cursor:snapshot.eventCursor,revision,display:{returned:messages.length,total:snapshot.total,truncated_fields:0}},requestId);
211
+ send(res,200,{version:1,context:{server_id:api.serverId,team_id:auth.team_id,principal_id:auth.principal_id},descriptor,working:await workingOf(api,auth.team_id,descriptor.id),names:await sessionNames(api,auth.team_id,descriptor),messages,before_cursor:beforeCursor,event_cursor:snapshot.eventCursor,revision,display:{returned:messages.length,total:snapshot.total,truncated_fields:0}},requestId);
212
212
  }
213
213
  async function readArtifact(api,req,res,auth,id,uploadId,requestId){
214
214
  const artifact=await api.store.readArtifact({teamId:auth.team_id,principalId:auth.principal_id,credentialId:auth.credential_id,ref:id,uploadId,now:api.clock.nowMs()}),bytes=Buffer.from(artifact.bytes),range=req.headers.range;