@volter/supercode-teams 0.3.95 → 0.3.96
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/machine-records.mjs +8 -13
- package/machine.mjs +24 -3
- package/package.json +1 -1
package/machine-records.mjs
CHANGED
|
@@ -21,8 +21,10 @@ const MIN_READ_MS = 2000;
|
|
|
21
21
|
export const BEAT_MS = 15_000;
|
|
22
22
|
export const STALE_MS = 45_000;
|
|
23
23
|
const backoff = (previous) => Math.min(300_000, Math.max(2000, (previous || 1000) * 2));
|
|
24
|
-
/** How often a machine whose daemon is too old to serve records is asked again
|
|
25
|
-
|
|
24
|
+
/** How often a machine whose daemon is too old to serve records is asked again: its update is what answers it, and the
|
|
25
|
+
* server says nothing when its link comes back within the grace (machine-events.mjs records a change of linked only),
|
|
26
|
+
* so it is asked each minute and said once. */
|
|
27
|
+
const TOO_OLD_RETRY_MS = 60_000;
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
30
|
* The producer: `readers[section](detail)` reads one section (async), `detail` naming what changed when the event knows
|
|
@@ -211,15 +213,6 @@ export class RecordHolder {
|
|
|
211
213
|
if (forgot) this.save();
|
|
212
214
|
for (const [machine, held] of this.held) if (!linked.has(machine) && held.state !== 'unlinked') this.let(machine, 'offline: not linked to the team\'s server');
|
|
213
215
|
for (const machine of linked) { const held = this.held.get(machine); if (!held || held.state === 'unlinked' || ((held.state === 'closed' || held.state === 'too-old') && !held.retry)) this.subscribe(machine); }
|
|
214
|
-
// a machine too old to serve records whose link changed since (it linked again: an update restarts its daemon) is
|
|
215
|
-
// asked again now, not at its ten-minute re-ask
|
|
216
|
-
const changedAt = new Map((items ?? []).filter((item) => item.linked).map((item) => [normal(item.name), item.changed_at ?? null]));
|
|
217
|
-
for (const [machine, held] of this.held) {
|
|
218
|
-
const at = changedAt.get(machine);
|
|
219
|
-
if (at == null) continue;
|
|
220
|
-
if (held.state === 'too-old' && held.linkChangedAt != null && at !== held.linkChangedAt) { held.linkChangedAt = at; this.subscribe(machine); continue; }
|
|
221
|
-
held.linkChangedAt = at;
|
|
222
|
-
}
|
|
223
216
|
}
|
|
224
217
|
let(machine, why) {
|
|
225
218
|
const held = this.held.get(machine); if (!held) return;
|
|
@@ -245,11 +238,13 @@ export class RecordHolder {
|
|
|
245
238
|
// opened again after a backoff, 2 s doubling to 5 min: an obligation of this machine, never a sweep. A daemon too
|
|
246
239
|
// old to serve records is asked again every TOO_OLD_RETRY_MS: only its update answers it (asked every 8-16 s, it
|
|
247
240
|
// filled the log)
|
|
241
|
+
const saidBefore = state === 'too-old' && held.saidTooOld;
|
|
248
242
|
held.backoff = state === 'too-old' ? TOO_OLD_RETRY_MS : backoff(held.backoff);
|
|
243
|
+
held.saidTooOld = state === 'too-old';
|
|
249
244
|
if (this.closed) return;
|
|
250
245
|
held.retry = setTimeout(() => { held.retry = null; if ((held.state === 'closed' || held.state === 'too-old') && !this.closed) this.subscribe(machine); }, held.backoff);
|
|
251
246
|
held.retry.unref?.();
|
|
252
|
-
this.log(`machine records from ${machine}: ${held.why}; read again in ${Math.round(held.backoff / 1000)} s`);
|
|
247
|
+
if (!saidBefore) this.log(`machine records from ${machine}: ${held.why}; read again in ${Math.round(held.backoff / 1000)} s${state === 'too-old' ? ' (and each minute after, said again only once it changes)' : ''}`);
|
|
253
248
|
};
|
|
254
249
|
// the peer answers within STALE_MS (any frame, its beat included) or its record is stale, with why, and reopened
|
|
255
250
|
const heard = () => { clearTimeout(held.quiet); held.quiet = setTimeout(() => closed(`no frame from ${machine} within ${STALE_MS / 1000} s (its record stream went quiet)`), STALE_MS); held.quiet.unref?.(); };
|
|
@@ -316,7 +311,7 @@ export class RecordHolder {
|
|
|
316
311
|
}
|
|
317
312
|
held.record = record;
|
|
318
313
|
} else return null;
|
|
319
|
-
Object.assign(held, { state: 'open', since: held.state === 'open' ? held.since : Date.now(), why: null, received_at: Date.now(), backoff: 0 });
|
|
314
|
+
Object.assign(held, { state: 'open', since: held.state === 'open' ? held.since : Date.now(), why: null, received_at: Date.now(), backoff: 0, saidTooOld: false });
|
|
320
315
|
this.save();
|
|
321
316
|
return null;
|
|
322
317
|
}
|
package/machine.mjs
CHANGED
|
@@ -622,6 +622,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
622
622
|
if (change.kind === 'removed' && change.key?.harness === 'codex') this.live.codexIndex({ removed: change.key.session_id });
|
|
623
623
|
else if (change.descriptor) this.live.codexIndex({ descriptor: change.descriptor });
|
|
624
624
|
}
|
|
625
|
+
this.emit('codex-index', { changes: params.changes ?? [] });
|
|
625
626
|
}
|
|
626
627
|
});
|
|
627
628
|
// The Codex sessions' own index (Codex rollouts only): the live map knows them from its first answer, never waiting on
|
|
@@ -634,6 +635,9 @@ export class TeamsMachine extends MachineCore {
|
|
|
634
635
|
this.live.codexRowsReplaced();
|
|
635
636
|
for (const descriptor of answer?.initial ?? []) if (descriptor?.locator) this.live.codexIndex({ descriptor });
|
|
636
637
|
this.live.codexIndexed();
|
|
638
|
+
// the records take their Codex rows from this index too (their own whole index leaves Codex out: one watch and
|
|
639
|
+
// one read of the rollouts)
|
|
640
|
+
this.emit('codex-index', { initial: answer?.initial ?? [] });
|
|
637
641
|
}, (error) => {
|
|
638
642
|
codexIndexBackoff = Math.min(300_000, Math.max(2000, (codexIndexBackoff || 1000) * 2));
|
|
639
643
|
this.emit('records-log', `live sessions: the Codex sessions' index could not be subscribed (${error.message}); tried again in ${Math.round(codexIndexBackoff / 1000)} s`);
|
|
@@ -794,7 +798,22 @@ export class TeamsMachine extends MachineCore {
|
|
|
794
798
|
// is back, and holds its own retry (2 s doubling to 5 min) until it is (MR4); nothing reads its fields as current.
|
|
795
799
|
const backoff = (previous) => Math.min(300_000, Math.max(2000, (previous || 1000) * 2));
|
|
796
800
|
const both = () => { producer.touch('sessions'); producer.touch('panes'); };
|
|
797
|
-
|
|
801
|
+
// every harness but Codex, whose rows come from the live map's Codex-only index ('codex-index' below)
|
|
802
|
+
const HARNESSES = ['claude-code', 'pi', 'opencode', 'goose', 'gemini', 'grok', 'openclaw', 'hermes', 'supercode'];
|
|
803
|
+
const isCodex = (key) => key.startsWith('codex:') || key.includes(':codex:');
|
|
804
|
+
const applyRow = (change) => {
|
|
805
|
+
if (change.kind === 'removed' && change.key) { this.sessionDescriptors?.delete(`sc:${machineName(this.env)}:${change.key.harness}:${change.key.session_id}`); locators.delete(`${change.key.harness}:${change.key.session_id}`); }
|
|
806
|
+
else if (change.descriptor?.locator) { const { harness, session_id } = change.descriptor.locator; this.sessionDescriptors?.set(`sc:${machineName(this.env)}:${harness}:${session_id}`, change.descriptor); locators.set(`${harness}:${session_id}`, change.descriptor.locator); }
|
|
807
|
+
};
|
|
808
|
+
this.on('codex-index', ({ initial = null, changes = [] }) => {
|
|
809
|
+
if (initial) {
|
|
810
|
+
for (const key of [...locators.keys()]) if (isCodex(key)) locators.delete(key);
|
|
811
|
+
for (const key of [...(this.sessionDescriptors?.keys() ?? [])]) if (isCodex(key)) this.sessionDescriptors.delete(key);
|
|
812
|
+
for (const descriptor of initial) applyRow({ descriptor });
|
|
813
|
+
}
|
|
814
|
+
for (const change of changes) applyRow(change);
|
|
815
|
+
both();
|
|
816
|
+
});
|
|
798
817
|
const locators = new Map(); // `${harness}:${session_id}` → the index's locator for it
|
|
799
818
|
const noteIndexParts = (failed, error) => {
|
|
800
819
|
// the index's own unread parts (a store, a folder, a registry it could not read): their sessions are unknown
|
|
@@ -817,8 +836,10 @@ export class TeamsMachine extends MachineCore {
|
|
|
817
836
|
let indexBackoff = 0;
|
|
818
837
|
const subscribe = () => this.#serveRequest('harness.v1.sessions.index.subscribe', { harnesses: HARNESSES, limit: 0 }, 60_000)
|
|
819
838
|
.then((answer) => {
|
|
820
|
-
this.recordsIndex = answer?.subscription ?? null; indexBackoff = 0;
|
|
821
|
-
|
|
839
|
+
this.recordsIndex = answer?.subscription ?? null; indexBackoff = 0;
|
|
840
|
+
// its own rows replaced; the Codex rows (the live map's index's) kept
|
|
841
|
+
for (const key of [...locators.keys()]) if (!isCodex(key)) locators.delete(key);
|
|
842
|
+
for (const key of [...(this.sessionDescriptors?.keys() ?? [])]) if (!isCodex(key)) this.sessionDescriptors.delete(key);
|
|
822
843
|
for (const descriptor of answer?.initial ?? []) if (descriptor?.locator) { locators.set(`${descriptor.locator.harness}:${descriptor.locator.session_id}`, descriptor.locator); this.sessionDescriptors?.set(`sc:${machineName(this.env)}:${descriptor.locator.harness}:${descriptor.locator.session_id}`, descriptor); }
|
|
823
844
|
producer.note('sessions_index', null); noteIndexParts(answer?.failed, null); both();
|
|
824
845
|
}, (error) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.96",
|
|
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",
|