@volter/supercode-teams 0.2.118 → 0.2.119
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 +25 -1
- package/package.json +1 -1
package/connector/index.mjs
CHANGED
|
@@ -207,6 +207,9 @@ export class TeamsConnector {
|
|
|
207
207
|
/** The first sync pass, for a caller that waits on it; the connector keeps running if it fails. */
|
|
208
208
|
this.firstPass = syncPass();
|
|
209
209
|
this.timer = setInterval(syncPass, 15000);
|
|
210
|
+
// Viewed file-backed sessions should not wait for catalog discovery. Poll
|
|
211
|
+
// only their cheap file stamps; full reads remain coalesced in capture lanes.
|
|
212
|
+
this.liveCaptureTimer = setInterval(() => this.captureViewedFiles(), 750);
|
|
210
213
|
this.heartbeatTimer = setInterval(() => {
|
|
211
214
|
if (this.abort.signal.aborted) return;
|
|
212
215
|
void heartbeat().catch(error => { this.lastError = error; });
|
|
@@ -243,6 +246,7 @@ export class TeamsConnector {
|
|
|
243
246
|
// reads any session's tail, so a beat costs one bounded scan per root, not one over every session on the
|
|
244
247
|
// machine; rules over the same root and harnesses share the scan.
|
|
245
248
|
const discoveries=new Map(),followCandidates=[],discovered=new Set(),activity=[];
|
|
249
|
+
this.liveFiles??=new Map();
|
|
246
250
|
this.turnStates??=new Map();
|
|
247
251
|
for(const rule of activeRules.values()){
|
|
248
252
|
const root=roots.find(item=>item.id===rule.selector.root_id);
|
|
@@ -268,6 +272,7 @@ export class TeamsConnector {
|
|
|
268
272
|
const turn=descriptor.activity?.turn;
|
|
269
273
|
if(typeof turn==='string'&&turn&&activity.length<4096){const key=`${harness}:${nativeSessionId}`,held=this.turnStates.get(key),since=held?.turn===turn?held.since:this.clock.nowMs();this.turnStates.set(key,{turn,since});activity.push({harness,native_id:nativeSessionId,turn,since});}
|
|
270
274
|
const options={locator,descriptor,harness,nativeSessionId,ruleId:rule.id,ruleRevision:rule.revision,backfill:rule.backfill,rootPath:workspace,configuration};
|
|
275
|
+
if(locator.storage?.kind==='file')this.liveFiles.set(`${harness}:${nativeSessionId}`,options);
|
|
271
276
|
// A session whose native store has not changed since this rule last captured it is not reloaded.
|
|
272
277
|
// One session that cannot be captured (a transcript that rewrote its history needs an explicit resync)
|
|
273
278
|
// is recorded, logged and skipped; it must not stop every other session, or the connector itself, from
|
|
@@ -289,12 +294,29 @@ export class TeamsConnector {
|
|
|
289
294
|
followCandidates.push({key:`${rule.id}:${harness}:${nativeSessionId}`,activity:this.sessionActivity(options),options});
|
|
290
295
|
}
|
|
291
296
|
}
|
|
297
|
+
for(const [key,options] of this.liveFiles)if(!discovered.has(key)||!activeRules.has(options.ruleId))this.liveFiles.delete(key);
|
|
292
298
|
this.nativeEffects.endDiscovery();
|
|
293
299
|
for(const key of this.turnStates.keys())if(!discovered.has(key))this.turnStates.delete(key);
|
|
294
300
|
this.reportedActivity=activity;
|
|
295
301
|
this.pruneNativeCache(discovered);
|
|
296
302
|
this.rotateFollowers(followCandidates);
|
|
297
303
|
}
|
|
304
|
+
captureViewedFiles(){
|
|
305
|
+
if(this.abort.signal.aborted)return;
|
|
306
|
+
this.liveFileCaptures??=new Set();this.liveFileStamps??=new Map();
|
|
307
|
+
for(const key of [...(this.watched??[])].slice(0,FOLLOW_LIMIT)){
|
|
308
|
+
const known=this.liveFiles?.get(key);
|
|
309
|
+
if(!known||this.liveFileCaptures.has(key))continue;
|
|
310
|
+
const options={...known};
|
|
311
|
+
if(!this.sessionChanged(options)||this.liveFileStamps.get(key)===options.stamp)continue;
|
|
312
|
+
this.liveFileStamps.set(key,options.stamp);this.liveFileCaptures.add(key);
|
|
313
|
+
// Re-read authorization when a live file changes; never keep a stale rule
|
|
314
|
+
// alive merely because somebody still has the transcript open.
|
|
315
|
+
void this.capture({...options,configuration:null,live:true}).catch(error=>{
|
|
316
|
+
this.liveFileStamps.delete(key);if(!this.abort.signal.aborted)this.noteSessionProblem(options,error);
|
|
317
|
+
}).finally(()=>this.liveFileCaptures.delete(key));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
298
320
|
// A session never published under a rule that keeps back history recorded before a time (a `since` window, or a
|
|
299
321
|
// `none` rule's capture_after), whose native file was last written before that time, holds only history the rule
|
|
300
322
|
// keeps back: loading it (a 228 MB transcript is gigabytes in the harness process and here) would publish nothing.
|
|
@@ -538,6 +560,8 @@ export class TeamsConnector {
|
|
|
538
560
|
const [key,options]=[...lane.queue.entries()].reduce((best,entry)=>written(entry)>written(best)?entry:best);lane.queue.delete(key);
|
|
539
561
|
let started=null;
|
|
540
562
|
try{
|
|
563
|
+
const seen=this.state.origins[key]?.capture_stamps?.[options.ruleId];
|
|
564
|
+
if(options.live&&seen?.rule_revision===options.ruleRevision&&seen.stamp===this.sessionStamp(options))continue;
|
|
541
565
|
const artifactBefore=statSync(realpathSync(options.locator.storage.path));
|
|
542
566
|
// A session is loaded again no sooner than ten times its last load took (a 0.5 s load: every 5 s; a load
|
|
543
567
|
// that takes minutes: every LARGE_CAPTURE_EVERY_MS), so a live session streams while the lanes stay light.
|
|
@@ -740,5 +764,5 @@ export class TeamsConnector {
|
|
|
740
764
|
}
|
|
741
765
|
async flushOutbox(){let latest=null;while(this.state.outbox.length){const item=this.state.outbox[0];let receipt;this.dropLostArtifacts(item);this.refitParts(item);try{receipt=await this.sendCapture(item);}catch(error){if(error.status===409&&error.code==='carry_mismatch'){const key=`${item.begin.harness}:${item.begin.native_session_id}`;this.lastError=error;process.stderr.write(`supercode teams: the server does not hold what a capture of ${item.begin.harness} session ${item.begin.native_session_id} carried; it goes whole next time\n`);this.state.outbox.shift();if(this.state.origins[key])this.state.origins[key]={...this.state.origins[key],observed_keys:[],observed_digests:[],carry_refused:true};this.save();continue;}if(error.status===400&&error.code==='invalid_request'||error.status===413){this.lastError=error;this.noteSessionProblem({harness:item.begin.harness,nativeSessionId:item.begin.native_session_id,ruleId:item.begin.rule_id??null},error);(this.droppedCaptures??=new Set()).add(`${item.begin.harness}:${item.begin.native_session_id}`);process.stderr.write(`supercode teams: dropped a queued capture of ${item.begin.harness} session ${item.begin.native_session_id} the server refused${error.status===413?' (one message is larger than a request may be)':' as malformed'}: ${error.message}\n`);this.state.outbox.shift();this.save();continue;}if(!await this.ruleRejected(item,error))throw error;this.lastError=error;process.stderr.write(`supercode teams: dropped a queued capture of ${item.begin.harness} session ${item.begin.native_session_id}; its sync rule changed or was removed: ${error.message}\n`);this.state.outbox.shift();this.save();continue;}const key=`${item.begin.harness}:${item.begin.native_session_id}`,prior=this.state.origins[key]??{},next={...prior,generation:item.begin.generation,sequence:item.begin.sequence,digest:item.commit.digest,observed_keys:item.observed?.keys??prior.observed_keys,observed_digests:item.observed?.digests??prior.observed_digests,last_rule_revision:item.begin.rule_revision,backfill_mode:item.backfillMode};if(item.clearBaseline){delete next.baseline;delete next.baseline_digests;delete next.baseline_tool_tail;}if(item.nativeDeferred)next.native_deferred=true;else delete next.native_deferred;if(!item.begin.carry)delete next.carry_refused;if(item.stamp&&item.begin.rule_id)next.capture_stamps={...prior.capture_stamps,[item.begin.rule_id]:{stamp:item.stamp,rule_revision:item.begin.rule_revision,backfill_mode:item.backfillMode}};this.state.origins[key]=next;this.state.outbox.shift();this.save();// A queued capture is often flushed by another session's capture: its own session's problem ends with it.
|
|
742
766
|
this.clearSessionProblem(item.begin.harness,item.begin.native_session_id);if(item.artifacts?.length)this.pruneSpill();latest=receipt;}return latest;}
|
|
743
|
-
stop(){try{this.flushState();}catch{}if(this.timer)clearInterval(this.timer);if(this.heartbeatTimer)clearInterval(this.heartbeatTimer);this.abort.abort();for(const follower of this.followers?.values()??[])follower.controller.abort();return this.nativeEffects.close();}
|
|
767
|
+
stop(){try{this.flushState();}catch{}if(this.timer)clearInterval(this.timer);if(this.liveCaptureTimer)clearInterval(this.liveCaptureTimer);if(this.heartbeatTimer)clearInterval(this.heartbeatTimer);this.abort.abort();for(const follower of this.followers?.values()??[])follower.controller.abort();return this.nativeEffects.close();}
|
|
744
768
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.119",
|
|
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",
|