@volter/supercode-teams 0.2.165 → 0.2.166

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.165",
3
+ "version": "0.2.166",
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",
@@ -28,11 +28,17 @@ import { fail, parseJson, result, send, guardedMutation, requireRevision, checkR
28
28
  /** The `sub` a token says it is for, unverified: for a log line only, never for a decision. */
29
29
  function unverifiedSubject(token){try{return String(JSON.parse(Buffer.from(String(token).split('.')[1]??'','base64url').toString('utf8')).sub??'').slice(0,80)||'-';}catch{return '-';}}
30
30
 
31
+ // URL encoding belongs to the HTTP boundary; native references retain their literal bytes in the store.
32
+ function sessionRef(value) {
33
+ try { return decodeURIComponent(value); }
34
+ catch { fail(400, 'invalid_request', 'session reference has invalid URL encoding'); }
35
+ }
36
+
31
37
  async function preAuth({ api, req, res, path, requestId }) {
32
38
  let early;
33
39
  if(req.method==='POST'&&(early=/^\/api\/v1\/teams\/([^/]+)\/sessions\/([^/]+)\/room-notices$/.exec(path))){
34
40
  const access=await delegatedRoomNoticeOf(api,bearer(req.headers.authorization));
35
- if(!access || access.teamId!==early[1] || access.sessionRef!==decodeURIComponent(early[2])) fail(401,'authentication_required','authentication required');
41
+ if(!access || access.teamId!==early[1] || access.sessionRef!==sessionRef(early[2])) fail(401,'authentication_required','authentication required');
36
42
  checkRate(api,`room-notices:${access.issuer}:${access.teamId}`,120,10);
37
43
  const receipt=await deliverSessionRoomNotice(api,access);
38
44
  send(res,200,{data:receipt},requestId);return true;
@@ -46,12 +52,12 @@ async function preAuth({ api, req, res, path, requestId }) {
46
52
  if(req.method==='POST'&&(early=/^\/api\/v1\/teams\/([^/]+)\/sessions\/([^/]+)\/viewer-errors$/.exec(path))){
47
53
  const presented=bearer(req.headers.authorization)??'';checkRate(api,`viewer-errors:${hashToken(presented)}`,30,10);
48
54
  const access=looksDelegated(presented)?await delegatedSessionOf(api,presented):null,body=await parseJson(req).catch(()=>({})),text=(value,limit)=>typeof value==='string'?value.replace(/\s+/g,' ').slice(0,limit):null;
49
- console.log(`viewer-error team=${early[1]} session=${decodeURIComponent(early[2]).slice(0,120)} subject=${access?access.subject:`unverified:${text(unverifiedSubject(presented),80)}`} scope=${access?.scope??'-'} room=${access?.room??'-'} stage=${text(body?.stage,40)} status=${Number.isSafeInteger(body?.status)?body.status:'-'} message=${JSON.stringify(text(body?.message,400))}`);
55
+ console.log(`viewer-error team=${early[1]} session=${sessionRef(early[2]).slice(0,120)} subject=${access?access.subject:`unverified:${text(unverifiedSubject(presented),80)}`} scope=${access?.scope??'-'} room=${access?.room??'-'} stage=${text(body?.stage,40)} status=${Number.isSafeInteger(body?.status)?body.status:'-'} message=${JSON.stringify(text(body?.message,400))}`);
50
56
  send(res,204,null,requestId);return true;
51
57
  }
52
58
  if(req.method==='GET'&&(early=/^\/api\/v1\/teams\/([^/]+)\/sessions\/([^/]+)(\/messages|\/events)?$/.exec(path))&&looksDelegated(bearer(req.headers.authorization))){
53
59
  const access=await delegatedSessionOf(api,bearer(req.headers.authorization));
54
- const ref=decodeURIComponent(early[2]);
60
+ const ref=sessionRef(early[2]);
55
61
  if(!access||access.teamId!==early[1]||access.sessionRef!==ref){console.log(`session-view refused team=${early[1]} session=${ref.slice(0,120)} subject=${access?access.subject:`unverified:${unverifiedSubject(bearer(req.headers.authorization))}`} reason=${!access?'token not admitted':'token names another session'}`);fail(401,'authentication_required','authentication required');}
56
62
  const auth={kind:'session_view',team_id:early[1],principal_id:null,credential_id:null,bound:{installationId:access.installationId,installedAt:access.installedAt,issuer:access.issuer,room:access.room,conversation:access.conversation,expiresAt:access.expiresAt}};
57
63
  const url=new URL(req.url,'http://local');
@@ -85,18 +91,18 @@ async function team({ api, req, res, auth, url, path, sub, requestId }) {
85
91
  if(req.method==='GET'&&sub==='/sessions/search'){await listSessions(api,res,auth,url,requestId,true);return true;}
86
92
  if(req.method==='GET'&&sub==='/sessions/events'){await handleCatalogEvents({api,req,res,auth,url,requestId,qobj,canReadMerged,readerProjection,fail});return true;}
87
93
  if(req.method==='POST'&&sub==='/correlations'){await correlateRoute(api,req,res,auth,path,requestId);return true;}
88
- if(req.method==='GET'&&(m=/^\/sessions\/([^/]+)$/.exec(sub))){await sessionDescriptor(api,res,auth,m[1],requestId);return true;}
89
- if((req.method==='PATCH'||req.method==='DELETE')&&(m=/^\/sessions\/([^/]+)$/.exec(sub))){await mutateSession(api,req,res,auth,m[1],requestId);return true;}
90
- if(req.method==='GET'&&(m=/^\/sessions\/([^/]+)\/messages$/.exec(sub))){await readSession(api,res,auth,m[1],url,requestId);return true;}
91
- if(req.method==='GET'&&(m=/^\/sessions\/([^/]+)\/artifacts\/([^/]+)$/.exec(sub))){await readArtifact(api,req,res,auth,m[1],m[2],requestId);return true;}
92
- if(req.method==='POST'&&(m=/^\/sessions\/([^/]+)\/exports$/.exec(sub))){await exportSessionRoute({api,req,res,auth,sessionId:m[1],requestId,parseJson,exact,fail,send,result,guardedMutation});return true;}
93
- if(req.method==='POST'&&(m=/^\/sessions\/([^/]+)\/continuations$/.exec(sub))){await continuationRoute({api,req,res,auth,sessionId:m[1],requestId,parseJson,exact,fail,send,result,guardedMutation});return true;}
94
- if(req.method==='GET'&&(m=/^\/sessions\/([^/]+)\/events$/.exec(sub))){await handleSessionEvents({api,req,res,auth,id:m[1],url,requestId,resolve,canReadMerged,readerProjection,fail});return true;}
95
- if(['GET','POST','DELETE'].includes(req.method)&&(m=/^\/sessions\/([^/]+)\/view-bindings$/.exec(sub))){await viewBindingRoute(api,req,res,auth,m[1],requestId);return true;}
96
- if((m=/^\/sessions\/([^/]+)\/access(?:\/([^/]+))?$/.exec(sub))){await accessRoute(api,req,res,auth,m[1],m[2],requestId);return true;}
94
+ if(req.method==='GET'&&(m=/^\/sessions\/([^/]+)$/.exec(sub))){await sessionDescriptor(api,res,auth,sessionRef(m[1]),requestId);return true;}
95
+ if((req.method==='PATCH'||req.method==='DELETE')&&(m=/^\/sessions\/([^/]+)$/.exec(sub))){await mutateSession(api,req,res,auth,sessionRef(m[1]),requestId);return true;}
96
+ if(req.method==='GET'&&(m=/^\/sessions\/([^/]+)\/messages$/.exec(sub))){await readSession(api,res,auth,sessionRef(m[1]),url,requestId);return true;}
97
+ if(req.method==='GET'&&(m=/^\/sessions\/([^/]+)\/artifacts\/([^/]+)$/.exec(sub))){await readArtifact(api,req,res,auth,sessionRef(m[1]),m[2],requestId);return true;}
98
+ if(req.method==='POST'&&(m=/^\/sessions\/([^/]+)\/exports$/.exec(sub))){await exportSessionRoute({api,req,res,auth,sessionId:sessionRef(m[1]),requestId,parseJson,exact,fail,send,result,guardedMutation});return true;}
99
+ if(req.method==='POST'&&(m=/^\/sessions\/([^/]+)\/continuations$/.exec(sub))){await continuationRoute({api,req,res,auth,sessionId:sessionRef(m[1]),requestId,parseJson,exact,fail,send,result,guardedMutation});return true;}
100
+ if(req.method==='GET'&&(m=/^\/sessions\/([^/]+)\/events$/.exec(sub))){await handleSessionEvents({api,req,res,auth,id:sessionRef(m[1]),url,requestId,resolve,canReadMerged,readerProjection,fail});return true;}
101
+ if(['GET','POST','DELETE'].includes(req.method)&&(m=/^\/sessions\/([^/]+)\/view-bindings$/.exec(sub))){await viewBindingRoute(api,req,res,auth,sessionRef(m[1]),requestId);return true;}
102
+ if((m=/^\/sessions\/([^/]+)\/access(?:\/([^/]+))?$/.exec(sub))){await accessRoute(api,req,res,auth,sessionRef(m[1]),m[2],requestId);return true;}
97
103
  if((m=/^\/machines\/([^/]+)\/grants(?:\/([^/]+))?$/.exec(sub))){await liveGrantRoute({api,req,res,auth,machineId:m[1],grantId:m[2],url,requestId,parseJson,exact,fail,send,result,guardedMutation,requireRevision});return true;}
98
104
  if((m=/^\/machines\/([^/]+)\/effects(?:\/([^/]+)(?:\/(claim|result))?)?$/.exec(sub))){await machineEffectRoute({api,req,res,auth,machineId:m[1],effectId:m[2],action:m[3],url,requestId,parseJson,exact,fail,send,result,guardedMutation});return true;}
99
- if(req.method==='POST'&&(m=/^\/sessions\/([^/]+)\/attachments$/.exec(sub))){await createAttachment({api,req,res,auth,sessionId:m[1],requestId,parseJson,exact,fail,send,result,guardedMutation,resolve});return true;}
105
+ if(req.method==='POST'&&(m=/^\/sessions\/([^/]+)\/attachments$/.exec(sub))){await createAttachment({api,req,res,auth,sessionId:sessionRef(m[1]),requestId,parseJson,exact,fail,send,result,guardedMutation,resolve});return true;}
100
106
  if(req.method==='POST'&&(m=/^\/attachments\/([^/]+)\/actions$/.exec(sub))){await attachmentActionRoute({api,req,res,auth,attachmentId:m[1],requestId,parseJson,exact,fail,send,result,guardedMutation,resolve});return true;}
101
107
  if(req.method==='DELETE'&&(m=/^\/attachments\/([^/]+)$/.exec(sub))){await detachAttachmentRoute({api,res,auth,attachmentId:m[1],requestId,fail,send,guardedMutation});return true;}
102
108
  if(req.method==='GET'&&(m=/^\/operations\/([^/]+)$/.exec(sub))){await operationRoute({api,res,auth,operationId:m[1],requestId,fail,send,result,guardedMutation});return true;}
@@ -159,12 +165,8 @@ async function listSessions(api,res,auth,url,requestId,search){
159
165
  {kind:'query',sql:'SELECT * FROM session_grants WHERE team_id=?',params:[auth.team_id]}
160
166
  );
161
167
  if(cursorKey)statements.push({kind:'query',sql:`SELECT s.*,v.revision version_revision,v.descriptor_json,v.sort_at FROM ${cursorVersions} JOIN session_versions v ON v.team_id=? AND v.session_id=json_extract(j.value,'$[0]') AND v.revision=json_extract(j.value,'$[1]') JOIN sessions s ON s.team_id=v.team_id AND s.id=v.session_id WHERE s.deleted_at IS NULL AND v.revision<>s.revision`,params:[...cursorKey,auth.team_id]});
162
- if(search){
163
- statements.push({kind:'query',sql:'SELECT m.id,m.session_id,s.revision revision,m.recorded_at,m.search_text FROM sessions s JOIN messages m ON m.team_id=s.team_id AND m.session_id=s.id AND m.revision<=s.revision AND s.revision<COALESCE(m.until_revision,m.revision+1) WHERE s.team_id=? AND s.deleted_at IS NULL',params:[auth.team_id]});
164
- if(cursorKey)statements.push({kind:'query',sql:`SELECT m.id,m.session_id,json_extract(j.value,'$[1]') revision,m.recorded_at,m.search_text FROM ${cursorVersions} JOIN messages m ON m.team_id=? AND m.session_id=json_extract(j.value,'$[0]') AND m.revision<=json_extract(j.value,'$[1]') AND json_extract(j.value,'$[1]')<COALESCE(m.until_revision,m.revision+1) JOIN sessions s ON s.team_id=m.team_id AND s.id=m.session_id WHERE s.deleted_at IS NULL AND json_extract(j.value,'$[1]')<>s.revision`,params:[...cursorKey,auth.team_id]});
165
- }
166
168
  const values=await api.store.snapshot(statements),offset=cursorKey?1:0,cursor=cursorKey?values[0]:null,team=values[offset],credential=values[offset+1],membership=values[offset+2],currentRows=values[offset+3],allGrants=values[offset+4];
167
- let at=offset+5;const olderRows=cursorKey?values[at++]:[],allMessages=search?[...values[at++],...(cursorKey?values[at++]:[])]:[];
169
+ const olderRows=cursorKey?values[offset+5]:[];
168
170
  const rows=[...currentRows,...olderRows],currentById=new Map(currentRows.map(row=>[row.id,row]));
169
171
  const boundaryNow=api.clock.nowMs();
170
172
  if(!membership?.active||credential?.revoked_at!=null||(credential?.expires_at!=null&&credential.expires_at<=boundaryNow))fail(401,'authentication_required','authentication required');
@@ -173,9 +175,8 @@ async function listSessions(api,res,auth,url,requestId,search){
173
175
  if(cursor.kind!==(search?'search':'list')||cursor.expires_at<=boundaryNow||cursor.auth_epoch!==team.auth_epoch||canonicalJson(JSON.parse(cursor.query_json))!==canonicalJson(normalizedQuery))fail(409,'resync_required','cursor expired, mismatched, or authorization changed');
174
176
  tuples=JSON.parse(cursor.snapshot_json);
175
177
  }else tuples=rows.filter(row=>row.revision===row.version_revision).map(row=>[row.id,row.version_revision]);
176
- const rowByVersion=new Map(rows.map(row=>[`${row.id}:${row.version_revision}`,row])),grantsBySession=new Map(),messagesByVersion=new Map();
178
+ const rowByVersion=new Map(rows.map(row=>[`${row.id}:${row.version_revision}`,row])),grantsBySession=new Map();
177
179
  for(const grant of allGrants){const list=grantsBySession.get(grant.session_id)??[];list.push(grant);grantsBySession.set(grant.session_id,list);}
178
- for(const message of allMessages){const key=`${message.session_id}:${message.revision}`,list=messagesByVersion.get(key)??[];list.push(message);messagesByVersion.set(key,list);}
179
180
  const hits=[],hitTuples=[];
180
181
  for(const [id,revision]of tuples){
181
182
  const row=rowByVersion.get(`${id}:${revision}`),current=currentById.get(id);
@@ -190,7 +191,20 @@ async function listSessions(api,res,auth,url,requestId,search){
190
191
  if(query.until!==undefined&&row.sort_at>=query.until)continue;
191
192
  if(query.include_children===false&&view.parent_id!==null)continue;
192
193
  let hit=view;
193
- if(search){const needle=normalizeSearch(query.query.trim()),messages=messagesByVersion.get(`${id}:${revision}`)??[],matches=[],title=normalizeSearch(view.title??'');if(title.includes(needle))matches.push({message_id:null,field:'title',excerpt:title,offset:title.indexOf(needle)});for(const message of messages){const text=normalizeSearch(message.search_text),at=text.indexOf(needle);if(at>=0&&matches.length<3)matches.push({message_id:message.id,field:'content',excerpt:[...text].slice(Math.max(0,at-120),at+needle.length+120).join(''),offset:at});}if(!matches.length)continue;hit={session:view,matches,match_at:Math.max(...messages.map(message=>message.recorded_at??0),row.sort_at),indexed_revision:revision};}
194
+ if(search){
195
+ const needle=normalizeSearch(query.query.trim()),matches=[],title=normalizeSearch(view.title??'');
196
+ if(title.includes(needle))matches.push({message_id:null,field:'title',excerpt:title,offset:[...title.slice(0,title.indexOf(needle))].length});
197
+ // search_text is normalized on ingestion. SQLite matches the pinned version and returns only three bounded
198
+ // excerpts; neither nonmatching/private transcripts nor the org's retained text enter the Worker's heap.
199
+ const found=await api.store.query(`SELECT m.id,instr(m.search_text,?)-1 offset,
200
+ substr(m.search_text,max(1,instr(m.search_text,?)-120),length(?)+240) excerpt
201
+ FROM messages m WHERE m.team_id=? AND m.session_id=? AND ${MESSAGE_AT('m')}
202
+ AND instr(m.search_text,?)>0 ORDER BY m.ordinal,m.id LIMIT ?`,[needle,needle,needle,auth.team_id,id,revision,revision,needle,3-matches.length]);
203
+ matches.push(...found.map(message=>({message_id:message.id,field:'content',excerpt:message.excerpt,offset:message.offset})));
204
+ if(!matches.length)continue;
205
+ const latest=await api.store.one(`SELECT max(recorded_at) at FROM messages m WHERE m.team_id=? AND m.session_id=? AND ${MESSAGE_AT('m')}`,[auth.team_id,id,revision,revision]);
206
+ hit={session:view,matches,match_at:Math.max(latest?.at??0,row.sort_at),indexed_revision:revision};
207
+ }
194
208
  hits.push(hit);hitTuples.push([id,revision]);
195
209
  }
196
210
  const limit=query.limit??50,page=hits.slice(0,limit);let next=null;