@volter/supercode-teams 0.2.39 → 0.2.41
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 +18 -7
- package/machine.mjs +17 -12
- package/package.json +1 -1
package/connector/index.mjs
CHANGED
|
@@ -137,6 +137,8 @@ export class TeamsConnector {
|
|
|
137
137
|
bindings: [...this.nativeEffects.published(), ...await bindings()],
|
|
138
138
|
roots: this.localRoots.map(({id,machine_id,name,harnesses})=>({id,machine_id,name,harnesses})),
|
|
139
139
|
...(this.hostKeyId ? { host_key_id: this.hostKeyId } : {}),
|
|
140
|
+
// What each synced session's harness is doing (its turn: working, idle, …), from the last discovery.
|
|
141
|
+
...(this.reportedActivity ? { activity: this.reportedActivity } : {}),
|
|
140
142
|
});
|
|
141
143
|
this.save();
|
|
142
144
|
});
|
|
@@ -204,7 +206,8 @@ export class TeamsConnector {
|
|
|
204
206
|
// A rule covers its root folder and every folder under it. Core discovery filters that subtree before it
|
|
205
207
|
// reads any session's tail, so a beat costs one bounded scan per root, not one over every session on the
|
|
206
208
|
// machine; rules over the same root and harnesses share the scan.
|
|
207
|
-
const discoveries=new Map(),followCandidates=[],discovered=new Set();
|
|
209
|
+
const discoveries=new Map(),followCandidates=[],discovered=new Set(),activity=[];
|
|
210
|
+
this.turnStates??=new Map();
|
|
208
211
|
for(const rule of activeRules.values()){
|
|
209
212
|
const root=roots.find(item=>item.id===rule.selector.root_id);
|
|
210
213
|
if(!root)continue;
|
|
@@ -225,6 +228,9 @@ export class TeamsConnector {
|
|
|
225
228
|
const locator=descriptor.locator,nativeSessionId=locator?.session_id,harness=locator?.harness;
|
|
226
229
|
if(!locator||!nativeSessionId||!harness)continue;
|
|
227
230
|
discovered.add(`${harness}:${nativeSessionId}`);
|
|
231
|
+
// A session's turn state as its harness reports it, and since when this connector has seen it so.
|
|
232
|
+
const turn=descriptor.activity?.turn;
|
|
233
|
+
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});}
|
|
228
234
|
const options={locator,descriptor,harness,nativeSessionId,ruleId:rule.id,ruleRevision:rule.revision,backfill:rule.backfill,rootPath:workspace,configuration};
|
|
229
235
|
// A session whose native store has not changed since this rule last captured it is not reloaded.
|
|
230
236
|
// One session that cannot be captured (a transcript that rewrote its history needs an explicit resync)
|
|
@@ -243,6 +249,8 @@ export class TeamsConnector {
|
|
|
243
249
|
}
|
|
244
250
|
}
|
|
245
251
|
this.nativeEffects.endDiscovery();
|
|
252
|
+
for(const key of this.turnStates.keys())if(!discovered.has(key))this.turnStates.delete(key);
|
|
253
|
+
this.reportedActivity=activity;
|
|
246
254
|
this.pruneNativeCache(discovered);
|
|
247
255
|
this.rotateFollowers(followCandidates);
|
|
248
256
|
}
|
|
@@ -415,10 +423,13 @@ export class TeamsConnector {
|
|
|
415
423
|
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]);
|
|
416
424
|
if(unchanged&&!retryNative){this.recordStamp(key,ruleId,ruleRevision,backfillPolicy.mode,stamp);return null;}
|
|
417
425
|
let effectivePrevious=previous;const clearBaseline=backfillPolicy.mode==='all';
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
426
|
+
// A `none` rule keeps back the history recorded before it became active (the server's capture_after), not
|
|
427
|
+
// whatever a session holds when first seen: a session begun after the rule is published from its first message.
|
|
428
|
+
const keptBefore=backfillPolicy.mode==='since'?backfillPolicy.since:backfillPolicy.mode==='none'&&Number.isSafeInteger(rule.capture_after)?rule.capture_after:null;
|
|
429
|
+
if(!previous&&backfillPolicy.mode==='none'&&keptBefore===null){const toolTail=trailingToolResults(messages);this.state.origins[key]={generation:1,sequence:0,digest:null,baseline:messages.map(x=>x.native_key),baseline_digests:messages.map(x=>hash(canonicalJson(x.value))),...(toolTail?{baseline_tool_tail:toolTail}:{}),observed_keys:messages.map(x=>x.native_key),observed_digests:messages.map(x=>hash(canonicalJson(x.value))),last_rule_revision:ruleRevision,backfill_mode:backfillPolicy.mode};this.recordStamp(key,ruleId,ruleRevision,backfillPolicy.mode,stamp);return null;}
|
|
430
|
+
if(!previous&&(backfillPolicy.mode==='since'||backfillPolicy.mode==='none')){
|
|
431
|
+
if(!Number.isSafeInteger(keptBefore))throw Object.assign(new Error('since backfill requires an integer timestamp'),{code:'invalid_request'});
|
|
432
|
+
const excluded=messages.filter(message=>message.recorded_at==null||message.recorded_at<keptBefore);
|
|
422
433
|
effectivePrevious={generation:1,sequence:0,digest:null,baseline:excluded.map(message=>message.native_key),baseline_digests:excluded.map(message=>hash(canonicalJson(message.value)))};
|
|
423
434
|
this.state.origins[key]=effectivePrevious;
|
|
424
435
|
}
|
|
@@ -435,8 +446,8 @@ export class TeamsConnector {
|
|
|
435
446
|
// A rule that started mid tool call kept back the core's placeholder result; the real result replaces it.
|
|
436
447
|
const settled=baselineMoved&&backfillPolicy.mode==='none'?settledToolTail(effectivePrevious,messages):null;
|
|
437
448
|
if(settled){baselineMoved=false;effectivePrevious={...effectivePrevious,baseline_digests:settled};this.state.origins[key]={...this.state.origins[key],baseline_digests:settled};this.save();}
|
|
438
|
-
if(baselineMoved&&
|
|
439
|
-
const excluded=messages.filter(message=>message.recorded_at==null||message.recorded_at<
|
|
449
|
+
if(baselineMoved&&Number.isSafeInteger(keptBefore)){
|
|
450
|
+
const excluded=messages.filter(message=>message.recorded_at==null||message.recorded_at<keptBefore);
|
|
440
451
|
effectivePrevious={...effectivePrevious,baseline:excluded.map(message=>message.native_key),baseline_digests:excluded.map(message=>hash(canonicalJson(message.value)))};
|
|
441
452
|
// A tool tail recorded for the old baseline describes positions that no longer hold; it goes with it.
|
|
442
453
|
delete effectivePrevious.baseline_tool_tail;
|
package/machine.mjs
CHANGED
|
@@ -419,19 +419,24 @@ export class TeamsMachine extends MachineCore {
|
|
|
419
419
|
return refuse(`no interact grant for ${target}`);
|
|
420
420
|
}
|
|
421
421
|
accept();
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
422
|
+
// The user-turn door every supercode has (harness.v1 sessions.message, as_user): the session's runtime input,
|
|
423
|
+
// its pane's empty composer, or its mailbox until the composer is free. The sender is named after the member.
|
|
424
|
+
const answer = (value) => { if (!stream.closed) { stream.write(JSON.stringify(value)); stream.close('delivered'); } };
|
|
425
|
+
this.#serveRequest('harness.v1.sessions.message', {
|
|
426
|
+
locator: { harness, session_id: session, storage: { kind: 'file', path: '' } },
|
|
427
|
+
text: request.text,
|
|
428
|
+
from_name: entry.name ?? principal ?? 'a Room member',
|
|
429
|
+
as_user: true,
|
|
430
|
+
}, 15_000).then((result) => {
|
|
431
|
+
const delivered = result?.message_id && !result.refusal;
|
|
432
|
+
this.log.append({ principal, cap: 'interact', target, outcome: delivered ? 'turn' : 'failed', detail: result?.refusal?.message?.slice(0, 200) });
|
|
433
|
+
answer(delivered
|
|
434
|
+
? { code: 0, turn_id: result.message_id, door: result.delivery?.how === 'waiting' ? 'waiting' : (result.delivery?.door ?? 'pane') }
|
|
435
|
+
: { code: 1, text: result?.refusal?.message ?? 'the session did not take the turn' });
|
|
436
|
+
}, (error) => {
|
|
437
|
+
this.log.append({ principal, cap: 'interact', target, outcome: 'failed', detail: error.message.slice(0, 200) });
|
|
438
|
+
answer({ code: 1, text: error.message });
|
|
433
439
|
});
|
|
434
|
-
child.stdin.end(JSON.stringify({ op: 'turn', harness, session, text: request.text, from_name: entry.name ?? principal ?? 'a Room member', via: 'room' }));
|
|
435
440
|
}
|
|
436
441
|
|
|
437
442
|
// ---- terminal ------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.41",
|
|
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",
|