@volter/supercode-teams 0.2.37 → 0.2.39
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/connector/index.mjs +10 -2
- package/package.json +1 -1
- package/server/harness-routes.mjs +10 -2
package/connector/index.mjs
CHANGED
|
@@ -60,6 +60,12 @@ const CHUNK_BYTES=1024*1024;
|
|
|
60
60
|
// A minute is four beats with no write: the turn is over, or waiting on the user or one long tool call.
|
|
61
61
|
// Smaller files translate in well under a second and keep getting a bundle with every capture.
|
|
62
62
|
const NATIVE_IDLE_MS=60000,NATIVE_IDLE_BYTES=8*1024*1024;
|
|
63
|
+
// Loading a transcript reads all of it, and a live one changes on every message: a 135 MB Claude transcript took
|
|
64
|
+
// over 30 s a load on a Windows PC, on every beat, and translating it held the harness server past 272 s while every
|
|
65
|
+
// other request queued behind. A file of LARGE_CAPTURE_BYTES or more is captured at most once per
|
|
66
|
+
// LARGE_CAPTURE_EVERY_MS (tried or not), its load given a second per megabyte, and one past NATIVE_MAX_BYTES is
|
|
67
|
+
// published without a native bundle, which could not be built in time.
|
|
68
|
+
const LARGE_CAPTURE_BYTES=8*1024*1024,LARGE_CAPTURE_EVERY_MS=5*60000,NATIVE_MAX_BYTES=64*1024*1024;
|
|
63
69
|
const fileStamp=stat=>`${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}`;
|
|
64
70
|
const recordedAt=entry=>{if(Number.isSafeInteger(entry.recorded_at))return entry.recorded_at;if(Number.isSafeInteger(entry.timestamp))return entry.timestamp;const value=entry.metadata?.timestamp??entry.value?.metadata?.timestamp??entry.timestamp;if(typeof value!=='string')return null;const parsed=Date.parse(value);return Number.isSafeInteger(parsed)?parsed:null;};
|
|
65
71
|
const serverOrigin=value=>{const url=new URL(value),loopback=url.hostname==='localhost'||url.hostname==='::1'||url.hostname==='[::1]'||/^127(?:\.\d{1,3}){3}$/.test(url.hostname);if(!['http:','https:'].includes(url.protocol)||url.username||url.password||url.search||url.hash||(url.pathname!==''&&url.pathname!=='/')||(url.protocol==='http:'&&!loopback))throw new TypeError('server must be an HTTPS origin (HTTP is loopback-only)');return url.origin;};
|
|
@@ -398,11 +404,13 @@ export class TeamsConnector {
|
|
|
398
404
|
if(!configuration)configuration=await this.configuration();const rule=configuration.rules.find(item=>item.id===ruleId&&item.revision===ruleRevision&&item.state==='active');if(!rule)throw Object.assign(new Error('sync rule is absent, inactive, or stale'),{code:'forbidden'});visibility=rule.visibility;
|
|
399
405
|
const artifactSource=locator?.storage?.kind==='file'&&existsSync(locator.storage.path)?realpathSync(locator.storage.path):null;
|
|
400
406
|
const artifactBefore=artifactSource?statSync(artifactSource):null;
|
|
401
|
-
const
|
|
407
|
+
const large=(artifactBefore?.size??0)>=LARGE_CAPTURE_BYTES,sessionKey=`${harness}:${nativeSessionId}`;
|
|
408
|
+
if(large){this.largeCaptures??=new Map();const last=this.largeCaptures.get(sessionKey);if(last!=null&&this.clock.nowMs()-last<LARGE_CAPTURE_EVERY_MS)return null;this.largeCaptures.set(sessionKey,this.clock.nowMs());}
|
|
409
|
+
const loaded=await this.core.session(locator).load(large?{timeoutMs:Math.max(30000,Math.ceil(artifactBefore.size/(1024*1024))*1000)}:undefined),configuredBackfill=rule.backfill??backfill??'all',backfillPolicy=typeof configuredBackfill==='string'?{mode:configuredBackfill,since:null}:configuredBackfill;
|
|
402
410
|
const key=`${harness}:${nativeSessionId}`,previous=this.state.origins[key]??null;
|
|
403
411
|
let messages=(loaded.messages??loaded.turns??[]).map((entry,index)=>{const value=entry.value??Object.fromEntries(Object.entries(entry).filter(([key])=>!['id','native_key','recorded_at','timestamp'].includes(key)));return{native_key:entry.id??entry.native_key??String(index),recorded_at:recordedAt(entry),value};});
|
|
404
412
|
// A session published without its native bundle is captured again, with the same messages, once the bundle can be built.
|
|
405
|
-
const nativeWanted=backfillPolicy.mode==='all'&&typeof this.core.translateSession==='function';
|
|
413
|
+
const nativeWanted=backfillPolicy.mode==='all'&&typeof this.core.translateSession==='function'&&!((artifactBefore?.size??0)>NATIVE_MAX_BYTES);
|
|
406
414
|
const retryNative=Boolean(previous?.native_deferred&&nativeWanted&&artifactBefore?.isFile()&&!previous?.baseline&&this.nativeReady(key,artifactBefore));
|
|
407
415
|
const unchanged=previous?.last_rule_revision===ruleRevision&&previous?.backfill_mode===backfillPolicy.mode&&previous?.observed_keys?.length===messages.length&&messages.every((message,index)=>message.native_key===previous.observed_keys[index]&&hash(canonicalJson(message.value))===previous.observed_digests?.[index]);
|
|
408
416
|
if(unchanged&&!retryNative){this.recordStamp(key,ruleId,ruleRevision,backfillPolicy.mode,stamp);return null;}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.39",
|
|
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",
|
|
@@ -172,9 +172,16 @@ async function listSessions(api,res,auth,url,requestId,search){
|
|
|
172
172
|
if(search)body.coverage={scope:'retained_text',indexed_at:boundaryNow,indexed_revision:team.journal_revision};
|
|
173
173
|
send(res,200,body,requestId);
|
|
174
174
|
}
|
|
175
|
+
/** A report older than this (four missed heartbeats) says nothing about what the session is doing now. */
|
|
176
|
+
const ACTIVITY_FRESH_MS=60000;
|
|
177
|
+
/** `{since}` while the session's harness is working on a turn, by its connector's latest fresh report; else null. */
|
|
178
|
+
async function workingOf(api,teamId,sessionId){
|
|
179
|
+
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]);
|
|
180
|
+
return row&&row.turn==='working'&&api.clock.nowMs()-row.observed_at<ACTIVITY_FRESH_MS?{since:row.since??row.observed_at}:null;
|
|
181
|
+
}
|
|
175
182
|
async function sessionDescriptor(api,res,auth,id,requestId){
|
|
176
183
|
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});
|
|
177
|
-
send(res,200,result(api,auth,JSON.parse(row.descriptor_json)),requestId,{ETag:`"${row.revision}"`});
|
|
184
|
+
send(res,200,result(api,auth,{...JSON.parse(row.descriptor_json),working:await workingOf(api,auth.team_id,row.id)}),requestId,{ETag:`"${row.revision}"`});
|
|
178
185
|
}
|
|
179
186
|
async function correlateRoute(api,req,res,auth,path,requestId){
|
|
180
187
|
if(!['user','service'].includes(auth.kind)||auth.role==='viewer')fail(403,'forbidden','credential cannot correlate sessions');
|
|
@@ -198,7 +205,8 @@ async function readSession(api,res,auth,id,url,requestId){
|
|
|
198
205
|
const snapshot=await api.store.readSessionSnapshot({teamId:auth.team_id,principalId:auth.principal_id,credentialId:auth.credential_id,ref:id,now:api.clock.nowMs(),bound:auth.bound,beforeHash:query.before?cursorHash(query.before):null,requestedRevision:query.revision,tail,nextCursorHash:cursorHash(nextCursor),nextCursorToken:nextCursor});
|
|
199
206
|
const {rows,beforeCursor,revision}=snapshot;
|
|
200
207
|
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)}));
|
|
201
|
-
|
|
208
|
+
const descriptor=JSON.parse(snapshot.descriptor_json);
|
|
209
|
+
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);
|
|
202
210
|
}
|
|
203
211
|
async function readArtifact(api,req,res,auth,id,uploadId,requestId){
|
|
204
212
|
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;
|