@volter/supercode-teams 0.2.54 → 0.2.56
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.mjs +17 -14
- package/package.json +1 -1
- package/server/harness-routes.mjs +1 -1
- package/worker/index.mjs +3 -2
package/machine.mjs
CHANGED
|
@@ -144,9 +144,10 @@ export class TeamsMachine extends MachineCore {
|
|
|
144
144
|
// Each pane launched here opens a terminal window on this machine's screen (popupWindow).
|
|
145
145
|
this.popups = Boolean(popups);
|
|
146
146
|
this.launches = new Launches(this.p.launches);
|
|
147
|
-
// Windows has no tmux: its panes
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
// Windows has no tmux: its panes live in the machine's pane server, a process of their own that outlives this
|
|
148
|
+
// daemon (a restart or an upgrade never ends a pane), as tmux panes live in tmux's server.
|
|
149
|
+
if (process.platform === 'win32' && !terminal.PaneServerHost) throw new Error('panes on Windows need a newer @volter/supercode-terminal (one with PaneServerHost)');
|
|
150
|
+
this.host = process.platform === 'win32' ? new terminal.PaneServerHost({ pipe: `\\\\.\\pipe\\supercode-panes-${fingerprint(this.identity.publicKeyPem)}` }) : new TmuxTerminalHost({
|
|
150
151
|
// Stable owner id so panes this daemon launched stay "owned" across restarts.
|
|
151
152
|
ownerId: `supercode-teams-${fingerprint(this.identity.publicKeyPem)}`,
|
|
152
153
|
...(tmuxSocket ? { socket: tmuxSocket } : {}),
|
|
@@ -459,7 +460,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
459
460
|
let release;
|
|
460
461
|
try {
|
|
461
462
|
release = this.claimAttachment(principal);
|
|
462
|
-
grant = this.host.issueAttachment(session.id, { mode });
|
|
463
|
+
grant = await this.host.issueAttachment(session.id, { mode });
|
|
463
464
|
} catch (error) {
|
|
464
465
|
release?.();
|
|
465
466
|
this.log.append({ principal, cap, target: `pane:${pane}`, outcome: 'refused', detail: error.message });
|
|
@@ -571,9 +572,11 @@ export class TeamsMachine extends MachineCore {
|
|
|
571
572
|
} catch { /* no harness evidence this tick: the process tree still answers */ }
|
|
572
573
|
}
|
|
573
574
|
const live = new Set();
|
|
574
|
-
|
|
575
|
+
// Every pane's screen, read before the rows are built (a pane server answers asynchronously).
|
|
576
|
+
const captures = await Promise.all(sessions.map((session) => this.#safeCapture(session.id)));
|
|
577
|
+
const rows = sessions.map((session, index) => {
|
|
575
578
|
if (session.contextKey) live.add(session.contextKey);
|
|
576
|
-
const capture =
|
|
579
|
+
const capture = captures[index];
|
|
577
580
|
const requested = session.contextKey ? doc.requests[session.contextKey] ?? null : null;
|
|
578
581
|
const own = harnessLiveness(harness.get(session.rootPid));
|
|
579
582
|
const evidence = {
|
|
@@ -595,10 +598,10 @@ export class TeamsMachine extends MachineCore {
|
|
|
595
598
|
|
|
596
599
|
/** One capture, never fatal: a pane that died between the listing and the read
|
|
597
600
|
* is evidence of nothing, not a failed door. */
|
|
598
|
-
#safeCapture(sessionId) {
|
|
601
|
+
async #safeCapture(sessionId) {
|
|
599
602
|
if (!sessionId) return '';
|
|
600
603
|
try {
|
|
601
|
-
return this.host.capture(sessionId, { lines: 120 });
|
|
604
|
+
return await this.host.capture(sessionId, { lines: 120 });
|
|
602
605
|
} catch {
|
|
603
606
|
return '';
|
|
604
607
|
}
|
|
@@ -687,7 +690,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
687
690
|
const composerKind = typeof kind === 'string' ? kind.trim().toLowerCase() : '';
|
|
688
691
|
const until = Date.now() + INITIAL_INPUT_READY_DEADLINE_MS;
|
|
689
692
|
while (!signal.aborted && Date.now() < until) {
|
|
690
|
-
const capture = this.#safeCapture(session.id);
|
|
693
|
+
const capture = await this.#safeCapture(session.id);
|
|
691
694
|
const unchangedMs = this.#screenUnchangedMs(session.id, capture, Date.now());
|
|
692
695
|
const blank = capture.trim() === '';
|
|
693
696
|
// A blank screen is the one reading that can mean the pane is GONE rather
|
|
@@ -1119,7 +1122,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
1119
1122
|
need(operator ? 'control' : 'control', `pane:${params.pane}`);
|
|
1120
1123
|
const session = await this.#paneByContext(params.pane);
|
|
1121
1124
|
if (!session) throw new Error(`no pane with context ${params.pane}`);
|
|
1122
|
-
this.host.closeSession(session.id);
|
|
1125
|
+
await this.host.closeSession(session.id);
|
|
1123
1126
|
act('close', `pane:${params.pane}`);
|
|
1124
1127
|
return { closed: params.pane };
|
|
1125
1128
|
}
|
|
@@ -1170,10 +1173,10 @@ export class TeamsMachine extends MachineCore {
|
|
|
1170
1173
|
if (!session) throw Object.assign(new Error(`no pane with context ${params.pane}`), { code: 404 });
|
|
1171
1174
|
if (this.suspended.has(params.pane)) throw Object.assign(new Error(`pane ${params.pane} is suspended: remote input is refused until resume`), { code: 423 });
|
|
1172
1175
|
const kind = this.#paneKind(params.pane, session);
|
|
1173
|
-
const draftOf = () => composerHoldsDraft(kind, this.host.capture(session.id, { lines: 200, preserveEscapes: true }));
|
|
1174
|
-
const first = draftOf();
|
|
1176
|
+
const draftOf = async () => composerHoldsDraft(kind, await this.host.capture(session.id, { lines: 200, preserveEscapes: true }));
|
|
1177
|
+
const first = await draftOf();
|
|
1175
1178
|
await new Promise((resolve) => setTimeout(resolve, COMPOSER_RECHECK_MS));
|
|
1176
|
-
const second = draftOf();
|
|
1179
|
+
const second = await draftOf();
|
|
1177
1180
|
if (first !== false || second !== false) {
|
|
1178
1181
|
return { delivered: false, reason: first === true || second === true ? 'draft' : 'no-composer' };
|
|
1179
1182
|
}
|
|
@@ -1185,7 +1188,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
1185
1188
|
need('observe', `pane:${params.pane}`);
|
|
1186
1189
|
const session = await this.#paneByContext(params.pane);
|
|
1187
1190
|
if (!session) throw new Error(`no pane with context ${params.pane}`);
|
|
1188
|
-
return { text: this.host.capture(session.id, { lines: params.lines ?? 200 }) };
|
|
1191
|
+
return { text: await this.host.capture(session.id, { lines: params.lines ?? 200 }) };
|
|
1189
1192
|
}
|
|
1190
1193
|
case 'files.stat': case 'files.list': case 'files.read': case 'files.write': case 'files.mkdir': case 'files.rename': case 'files.delete': case 'files.search': {
|
|
1191
1194
|
// Authorize the REAL path: a symlink under a granted root must not reach outside it. Reading (stat, list,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.56",
|
|
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",
|
|
@@ -284,7 +284,7 @@ async function captureCommit(api,req,res,auth,sourceId,captureId,requestId){cons
|
|
|
284
284
|
if(!origin)statements.push({sql:'INSERT INTO sessions(team_id,id,harness,custodian_id,visibility,revision,created_at) VALUES (?,?,?,?,?,?,?)',params:[auth.team_id,sid,cap.harness,source.custodian_id,lineage?'restricted':begin.visibility,revisionParam,boundaryNow]},{sql:'INSERT INTO origins(team_id,source_id,harness,native_id,session_id,generation,sequence,digest) VALUES (?,?,?,?,?,?,?,?)',params:[auth.team_id,sourceId,cap.harness,cap.native_id,sid,cap.generation,cap.sequence,commit.digest]});else statements.push({sql:'UPDATE sessions SET revision=? WHERE team_id=? AND id=?',params:[revisionParam,auth.team_id,sid]},{sql:'UPDATE origins SET generation=?,sequence=?,digest=? WHERE team_id=? AND source_id=? AND harness=? AND native_id=?',params:[cap.generation,cap.sequence,commit.digest,auth.team_id,sourceId,cap.harness,cap.native_id]});
|
|
285
285
|
if(lineage){lineageValue.new_session_id=sid;const operationRow=await api.store.one('SELECT value_json FROM operations WHERE team_id=? AND id=?',[auth.team_id,lineageValue.operation_id]),operationValue=JSON.parse(operationRow.value_json);operationValue.result={...operationValue.result,new_session_id:sid};statements.push({sql:"UPDATE continuations SET value_json=? WHERE team_id=? AND id=? AND state='running' AND value_json=?",params:[JSON.stringify(lineageValue),auth.team_id,lineage.id,lineage.value_json]},{sql:'UPDATE operations SET value_json=? WHERE team_id=? AND id=?',params:[JSON.stringify(operationValue),auth.team_id,lineageValue.operation_id]});lineageGuards.push({sql:"SELECT 1 ok FROM continuations WHERE team_id=? AND id=? AND state='running' AND value_json=?",params:[auth.team_id,lineage.id,lineage.value_json],code:'conflict',status:409,message:'continuation lineage changed'});}
|
|
286
286
|
statements.push({sql:'INSERT INTO session_versions(team_id,session_id,revision,descriptor_json,source_projection_json,search_title,sort_at,created_at) VALUES (?,?,?,?,?,?,?,?)',params:[auth.team_id,sid,revisionParam,{$journalJsonValue:descriptor},JSON.stringify({source_id:sourceId}),normalizeSearch(commit.title??''),sortAt,boundaryNow]});let ordinal=0;for(const m of messages){let mid=(await api.store.one('SELECT id FROM message_ids WHERE team_id=? AND source_id=? AND harness=? AND native_id=? AND native_key=?',[auth.team_id,sourceId,cap.harness,cap.native_id,m.native_key]))?.id;if(!mid){mid=api.ids.next('message');statements.push({sql:'INSERT INTO message_ids(team_id,source_id,harness,native_id,native_key,id) VALUES (?,?,?,?,?,?)',params:[auth.team_id,sourceId,cap.harness,cap.native_id,m.native_key,mid]});}statements.push({sql:'INSERT INTO messages(team_id,session_id,revision,ordinal,id,source_id,native_key,recorded_at,value_json,search_text) VALUES (?,?,?,?,?,?,?,?,?,?)',params:[auth.team_id,sid,revisionParam,ordinal++,mid,sourceId,m.native_key,m.recorded_at,JSON.stringify(m.value),normalizeSearch(textFromContent(m.value.content))]});}
|
|
287
|
-
const bundle=commit.native_bundle??null,legacyBundle=commit.artifacts.length?captureId:null;for(let index=0;index<commit.artifacts.length;index++){const a=commit.artifacts[index],file=bundle?.files.find(x=>x.upload_id===a.upload_id);statements.push({sql:'UPDATE uploads SET manifest_json=?,committed=1 WHERE team_id=? AND id=?',params:[JSON.stringify(a),auth.team_id,a.upload_id]},{sql:'INSERT INTO session_artifacts(team_id,session_id,revision,bundle_id,upload_id,format,provenance,file_role,fidelity,residue_json) VALUES (?,?,?,?,?,?,?,?,?,?)',params:[auth.team_id,sid,revisionParam,bundle?.id??legacyBundle,a.upload_id,bundle?.harness??cap.harness,'capture',file?.role??(index===0?'primary':'bundle'),bundle?.fidelity??'semantic',JSON.stringify(bundle?.residue??['native_bundle_metadata_unavailable'])]});}const storedReceipt={source_id:sourceId,capture_id:captureId,session_id:sid,generation:cap.generation,sequence:cap.sequence,digest:commit.digest,revision:'__JOURNAL_REVISION__',duplicate:false};statements.push({sql:"UPDATE captures SET state='committed',commit_json=?,receipt_json=? WHERE team_id=? AND id=?",params:[JSON.stringify(commit),{$journalJsonValue:storedReceipt},auth.team_id,captureId]},{sql:'UPDATE sources SET last_received_at=? WHERE team_id=? AND id=?',params:[boundaryNow,auth.team_id,sourceId]},{sql:'UPDATE teams SET journal_revision=? WHERE id=?',params:[revisionParam,auth.team_id]},{sql:'INSERT INTO journal(team_id,revision,session_id,kind,created_at) VALUES (?,?,?,?,?)',params:[auth.team_id,revisionParam,sid,'capture',boundaryNow]});const guards=[{sql:'SELECT 1 ok FROM credentials WHERE id=? AND source_id=? AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at>?)',params:[auth.credential_id,sourceId,boundaryNow],code:'authentication_required',status:401,message:'source credential expired or revoked'},...lineageGuards,{sql:"SELECT 1 ok FROM captures c JOIN sources s ON s.team_id=c.team_id AND s.id=c.source_id LEFT JOIN machines m ON m.team_id=s.team_id AND m.id=s.machine_id LEFT JOIN sync_rules r ON r.team_id=c.team_id AND r.id=json_extract(c.begin_json,'$.rule_id') WHERE c.team_id=? AND c.id=? AND c.source_id=? AND c.state='staging' AND s.state='active' AND (s.kind!='connector' OR (m.revoked_at IS NULL AND r.machine_id=m.id AND r.source_id=s.id AND r.state='active' AND r.revision=json_extract(c.begin_json,'$.rule_revision') AND r.visibility=json_extract(c.begin_json,'$.visibility')))",params:[auth.team_id,captureId,sourceId],code:'conflict',status:409,message:'capture authorization changed'}],storedResponse=result(api,auth,storedReceipt),{revision}=await guardedMutation(api,res,auth,guards,statements,{status:200,body:storedResponse,targetId:captureId,journal:true}),receipt={...storedReceipt,revision};api.events.publish(auth.team_id,{type:'session_changed',cursor:String(revision),revision,session_id:sid,reason:'capture',read_required:true});send(res,200,result(api,auth,receipt),requestId);
|
|
287
|
+
const bundle=commit.native_bundle??null,legacyBundle=commit.artifacts.length?captureId:null;for(let index=0;index<commit.artifacts.length;index++){const a=commit.artifacts[index],file=bundle?.files.find(x=>x.upload_id===a.upload_id);statements.push({sql:'UPDATE uploads SET manifest_json=?,committed=1 WHERE team_id=? AND id=?',params:[JSON.stringify(a),auth.team_id,a.upload_id]},{sql:'INSERT INTO session_artifacts(team_id,session_id,revision,bundle_id,upload_id,format,provenance,file_role,fidelity,residue_json) VALUES (?,?,?,?,?,?,?,?,?,?)',params:[auth.team_id,sid,revisionParam,bundle?.id??legacyBundle,a.upload_id,bundle?.harness??cap.harness,'capture',file?.role??(index===0?'primary':'bundle'),bundle?.fidelity??'semantic',JSON.stringify(bundle?.residue??['native_bundle_metadata_unavailable'])]});}const storedReceipt={source_id:sourceId,capture_id:captureId,session_id:sid,generation:cap.generation,sequence:cap.sequence,digest:commit.digest,revision:'__JOURNAL_REVISION__',duplicate:false};statements.push({sql:"UPDATE captures SET state='committed',commit_json=?,receipt_json=? WHERE team_id=? AND id=?",params:[JSON.stringify(commit),{$journalJsonValue:storedReceipt},auth.team_id,captureId]},{sql:'DELETE FROM capture_parts WHERE team_id=? AND capture_id=?',params:[auth.team_id,captureId]},{sql:'UPDATE sources SET last_received_at=? WHERE team_id=? AND id=?',params:[boundaryNow,auth.team_id,sourceId]},{sql:'UPDATE teams SET journal_revision=? WHERE id=?',params:[revisionParam,auth.team_id]},{sql:'INSERT INTO journal(team_id,revision,session_id,kind,created_at) VALUES (?,?,?,?,?)',params:[auth.team_id,revisionParam,sid,'capture',boundaryNow]});const guards=[{sql:'SELECT 1 ok FROM credentials WHERE id=? AND source_id=? AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at>?)',params:[auth.credential_id,sourceId,boundaryNow],code:'authentication_required',status:401,message:'source credential expired or revoked'},...lineageGuards,{sql:"SELECT 1 ok FROM captures c JOIN sources s ON s.team_id=c.team_id AND s.id=c.source_id LEFT JOIN machines m ON m.team_id=s.team_id AND m.id=s.machine_id LEFT JOIN sync_rules r ON r.team_id=c.team_id AND r.id=json_extract(c.begin_json,'$.rule_id') WHERE c.team_id=? AND c.id=? AND c.source_id=? AND c.state='staging' AND s.state='active' AND (s.kind!='connector' OR (m.revoked_at IS NULL AND r.machine_id=m.id AND r.source_id=s.id AND r.state='active' AND r.revision=json_extract(c.begin_json,'$.rule_revision') AND r.visibility=json_extract(c.begin_json,'$.visibility')))",params:[auth.team_id,captureId,sourceId],code:'conflict',status:409,message:'capture authorization changed'}],storedResponse=result(api,auth,storedReceipt),{revision}=await guardedMutation(api,res,auth,guards,statements,{status:200,body:storedResponse,targetId:captureId,journal:true}),receipt={...storedReceipt,revision};api.events.publish(auth.team_id,{type:'session_changed',cursor:String(revision),revision,session_id:sid,reason:'capture',read_required:true});send(res,200,result(api,auth,receipt),requestId);
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
/**
|
package/worker/index.mjs
CHANGED
|
@@ -117,8 +117,9 @@ export class TeamsServer extends DurableObject {
|
|
|
117
117
|
|
|
118
118
|
async alarm() {
|
|
119
119
|
await this.ready;
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
const done = this.api.serverId ? await maintain(this.api).catch(() => null) : null;
|
|
121
|
+
// A backlog (versions or parts left over) is worked again in seconds, not a minute.
|
|
122
|
+
await this.ctx.storage.setAlarm(Date.now() + (done?.backlog ? 5_000 : MAINTAIN_MS));
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
|