@volter/supercode-teams 0.2.139 → 0.2.141

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/supercode-teams",
3
- "version": "0.2.139",
3
+ "version": "0.2.141",
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",
@@ -7,6 +7,7 @@
7
7
  // idempotency check, and last among a team's routes. Each hook returns true when it
8
8
  // answered the request.
9
9
  import { bearer, hashToken, canRead, canManage, isAdmin } from '@volter/teams/server/auth';
10
+ import { storageReport } from './storage-report.mjs';
10
11
  import { validateListQuery, validateSearchQuery, validateReadQuery, validateCaptureBegin, validateCapturePart, canonicalJson, carryDigest } from '../contracts/index.mjs';
11
12
  import { normalizeSearch, textFromContent, cursorHash, cursorToken } from './catalog.mjs';
12
13
  import { captureDigest, captureMessages, handleOtlpRoute, sha256, validateArtifactName } from './ingest.mjs';
@@ -77,6 +78,7 @@ async function beforeIdempotency({ api, req, res, auth, url, path, sub, requestI
77
78
 
78
79
  async function team({ api, req, res, auth, url, path, sub, requestId }) {
79
80
  let m;
81
+ if(req.method==='GET'&&sub==='/storage/uploads'){await storageReport({api,res,auth,url,requestId});return true;}
80
82
  if(req.method==='GET'&&sub==='/sessions'){await listSessions(api,res,auth,url,requestId,false);return true;}
81
83
  if(req.method==='GET'&&sub==='/sessions/search'){await listSessions(api,res,auth,url,requestId,true);return true;}
82
84
  if(req.method==='GET'&&sub==='/sessions/events'){await handleCatalogEvents({api,req,res,auth,url,requestId,qobj,canReadMerged,readerProjection,fail});return true;}
@@ -197,14 +199,15 @@ async function listSessions(api,res,auth,url,requestId,search){
197
199
  }
198
200
  /** A report older than this (four missed heartbeats) says nothing about what the session is doing now. */
199
201
  const ACTIVITY_FRESH_MS=60000;
200
- /** `{since}` while the session's harness is working on a turn, by its connector's latest fresh report; else null. */
201
- async function workingOf(api,teamId,sessionId){
202
+ /** The connector's latest fresh native turn report; stale or unrecognized reports remain unknown. */
203
+ async function activityOf(api,teamId,sessionId){
202
204
  const row=await api.store.one('SELECT a.turn,a.since,a.observed_at FROM session_activity a JOIN origins o ON o.team_id=a.team_id AND o.source_id=a.source_id AND o.harness=a.harness AND o.native_id=a.native_id WHERE a.team_id=? AND o.deleted_at IS NULL AND o.session_id IN (SELECT id FROM sessions WHERE team_id=? AND (id=? OR canonical_id=?)) ORDER BY a.observed_at DESC LIMIT 1',[teamId,teamId,sessionId,sessionId]);
203
- return row&&row.turn==='working'&&api.clock.nowMs()-row.observed_at<ACTIVITY_FRESH_MS?{since:row.since??row.observed_at}:null;
205
+ return row&&['working','idle','needs_input'].includes(row.turn)&&api.clock.nowMs()-row.observed_at<ACTIVITY_FRESH_MS?{state:row.turn,since:row.since??row.observed_at,observedAt:row.observed_at}:{state:'unknown'};
204
206
  }
205
207
  async function sessionDescriptor(api,res,auth,id,requestId){
206
208
  const row=await api.store.authorizedSession({teamId:auth.team_id,principalId:auth.principal_id,credentialId:auth.credential_id,ref:id,now:api.clock.nowMs(),bound:auth.bound});
207
- send(res,200,result(api,auth,{...JSON.parse(row.descriptor_json),working:await workingOf(api,auth.team_id,row.id)}),requestId,{ETag:`"${row.revision}"`});
209
+ const activity=await activityOf(api,auth.team_id,row.id);
210
+ send(res,200,result(api,auth,{...JSON.parse(row.descriptor_json),turnActivity:activity,working:activity.state==='working'?{since:activity.since}:null}),requestId,{ETag:`"${row.revision}"`});
208
211
  }
209
212
  async function correlateRoute(api,req,res,auth,path,requestId){
210
213
  if(!['user','service'].includes(auth.kind)||auth.role==='viewer')fail(403,'forbidden','credential cannot correlate sessions');
@@ -230,7 +233,8 @@ async function readSession(api,res,auth,id,url,requestId){
230
233
  const {rows,beforeCursor,revision}=snapshot;
231
234
  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)}));
232
235
  const descriptor=JSON.parse(snapshot.descriptor_json);
233
- 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);
236
+ const activity=await activityOf(api,auth.team_id,descriptor.id);
237
+ send(res,200,{version:1,context:{server_id:api.serverId,team_id:auth.team_id,principal_id:auth.principal_id},descriptor,turnActivity:activity,working:activity.state==='working'?{since:activity.since}:null,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);
234
238
  }
235
239
  async function readArtifact(api,req,res,auth,id,uploadId,requestId){
236
240
  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;
@@ -0,0 +1,31 @@
1
+ // Administrative, read-only retention inventory. No artifact bytes, native
2
+ // paths, prompts or credentials leave this door; cursors never write storage.
3
+ import { isAdmin } from '@volter/teams/server/auth';
4
+ import { fail, result, send } from '@volter/teams/server/http';
5
+
6
+ export async function storageReport({ api, res, auth, url, requestId }) {
7
+ if (!isAdmin(auth)) fail(403, 'forbidden', 'storage inventory requires a team administrator');
8
+ for (const key of url.searchParams.keys()) if (key !== 'after') fail(400, 'invalid_request', `unknown query field ${key}`);
9
+ const after = url.searchParams.get('after') ?? '';
10
+ if (after && !/^upload_[a-zA-Z0-9_-]{1,100}$/.test(after)) fail(400, 'invalid_request', 'invalid upload cursor');
11
+ const rows = await api.store.query(`WITH page AS MATERIALIZED (
12
+ SELECT * FROM uploads WHERE team_id=? AND id>? ORDER BY id LIMIT 100
13
+ ), provenance AS MATERIALIZED (
14
+ SELECT c.team_id,json_extract(a.value,'$.upload_id') upload_id,
15
+ c.native_id,c.harness,MIN(c.created_at) captured_at
16
+ FROM captures c,json_each(c.commit_json,'$.artifacts') a
17
+ WHERE c.team_id=? AND json_extract(a.value,'$.upload_id') IN (SELECT id FROM page)
18
+ GROUP BY c.team_id,json_extract(a.value,'$.upload_id')
19
+ ) SELECT u.id,u.source_id,s.machine_id,m.name machine,u.created_at,u.committed,
20
+ p.native_id,p.harness,p.captured_at,
21
+ json_extract(u.manifest_json,'$.role') role,json_extract(u.manifest_json,'$.media_type') media_type,
22
+ json_extract(u.manifest_json,'$.sha256') sha256,
23
+ (SELECT COALESCE(SUM(length(bytes)),0) FROM artifact_chunks c WHERE c.team_id=u.team_id AND c.upload_id=u.id) bytes,
24
+ EXISTS (SELECT 1 FROM session_artifacts a WHERE a.team_id=u.team_id AND a.upload_id=u.id) version_referenced,
25
+ EXISTS (SELECT 1 FROM continuations c WHERE c.team_id=u.team_id AND instr(c.value_json,u.id)>0) continuation_referenced,
26
+ EXISTS (SELECT 1 FROM captures c WHERE c.team_id=u.team_id AND c.state='staging' AND instr(c.begin_json,u.id)>0) staging_referenced
27
+ FROM page u JOIN sources s ON s.team_id=u.team_id AND s.id=u.source_id
28
+ LEFT JOIN machines m ON m.team_id=s.team_id AND m.id=s.machine_id
29
+ LEFT JOIN provenance p ON p.team_id=u.team_id AND p.upload_id=u.id ORDER BY u.id`, [auth.team_id, after, auth.team_id]);
30
+ send(res, 200, result(api, auth, { items: rows, next: rows.length === 100 ? rows.at(-1).id : null }), requestId);
31
+ }
package/worker/index.mjs CHANGED
@@ -114,7 +114,7 @@ export class TeamsServer extends DurableObject {
114
114
  this.ready = ctx.blockConcurrencyWhile(async () => {
115
115
  // Read before migrations/maintenance: allocation failures must not hide
116
116
  // the table responsible, including hosts with a lower storage limit.
117
- logStorageBreakdown(ctx.storage.sql);
117
+ console.log(`storage: database_bytes=${ctx.storage.sql.databaseSize}`);
118
118
  const db = durableSql(ctx.storage);
119
119
  migrate({ exec: db.exec, tx: db.tx, version: () => ctx.storage.kv.get('schema_version') ?? 0, setVersion: (n) => ctx.storage.kv.put('schema_version', n) });
120
120
  this.api = createTeamsApi({ publicOrigin: normalizePublicOrigin(env.PUBLIC_ORIGIN), identityIssuer: env.VOLTER_ISSUER || null, identityOrganizations: identityOrganizationsFromEnv(env), delegatedIssuer: env.DELEGATED_ISSUER || null });
@@ -131,7 +131,7 @@ export class TeamsServer extends DurableObject {
131
131
  this.handler = createHttpHandler(this.api);
132
132
  // Retention must also run after a restart: a full object may have failed to re-arm its alarm.
133
133
  if (this.api.serverId) await maintain(this.api).catch(error => console.error(`maintain startup: ${error.message}`));
134
- if (!(await ctx.storage.getAlarm())) await ctx.storage.setAlarm(Date.now() + MAINTAIN_MS);
134
+ if (!(await ctx.storage.getAlarm())) await ctx.storage.setAlarm(Date.now() + MAINTAIN_MS).catch(error => console.error(`maintain alarm scheduling failed: ${error.message}`));
135
135
  });
136
136
  }
137
137