@volter/supercode-teams 0.2.20

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.
Files changed (79) hide show
  1. package/README.md +119 -0
  2. package/attention.mjs +191 -0
  3. package/bin/fleet.mjs +151 -0
  4. package/bin/teams.mjs +291 -0
  5. package/bin/workspace/args.mjs +66 -0
  6. package/bin/workspace/files.mjs +110 -0
  7. package/bin/workspace/lifecycle.mjs +113 -0
  8. package/bin/workspace/volter-device.mjs +42 -0
  9. package/bin/workspace/volter-login.mjs +185 -0
  10. package/bin/workspace.mjs +726 -0
  11. package/browser-attach.mjs +90 -0
  12. package/client.mjs +212 -0
  13. package/connector/effects.mjs +95 -0
  14. package/connector/index.d.ts +69 -0
  15. package/connector/index.mjs +174 -0
  16. package/connector/native-continuation.d.ts +8 -0
  17. package/connector/native-continuation.mjs +50 -0
  18. package/connector/native-effects.mjs +164 -0
  19. package/context.mjs +34 -0
  20. package/contracts/adapter-map.json +16 -0
  21. package/contracts/index.d.ts +299 -0
  22. package/contracts/index.mjs +37 -0
  23. package/execution.mjs +206 -0
  24. package/fleet.mjs +78 -0
  25. package/index.mjs +12 -0
  26. package/machine-door.mjs +36 -0
  27. package/machine.mjs +1398 -0
  28. package/package.json +80 -0
  29. package/porthole-hook.cjs +137 -0
  30. package/remote/job-exec.py +146 -0
  31. package/remote/wire.py +23 -0
  32. package/server/arc2-transactions.mjs +125 -0
  33. package/server/attachment-relay.mjs +149 -0
  34. package/server/catalog.mjs +7 -0
  35. package/server/continuations.mjs +56 -0
  36. package/server/correlation.d.ts +4 -0
  37. package/server/correlation.mjs +17 -0
  38. package/server/effects-transactions.mjs +119 -0
  39. package/server/effects.mjs +97 -0
  40. package/server/event-routes.mjs +208 -0
  41. package/server/exports.mjs +46 -0
  42. package/server/harness-routes.mjs +248 -0
  43. package/server/index.d.ts +31 -0
  44. package/server/index.mjs +52 -0
  45. package/server/ingest.mjs +24 -0
  46. package/server/node-upgrade.mjs +39 -0
  47. package/server/otlp/collector-config.d.ts +2 -0
  48. package/server/otlp/collector-config.mjs +26 -0
  49. package/server/otlp/decoders/index.d.ts +63 -0
  50. package/server/otlp/decoders/index.mjs +311 -0
  51. package/server/otlp/index.d.ts +4 -0
  52. package/server/otlp/index.mjs +4 -0
  53. package/server/otlp/projection.d.ts +5 -0
  54. package/server/otlp/projection.mjs +17 -0
  55. package/server/otlp/receiver.d.ts +3 -0
  56. package/server/otlp/receiver.mjs +36 -0
  57. package/server/otlp/schema/google/protobuf/any.proto +162 -0
  58. package/server/otlp/schema/google/rpc/status.proto +48 -0
  59. package/server/otlp/schema/manifest.json +22 -0
  60. package/server/otlp/schema/opentelemetry/proto/collector/logs/v1/logs_service.proto +77 -0
  61. package/server/otlp/schema/opentelemetry/proto/collector/metrics/v1/metrics_service.proto +77 -0
  62. package/server/otlp/schema/opentelemetry/proto/collector/trace/v1/trace_service.proto +77 -0
  63. package/server/otlp/schema/opentelemetry/proto/common/v1/common.proto +154 -0
  64. package/server/otlp/schema/opentelemetry/proto/logs/v1/logs.proto +226 -0
  65. package/server/otlp/schema/opentelemetry/proto/metrics/v1/metrics.proto +735 -0
  66. package/server/otlp/schema/opentelemetry/proto/resource/v1/resource.proto +45 -0
  67. package/server/otlp/schema/opentelemetry/proto/trace/v1/trace.proto +359 -0
  68. package/server/otlp/transport.d.ts +10 -0
  69. package/server/otlp/transport.mjs +424 -0
  70. package/server/otlp/wire-codec.mjs +153 -0
  71. package/server/store-core.mjs +139 -0
  72. package/server/store-worker.mjs +23 -0
  73. package/worker/fetch-adapter.mjs +49 -0
  74. package/worker/index.mjs +127 -0
  75. package/worker/websocket-duplex.mjs +29 -0
  76. package/worker/wrangler.toml +38 -0
  77. package/workspace-client.d.ts +75 -0
  78. package/workspace-client.mjs +171 -0
  79. package/workspace-move.mjs +38 -0
