@volter/supercode-orchestrator 0.5.13 → 0.5.14
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/agents.mjs +20 -3
- package/bin/agent-door.mjs +7 -3
- package/board/dispatch.mjs +1 -1
- package/board/event-relay.mjs +6 -2
- package/loop.mjs +1 -1
- package/package.json +2 -2
package/agents.mjs
CHANGED
|
@@ -9,8 +9,10 @@ import { runtimeDoor } from './board/runtime-door.mjs';
|
|
|
9
9
|
import { resolveAgentProfile } from './agent-profiles.mjs';
|
|
10
10
|
import { compareAgentProfile } from '@volter/teams/agent-profile';
|
|
11
11
|
const entry=fileURLToPath(new URL('./bin/agent-door.mjs',import.meta.url));
|
|
12
|
+
// The caller each door names to Teams (X-Supercode-Caller): this host's own command words, then the door's operation.
|
|
13
|
+
const HOST_CALLER=(process.env.SUPERCODE_CALLER_NAME??`orchestrator ${process.argv.slice(2).filter(arg=>!arg.startsWith('-')).slice(0,2).filter(arg=>/^[a-z][a-z0-9-]{0,30}$/.test(arg)).join(' ')}`).slice(0,80);
|
|
12
14
|
export function agentDoor(op,args=[],{context,timeout=25000}={}){
|
|
13
|
-
const reply=spawnSync(process.execPath,[entry],{input:JSON.stringify({op,args,context}),encoding:'utf8',timeout,maxBuffer:4*1024*1024});
|
|
15
|
+
const reply=spawnSync(process.execPath,[entry],{input:JSON.stringify({op,args,context}),encoding:'utf8',timeout,maxBuffer:4*1024*1024,env:{...process.env,SUPERCODE_CALLER_NAME:`${HOST_CALLER} agent-door ${op}`}});
|
|
14
16
|
if(reply.status!==0){let detail;try{detail=JSON.parse(reply.stderr);}catch{detail={message:reply.error?.message??String(reply.stderr).slice(-1000)};}throw Object.assign(new Error(detail.message??'Teams agent door unavailable'),detail);}
|
|
15
17
|
return JSON.parse(reply.stdout);
|
|
16
18
|
}
|
|
@@ -365,8 +367,23 @@ export function recordNativeAgentSession(root,runtimeRef,session) {
|
|
|
365
367
|
return agentRunForSession(root,[runtimeRef]);
|
|
366
368
|
}
|
|
367
369
|
/** Join an observed native session through the public catalogue; unknown catalogue state remains pending. */
|
|
370
|
+
// A run whose session ownership Teams refused (a 4xx answer to the claim: not found, forbidden, conflict, signed out)
|
|
371
|
+
// is asked again after a growing wait (two minutes, doubling to an hour), not every round: each ask spends the shared
|
|
372
|
+
// credential's allowance. Anything else (the rate limit, a timeout, a 5xx, the local daemon's bind of a session just
|
|
373
|
+
// spawned) is not the run's own and is asked again next round.
|
|
374
|
+
const runRetry=new Map();
|
|
375
|
+
const retryKey=(org,row)=>`${org}/${row.agent_id}/${row.session_ref}`;
|
|
376
|
+
const refused=error=>error?.status>=400&&error.status<500&&error.status!==408&&error.status!==429;
|
|
368
377
|
export function synchronizeAgentRun(root,org,row,{context,intended}={}) {
|
|
369
|
-
const
|
|
378
|
+
try{const answer=joinAgentRun(root,org,row,{context,intended});runRetry.delete(retryKey(org,row));return answer;}
|
|
379
|
+
catch(error){
|
|
380
|
+
if(error?.claimRefused){const wait=Math.min((runRetry.get(retryKey(org,row))?.wait??60000)*2,3600000);runRetry.set(retryKey(org,row),{at:Date.now()+wait,wait});}
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function joinAgentRun(root,org,row,{context,intended}) {
|
|
385
|
+
const claim=(...args)=>{try{return agentDoor(...args);}catch(error){throw Object.assign(error,{claimRefused:refused(error)});}};
|
|
386
|
+
const response=claim('ownSession',[row.agent_id,{session:row.session_ref,main:row.main!==0&&row.main!==false,...(row.starter_session?{starter_session:row.starter_session}:{})},digest(`${org}/${row.agent_id}/${row.session_ref}`)],{context});
|
|
370
387
|
const intent=agentIntent(root,org,row.agent_id);
|
|
371
388
|
const runtime=agentDoor('bindContext',[row.agent_id,row.session_ref,row.machine,intent?.assignments??[],intended??intent?.profile.resolved],{context});
|
|
372
389
|
publishAgentRun(root,org,row.session_ref);
|
|
@@ -381,7 +398,7 @@ export function recordAgentRun(root,org,id,{session,machine=null,run,profile=nul
|
|
|
381
398
|
db.exec('COMMIT');
|
|
382
399
|
}catch(error){try{db.exec('ROLLBACK');}catch{}throw error;}finally{db.close();}
|
|
383
400
|
}
|
|
384
|
-
export function pendingAgentRuns(root,org){const db=store(root);try{return db.prepare('SELECT * FROM agent_runs WHERE org=? AND published=0 ORDER BY rowid LIMIT 100').all(org);}finally{db.close();}}
|
|
401
|
+
export function pendingAgentRuns(root,org){const db=store(root);try{return db.prepare('SELECT * FROM agent_runs WHERE org=? AND published=0 ORDER BY rowid LIMIT 100').all(org).filter(row=>!(runRetry.get(retryKey(org,row))?.at>Date.now()));}finally{db.close();}}
|
|
385
402
|
/** Session references come from the bound runtime, never from a model's requested agent id. */
|
|
386
403
|
export function agentRunForSession(root, references) {
|
|
387
404
|
const db = store(root);
|
package/bin/agent-door.mjs
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
import { fromContext } from '@volter/teams/context';
|
|
4
4
|
import { supercodeHome } from '@volter/teams/home';
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
|
-
import { openMachine } from '@volter/teams/machine-door';
|
|
6
|
+
import { openMachine, normalMachineName } from '@volter/teams/machine-door';
|
|
7
|
+
import { hostname } from 'node:os';
|
|
7
8
|
import { StreamRpc } from '@volter/teams/client';
|
|
9
|
+
// This machine is reached through its own daemon's socket, never through the Teams server: the server route lists the
|
|
10
|
+
// org's machines and opens a channel on the shared credential's allowance for a request that never leaves the box.
|
|
11
|
+
const reach=machine=>machine&&normalMachineName(machine)!==normalMachineName(hostname())?machine:null;
|
|
8
12
|
let input='';for await(const chunk of process.stdin)input+=chunk;
|
|
9
13
|
try{
|
|
10
14
|
const {op,args=[],context}=JSON.parse(input);
|
|
@@ -28,14 +32,14 @@ try{
|
|
|
28
32
|
else if(op==='reader')result=await team.agents.delegateReader(args[0],{},{...options,ifMatch:args[1]});
|
|
29
33
|
else if(op==='bindContext'){
|
|
30
34
|
const key=createHash('sha256').update(`${scope.origin}/${scope.team_id}/${args[0]}`).digest('hex');
|
|
31
|
-
const channel=await openMachine({machine:args[2]
|
|
35
|
+
const channel=await openMachine({machine:reach(args[2]),context:context??process.env.SUPERCODE_CONTEXT});
|
|
32
36
|
try{const rpc=new StreamRpc(await channel.open('node'));result=await rpc.call('harness.v1.agents.bindSession',{session:args[1],context:`agent-${key.slice(0,32)}`,assignments:args[3]??[],intended:args[4]??null});}
|
|
33
37
|
finally{channel.close();}
|
|
34
38
|
}
|
|
35
39
|
else if(op==='readerContext'){
|
|
36
40
|
const identity=await team.agents.get(args[0],options);
|
|
37
41
|
const reader=await team.agents.delegateReader(args[0],{expires_in_ms:86400000},{...options,ifMatch:identity.data.revision});
|
|
38
|
-
const channel=await openMachine({machine:args[1]
|
|
42
|
+
const channel=await openMachine({machine:reach(args[1]),context:context??process.env.SUPERCODE_CONTEXT});
|
|
39
43
|
try{const rpc=new StreamRpc(await channel.open('node'));result=await rpc.call('harness.v1.agents.context',{origin:scope.origin,team:scope.team_id,agent:args[0],credential:reader.data});}
|
|
40
44
|
finally{channel.close();}
|
|
41
45
|
}
|
package/board/dispatch.mjs
CHANGED
|
@@ -1189,7 +1189,7 @@ function reconcileLaunches(board, fleet, report, entry, effects, settings, lanes
|
|
|
1189
1189
|
for(const [org,context] of orgs)for(const row of pendingAgentRuns(board.root,org)){
|
|
1190
1190
|
const launch=agentRuns.find(run=>String(run.id)===String(row.run_id));
|
|
1191
1191
|
try{synchronizeAgentRun(board.root,org,row,{context,intended:json(launch?.metadata,{})?.supercode?.agent_profile_intent});}
|
|
1192
|
-
catch(error){report.queued.push([row.run_id,`agent session sync pending: ${error.message}`]);}
|
|
1192
|
+
catch(error){report.queued.push([row.run_id,`agent session sync pending: ${error.message}`]);if(error.code==='rate_limited')break;}
|
|
1193
1193
|
}
|
|
1194
1194
|
const deferred=view(board,db=>db.prepare("SELECT t.id,r.started_at FROM tasks t JOIN task_runs r ON r.id=t.current_run_id WHERE r.ended_at IS NULL AND json_extract(r.metadata,'$.supercode.launch_deferred')=1").all());
|
|
1195
1195
|
// A deferred launch was never issued (a reading or door was not yet available): it is the start its manager, creator
|
package/board/event-relay.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { boardSourceName, publishableCard, recordCardPublications } from './publ
|
|
|
6
6
|
import { listBoards, openBoard } from './store.mjs';
|
|
7
7
|
|
|
8
8
|
export function boardEventRelay({root,context,supercodeHome,log=()=>{}}) {
|
|
9
|
-
let work=null,dirty=false,closed=false,retry=null,reconcile=true;const sources=new Map();
|
|
9
|
+
let work=null,dirty=false,closed=false,retry=null,reconcile=true,heldUntil=0;const sources=new Map();
|
|
10
10
|
const run=async()=>{
|
|
11
11
|
if(reconcile){
|
|
12
12
|
// Establish every native source head before any network operation. A
|
|
@@ -37,10 +37,14 @@ export function boardEventRelay({root,context,supercodeHome,log=()=>{}}) {
|
|
|
37
37
|
};
|
|
38
38
|
const wake=(refresh=true)=>{
|
|
39
39
|
reconcile ||= refresh;dirty=true;if(work||closed)return work;
|
|
40
|
+
// Refused for the rate limit, delivery waits out the server's retry-after: a board write meanwhile would only ask
|
|
41
|
+
// again and spend the allowance the credential shares with every other caller.
|
|
42
|
+
if(Date.now()<heldUntil)return null;
|
|
40
43
|
work=(async()=>{while(dirty&&!closed){dirty=false;await run();}})().catch(error=>{
|
|
41
44
|
log(`board event delivery pending: ${error.code?`${error.code}: `:''}${error.message}`);
|
|
42
45
|
// Retry durable delivery, not a read of the board's current state.
|
|
43
|
-
|
|
46
|
+
const wait=error.code==='rate_limited'?Math.max(5000,Number(error.details?.retry_after_seconds??30)*1000):5000;heldUntil=Date.now()+wait;
|
|
47
|
+
clearTimeout(retry);retry=setTimeout(()=>{heldUntil=0;wake(false);},wait);retry.unref?.();
|
|
44
48
|
}).finally(()=>{work=null;});return work;
|
|
45
49
|
};
|
|
46
50
|
return {wake,close(){closed=true;clearTimeout(retry);}};
|
package/loop.mjs
CHANGED
|
@@ -200,7 +200,7 @@ export class Orchestrator {
|
|
|
200
200
|
for(const pending of synchronizeLocalAgents({root:this.state.root,profiles:this.state.profiles,context,declarations:this.state.agents??{}}))this.log('warn',`agent ${pending.agent} declaration pending: ${pending.error}`);
|
|
201
201
|
for(const row of pendingAgentRuns(this.state.root,scope.org)){
|
|
202
202
|
try{synchronizeAgentRun(this.state.root,scope.org,row,{context,intended:row.profile_json?JSON.parse(row.profile_json):undefined});}
|
|
203
|
-
catch(error){this.log('warn',`agent session ${row.session_ref}: ${error.message}`);}
|
|
203
|
+
catch(error){this.log('warn',`agent session ${row.session_ref}: ${error.message}`);if(error.code==='rate_limited')break;}
|
|
204
204
|
}
|
|
205
205
|
}catch(error){this.log('warn',`agent session synchronization: ${error.message}`);}
|
|
206
206
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The orchestrator runtime over the Volter Harness ontology: one typed operational model whose folder is its serialization, read and written through the harness orchestration doors (docs/ORCHESTRATOR-IR.md)",
|
|
6
6
|
"exports": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@volter/supercode-harness-sdk": "^0.3.41",
|
|
28
|
-
"@volter/teams": "0.2.
|
|
28
|
+
"@volter/teams": "0.2.8",
|
|
29
29
|
"@volter/ztrack": "^2.4.23",
|
|
30
30
|
"yaml": "^2.9.0"
|
|
31
31
|
},
|