@volter/supercode-teams 0.2.155 → 0.2.157
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 +5 -1
- package/bin/workspace/args.mjs +2 -2
- package/bin/workspace.mjs +2 -2
- package/package.json +1 -1
- package/server/harness-routes.mjs +12 -9
- package/server/index.mjs +2 -2
- package/worker/index.mjs +1 -1
- package/worker/wrangler.toml +6 -2
package/README.md
CHANGED
|
@@ -47,7 +47,11 @@ a kind another product serves on the same machine. `title.set` needs `control` o
|
|
|
47
47
|
|
|
48
48
|
A workplace can open one pane or one folder for someone who is not a member of the team. A
|
|
49
49
|
server started with `--delegated-issuer ORIGIN` (the Worker's `DELEGATED_ISSUER`) trusts short-lived
|
|
50
|
-
tokens that issuer signs
|
|
50
|
+
tokens that issuer signs. `--delegated-issuers ORIGIN,ORIGIN` (the Worker's
|
|
51
|
+
`DELEGATED_ISSUERS`) adds an explicit list of trusted origins; the legacy singular setting
|
|
52
|
+
remains supported. Keys are cached separately per origin, and each token must match a
|
|
53
|
+
publication or session binding for that exact issuer. Staging never opens a production
|
|
54
|
+
binding or publication. Tokens use Ed25519 (keys at `ORIGIN/.well-known/jwks.json`, audience this server's
|
|
51
55
|
public origin), each naming a pane (`pane:<key>`) or a folder (`root:/path`) on one machine of one
|
|
52
56
|
team, and `view` or `control`. A token opens only a surface the machine's custodian **published** on
|
|
53
57
|
this server (the machine's publications, `/machines/{id}/publications`): the same team, machine and
|
package/bin/workspace/args.mjs
CHANGED
|
@@ -10,8 +10,8 @@ const VALUES = new Set([
|
|
|
10
10
|
'source', 'cursor', 'limit', 'tail', 'before', 'after', 'revision', 'role',
|
|
11
11
|
'out', 'ttl', 'type', 'name', 'visibility', 'backfill', 'format', 'to',
|
|
12
12
|
'grant', 'binding', 'caps', 'fidelity', 'cwd', 'root', 'workspace-from', 'since', 'until', 'session',
|
|
13
|
-
'on', 'timeout', 'plan', 'tmux-socket', 'volter-issuer', 'delegated-issuer', 'workplace', 'organization', 'key', 'room',
|
|
14
|
-
'conversation', 'rh2', 'scope',
|
|
13
|
+
'on', 'timeout', 'plan', 'tmux-socket', 'volter-issuer', 'delegated-issuer', 'delegated-issuers', 'workplace', 'organization', 'key', 'room',
|
|
14
|
+
'conversation', 'rh2', 'scope', 'agent',
|
|
15
15
|
]);
|
|
16
16
|
|
|
17
17
|
export function parseArgs(argv) {
|
package/bin/workspace.mjs
CHANGED
|
@@ -120,7 +120,7 @@ async function serverCommand({ flags, sub, rest, home, output }) {
|
|
|
120
120
|
// The Node host's store is node:sqlite (Node 22.5+); only this verb loads it, so every client
|
|
121
121
|
// verb runs on the Node 20 the package declares.
|
|
122
122
|
const { createTeamsServer } = await import('../server/index.mjs');
|
|
123
|
-
const server = createTeamsServer({ dataDir: flags.data_dir ?? resolve(home, 'teams', 'server'), listen: hostPort(flags.listen ?? '127.0.0.1:7331'), publicOrigin: flags.public_origin ?? null, identityIssuer: flags.volter_issuer ?? null, delegatedIssuer: flags.delegated_issuer ?? null, ...(flags.init_team ? { initTeam: flags.init_team } : {}) });
|
|
123
|
+
const server = createTeamsServer({ dataDir: flags.data_dir ?? resolve(home, 'teams', 'server'), listen: hostPort(flags.listen ?? '127.0.0.1:7331'), publicOrigin: flags.public_origin ?? null, identityIssuer: flags.volter_issuer ?? null, delegatedIssuer: flags.delegated_issuer ?? null, delegatedIssuers: flags.delegated_issuers?.split(',').map(origin => origin.trim()) ?? [], ...(flags.init_team ? { initTeam: flags.init_team } : {}) });
|
|
124
124
|
output(await server.start());
|
|
125
125
|
await new Promise(resolveStop => { const stop = async () => { await server.close(); resolveStop(); }; process.once('SIGINT', stop); process.once('SIGTERM', stop); });
|
|
126
126
|
}
|
|
@@ -999,7 +999,7 @@ function printHelp() {
|
|
|
999
999
|
auth login|status|switch|logout (one Volter sign-in; "supercode login" is "auth login")
|
|
1000
1000
|
login [ORIGIN] [--team TEAM] [--as CONTEXT] [--token-stdin|--invite-stdin] (Volter sign-in in the browser by default)
|
|
1001
1001
|
identity link
|
|
1002
|
-
server start [--volter-issuer ORIGIN] [--delegated-issuer ORIGIN]
|
|
1002
|
+
server start [--volter-issuer ORIGIN] [--delegated-issuer ORIGIN] [--delegated-issuers ORIGIN,ORIGIN]
|
|
1003
1003
|
publish pane|root TARGET --room ROOM --workplace ORIGIN --organization ORG [--scope view|control] [--name] [--key] [--machine] [--token-stdin] [--record-only]
|
|
1004
1004
|
unpublish SURFACE --room ROOM --workplace ORIGIN --organization ORG [--machine] [--token-stdin] [--record-only|--workplace-only]
|
|
1005
1005
|
connect|disconnect|status
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.157",
|
|
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",
|
|
@@ -359,14 +359,17 @@ async function captureCommit(api,req,res,auth,sourceId,captureId,requestId){cons
|
|
|
359
359
|
carriedRevision = session.revision;
|
|
360
360
|
const hash = createHash('sha256').update('['), sent = new Set(messages.map(m => m.native_key));
|
|
361
361
|
while (carriedCount < count) {
|
|
362
|
-
const
|
|
363
|
-
WHERE m.team_id=? AND m.session_id=? AND ${MESSAGE_AT()}
|
|
364
|
-
[auth.team_id, origin.session_id,
|
|
365
|
-
if (!
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
362
|
+
const page = await api.store.query(`SELECT ordinal,source_id,native_key,recorded_at,value_json FROM messages m
|
|
363
|
+
WHERE m.team_id=? AND m.session_id=? AND m.ordinal>=? AND m.ordinal<? AND ${MESSAGE_AT()} ORDER BY m.ordinal LIMIT 16`,
|
|
364
|
+
[auth.team_id, origin.session_id, carriedCount, count, carriedRevision, carriedRevision]);
|
|
365
|
+
if (!page.length) fail(409, 'carry_mismatch', 'the carried messages are not what this session holds; send the capture whole');
|
|
366
|
+
for (const row of page) {
|
|
367
|
+
if (row.ordinal !== carriedCount || row.source_id !== sourceId) fail(409, 'carry_mismatch', 'the carried messages are not what this session holds; send the capture whole');
|
|
368
|
+
if (sent.has(row.native_key)) fail(409, 'capture_conflict', 'duplicate native message key');
|
|
369
|
+
if (carriedCount) hash.update(',');
|
|
370
|
+
hash.update(canonicalJson({native_key:row.native_key, recorded_at:row.recorded_at ?? null, value:JSON.parse(row.value_json)}));
|
|
371
|
+
carriedCount++;
|
|
372
|
+
}
|
|
370
373
|
}
|
|
371
374
|
if (hash.update(']').digest('hex') !== digest) fail(409, 'carry_mismatch', 'the carried messages are not what this session holds; send the capture whole');
|
|
372
375
|
}
|
|
@@ -403,7 +406,7 @@ async function viewBindingRoute(api,req,res,auth,ref,requestId){
|
|
|
403
406
|
if(session.custodian_id!==auth.principal_id)fail(403,'forbidden',"only the session's owner binds it to a view");
|
|
404
407
|
const body=await parseJson(req);exact(body,['issuer','room','conversation']);
|
|
405
408
|
if(typeof body.room!=='string'||!body.room||typeof body.conversation!=='string'||!body.conversation)fail(400,'invalid_request','room and conversation are required');
|
|
406
|
-
if(!api.
|
|
409
|
+
if(!api.delegatedIssuers.includes(body.issuer))fail(400,'invalid_request',`this server accepts views signed by ${api.delegatedIssuers.join(', ')||'no workplace'}`);
|
|
407
410
|
const key=[auth.team_id,session.id,body.issuer,body.room,body.conversation];
|
|
408
411
|
await api.store.transaction(req.method==='POST'
|
|
409
412
|
?[{sql:'INSERT OR IGNORE INTO session_view_bindings(team_id,session_id,issuer,room,conversation,bound_by,created_at) VALUES (?,?,?,?,?,?,?)',params:[...key,auth.principal_id,api.clock.nowMs()]}]
|
package/server/index.mjs
CHANGED
|
@@ -16,12 +16,12 @@ import { createTeamsApi, bootstrapServer, maintain, normalizePublicOrigin, produ
|
|
|
16
16
|
|
|
17
17
|
function atomicJson(path,value){const tmp=`${path}.${process.pid}.${randomBytes(12).toString('hex')}.tmp`;let fd;try{fd=openSync(tmp,'wx',0o600);writeFileSync(fd,JSON.stringify(value,null,2)+'\n');closeSync(fd);fd=undefined;renameSync(tmp,path);chmodSync(path,0o600);}catch(error){if(fd!==undefined)closeSync(fd);try{unlinkSync(tmp);}catch{}throw error;}}
|
|
18
18
|
|
|
19
|
-
export function createTeamsServer({dataDir,listen={host:'127.0.0.1',port:7331},publicOrigin=null,identityIssuer=null,delegatedIssuer=null,clock=productionClock,ids=productionIds,initTeam,uploadLimitBytes=256*1024*1024}={}) {
|
|
19
|
+
export function createTeamsServer({dataDir,listen={host:'127.0.0.1',port:7331},publicOrigin=null,identityIssuer=null,delegatedIssuer=null,delegatedIssuers=[],clock=productionClock,ids=productionIds,initTeam,uploadLimitBytes=256*1024*1024}={}) {
|
|
20
20
|
if(!dataDir) throw new TypeError('dataDir is required');
|
|
21
21
|
const loopback=['127.0.0.1','::1','localhost'].includes(listen.host??'127.0.0.1');
|
|
22
22
|
publicOrigin=normalizePublicOrigin(publicOrigin);
|
|
23
23
|
if(!loopback&&!publicOrigin?.startsWith('https:'))throw new TypeError('non-loopback Teams servers require an HTTPS publicOrigin');
|
|
24
|
-
const api=createTeamsApi({clock,ids,publicOrigin,identityIssuer,identityOrganizations:identityIssuer?identityOrganizationsFromEnv(process.env):null,delegatedIssuer,uploadLimitBytes});
|
|
24
|
+
const api=createTeamsApi({clock,ids,publicOrigin,identityIssuer,identityOrganizations:identityIssuer?identityOrganizationsFromEnv(process.env):null,delegatedIssuer,delegatedIssuers,uploadLimitBytes});
|
|
25
25
|
// The harness's own routes on this server (harness-routes.mjs); the handler owns the neutral ones.
|
|
26
26
|
api.attach({harnessRoutes});
|
|
27
27
|
const events=api.events;
|
package/worker/index.mjs
CHANGED
|
@@ -81,7 +81,7 @@ export class TeamsServer extends DurableObject {
|
|
|
81
81
|
console.log(`storage: database_bytes=${ctx.storage.sql.databaseSize}`);
|
|
82
82
|
const db = durableSql(ctx.storage);
|
|
83
83
|
migrate({ exec: db.exec, tx: db.tx, version: () => ctx.storage.kv.get('schema_version') ?? 0, setVersion: (n) => ctx.storage.kv.put('schema_version', n) });
|
|
84
|
-
this.api = createTeamsApi({ publicOrigin: normalizePublicOrigin(env.PUBLIC_ORIGIN), identityIssuer: env.VOLTER_ISSUER || null, identityOrganizations: identityOrganizationsFromEnv(env), delegatedIssuer: env.DELEGATED_ISSUER || null });
|
|
84
|
+
this.api = createTeamsApi({ publicOrigin: normalizePublicOrigin(env.PUBLIC_ORIGIN), identityIssuer: env.VOLTER_ISSUER || null, identityOrganizations: identityOrganizationsFromEnv(env), delegatedIssuer: env.DELEGATED_ISSUER || null, delegatedIssuers: env.DELEGATED_ISSUERS ? env.DELEGATED_ISSUERS.split(',').map(origin => origin.trim()) : [] });
|
|
85
85
|
this.api.storageUsage = () => {
|
|
86
86
|
const sql = ctx.storage.sql;
|
|
87
87
|
let freeBytes = null;
|
package/worker/wrangler.toml
CHANGED
|
@@ -4,20 +4,24 @@
|
|
|
4
4
|
# --var PUBLIC_ORIGIN:https://<worker>.<account>.workers.dev --var INIT_TEAM:<first team>
|
|
5
5
|
# The token printed above is the owner's first credential (`supercode teams login <origin> --token-stdin`).
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
7
|
+
# Origin and identity variables are retained with keep_vars. The explicit delegated
|
|
8
|
+
# allowlist below is versioned and installed by deploy-teams CI.
|
|
9
9
|
# PUBLIC_ORIGIN the https origin the Worker is served at (required)
|
|
10
10
|
# INIT_TEAM the first team's name, used once at bootstrap
|
|
11
11
|
# VOLTER_ISSUER optional: members sign in with Volter (supercode teams login --volter); the issuer
|
|
12
12
|
# must have this origin registered as a resource
|
|
13
13
|
# DELEGATED_ISSUER optional: a workplace (RH2) whose delegated tokens open published panes and folders and
|
|
14
14
|
# bound sessions; its keys are at <issuer>/.well-known/jwks.json
|
|
15
|
+
# DELEGATED_ISSUERS comma-separated exact origins, additive with legacy DELEGATED_ISSUER
|
|
15
16
|
name = "supercode-teams"
|
|
16
17
|
main = "index.mjs"
|
|
17
18
|
compatibility_date = "2026-09-01"
|
|
18
19
|
compatibility_flags = ["nodejs_compat"]
|
|
19
20
|
keep_vars = true
|
|
20
21
|
|
|
22
|
+
[vars]
|
|
23
|
+
DELEGATED_ISSUERS = "https://pilot.runhuman.com,https://rh2-staging.voltertest.xyz"
|
|
24
|
+
|
|
21
25
|
# Workers Logs: the Worker's log lines (viewer-error, session-view refused, maintain) kept and searchable after the fact.
|
|
22
26
|
[observability]
|
|
23
27
|
enabled = true
|