@volter/supercode-teams 0.2.57 → 0.2.59
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 +13 -3
- package/package.json +1 -1
- package/server/event-routes.mjs +4 -0
- package/worker/index.mjs +2 -0
package/connector/index.mjs
CHANGED
|
@@ -66,6 +66,9 @@ const NATIVE_IDLE_MS=60000,NATIVE_IDLE_BYTES=8*1024*1024;
|
|
|
66
66
|
// LARGE_CAPTURE_EVERY_MS (tried or not), its load given a second per megabyte, and one past NATIVE_MAX_BYTES is
|
|
67
67
|
// published without a native bundle, which could not be built in time.
|
|
68
68
|
const LARGE_CAPTURE_BYTES=8*1024*1024,LARGE_CAPTURE_EVERY_MS=5*60000,NATIVE_MAX_BYTES=64*1024*1024,FAST_LOAD_MS=5000;
|
|
69
|
+
// Each capture writes the session's whole transcript as a new version on the server, so a session is captured at
|
|
70
|
+
// most this often (a later change is captured when the wait ends): a busy fleet filled the server's database.
|
|
71
|
+
const CAPTURE_FLOOR_MS=5000,LARGE_CAPTURE_FLOOR_MS=20000;
|
|
69
72
|
// A turn a Room member typed here waits this long to be found in its session's transcript.
|
|
70
73
|
const TURN_NOTE_MS=24*3600000;
|
|
71
74
|
const messageText=value=>typeof value?.content==='string'?value.content.trim():Array.isArray(value?.content)?value.content.map(part=>typeof part==='string'?part:part?.type==='text'?part.text??'':'').join('').trim():'';
|
|
@@ -135,7 +138,7 @@ export class TeamsConnector {
|
|
|
135
138
|
const heartbeat = () => {
|
|
136
139
|
const next = beating.then(async () => {
|
|
137
140
|
this.state.sequence += 1;
|
|
138
|
-
await this.request('POST', `/machines/${this.machineId}/heartbeat`, {
|
|
141
|
+
const reply = await this.request('POST', `/machines/${this.machineId}/heartbeat`, {
|
|
139
142
|
incarnation_id: this.state.incarnation_id, sequence: this.state.sequence,
|
|
140
143
|
bindings: [...this.nativeEffects.published(), ...await bindings()],
|
|
141
144
|
roots: this.localRoots.map(({id,machine_id,name,harnesses})=>({id,machine_id,name,harnesses})),
|
|
@@ -143,6 +146,8 @@ export class TeamsConnector {
|
|
|
143
146
|
// What each synced session's harness is doing (its turn: working, idle, …), from the last discovery.
|
|
144
147
|
...(this.reportedActivity ? { activity: this.reportedActivity } : {}),
|
|
145
148
|
});
|
|
149
|
+
// The sessions someone is following now (a viewer or channel open on them) skip the capture floors.
|
|
150
|
+
if (Array.isArray(reply?.watched)) this.watched = new Set(reply.watched.map(item => `${item.harness}:${item.native_id}`));
|
|
146
151
|
this.save();
|
|
147
152
|
});
|
|
148
153
|
beating = next.catch(() => {});
|
|
@@ -409,6 +414,11 @@ export class TeamsConnector {
|
|
|
409
414
|
async capture(options) {
|
|
410
415
|
if(!options.preloaded&&this.largeSession(options))return this.captureLarge(options);
|
|
411
416
|
if(!options.preloaded){
|
|
417
|
+
const key=`${options.harness}:${options.nativeSessionId}`,now=this.clock.nowMs();
|
|
418
|
+
this.lastCaptures??=new Map();this.captureRetry??=new Map();
|
|
419
|
+
const wait=this.watched?.has(key)?0:CAPTURE_FLOOR_MS-(now-(this.lastCaptures.get(key)??0));
|
|
420
|
+
if(wait>0){if(!this.captureRetry.has(key))this.captureRetry.set(key,setTimeout(()=>{this.captureRetry.delete(key);this.capture(options).catch(()=>{});},wait).unref?.());return null;}
|
|
421
|
+
this.lastCaptures.set(key,now);
|
|
412
422
|
const {locator}=options,path=locator?.storage?.kind==='file'&&existsSync(locator.storage.path)?realpathSync(locator.storage.path):null;
|
|
413
423
|
const artifactBefore=path?statSync(path):null;
|
|
414
424
|
options={...options,preloaded:{loaded:await (options.live?this.liveCore:this.core).session(locator).load(),artifactBefore}};
|
|
@@ -465,8 +475,8 @@ export class TeamsConnector {
|
|
|
465
475
|
const artifactBefore=statSync(realpathSync(options.locator.storage.path));
|
|
466
476
|
// A session is loaded again no sooner than ten times its last load took (a 0.5 s load: every 5 s; a load
|
|
467
477
|
// that takes minutes: every LARGE_CAPTURE_EVERY_MS), so a live session streams while the lanes stay light.
|
|
468
|
-
// A change that arrives sooner is captured when its wait ends.
|
|
469
|
-
this.largeCaptures??=new Map();const last=this.largeCaptures.get(key),wait=last?Math.min(LARGE_CAPTURE_EVERY_MS,last.ms*10)-(this.clock.nowMs()-last.at):0;
|
|
478
|
+
// A change that arrives sooner is captured when its wait ends. A session someone is following has no wait.
|
|
479
|
+
this.largeCaptures??=new Map();const last=this.largeCaptures.get(key),wait=last&&!this.watched?.has(key)?Math.min(LARGE_CAPTURE_EVERY_MS,Math.max(LARGE_CAPTURE_FLOOR_MS,last.ms*10))-(this.clock.nowMs()-last.at):0;
|
|
470
480
|
if(wait>0){this.largeRetry??=new Map();if(!this.largeRetry.has(key))this.largeRetry.set(key,setTimeout(()=>{this.largeRetry.delete(key);this.captureLarge(options);},wait).unref?.());continue;}
|
|
471
481
|
started=this.clock.nowMs();
|
|
472
482
|
const loaded=await lane.core.session(options.locator).load({timeoutMs:Math.max(30000,Math.ceil(artifactBefore.size/(1024*1024))*1000)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.59",
|
|
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",
|
package/server/event-routes.mjs
CHANGED
|
@@ -132,6 +132,10 @@ export async function handleSessionEvents({ api, req, res, auth, id, url, reques
|
|
|
132
132
|
}
|
|
133
133
|
});
|
|
134
134
|
stream.cleanup(api.events.subscribe(`session:${auth.team_id}:${sessionId}`, enqueue));
|
|
135
|
+
// Who is watching this session live: its machine captures a watched session as fast as it changes (heartbeat).
|
|
136
|
+
const watchers = api.watchers ??= new Map(), watchKey = `${auth.team_id}:${sessionId}`;
|
|
137
|
+
watchers.set(watchKey, (watchers.get(watchKey) ?? 0) + 1);
|
|
138
|
+
stream.cleanup(() => { const left = (watchers.get(watchKey) ?? 1) - 1; if (left > 0) watchers.set(watchKey, left); else watchers.delete(watchKey); });
|
|
135
139
|
stream.cleanup(api.events.subscribe(`team:${auth.team_id}`, event => { if (event.type === 'authorization_changed') enqueue(); }));
|
|
136
140
|
// The snapshot pins its journal cursor atomically. Replay closes the snapshot/subscription gap.
|
|
137
141
|
await enqueue();
|
package/worker/index.mjs
CHANGED
|
@@ -118,6 +118,8 @@ export class TeamsServer extends DurableObject {
|
|
|
118
118
|
async alarm() {
|
|
119
119
|
await this.ready;
|
|
120
120
|
const done = this.api.serverId ? await maintain(this.api).catch(() => null) : null;
|
|
121
|
+
// The database's size on every full pass, where `wrangler tail` reads it.
|
|
122
|
+
if (!done?.backlog) console.log(`maintain: database ${(this.ctx.storage.sql.databaseSize / 1048576).toFixed(1)} MB`);
|
|
121
123
|
// A backlog (versions or parts left over) is worked again in seconds, not a minute.
|
|
122
124
|
await this.ctx.storage.setAlarm(Date.now() + (done?.backlog ? 3_000 : MAINTAIN_MS));
|
|
123
125
|
}
|