@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,171 @@
1
+ const trimOrigin=value=>{const u=new URL(value),loopback=u.hostname==='localhost'||u.hostname==='::1'||u.hostname==='[::1]'||/^127(?:\.\d{1,3}){3}$/.test(u.hostname);if(!['http:','https:'].includes(u.protocol)||u.username||u.password||u.search||u.hash||(u.pathname!==''&&u.pathname!=='/')||(u.protocol==='http:'&&!loopback))throw new TypeError('server must be an HTTPS origin (HTTP is loopback-only)');return u.origin;};
2
+ const randomKey=()=>globalThis.crypto.randomUUID().replaceAll('-','')+globalThis.crypto.randomUUID().replaceAll('-','');
3
+ export class TeamsClient {
4
+ constructor({server,credential,fetch:fetchImpl=(...args)=>globalThis.fetch(...args)}){this.server=trimOrigin(server);this.credential=credential;this.fetch=fetchImpl;if(typeof fetchImpl!=='function')throw new TypeError('fetch is required');}
5
+ async request(method,path,{body,signal,idempotencyKey,ifMatch}={}) {
6
+ const key=idempotencyKey??(method==='GET'?null:randomKey());let delay=250,last;
7
+ for(let attempt=0;attempt<5;attempt++) { try {
8
+ const response=await this.fetch(this.server+path,{method,signal,redirect:'manual',headers:{Accept:'application/json',Authorization:`Bearer ${this.credential}`,...(body!==undefined?{'Content-Type':'application/json'}:{}),...(key?{'Idempotency-Key':key}:{}),...(ifMatch!==undefined?{'If-Match':String(ifMatch)}:{})},body:body===undefined?undefined:JSON.stringify(body)});
9
+ if(response.type==='opaqueredirect'||(response.status>=300&&response.status<400))throw Object.assign(new Error('cross-origin redirects are refused'),{code:'invalid_request',status:400,retryable:false});
10
+ const value=response.status===204?null:await response.json();if(!response.ok)throw Object.assign(new Error(value?.error?.message??`HTTP ${response.status}`),value?.error,{status:response.status});return value;
11
+ } catch(error) { last=error;if(method!=='GET'||signal?.aborted||!(!error.status||error.retryable)||attempt===4)throw error;await retryWait(delay,signal);delay=Math.min(delay*2,5000); } }
12
+ throw last;
13
+ }
14
+ me(options){return this.request('GET','/api/v1/me',options);} capabilities(options){return this.request('GET','/api/v1/capabilities',options);} team(id){return new TeamClient(this,id);}
15
+ teams(queryValue={},options){return this.request('GET','/api/v1/teams'+query(queryValue),options);}
16
+ createTeam(body,options){return this.request('POST','/api/v1/teams',{...options,body});}
17
+ }
18
+ const segment = value => {
19
+ if (typeof value !== 'string' || !value || value.length > 256) throw new TypeError('a nonempty resource ID is required');
20
+ return encodeURIComponent(value);
21
+ };
22
+ class TeamClient {
23
+ constructor(root, id) {
24
+ this.root = root; this.id = id;
25
+ const base = `/api/v1/teams/${segment(id)}`;
26
+ const get = (path, options) => root.request('GET', base + path, options);
27
+ const post = (path, body, options) => root.request('POST', base + path, { ...options, body });
28
+ const patch = (path, body, options) => root.request('PATCH', base + path, { ...options, body });
29
+ const remove = (path, options) => root.request('DELETE', base + path, options);
30
+ const resource = (path, id) => `${path}/${segment(id)}`;
31
+ this.sessions = {
32
+ list: (q = {}, o) => get('/sessions' + query(q), o),
33
+ search: (q, o) => get('/sessions/search' + query(q), o),
34
+ watch: (q = {}, o = {}) => events(root, base + '/sessions/events' + query(q), o),
35
+ get: id => new SessionClient(root, base, id),
36
+ };
37
+ this.members = {
38
+ list: (q = {}, o) => get('/members' + query(q), o),
39
+ update: (id, body, o) => patch(resource('/members', id), body, o),
40
+ remove: (id, o) => remove(resource('/members', id), o),
41
+ };
42
+ this.groups = {
43
+ list: (q = {}, o) => get('/groups' + query(q), o),
44
+ create: (body, o) => post('/groups', body, o),
45
+ remove: (id, o) => remove(resource('/groups', id), o),
46
+ addMember: (id, member, o) => root.request('PUT', base + resource('/groups', id) + resource('/members', member), o),
47
+ removeMember: (id, member, o) => remove(resource('/groups', id) + resource('/members', member), o),
48
+ };
49
+ this.machines = {
50
+ list: (q = {}, o) => get('/machines' + query(q), o),
51
+ get: (id, o) => get(resource('/machines', id), o),
52
+ create: (body, o) => post('/machines', body, o),
53
+ roots: (id, q = {}, o) => get(resource('/machines', id) + '/roots' + query(q), o),
54
+ remove: (id, o) => remove(resource('/machines', id), o),
55
+ liveGrants: machineId => {
56
+ const path = resource('/machines', machineId) + '/grants';
57
+ return {
58
+ list: (o) => get(path, o), get: (id, o) => get(resource(path, id), o),
59
+ create: (body, o) => post(path, body, o), remove: (id, o) => remove(resource(path, id), o),
60
+ };
61
+ },
62
+ };
63
+ this.sources = {
64
+ list: (q = {}, o) => get('/sources' + query(q), o),
65
+ get: (id, o) => get(resource('/sources', id), o),
66
+ create: (body, o) => post('/sources', body, o),
67
+ update: (id, body, o) => patch(resource('/sources', id), body, o),
68
+ credential: (id, body, o) => post(resource('/sources', id) + '/credentials', body, o),
69
+ configuration: (id, format = 'otel-collector', o) => get(resource('/sources', id) + '/config' + query({ format }), o),
70
+ };
71
+ this.syncRules = {
72
+ list: (q = {}, o) => get('/sync-rules' + query(q), o), get: (id, o) => get(resource('/sync-rules', id), o),
73
+ create: (body, o) => post('/sync-rules', body, o), update: (id, body, o) => patch(resource('/sync-rules', id), body, o),
74
+ remove: (id, o) => remove(resource('/sync-rules', id), o),
75
+ };
76
+ this.credentials = {
77
+ create: (body, o) => post('/credentials', body, o), remove: (id, o) => remove(resource('/credentials', id), o),
78
+ };
79
+ this.invitations = { create: (body, o) => post('/invitations', body, o) };
80
+ this.operations = { get: (id, o) => get(resource('/operations', id), o) };
81
+ this.continuations = {
82
+ get: (id, o) => get(resource('/continuations', id), o),
83
+ authorizeDestination: (id, body, o) => post(resource('/continuations', id) + '/authorize-destination', body, o),
84
+ launch: (id, o) => post(resource('/continuations', id) + '/launch', undefined, o),
85
+ completeLocal: (id, body, o) => post(resource('/continuations', id) + '/complete-local', body, o),
86
+ };
87
+ this.attachments = {
88
+ actions: (id, body, o) => post(resource('/attachments', id) + '/actions', body, o),
89
+ detach: (id, o) => remove(resource('/attachments', id), o),
90
+ stream: (attachment, o = {}) => attachmentEvents(root, base, attachment, o),
91
+ };
92
+ }
93
+ }
94
+ class SessionClient {
95
+ constructor(root, base, id) { this.root = root; this.path = `${base}/sessions/${segment(id)}`; }
96
+ descriptor = o => this.root.request('GET', this.path, o);
97
+ read = (q = {}, o) => this.root.request('GET', this.path + '/messages' + query(q), o);
98
+ follow = (q = {}, o = {}) => events(this.root, this.path + '/events' + query(q), o);
99
+ access = o => this.root.request('GET', this.path + '/access', o);
100
+ grant = (body, o) => this.root.request('POST', this.path + '/access', { ...o, body });
101
+ revoke = (id, o) => this.root.request('DELETE', this.path + '/access/' + segment(id), o);
102
+ export = (body, o) => this.root.request('POST', this.path + '/exports', { ...o, body });
103
+ prepare = (body, o) => this.root.request('POST', this.path + '/continuations', { ...o, body });
104
+ attach = (body, o) => this.root.request('POST', this.path + '/attachments', { ...o, body });
105
+ artifact = async (id, { signal, range } = {}) => {
106
+ if (range !== undefined && !/^bytes=(?:[0-9]+-[0-9]*|-[0-9]+)$/.test(range)) throw new TypeError('range must be one byte range');
107
+ const response = await this.root.fetch(this.root.server + this.path + '/artifacts/' + segment(id), {
108
+ signal, redirect: 'manual', headers: { Authorization: `Bearer ${this.root.credential}`, ...(range ? { Range: range } : {}) },
109
+ });
110
+ rejectRedirect(response);
111
+ if (!response.ok) throw await sseError(response);
112
+ return response;
113
+ };
114
+ }
115
+ function rejectRedirect(response) {
116
+ if (response.type === 'opaqueredirect' || response.status >= 300 && response.status < 400) throw Object.assign(new Error('cross-origin redirects are refused'), { code: 'invalid_request', status: 400, retryable: false });
117
+ }
118
+ async function* attachmentEvents(root, base, attachment, { signal } = {}) {
119
+ const expected = root.server + base + '/attachments/' + segment(attachment.id) + '/stream';
120
+ if (attachment.stream_format !== 'observed_runtime_event' || attachment.stream_url !== expected || typeof attachment.credential !== 'string' || !attachment.credential) throw new TypeError('attachment stream target or credential is invalid');
121
+ let response;
122
+ try {
123
+ response = await root.fetch(expected, { signal, redirect: 'manual', headers: { Accept: 'text/event-stream', Authorization: `Bearer ${attachment.credential}` } });
124
+ rejectRedirect(response);
125
+ if (!response.ok) throw await sseError(response);
126
+ let buffer = '';
127
+ for await (const chunk of response.body.pipeThrough(new TextDecoderStream())) {
128
+ buffer += chunk;
129
+ let match;
130
+ while ((match = /\r?\n\r?\n/.exec(buffer))) {
131
+ const block = buffer.slice(0, match.index); buffer = buffer.slice(match.index + match[0].length);
132
+ if (new TextEncoder().encode(block).length > 1024 * 1024 + 4096) throw Object.assign(new Error('attachment frame exceeds 1 MiB'), { code: 'resync_required', retryable: false });
133
+ const lines = block.split(/\r?\n/), kind = lines.find(line => line.startsWith('event:'))?.slice(6).trim();
134
+ const data = lines.filter(line => line.startsWith('data:')).map(line => line.slice(5).replace(/^ /, '')).join('\n');
135
+ if (!data) continue;
136
+ if (kind === 'access_revoked' || kind === 'resync_required') throw Object.assign(new Error(`attachment stream ${kind}`), { code: kind, retryable: false });
137
+ if (kind !== 'runtime') throw Object.assign(new Error('unexpected attachment stream event'), { code: 'invalid_request', retryable: false });
138
+ const frame = JSON.parse(data);
139
+ yield frame;
140
+ if (frame.type === 'closed' || frame.type === 'error' && frame.terminal) return;
141
+ }
142
+ if (new TextEncoder().encode(buffer).length > 1024 * 1024 + 4096) throw Object.assign(new Error('attachment frame exceeds 1 MiB'), { code: 'resync_required', retryable: false });
143
+ }
144
+ if (!signal?.aborted) throw Object.assign(new Error('attachment stream ended; create a new attachment to reconnect'), { code: 'resync_required', retryable: false });
145
+ } catch (error) { if (!signal?.aborted) throw error; }
146
+ }
147
+ const query=q=>{const p=new URLSearchParams();for(const [k,v]of Object.entries(q))if(v!==undefined)p.set(k,String(v));return p.size?'?'+p:'';};
148
+ const retryWait=(delay,signal)=>new Promise((resolve,reject)=>{const done=()=>{signal?.removeEventListener('abort',abort);resolve();},timer=setTimeout(done,delay),abort=()=>{clearTimeout(timer);signal?.removeEventListener('abort',abort);reject(signal.reason);};signal?.addEventListener('abort',abort,{once:true});});
149
+ async function sseError(response){let value=null;try{value=await response.json();}catch{}return Object.assign(new Error(value?.error?.message??`SSE HTTP ${response.status}`),value?.error,{status:response.status,retryable:value?.error?.retryable??(response.status===429||response.status>=500)});}
150
+ async function* events(root,path,{signal,after}={}){
151
+ let cursor=after??null,lastYielded=null,lastError=null;
152
+ for(let attempt=0;attempt<5&&!signal?.aborted;attempt++){
153
+ const suffix=cursor?path+(path.includes('?')?'&':'?')+'after='+encodeURIComponent(cursor):path;
154
+ try{
155
+ const response=await root.fetch(root.server+suffix,{signal,redirect:'manual',headers:{Accept:'text/event-stream',Authorization:`Bearer ${root.credential}`,...(cursor?{'Last-Event-ID':cursor}:{})}});
156
+ if(response.type==='opaqueredirect'||response.status>=300&&response.status<400)throw Object.assign(new Error('cross-origin redirects are refused'),{code:'invalid_request',status:400,retryable:false});
157
+ if(!response.ok)throw await sseError(response);
158
+ let buffer='';
159
+ for await(const chunk of response.body.pipeThrough(new TextDecoderStream())){
160
+ buffer+=chunk;let at;
161
+ while((at=buffer.indexOf('\n\n'))>=0){
162
+ const block=buffer.slice(0,at);buffer=buffer.slice(at+2);const id=block.split('\n').find(line=>line.startsWith('id: '))?.slice(4),data=block.split('\n').filter(line=>line.startsWith('data: ')).map(line=>line.slice(6)).join('\n');if(!data)continue;
163
+ const event=JSON.parse(data),terminal=event?.terminal||event?.type==='access_revoked'||event?.type==='resync_required',nextCursor=id??cursor;cursor=nextCursor;if(!terminal&&nextCursor&&nextCursor===lastYielded)continue;if(nextCursor)lastYielded=nextCursor;yield event;if(terminal)return;
164
+ }
165
+ }
166
+ lastError=Object.assign(new Error('SSE stream ended before a terminal event'),{retryable:true});
167
+ }catch(error){if(signal?.aborted)return;if(error.retryable===false||error.status&&error.status!==429&&error.status<500)throw error;lastError=error;}
168
+ if(attempt<4)await retryWait(Math.min(250*2**attempt,5000),signal).catch(()=>{});
169
+ }
170
+ if(!signal?.aborted&&lastError)throw lastError;
171
+ }
@@ -0,0 +1,38 @@
1
+ // A project moving from one machine to another (docs/plans/remote-execution.md, decision 4),
2
+ // shared by `teams workspace move` and `sessions continue --workspace-from`. Both machines must
3
+ // run no exec jobs; the destination publishes only when every byte arrived (execution.mjs).
4
+ import { createHash } from 'node:crypto';
5
+ import { openMachine } from './machine-door.mjs';
6
+ import { nodeRpc } from './client.mjs';
7
+
8
+ /** `from`/`to` are machine names, or null for this machine's own daemon. */
9
+ export async function moveWorkspace({ from, to, path, toPath = path, context = null, home }) {
10
+ const [source$, sink$] = await Promise.all([from, to].map((machine) => openMachine({ machine, context, home })));
11
+ const fail = (message) => Object.assign(new Error(message), { code: 'conflict', exitCode: 1 });
12
+ try {
13
+ for (const [name, channel] of [[from ?? 'this machine', source$], [to ?? 'this machine', sink$]]) {
14
+ const { jobs } = await (await nodeRpc(channel)).call('jobs.list', {});
15
+ if (jobs.some((job) => job.kind === 'exec')) throw fail(`${name} runs exec jobs; a workspace moves between quiet machines`);
16
+ }
17
+ // The destination opens first; the source sends nothing until {start: true}, written once
18
+ // its listeners are attached.
19
+ const sink = await sink$.open('workspace', { op: 'import', path: toPath });
20
+ const answered = new Promise((resolveSink) => sink.on('close', resolveSink));
21
+ const hash = createHash('sha256');
22
+ let bytes = 0;
23
+ const source = await source$.open('workspace', { op: 'export', path });
24
+ source.on('data', (chunk) => { const data = Buffer.from(chunk, 'base64'); hash.update(data); bytes += data.length; if (!sink.closed) sink.write(chunk); });
25
+ const exported = new Promise((resolveEnd) => source.on('close', resolveEnd));
26
+ source.write(JSON.stringify({ start: true }));
27
+ const reason = await Promise.race([exported, answered.then((why) => `destination closed: ${why}`)]);
28
+ const sha256 = hash.digest('hex');
29
+ if (reason !== 'end') { sink.close('abort'); source.close('abort'); throw fail(`workspace not moved: ${reason}`); }
30
+ sink.write(JSON.stringify({ commit: sha256 }));
31
+ const outcome = await answered;
32
+ if (!String(outcome).startsWith('published')) throw fail(`workspace not moved: ${outcome}`);
33
+ return { from: from ?? 'local', to: to ?? 'local', path: toPath, bytes, sha256 };
34
+ } finally {
35
+ await new Promise((r) => setTimeout(r, 200));
36
+ source$.close(); sink$.close();
37
+ }
38
+ }