@@ -0,0 +1,139 @@
1
+ // The Teams store's operations, independent of where SQLite runs. A host supplies
2
+ // four primitives over its own SQLite: `all`, `get` (first row or null), `run`
3
+ // (returns { changes }) and `tx` (runs a function atomically). The Node host
4
+ // runs these in a worker thread over node:sqlite; the Durable Object host runs
5
+ // them on the object's own SQLite.
6
+ import { ingestOtlpBatch, correlateSessions } from './arc2-transactions.mjs';
7
+ import { claimEffect, getEffect, listEffects, reportEffect } from './effects-transactions.mjs';
8
+ import { MIGRATIONS } from '@volter/teams/server/migrations';
9
+
10
+ /** Bring a database to the newest schema. `version()`/`setVersion(n)` name where the host records it. */
11
+ export function migrate({ exec, tx, version, setVersion }) {
12
+ const current = version();
13
+ if (current > MIGRATIONS.length) throw new Error(`database schema ${current} is newer than supported schema ${MIGRATIONS.length}`);
14
+ for (let next = current + 1; next <= MIGRATIONS.length; next += 1) {
15
+ const ddl = MIGRATIONS[next - 1].replace(/^PRAGMA .*;\s*$/gm, '');
16
+ tx(() => { exec(ddl); setVersion(next); });
17
+ }
18
+ }
19
+
20
+ export function createStoreHandlers({ all, get, run, tx }) {
21
+ const journalValue=(value,revision)=>value==='__JOURNAL_REVISION__'?revision:Array.isArray(value)?value.map(item=>journalValue(item,revision)):value&&typeof value==='object'?Object.fromEntries(Object.entries(value).map(([key,item])=>[key,journalValue(item,revision)])):value;
22
+ const journalParam=(value,revision)=>value&&typeof value==='object'&&value.$journalRevision===true?revision:value&&typeof value==='object'&&Object.hasOwn(value,'$journalJson')?JSON.stringify({...value.$journalJson,revision}):value&&typeof value==='object'&&Object.hasOwn(value,'$journalJsonValue')?JSON.stringify(journalValue(value.$journalJsonValue,revision)):value;
23
+ const domainError=(status,code,message)=>{const error=new Error(message);error.status=status;error.code=code;throw error;};
24
+ const authorizedSession=(args)=>{
25
+ const {teamId,principalId,credentialId,ref,now}=args;
26
+ const credential=get("SELECT id FROM credentials WHERE id=? AND principal_id=? AND kind IN ('user','service') AND (team_id IS NULL OR team_id=?) AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at>?)",[credentialId,principalId,teamId,now]);
27
+ const membership=get('SELECT role FROM memberships WHERE team_id=? AND principal_id=? AND active=1',[teamId,principalId]);
28
+ if(!credential||!membership)domainError(401,'authentication_required','authentication required');
29
+ if(typeof ref!=='string'||ref.length<8)domainError(404,'not_found','not found');
30
+ const groups=all('SELECT group_id FROM group_members WHERE team_id=? AND principal_id=?',[teamId,principalId]).map(row=>row.group_id);
31
+ const candidates=all('SELECT * FROM sessions WHERE team_id=? AND id LIKE ? AND deleted_at IS NULL',[teamId,`${ref}%`]);
32
+ const visible=new Map();
33
+ for(const candidate of candidates){
34
+ let root=candidate;const seen=new Set();
35
+ while(root.canonical_id){if(seen.has(root.id))domainError(409,'conflict','correlation cycle');seen.add(root.id);root=get('SELECT * FROM sessions WHERE team_id=? AND id=? AND deleted_at IS NULL',[teamId,root.canonical_id]);if(!root)break;}
36
+ if(!root)continue;
37
+ const family=all('WITH RECURSIVE family(id) AS (SELECT ? UNION SELECT s.id FROM sessions s JOIN family f ON s.canonical_id=f.id WHERE s.team_id=?) SELECT s.* FROM sessions s JOIN family f ON f.id=s.id WHERE s.team_id=?',[root.id,teamId,teamId]);
38
+ const readable=family.every(session=>{
39
+ if(session.deleted_at!=null)return false;
40
+ if(session.visibility==='team'||session.custodian_id===principalId)return true;
41
+ const grants=all('SELECT subject_kind,subject_id,expires_at FROM session_grants WHERE team_id=? AND session_id=?',[teamId,session.id]);
42
+ return grants.some(grant=>(grant.subject_kind==='principal'&&grant.subject_id===principalId||grant.subject_kind==='group'&&groups.includes(grant.subject_id))&&(grant.expires_at==null||grant.expires_at>now));
43
+ });
44
+ if(readable)visible.set(root.id,root);
45
+ }
46
+ if(visible.size>1)domainError(400,'ambiguous_reference','ambiguous session reference');
47
+ if(visible.size===0)domainError(404,'not_found','not found');
48
+ const session=[...visible.values()][0];
49
+ const version=get('SELECT descriptor_json FROM session_versions WHERE team_id=? AND session_id=? AND revision=?',[teamId,session.id,session.revision]);
50
+ if(!version)domainError(404,'not_found','not found');
51
+ const projection=JSON.parse(version.descriptor_json);
52
+ if(!args.projectionDepth){
53
+ for(const field of ['parent_id','continued_from'])if(projection[field]){
54
+ try{authorizedSession({...args,ref:projection[field],projectionDepth:1});}catch{projection[field]=null;}
55
+ }
56
+ }
57
+ return {session:{...session,descriptor_json:JSON.stringify(projection)},membership};
58
+ };
59
+ const readSessionSnapshot=args=>tx(()=>{
60
+ const {teamId,principalId,credentialId,ref,now,beforeHash,requestedRevision,tail,nextCursorHash,nextCursorToken}=args;
61
+ const {session}=authorizedSession(args);const team=get('SELECT auth_epoch,journal_revision FROM teams WHERE id=?',[teamId]);
62
+ let revision,ordinals;
63
+ if(beforeHash){
64
+ const cursor=get("SELECT * FROM cursors WHERE id=? AND team_id=? AND principal_id=? AND kind='history'",[beforeHash,teamId,principalId]);
65
+ if(!cursor||cursor.expires_at<=now||cursor.auth_epoch!==team.auth_epoch)domainError(409,'resync_required','history cursor expired or authorization changed');
66
+ const snapshot=JSON.parse(cursor.snapshot_json);
67
+ if(snapshot.session_id!==session.id)domainError(409,'resync_required','history cursor belongs to another session');
68
+ revision=snapshot.revision;ordinals=snapshot.ordinals;
69
+ }else{
70
+ revision=requestedRevision??session.revision;
71
+ if(!get('SELECT 1 ok FROM session_versions WHERE team_id=? AND session_id=? AND revision=?',[teamId,session.id,revision]))domainError(404,'not_found','session revision not found');
72
+ ordinals=all('SELECT ordinal FROM messages WHERE team_id=? AND session_id=? AND revision=? ORDER BY ordinal',[teamId,session.id,revision]).map(row=>row.ordinal);
73
+ }
74
+ const pageOrdinals=ordinals.slice(-tail),remaining=ordinals.slice(0,-tail);
75
+ const rows=pageOrdinals.length?all(`SELECT * FROM messages WHERE team_id=? AND session_id=? AND revision=? AND ordinal IN (${pageOrdinals.map(()=>'?').join(',')}) ORDER BY ordinal`,[teamId,session.id,revision,...pageOrdinals]):[];
76
+ const version=get('SELECT descriptor_json FROM session_versions WHERE team_id=? AND session_id=? AND revision=?',[teamId,session.id,revision]);
77
+ if(!version)domainError(404,'not_found','session revision not found');
78
+ let descriptorJson=version.descriptor_json;
79
+ if(revision===session.revision)descriptorJson=session.descriptor_json;
80
+ else{
81
+ const historical=JSON.parse(descriptorJson);
82
+ for(const field of ['parent_id','continued_from'])if(historical[field]){
83
+ try{authorizedSession({...args,ref:historical[field],projectionDepth:1});}catch{historical[field]=null;}
84
+ }
85
+ descriptorJson=JSON.stringify(historical);
86
+ }
87
+ let beforeCursor=null;
88
+ if(remaining.length){beforeCursor=nextCursorToken;run('INSERT INTO cursors(id,team_id,principal_id,kind,query_json,auth_epoch,revision,snapshot_json,expires_at) VALUES (?,?,?,?,?,?,?,?,?)',[nextCursorHash,teamId,principalId,'history',JSON.stringify({tail}),team.auth_epoch,team.journal_revision,JSON.stringify({session_id:session.id,revision,ordinals:remaining}),now+900000]);}
89
+ return {descriptor_json:descriptorJson,rows,beforeCursor,eventCursor:String(team.journal_revision),revision,total:ordinals.length};
90
+ });
91
+ const readArtifact=args=>tx(()=>{
92
+ const {session}=authorizedSession(args);
93
+ const upload=get(`SELECT u.manifest_json FROM session_artifacts a JOIN uploads u ON u.team_id=a.team_id AND u.id=a.upload_id
94
+ WHERE a.team_id=? AND a.session_id=? AND a.upload_id=? AND u.committed=1`,[args.teamId,session.id,args.uploadId]);
95
+ if(!upload)domainError(404,'not_found','artifact not found');
96
+ const chunks=all('SELECT bytes FROM artifact_chunks WHERE team_id=? AND upload_id=? ORDER BY chunk_index',[args.teamId,args.uploadId]);
97
+ return {manifest:JSON.parse(upload.manifest_json),bytes:Buffer.concat(chunks.map(row=>row.bytes))};
98
+ });
99
+ return {
100
+ exec: ({sql,params=[]}) => run(sql,params), query: ({sql,params=[]}) => all(sql,params), one: ({sql,params=[]}) => get(sql,params),
101
+ transaction: ({statements}) => tx(() => statements.map(s => s.kind === 'query' ? all(s.sql,s.params) : s.kind === 'one' ? get(s.sql,s.params) : run(s.sql,s.params))),
102
+ snapshot: ({statements}) => tx(() => statements.map(s => s.kind === 'one' ? get(s.sql,s.params) : all(s.sql,s.params))),
103
+ guardedTransaction: ({guards=[],statements}) => tx(() => { for(const guard of guards){const row=get(guard.sql,guard.params);if(!row){const error=new Error(guard.message??'transaction predicate changed');error.code=guard.code??'conflict';error.status=guard.status??409;throw error;}}return statements.map(s=>s.kind==='query'?all(s.sql,s.params):s.kind==='one'?get(s.sql,s.params):run(s.sql,s.params));}),
104
+ journalTransaction: ({teamId,guards=[],statements}) => tx(() => { for(const guard of guards){const row=get(guard.sql,guard.params);if(!row){const error=new Error(guard.message??'transaction predicate changed');error.code=guard.code??'conflict';error.status=guard.status??409;throw error;}}const team=get('SELECT journal_revision FROM teams WHERE id=?',[teamId]);if(!team)throw new Error('team not found');const revision=team.journal_revision+1;const results=statements.map(s=>{const params=(s.params??[]).map(value=>journalParam(value,revision));return s.kind==='query'?all(s.sql,params):s.kind==='one'?get(s.sql,params):run(s.sql,params);});return{revision,results};}),
105
+ ingestOtlpBatch: args => tx(() => ingestOtlpBatch({get,all,run},args)),
106
+ correlateSessions: args => tx(() => correlateSessions({get,all,run},args)),
107
+ listEffects: args => tx(() => listEffects({get,all,run},args)),
108
+ getEffect: args => tx(() => getEffect({get,all,run},args)),
109
+ claimEffect: args => tx(() => claimEffect({get,all,run},args)),
110
+ reportEffect: args => tx(() => reportEffect({get,all,run},args)),
111
+ authorizedSession: args => tx(() => authorizedSession(args).session),
112
+ readSessionSnapshot,
113
+ readArtifact,
114
+ };
115
+ }
116
+
117
+ /** The store's async surface. `call(op, args)` is the host's way to one handler. */
118
+ export class StoreClient {
119
+ query(sql,params=[]) { return this.call('query',{sql,params}); } one(sql,params=[]) { return this.call('one',{sql,params}); } exec(sql,params=[]) { return this.call('exec',{sql,params}); } transaction(statements) { return this.call('transaction',{statements}); }
120
+ snapshot(statements) { return this.call('snapshot',{statements}); }
121
+ guardedTransaction(guards,statements) { return this.call('guardedTransaction',{guards,statements}); }
122
+ journalTransaction(teamId,guards,statements) { return this.call('journalTransaction',{teamId,guards,statements}); }
123
+ ingestOtlpBatch(args) { return this.call('ingestOtlpBatch',args); }
124
+ correlateSessions(args) { return this.call('correlateSessions',args); }
125
+ listEffects(args) { return this.call('listEffects',args); }
126
+ getEffect(args) { return this.call('getEffect',args); }
127
+ claimEffect(args) { return this.call('claimEffect',args); }
128
+ reportEffect(args) { return this.call('reportEffect',args); }
129
+ authorizedSession(args) { return this.call('authorizedSession',args); }
130
+ readSessionSnapshot(args) { return this.call('readSessionSnapshot',args); }
131
+ readArtifact(args) { return this.call('readArtifact',args); }
132
+ }
133
+
134
+ /** A store whose handlers run in this thread (the Durable Object host). */
135
+ export class DirectStore extends StoreClient {
136
+ constructor(handlers) { super(); this.handlers = handlers; }
137
+ call(op, args = {}) { try { return Promise.resolve(this.handlers[op](args)); } catch (error) { return Promise.reject(error); } }
138
+ async close() {}
139
+ }
@@ -0,0 +1,23 @@
1
+ import { parentPort, workerData, isMainThread, Worker } from 'node:worker_threads';
2
+ import { DatabaseSync } from 'node:sqlite';
3
+ import { createStoreHandlers, migrate, StoreClient } from './store-core.mjs';
4
+
5
+ // The Node host's store: node:sqlite in a worker thread, so a long statement never
6
+ // blocks the HTTP loop. The operations themselves are store-core's.
7
+ if (!isMainThread) {
8
+ const db = new DatabaseSync(workerData.dbPath, { open: true, enableForeignKeyConstraints: true, allowExtension: false });
9
+ db.exec('PRAGMA foreign_keys=ON; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA busy_timeout=5000');
10
+ const all = (sql, p=[]) => db.prepare(sql).all(...p);
11
+ const get = (sql, p=[]) => db.prepare(sql).get(...p) ?? null;
12
+ const run = (sql, p=[]) => db.prepare(sql).run(...p);
13
+ const tx = fn => { db.exec('BEGIN IMMEDIATE'); try { const out=fn(); db.exec('COMMIT'); return out; } catch(e) { db.exec('ROLLBACK'); throw e; } };
14
+ migrate({ exec: (sql) => db.exec(sql), tx, version: () => db.prepare('PRAGMA user_version').get().user_version, setVersion: (n) => db.exec(`PRAGMA user_version=${n}`) });
15
+ const handlers = { ...createStoreHandlers({ all, get, run, tx }), close: () => { db.close(); return true; } };
16
+ parentPort.on('message', ({id,op,args}) => { try { parentPort.postMessage({id,result:handlers[op](args)}); } catch(error) { parentPort.postMessage({id,error:{message:error.message,code:error.code,status:error.status,stack:error.stack}}); } });
17
+ }
18
+
19
+ export class TeamsStore extends StoreClient {
20
+ constructor(dbPath) { super(); this.worker=new Worker(new URL(import.meta.url),{workerData:{dbPath}}); this.next=1; this.pending=new Map(); this.worker.on('message',m=>{const p=this.pending.get(m.id); if(!p)return; this.pending.delete(m.id); m.error?p.reject(Object.assign(new Error(m.error.message),m.error)):p.resolve(m.result);}); this.worker.on('error',e=>{for(const p of this.pending.values())p.reject(e);this.pending.clear();}); }
21
+ call(op,args={}) { return new Promise((resolve,reject)=>{const id=this.next++;this.pending.set(id,{resolve,reject});this.worker.postMessage({id,op,args});}); }
22
+ async close() { await this.call('close'); await this.worker.terminate(); }
23
+ }
@@ -0,0 +1,49 @@
1
+ // Serve a Fetch-API Request through the server's Node-style (req, res) handler.
2
+ // The handler reads req.method/url/headers/socket, iterates the body and waits
3
+ // for 'close'; it writes res.writeHead/write/end (server-sent events included),
4
+ // listens for 'drain'/'close' and reads writableLength. That is all modelled here.
5
+ import { EventEmitter } from 'node:events';
6
+ import { Readable } from 'node:stream';
7
+
8
+ export function serveFetch(handler, request, { remoteAddress = null } = {}) {
9
+ const url = new URL(request.url);
10
+ const headers = {};
11
+ for (const [key, value] of request.headers) headers[key.toLowerCase()] = value;
12
+ const body = request.body ? Readable.fromWeb(request.body) : Readable.from([]);
13
+ const req = Object.assign(body, { method: request.method, url: url.pathname + url.search, headers, socket: { remoteAddress } });
14
+ const encoder = new TextEncoder();
15
+ const { readable, writable } = new TransformStream();
16
+ const writer = writable.getWriter();
17
+ let settle;
18
+ const head = new Promise((resolve) => { settle = resolve; });
19
+ const res = new EventEmitter();
20
+ let status = 200, responseHeaders = {}, started = false, ended = false, pending = 0;
21
+ const start = () => { if (started) return; started = true; settle(new Response(readable, { status, headers: responseHeaders })); };
22
+ const closed = () => { if (!ended) { ended = true; res.emit('close'); req.emit('close'); } };
23
+ request.signal?.addEventListener('abort', () => { closed(); writer.abort().catch(() => {}); });
24
+ Object.assign(res, {
25
+ get writableLength() { return pending; },
26
+ get headersSent() { return started; },
27
+ writeHead(code, values = {}) { status = code; responseHeaders = { ...responseHeaders, ...values }; if (code === 204 || code === 304) { started = true; settle(new Response(null, { status, headers: responseHeaders })); } else start(); return res; },
28
+ setHeader(name, value) { responseHeaders[name] = value; },
29
+ write(chunk) {
30
+ start();
31
+ if (ended) return false;
32
+ const bytes = typeof chunk === 'string' ? encoder.encode(chunk) : chunk;
33
+ pending += bytes.byteLength;
34
+ writer.write(bytes).then(() => { pending -= bytes.byteLength; if (pending === 0) res.emit('drain'); }, () => closed());
35
+ return pending < 65536;
36
+ },
37
+ end(chunk) {
38
+ if (status === 204 || status === 304) { closed(); return res; }
39
+ if (chunk !== undefined && chunk !== null) res.write(chunk); else start();
40
+ writer.close().catch(() => {}).finally(closed);
41
+ return res;
42
+ },
43
+ });
44
+ Promise.resolve().then(() => handler(req, res)).catch((error) => {
45
+ if (!started) { status = 500; responseHeaders = { 'Content-Type': 'application/json' }; res.end(JSON.stringify({ version: 1, error: { code: 'internal', message: error.message } })); }
46
+ else writer.abort(error).catch(() => {});
47
+ });
48
+ return head;
49
+ }
@@ -0,0 +1,127 @@
1
+ // The Cloudflare host: a Worker that sends every request to one Durable Object,
2
+ // which runs the Teams server (core.mjs) on its own SQLite. The machine doors are
3
+ // WebSockets the object holds; `maintain` runs on the object's alarm.
4
+ //
5
+ // Configuration (wrangler.toml [vars] and secrets):
6
+ // PUBLIC_ORIGIN the https origin the Worker is served at
7
+ // INIT_TEAM the first team's name (used once, at bootstrap)
8
+ // VOLTER_ISSUER optional: an identity service members may sign in with (https://id.volter.ai)
9
+ // VOLTER_ORG_CLIENT_ID, VOLTER_ORG_CLIENT_SECRET, VOLTER_TEAM_ORGS
10
+ // optional: this server's product credential at VOLTER_ISSUER, and the teams whose
11
+ // organization there admits its members with no invitation (`team=slug,…`)
12
+ // DELEGATED_ISSUER optional: a workplace (RH2) whose delegated tokens open one pane or folder at /view
13
+ // SUPERCODE_TEAMS_BOOTSTRAP_TOKEN secret: the owner's first credential, minted with
14
+ // `supercode teams server token`
15
+ import { DurableObject } from 'cloudflare:workers';
16
+ import { createTeamsApi, bootstrapServer, maintain, normalizePublicOrigin } from '@volter/teams/server/core';
17
+ import { createStoreHandlers, migrate, DirectStore } from '../server/store-core.mjs';
18
+ import { createHttpHandler, authenticate } from '@volter/teams/server/http';
19
+ import { harnessRoutes } from '../server/harness-routes.mjs';
20
+ import { authorizeMachineUpgrade, acceptMachineSocket, LINK_PROTOCOL, CHANNEL_PROTOCOL } from '@volter/teams/server/machine-link';
21
+ import { setOtlpSchemaSources } from '../server/otlp/transport.mjs';
22
+ import { identityOrganizationsFromEnv } from '@volter/teams/server/identity-orgs';
23
+ import { setViewerSources } from '@volter/teams/server/viewer';
24
+ import { serveFetch } from './fetch-adapter.mjs';
25
+ import { webSocketDuplex } from './websocket-duplex.mjs';
26
+ // The hosted viewer's files, bundled as text (wrangler.toml rules) and served at /view.
27
+ import viewerPage from '../../volter-teams/viewer/index.html';
28
+ import viewerScript from '../../volter-teams/viewer/viewer.mjs';
29
+ import viewerChannel from '../../volter-teams/browser-channel.mjs';
30
+ import viewerRpc from '../../volter-teams/stream-rpc.mjs';
31
+ import proto0 from '../server/otlp/schema/google/protobuf/any.proto';
32
+ import proto1 from '../server/otlp/schema/google/rpc/status.proto';
33
+ import proto2 from '../server/otlp/schema/opentelemetry/proto/collector/logs/v1/logs_service.proto';
34
+ import proto3 from '../server/otlp/schema/opentelemetry/proto/collector/metrics/v1/metrics_service.proto';
35
+ import proto4 from '../server/otlp/schema/opentelemetry/proto/collector/trace/v1/trace_service.proto';
36
+ import proto5 from '../server/otlp/schema/opentelemetry/proto/common/v1/common.proto';
37
+ import proto6 from '../server/otlp/schema/opentelemetry/proto/logs/v1/logs.proto';
38
+ import proto7 from '../server/otlp/schema/opentelemetry/proto/metrics/v1/metrics.proto';
39
+ import proto8 from '../server/otlp/schema/opentelemetry/proto/resource/v1/resource.proto';
40
+ import proto9 from '../server/otlp/schema/opentelemetry/proto/trace/v1/trace.proto';
41
+
42
+ setViewerSources({ 'viewer/index.html': viewerPage, 'viewer/viewer.mjs': viewerScript, 'browser-channel.mjs': viewerChannel, 'stream-rpc.mjs': viewerRpc });
43
+
44
+ setOtlpSchemaSources({
45
+ 'google/protobuf/any.proto': proto0,
46
+ 'google/rpc/status.proto': proto1,
47
+ 'opentelemetry/proto/collector/logs/v1/logs_service.proto': proto2,
48
+ 'opentelemetry/proto/collector/metrics/v1/metrics_service.proto': proto3,
49
+ 'opentelemetry/proto/collector/trace/v1/trace_service.proto': proto4,
50
+ 'opentelemetry/proto/common/v1/common.proto': proto5,
51
+ 'opentelemetry/proto/logs/v1/logs.proto': proto6,
52
+ 'opentelemetry/proto/metrics/v1/metrics.proto': proto7,
53
+ 'opentelemetry/proto/resource/v1/resource.proto': proto8,
54
+ 'opentelemetry/proto/trace/v1/trace.proto': proto9
55
+ });
56
+
57
+ const MAINTAIN_MS = 60_000;
58
+ const BOOTSTRAP_TOKEN = /^sct_(cred_[0-9a-f]{32})\.[A-Za-z0-9_-]{16,}$/;
59
+
60
+ /** SQLite primitives over the object's storage, in store-core's shape. */
61
+ function durableSql(storage) {
62
+ const sql = storage.sql;
63
+ const param = (value) => (value instanceof Uint8Array ? value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength) : value);
64
+ const row = (value) => { for (const key of Object.keys(value)) if (value[key] instanceof ArrayBuffer) value[key] = Buffer.from(value[key]); return value; };
65
+ const all = (query, params = []) => sql.exec(query, ...params.map(param)).toArray().map(row);
66
+ const get = (query, params = []) => all(query, params)[0] ?? null;
67
+ const run = (query, params = []) => { sql.exec(query, ...params.map(param)); return { changes: sql.exec('SELECT changes() AS c').one().c }; };
68
+ const tx = (fn) => storage.transactionSync(fn);
69
+ return { all, get, run, tx, exec: (query) => sql.exec(query) };
70
+ }
71
+
72
+ export class TeamsServer extends DurableObject {
73
+ constructor(ctx, env) {
74
+ super(ctx, env);
75
+ this.ready = ctx.blockConcurrencyWhile(async () => {
76
+ const db = durableSql(ctx.storage);
77
+ migrate({ exec: db.exec, tx: db.tx, version: () => ctx.storage.kv.get('schema_version') ?? 0, setVersion: (n) => ctx.storage.kv.put('schema_version', n) });
78
+ this.api = createTeamsApi({ publicOrigin: normalizePublicOrigin(env.PUBLIC_ORIGIN), identityIssuer: env.VOLTER_ISSUER || null, identityOrganizations: identityOrganizationsFromEnv(env), delegatedIssuer: env.DELEGATED_ISSUER || null });
79
+ this.api.attach({ store: new DirectStore(createStoreHandlers(db)), harnessRoutes });
80
+ const existing = await this.api.store.one("SELECT value FROM server_meta WHERE key='server_id'");
81
+ if (existing) this.api.attach({ serverId: existing.value });
82
+ else {
83
+ const match = BOOTSTRAP_TOKEN.exec(env.SUPERCODE_TEAMS_BOOTSTRAP_TOKEN ?? '');
84
+ if (match) {
85
+ const ids = this.api.ids;
86
+ this.api.attach({ serverId: await bootstrapServer(this.api, { initTeam: env.INIT_TEAM, bootstrap: { server_id: ids.next('server'), team_id: ids.next('team'), principal_id: ids.next('user'), credential_id: match[1], token: env.SUPERCODE_TEAMS_BOOTSTRAP_TOKEN } }) });
87
+ }
88
+ }
89
+ this.handler = createHttpHandler(this.api);
90
+ if (!(await ctx.storage.getAlarm())) await ctx.storage.setAlarm(Date.now() + MAINTAIN_MS);
91
+ });
92
+ }
93
+
94
+ async fetch(request) {
95
+ await this.ready;
96
+ if (!this.api.serverId) return Response.json({ version: 1, error: { code: 'unavailable', message: 'set the SUPERCODE_TEAMS_BOOTSTRAP_TOKEN secret (supercode teams server token) before first use' } }, { status: 503 });
97
+ if (request.headers.get('upgrade')?.toLowerCase() === 'websocket') return this.upgrade(request);
98
+ return serveFetch(this.handler, request, { remoteAddress: request.headers.get('cf-connecting-ip') });
99
+ }
100
+
101
+ async upgrade(request) {
102
+ const headers = {};
103
+ for (const [key, value] of request.headers) headers[key.toLowerCase()] = value;
104
+ const url = new URL(request.url);
105
+ const decision = await authorizeMachineUpgrade(this.api, { authenticate, url: url.pathname + url.search, headers });
106
+ if (decision.status) return Response.json({ error: { code: decision.code, message: decision.message } }, { status: decision.status });
107
+ const offered = String(headers['sec-websocket-protocol'] ?? '').split(',').map((value) => value.trim());
108
+ const protocol = offered.find((value) => value === LINK_PROTOCOL || value === CHANNEL_PROTOCOL);
109
+ if (!protocol) return Response.json({ error: { code: 'invalid_request', message: 'offer the door protocol' } }, { status: 400 });
110
+ const [client, server] = Object.values(new WebSocketPair());
111
+ server.accept();
112
+ void acceptMachineSocket(this.api, decision, webSocketDuplex(server), (reason) => { try { server.close(1000, String(reason ?? 'closed').slice(0, 100)); } catch {} });
113
+ return new Response(null, { status: 101, webSocket: client, headers: { 'Sec-WebSocket-Protocol': protocol } });
114
+ }
115
+
116
+ async alarm() {
117
+ await this.ready;
118
+ if (this.api.serverId) await maintain(this.api).catch(() => {});
119
+ await this.ctx.storage.setAlarm(Date.now() + MAINTAIN_MS);
120
+ }
121
+ }
122
+
123
+ export default {
124
+ fetch(request, env) {
125
+ return env.TEAMS_SERVER.get(env.TEAMS_SERVER.idFromName('server')).fetch(request);
126
+ },
127
+ };
@@ -0,0 +1,29 @@
1
+ // A Workers WebSocket as a Node duplex byte stream, for the machine channel.
2
+ import { Duplex } from 'node:stream';
3
+
4
+ export function webSocketDuplex(socket) {
5
+ const duplex = new Duplex({
6
+ read() {},
7
+ write(chunk, _encoding, done) {
8
+ try { socket.send(chunk); done(); } catch (error) { done(error); }
9
+ },
10
+ final(done) { try { socket.close(1000, 'closed'); } catch {} done(); },
11
+ destroy(error, done) { try { socket.close(1000, 'closed'); } catch {} done(error); },
12
+ });
13
+ // A binary message arrives as an ArrayBuffer or, from some runtimes, a Blob (an async read):
14
+ // one chain keeps frames in the order they arrived either way.
15
+ let order = Promise.resolve();
16
+ socket.addEventListener('message', (event) => {
17
+ const data = event.data;
18
+ const bytes = typeof data === 'string' ? Buffer.from(data)
19
+ : data instanceof ArrayBuffer ? Buffer.from(data)
20
+ : ArrayBuffer.isView(data) ? Buffer.from(data.buffer, data.byteOffset, data.byteLength)
21
+ : data.arrayBuffer().then((buffer) => Buffer.from(buffer));
22
+ if (bytes instanceof Promise) bytes.catch(() => {});
23
+ order = order.then(async () => { if (!duplex.destroyed) duplex.push(await bytes); }).catch(() => duplex.destroy());
24
+ });
25
+ socket.addEventListener('close', () => { order = order.then(() => { duplex.push(null); duplex.destroy(); }); });
26
+ // A peer that vanished without a close frame arrives as an error event (workerd logs it too).
27
+ socket.addEventListener('error', () => duplex.destroy());
28
+ return duplex;
29
+ }
@@ -0,0 +1,38 @@
1
+ # A hosted Teams server on Cloudflare: one Worker, one Durable Object (SQLite).
2
+ # supercode teams server token | npx wrangler secret put SUPERCODE_TEAMS_BOOTSTRAP_TOKEN
3
+ # npx wrangler deploy --config sdk/teams/worker/wrangler.toml
4
+ # Set name and PUBLIC_ORIGIN for your deployment; the token printed above is the
5
+ # owner's first credential (`supercode teams login <origin> --token-stdin`).
6
+ name = "supercode-teams"
7
+ main = "index.mjs"
8
+ compatibility_date = "2026-09-01"
9
+ compatibility_flags = ["nodejs_compat"]
10
+
11
+ [vars]
12
+ PUBLIC_ORIGIN = "https://supercode-teams.example.workers.dev"
13
+ INIT_TEAM = "Team"
14
+ # Optional: members sign in with Volter (supercode teams login --volter); the
15
+ # issuer must have this origin registered as a resource.
16
+ # VOLTER_ISSUER = "https://id.volter.ai"
17
+ # Optional: a workplace (RH2) whose delegated tokens open one pane or folder in the
18
+ # hosted viewer (/view); its keys are at <issuer>/.well-known/jwks.json.
19
+ # DELEGATED_ISSUER = "https://pilot.runhuman.com"
20
+
21
+ [[durable_objects.bindings]]
22
+ name = "TEAMS_SERVER"
23
+ class_name = "TeamsServer"
24
+
25
+ [[migrations]]
26
+ tag = "v1"
27
+ new_sqlite_classes = ["TeamsServer"]
28
+
29
+ [[rules]]
30
+ type = "Text"
31
+ globs = ["**/*.proto"]
32
+ fallthrough = true
33
+
34
+ # The hosted viewer's files, served as text at /view (server/viewer.mjs).
35
+ [[rules]]
36
+ type = "Text"
37
+ globs = ["**/volter-teams/viewer/index.html", "**/volter-teams/viewer/viewer.mjs", "**/volter-teams/browser-channel.mjs", "**/volter-teams/stream-rpc.mjs"]
38
+ fallthrough = true
@@ -0,0 +1,75 @@
1
+ import type {
2
+ Access, Attachment, AttachmentRequest, Continuation, CreateSource, Descriptor, EnrollMachine,
3
+ Event, ExportRequest, FollowQuery, Group, ListQuery, Machine, Me, Membership, Operation, Page,
4
+ PrepareContinuation, ReadQuery, ReadResult, Result, SearchPage, SearchQuery, SessionGrant,
5
+ Root, Source, SyncRule, ServerPage, Team, AttachmentFrame, LiveGrant, LocalCompletion, NativeEffect, CreateInvitation, Invitation,
6
+ } from './contracts/index.js';
7
+
8
+ export interface RequestOptions { signal?: AbortSignal; idempotencyKey?: string; ifMatch?: number }
9
+ export interface StreamOptions extends RequestOptions { after?: string }
10
+ export type AttachmentAction = Exclude<NativeEffect, {kind:'attach'|'continue'}> extends infer E ? E extends {kind:infer K;payload:infer P} ? {kind:K} & Omit<P,'attachment_id'> : never : never;
11
+ export interface LiveGrantCollectionClient {
12
+ list(options?:RequestOptions):Promise<Page<LiveGrant>>;
13
+ get(id:string,options?:RequestOptions):Promise<Result<LiveGrant>>;
14
+ create(body:Omit<LiveGrant,'id'|'revision'>,options?:RequestOptions):Promise<Result<LiveGrant>>;
15
+ remove(id:string,options?:RequestOptions):Promise<null>;
16
+ }
17
+ export interface Capabilities { version:1; server_id:string; protocol:'teams.v1'; operations:string[]; auth_modes:string[] }
18
+ export interface CollectionClient<T,Create=unknown,Update=unknown> {
19
+ list(query?:Record<string,unknown>,options?:RequestOptions):Promise<Page<T>>;
20
+ get(id:string,options?:RequestOptions):Promise<Result<T>>;
21
+ create(body:Create,options?:RequestOptions):Promise<Result<T>>;
22
+ update(id:string,body:Update,options?:RequestOptions):Promise<Result<T>>;
23
+ remove(id:string,options?:RequestOptions):Promise<null>;
24
+ }
25
+ export interface ReadCollectionClient<T> {
26
+ list(query?:Record<string,unknown>,options?:RequestOptions):Promise<Page<T>>;
27
+ get(id:string,options?:RequestOptions):Promise<Result<T>>;
28
+ }
29
+ export interface MachineCollectionClient extends ReadCollectionClient<Machine> {
30
+ create(body:EnrollMachine,options?:RequestOptions):Promise<Result<{machine:Machine;source:Source;credential:import('./contracts/index.js').TokenCredential|null}>>;
31
+ remove(id:string,options?:RequestOptions):Promise<null>;
32
+ }
33
+ export declare class TeamsClient {
34
+ constructor(options:{server:string;credential:string;fetch?:typeof fetch});
35
+ me(options?:RequestOptions):Promise<Me>;
36
+ capabilities(options?:RequestOptions):Promise<Capabilities>;
37
+ teams(query?:{limit?:number;cursor?:string},options?:RequestOptions):Promise<ServerPage<Team>>;
38
+ createTeam(body:{name:string},options?:RequestOptions):Promise<Result<Team>>;
39
+ team(id:string):TeamClient;
40
+ }
41
+ export interface TeamClient {
42
+ sessions:{list(query?:ListQuery,options?:RequestOptions):Promise<Page<Descriptor>>;search(query:SearchQuery,options?:RequestOptions):Promise<SearchPage>;watch(query?:ListQuery,options?:StreamOptions):AsyncIterable<Event>;get(id:string):SessionClient};
43
+ members:{list(query?:Record<string,unknown>,options?:RequestOptions):Promise<Page<Membership>>;update(id:string,body:{role:Membership['role']},options?:RequestOptions):Promise<Result<Membership>>;remove(id:string,options?:RequestOptions):Promise<null>};
44
+ groups:{list(query?:Record<string,unknown>,options?:RequestOptions):Promise<Page<Group>>;create(body:{name:string},options?:RequestOptions):Promise<Result<Group>>;remove(id:string,options?:RequestOptions):Promise<null>;addMember(groupId:string,memberId:string,options?:RequestOptions):Promise<null>;removeMember(groupId:string,memberId:string,options?:RequestOptions):Promise<null>};
45
+ machines:MachineCollectionClient & {roots(id:string,query?:Record<string,unknown>,options?:RequestOptions):Promise<Page<Root>>;liveGrants(machineId:string):LiveGrantCollectionClient};
46
+ sources:{list(query?:Record<string,unknown>,options?:RequestOptions):Promise<Page<Source>>;get(id:string,options?:RequestOptions):Promise<Result<Source>>;create(body:CreateSource,options?:RequestOptions):Promise<Result<Source>>;update(id:string,body:Partial<Pick<Source,'state'|'custodian_id'|'default_visibility'>>,options?:RequestOptions):Promise<Result<Source>>;credential(id:string,body:{rotate:boolean},options?:RequestOptions):Promise<Result<import('./contracts/index.js').TokenCredential|null>>;configuration(id:string,format?:'otel-collector',options?:RequestOptions):Promise<Result<import('./contracts/index.js').SourceCollectorConfig>>};
47
+ syncRules:{list(query?:Record<string,unknown>,options?:RequestOptions):Promise<Page<SyncRule>>;get(id:string,options?:RequestOptions):Promise<Result<SyncRule>>;create(body:Omit<SyncRule,'id'|'state'|'revision'|'capture_after'>,options?:RequestOptions):Promise<Result<SyncRule>>;update(id:string,body:{state:'paused';backfill?:never}|{state:'active';backfill:SyncRule['backfill']},options?:RequestOptions):Promise<Result<SyncRule>>;remove(id:string,options?:RequestOptions):Promise<null>};
48
+ credentials:{create(body:{kind:'user'|'service';expires_in_ms:number},options?:RequestOptions):Promise<Result<import('./contracts/index.js').TokenCredential|null>>;remove(id:string,options?:RequestOptions):Promise<null>};
49
+ invitations:{create(body:CreateInvitation,options?:RequestOptions):Promise<Result<Invitation>>};
50
+ operations:{get(id:string,options?:RequestOptions):Promise<Result<Operation>>};
51
+ continuations:{
52
+ get(id:string,options?:RequestOptions):Promise<Result<Continuation>>;
53
+ authorizeDestination(id:string,body:{expires_at:number},options?:RequestOptions):Promise<Result<Continuation>>;
54
+ launch(id:string,options?:RequestOptions):Promise<Result<Operation>>;
55
+ completeLocal(id:string,body:LocalCompletion,options?:RequestOptions):Promise<Result<Operation>>;
56
+ };
57
+ attachments:{
58
+ actions(id:string,body:AttachmentAction,options?:RequestOptions):Promise<Result<Operation>>;
59
+ detach(id:string,options?:RequestOptions):Promise<null>;
60
+ /** Native frames verbatim. Terminal Teams policy events throw a typed error. No token replay/reconnect. */
61
+ stream(attachment:Attachment,options?:Pick<RequestOptions,'signal'>):AsyncIterable<AttachmentFrame>;
62
+ };
63
+ }
64
+ export interface SessionClient {
65
+ artifact(uploadId:string,options?:{signal?:AbortSignal;range?:string}):Promise<Response>;
66
+ descriptor(options?:RequestOptions):Promise<Result<Descriptor>>;
67
+ read(query?:ReadQuery,options?:RequestOptions):Promise<ReadResult>;
68
+ follow(query?:FollowQuery,options?:StreamOptions):AsyncIterable<Event>;
69
+ access(options?:RequestOptions):Promise<Result<Access>>;
70
+ grant(body:Omit<SessionGrant,'id'>,options?:RequestOptions):Promise<Result<Access>>;
71
+ revoke(id:string,options?:RequestOptions):Promise<Result<Access>>;
72
+ export(body:ExportRequest,options?:RequestOptions):Promise<Result<Operation>>;
73
+ prepare(body:PrepareContinuation,options?:RequestOptions):Promise<Result<Continuation>>;
74
+ attach(body:AttachmentRequest,options?:RequestOptions):Promise<Result<Attachment>>;
75
+ }