@volter/supercode-teams 0.3.154 → 0.3.155
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/README.md +4 -1
- package/bin/workspace.mjs +7 -1
- package/connector/index.mjs +4 -1
- package/machine.mjs +45 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,10 @@ verified caller at its local origin. `harness.v1.sessions.close_remote` accepts
|
|
|
27
27
|
that forwarded caller at the target's Teams door only, under pane control. It
|
|
28
28
|
retains native live-work checks, then calls the existing pane-close door to
|
|
29
29
|
recheck its grant, recorded starter and host entry. It adds no caller identity
|
|
30
|
-
or origin-attestation protocol.
|
|
30
|
+
or origin-attestation protocol. Native activity coverage on this path currently
|
|
31
|
+
includes Claude Code and Codex. Other recognized harness panes refuse without
|
|
32
|
+
`foreign: true` when their activity cannot be read; plain command panes are
|
|
33
|
+
handled separately. A nonstarter needs explicit
|
|
31
34
|
`foreign: true` for that one pane. These are node-door methods, not additions to
|
|
32
35
|
the native harness service. [Scope and qualification](../../docs/evidence/remote-pane-close-2026-10-08.md).
|
|
33
36
|
|
package/bin/workspace.mjs
CHANGED
|
@@ -1160,6 +1160,11 @@ async function connectorCommand(verb, { flags, rest, output, client, context, te
|
|
|
1160
1160
|
// on the machine credential and the daemon keeps the agent's reader context for it; a pane session's agent is the
|
|
1161
1161
|
// one its launch recorded.
|
|
1162
1162
|
connector.recordAgentSession=binding=>machine.bindAgentSession(binding,{delegator:context.machine_only?null:contextName});
|
|
1163
|
+
machine.agentDelegator=context.machine_only?null:contextName;
|
|
1164
|
+
machine.agentSessionIntent=session=>{
|
|
1165
|
+
const item=connector.state.agent_sessions?.[session];
|
|
1166
|
+
return item&&['pending','bound'].includes(item.state)?{agent:item.agent,org:item.org,context:item.context,assignments:item.assignments}:null;
|
|
1167
|
+
};
|
|
1163
1168
|
machine.onLaunchAgent=(session,environment)=>connector.queueAgentSession(session,environment);
|
|
1164
1169
|
connector.flushAgentIdentity=()=>machine.flushAgentIdentity({delegator:context.machine_only?null:contextName});
|
|
1165
1170
|
connector.agentClosureReceipt=async id=>{
|
|
@@ -1411,6 +1416,7 @@ function humanOutput(value) {
|
|
|
1411
1416
|
const data = value?.data ?? value;
|
|
1412
1417
|
if (data === null) return `${context}Done.\n`;
|
|
1413
1418
|
if (typeof data === 'string') return `${context}${data}\n`;
|
|
1419
|
+
if(typeof data?.session==='string'&&data.agent)return `${context}Session: ${data.session}\nAgent: ${data.agent.path??data.agent.name??data.agent.id} (${data.agent.id})\n${data.main?'Main session':'Concurrent session'}${data.source?`\nSource: ${data.source}`:''}\n`;
|
|
1414
1420
|
const row = humanRow(data);
|
|
1415
1421
|
return `${context}${Object.entries(row).map(([key, item]) => `${key}: ${item}`).join('\n')}\n`;
|
|
1416
1422
|
}
|
|
@@ -1423,7 +1429,7 @@ function humanTranscript(descriptor, messages) {
|
|
|
1423
1429
|
const lines = messages.map(message => { const value = message.value ?? message; const body = [text(value.content ?? value.text).trim(), calls(value)].filter(Boolean).join('\n'); return `\n${value.role ?? 'message'}:\n${body || '(no text)'}\n`; });
|
|
1424
1430
|
return head + coverage + (lines.length ? lines.join('') : '\n(no messages in this window)\n');
|
|
1425
1431
|
}
|
|
1426
|
-
function humanRow(value) { if (!value || typeof value !== 'object') return { value }; const descriptor = value.session
|
|
1432
|
+
function humanRow(value) { if (!value || typeof value !== 'object') return { value }; const descriptor = value.session&&typeof value.session==='object'?value.session:value; return Object.fromEntries(Object.entries(descriptor).filter(([, item]) => item === null || ['string', 'number', 'boolean'].includes(typeof item)).slice(0, 8)); }
|
|
1427
1433
|
async function stream(iterator, json) { for await (const event of iterator) process.stdout.write(json ? `${JSON.stringify(event)}\n` : humanOutput(event)); }
|
|
1428
1434
|
function unsupported(message) { throw Object.assign(new Error(message), { code: 'unsupported', exitCode: 5 }); }
|
|
1429
1435
|
function notFound(message) { throw Object.assign(new Error(message), { code: 'not_found', exitCode: 4 }); }
|
package/connector/index.mjs
CHANGED
|
@@ -1256,12 +1256,15 @@ export class TeamsConnector {
|
|
|
1256
1256
|
for(const [session,item] of Object.entries(sessions)){
|
|
1257
1257
|
if(item.state!=='pending'||item.next_at>now)continue;
|
|
1258
1258
|
try{
|
|
1259
|
+
// Reading as the launch's agent needs its delegation, not a transcript already captured in Teams. Keep the
|
|
1260
|
+
// reader first, so a slow initial capture cannot strand the session's whoami/sessions commands.
|
|
1261
|
+
const binding=await this.recordAgentSession?.({session,agent:item.agent,org:item.org,context:item.context,assignments:item.assignments});
|
|
1262
|
+
if(binding)item.context=binding.context;
|
|
1259
1263
|
if(!item.declared){
|
|
1260
1264
|
await this.request('POST',`/machines/${this.machineId}/agent-sessions`,{session,agent:item.agent,main:item.main});item.declared=true;
|
|
1261
1265
|
// its lineage exists now: a session held for want of it is sent again in this pass
|
|
1262
1266
|
if(this.state.profile_holds?.[session])delete this.state.profile_holds[session];
|
|
1263
1267
|
}
|
|
1264
|
-
await this.recordAgentSession?.({session,agent:item.agent,org:item.org,context:item.context,assignments:item.assignments});
|
|
1265
1268
|
item.state='bound';item.bound_at=now;delete item.error;
|
|
1266
1269
|
}catch(error){
|
|
1267
1270
|
item.attempts+=1;item.error=errorSummary(error,now);
|
package/machine.mjs
CHANGED
|
@@ -365,6 +365,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
365
365
|
/** Typed lines by key → the answer their first attempt gives (see #serveTurn). */
|
|
366
366
|
#turnKeys = new Map();
|
|
367
367
|
#sessionBindings = new Map();
|
|
368
|
+
#agentReaders = new Map();
|
|
368
369
|
|
|
369
370
|
/** Native process evidence, published by the connector; a missing transport never reports an end. */
|
|
370
371
|
async sessionBindings(machineId,incarnationId) {
|
|
@@ -402,10 +403,10 @@ export class TeamsMachine extends MachineCore {
|
|
|
402
403
|
|
|
403
404
|
/** An agent's delegated reader credential installed as a context of this machine's supercode home: what its
|
|
404
405
|
* sessions' Teams verbs run under. */
|
|
405
|
-
async #installAgentContext({origin,team,agent,credential}){
|
|
406
|
+
async #installAgentContext({origin,team,agent,credential},signal=AbortSignal.timeout(15000)){
|
|
406
407
|
if(typeof origin!=='string'||typeof team!=='string'||typeof agent!=='string'||typeof credential?.token!=='string')throw Object.assign(new Error('agent context requires server, Org, identity and delegated credential'),{code:400});
|
|
407
408
|
const client=new TeamsClient({server:origin,credential:credential.token});
|
|
408
|
-
const me=await client.me({signal
|
|
409
|
+
const me=await client.me({signal});
|
|
409
410
|
if(me.principal.id!==agent||me.principal.kind!=='agent'||!me.teams.some(item=>item.team.id===team))throw Object.assign(new Error('delegated credential does not belong to this agent and Org'),{code:403});
|
|
410
411
|
const key=createHash('sha256').update(`${client.server}/${team}/${agent}`).digest('hex');
|
|
411
412
|
const name=`agent-${key.slice(0,32)}`,ref=`cred_${key}`,contexts=new WorkspaceContexts(supercodeHome(this.env));
|
|
@@ -414,23 +415,49 @@ export class TeamsMachine extends MachineCore {
|
|
|
414
415
|
return {context:name,agent,org:team,expires_at:credential.expires_at};
|
|
415
416
|
}
|
|
416
417
|
|
|
418
|
+
/** A reader is reused only while it belongs to this agent and Org and has not expired. Concurrent sessions of
|
|
419
|
+
* one agent share a renewal. The connected person's authority is used only to delegate; no command runs as them. */
|
|
420
|
+
async #agentReader({agent,org,context},delegator){
|
|
421
|
+
const contexts=new WorkspaceContexts(supercodeHome(this.env)),document=contexts.read();
|
|
422
|
+
let selected=context?document.contexts[context]:null;
|
|
423
|
+
if(selected&&(selected.principal_id!==agent||(org&&selected.team_id!==org)))
|
|
424
|
+
throw Object.assign(new Error('agent reader context belongs to another agent or Org'),{code:403});
|
|
425
|
+
if(!selected){
|
|
426
|
+
const existing=Object.entries(document.contexts).find(([,item])=>item.principal_id===agent&&item.team_id===org);
|
|
427
|
+
if(existing)[context,selected]=existing;
|
|
428
|
+
}
|
|
429
|
+
org??=selected?.team_id;
|
|
430
|
+
const credential=selected?contexts.credential(selected.credential_ref):null;
|
|
431
|
+
if(credential?.token&&credential.server_id===selected.server_id&&credential.origin===selected.origin
|
|
432
|
+
&&Number.isSafeInteger(credential.expires_at)&&credential.expires_at>Date.now())return {context,org};
|
|
433
|
+
if(!delegator)return {context:null,org};
|
|
434
|
+
const key=`${org??''}/${agent}`;
|
|
435
|
+
if(this.#agentReaders.has(key))return this.#agentReaders.get(key);
|
|
436
|
+
const renewal=(async()=>{
|
|
437
|
+
const source=fromContext({supercodeHome:supercodeHome(this.env),name:delegator}),signal=AbortSignal.timeout(20000);
|
|
438
|
+
if(org&&source.context.team_id!==org)throw Object.assign(new Error('agent reader renewal belongs to another Org'),{code:403});
|
|
439
|
+
const team=source.client.team(source.context.team_id),identity=(await team.agents.get(agent,{signal})).data;
|
|
440
|
+
if(identity.state!=='active')throw Object.assign(new Error('only an active agent receives a reader context'),{code:403});
|
|
441
|
+
const reader=await team.agents.delegateReader(identity.id,{expires_in_ms:86400000},{signal,ifMatch:identity.revision});
|
|
442
|
+
return this.#installAgentContext({origin:source.context.origin,team:source.context.team_id,agent:identity.id,credential:reader.data},signal);
|
|
443
|
+
})();
|
|
444
|
+
this.#agentReaders.set(key,renewal);
|
|
445
|
+
try{return await renewal;}finally{this.#agentReaders.delete(key);}
|
|
446
|
+
}
|
|
447
|
+
|
|
417
448
|
/**
|
|
418
449
|
* A session this machine's connector declared its agent's (D138 step 5): the agent's reader context, as its launch
|
|
419
450
|
* named it, is kept for the session (agent-session-contexts.json, read by `harness.v1.agents.session`), so its Teams
|
|
420
|
-
* verbs run as that agent.
|
|
421
|
-
*
|
|
422
|
-
* keeps the session without one.
|
|
451
|
+
* verbs run as that agent. Missing or expired readers are delegated through the connected person's context, which
|
|
452
|
+
* manages the agent. A machine connected on its machine credential alone keeps the session without one.
|
|
423
453
|
*/
|
|
424
454
|
async bindAgentSession({session,agent,org,context=null,assignments=[]},{delegator=null}={}){
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
org??=selected.context.team_id;
|
|
433
|
-
}
|
|
455
|
+
const previous=readJson(join(this.home,'agent-session-contexts.json'),{sessions:{}}).sessions[session];
|
|
456
|
+
if(previous&&(previous.agent!==agent||(org&&previous.org!==org)))
|
|
457
|
+
throw Object.assign(new Error('a recorded session cannot change agent or Org'),{code:403});
|
|
458
|
+
context??=previous?.context;
|
|
459
|
+
org??=previous?.org;
|
|
460
|
+
({context,org}=await this.#agentReader({agent,org,context},delegator));
|
|
434
461
|
const file=join(this.home,'agent-session-contexts.json'),record=readJson(file,{version:1,sessions:{}});
|
|
435
462
|
record.sessions[session]={context,agent,org,assignments};
|
|
436
463
|
const temporary=`${file}.${randomBytes(12).toString('hex')}.tmp`;
|
|
@@ -3390,7 +3417,10 @@ export class TeamsMachine extends MachineCore {
|
|
|
3390
3417
|
case 'harness.v1.agents.session': {
|
|
3391
3418
|
if(!operator)throw Object.assign(new Error('local agent session lookup requires operator authority'),{code:403});
|
|
3392
3419
|
if(typeof params.session!=='string')throw Object.assign(new Error('name a native session'),{code:400});
|
|
3393
|
-
|
|
3420
|
+
const binding=readJson(join(this.home,'agent-session-contexts.json'),{sessions:{}}).sessions[params.session]
|
|
3421
|
+
??this.agentSessionIntent?.(params.session);
|
|
3422
|
+
if(!binding)return null;
|
|
3423
|
+
return this.bindAgentSession({session:params.session,...binding},{delegator:this.agentDelegator??null});
|
|
3394
3424
|
}
|
|
3395
3425
|
case 'harness.v1.agents.closureReceipt': {
|
|
3396
3426
|
if(!operator)throw Object.assign(new Error('native closure receipts require operator authority'),{code:403});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.155",
|
|
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",
|