@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,726 @@
1
+ #!/usr/bin/env node
2
+ import { hostname } from 'node:os';
3
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
4
+ import { basename, join, resolve } from 'node:path';
5
+ import { execFileSync, spawn } from 'node:child_process';
6
+ import { createHash } from 'node:crypto';
7
+ import { SupercodeHarnessClient } from '@volter/supercode-harness-sdk';
8
+ import { WorkspaceContexts, fromContext, loginContext } from '../context.mjs';
9
+ import { productionIds } from '@volter/teams/server/core';
10
+ import { makeToken } from '@volter/teams/server/auth';
11
+ import { moveWorkspace } from '../workspace-move.mjs';
12
+ import { TeamsConnector } from '../connector/index.mjs';
13
+ import { CONTINUATION_FIDELITY, CONTINUATION_HARNESSES } from '../connector/native-continuation.mjs';
14
+ import { TeamsMachine } from '../machine.mjs';
15
+ import { teamsHome, paths } from '@volter/teams/home';
16
+ import { loadOrCreateKeyPair, publicKeyId } from '@volter/teams/identity';
17
+ import { duration, parseArgs, positiveInteger, requireArgs, usage } from './workspace/args.mjs';
18
+ import { reserveArtifactDirectory, reserveProtected, writeProtected } from './workspace/files.mjs';
19
+ import { acquireLifecycle, activeSocket, requestStop } from './workspace/lifecycle.mjs';
20
+ import { volterDeviceSignIn } from './workspace/volter-device.mjs';
21
+ import { loginSummary, signInToTeams } from './workspace/volter-login.mjs';
22
+
23
+ const ERROR_EXITS = { invalid_request: 2, ambiguous_reference: 2, authentication_required: 3, not_found: 4, forbidden: 4, unsupported: 5, machine_offline: 5, controller_busy: 6, conflict: 6, resync_required: 6, revision_mismatch: 6, idempotency_conflict: 6, batch_gap: 6, capture_conflict: 6, source_paused: 6 };
24
+
25
+ export async function workspaceMain(argv = process.argv.slice(2), env = process.env, dependencies = {}) {
26
+ const { flags, positionals } = parseArgs(argv);
27
+ const teamsPrefix = positionals[0] === 'teams';
28
+ if (teamsPrefix) positionals.shift();
29
+ const [verb, sub, ...rest] = positionals;
30
+ if (flags.help || verb === undefined) return printHelp();
31
+ const home = env.SUPERCODE_HOME ?? resolve(teamsHome(), '..');
32
+ const contexts = new WorkspaceContexts(home);
33
+ const outputStream = dependencies.stdout ?? process.stdout;
34
+ const output = value => outputStream.write(flags.json ? `${JSON.stringify(value)}\n` : humanOutput(value));
35
+ if (verb === 'context') return contextCommand({ contexts, flags, sub, rest, output });
36
+ if (verb === 'server') return serverCommand({ flags, sub, rest, home, output });
37
+ if (verb === 'login') return loginCommand({ flags, origin: sub, rest, home, output });
38
+ const explicitContext = flags.context ?? env.SUPERCODE_CONTEXT;
39
+ let selected = explicitContext ?? contexts.read().current;
40
+ const urlReference = verb === 'sessions' ? rest.find(value => /^https?:\/\//.test(value)) : null;
41
+ if (!explicitContext && urlReference) selected = contextForUrl(contexts.read(), urlReference) ?? selected;
42
+ if (!selected) usage('no workspace context selected');
43
+ if (selected === 'local') return localCommand({ verb, sub, rest, flags, output });
44
+ const { client, context } = fromContext({ supercodeHome: home, name: selected, fetch: globalThis.fetch });
45
+ const team = client.team(context.team_id);
46
+ const command = { flags, rest, output, client, context, team, explicitContext, home, contextName: selected, env, dependencies };
47
+ if (teamsPrefix && ['list', 'create'].includes(verb)) return teamsCommand(verb, sub, command);
48
+ if (verb === 'sessions') return sessionsCommand(sub, command);
49
+ if (verb === 'members') return membersCommand(sub, command);
50
+ if (verb === 'groups') return groupsCommand(sub, command);
51
+ if (verb === 'sources') return sourcesCommand(sub, command);
52
+ if (verb === 'machines') return machinesCommand(sub, command);
53
+ if (verb === 'identity') return identityCommand(sub, command);
54
+ if (verb === 'sync') return syncCommand(sub, command);
55
+ if (['connect', 'disconnect', 'status'].includes(verb)) return connectorCommand(verb, command, home);
56
+ if (verb === 'publish' || verb === 'unpublish') return publishCommand(verb, sub, command);
57
+ usage(`unsupported workspace command ${verb}`);
58
+ }
59
+
60
+ async function teamsCommand(verb, sub, { flags, rest, output, client }) {
61
+ if (verb === 'list') { only(flags, ['json', 'help', 'context', 'cursor', 'limit']); requireArgs(rest, 0, 0, 'teams list'); return output(await client.teams({ cursor: flags.cursor, limit: positiveInteger(flags.limit, 50, 'limit') })); }
62
+ const names = [sub, ...rest].filter(value => value !== undefined);
63
+ only(flags, ['json', 'help', 'context']); requireArgs(names, 1, 1, 'teams create NAME');
64
+ return output(await client.createTeam({ name: names[0] }));
65
+ }
66
+
67
+ async function localCommand({ verb, sub, rest, flags, output }) {
68
+ if (verb !== 'sessions' || !['list', 'ls'].includes(sub)) unsupported('this command is unavailable in the local context');
69
+ only(flags, ['json', 'help', 'context', 'cwd', 'harness', 'limit', 'include_children']);
70
+ requireArgs(rest, 0, 0, 'sessions list');
71
+ const binary = process.env.SUPERCODE_BIN ?? process.env.SUPERCODE_TEAMS_TEST_BIN ?? 'supercode';
72
+ const core = new SupercodeHarnessClient({ command: binary, args: ['harness', 'serve'] });
73
+ try {
74
+ const page = await core.discover({ workspace: flags.cwd ?? undefined, harnesses: flags.harness ? [flags.harness] : undefined, limit: positiveInteger(flags.limit, 50, 'limit'), includeChildSessions: Boolean(flags.include_children) });
75
+ return output({ version: 1, context: { local: true }, items: page.sessions ?? [], next_cursor: page.next_cursor ?? null, coverage: page.coverage ?? null });
76
+ } finally { core.close(); }
77
+ }
78
+
79
+ async function contextCommand({ contexts, flags, sub, rest, output }) {
80
+ if (sub === 'list') { requireArgs(rest, 0, 0, 'context list'); return output(contexts.read()); }
81
+ if (sub === 'show') {
82
+ requireArgs(rest, 0, 1, 'context show [NAME]');
83
+ const document = contexts.read(); const name = rest[0] ?? document.current;
84
+ if (!name) usage('no workspace context selected');
85
+ if (name === 'local') return output({ name, local: true });
86
+ if (!document.contexts[name]) notFound(`unknown context ${name}`);
87
+ return output({ name, ...document.contexts[name] });
88
+ }
89
+ if (sub === 'use') { requireArgs(rest, 1, 1, 'context use NAME'); contexts.use(rest[0]); return output({ current: rest[0] }); }
90
+ if (sub === 'remove') { requireArgs(rest, 1, 1, 'context remove NAME'); contexts.remove(rest[0]); return output({ removed: rest[0] }); }
91
+ if (sub === 'create') {
92
+ requireArgs(rest, 1, 1, 'context create NAME --server ORIGIN --team ID --credential-ref ID');
93
+ for (const key of ['server', 'team', 'credential_ref']) if (!flags[key]) usage(`--${key.replaceAll('_', '-')} is required`);
94
+ return output(await contexts.createAuthenticated(rest[0], { origin: flags.server, teamId: flags.team, credentialRef: flags.credential_ref, fetch: globalThis.fetch }));
95
+ }
96
+ usage('context create|list|show|use|remove');
97
+ }
98
+
99
+ async function serverCommand({ flags, sub, rest, home, output }) {
100
+ requireArgs(rest, 0, 0, 'teams server start|token');
101
+ if (sub === 'token') {
102
+ // A fresh owner credential for a hosted server's first start (its bootstrap secret). Printed to
103
+ // stdout only, for a pipe into the platform's secret store and then `teams login --token-stdin`.
104
+ process.stdout.write(makeToken(productionIds.next('credential')) + '\n');
105
+ return;
106
+ }
107
+ if (sub !== 'start') usage('teams server start|token');
108
+ // The Node host's store is node:sqlite (Node 22.5+); only this verb loads it, so every client
109
+ // verb runs on the Node 20 the package declares.
110
+ const { createTeamsServer } = await import('../server/index.mjs');
111
+ 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 } : {}) });
112
+ output(await server.start());
113
+ await new Promise(resolveStop => { const stop = async () => { await server.close(); resolveStop(); }; process.once('SIGINT', stop); process.once('SIGTERM', stop); });
114
+ }
115
+
116
+ /** The Teams server a login reaches when none is named: Volter's. */
117
+ const DEFAULT_TEAMS_ORIGIN = 'https://supercode-teams.aaron-0ed.workers.dev';
118
+
119
+ async function loginCommand({ flags, origin, rest, home, output }) {
120
+ requireArgs(rest, 0, 0, 'teams login [ORIGIN] [--team NAME_OR_ID] [--as NAME]');
121
+ if (flags.token_stdin && flags.invite_stdin) usage('choose only one of --token-stdin or --invite-stdin');
122
+ origin ??= process.env.SUPERCODE_TEAMS_ORIGIN ?? DEFAULT_TEAMS_ORIGIN;
123
+ const local = ['localhost', '127.0.0.1', '::1', '[::1]'].includes(new URL(origin).hostname);
124
+ if (!flags.team && (flags.token_stdin || (local && !flags.volter))) usage('--team names the team to sign in to with a token');
125
+ // Signing in with Volter (protocol P02), the default for a remote server: the member linked to that identity, a
126
+ // member of the organization the server links to a team, or with --invite-stdin the invited member. Without --team
127
+ // the person chooses among the teams they may use (volter-login.mjs), each a context named after it unless --as
128
+ // names the one.
129
+ if (flags.volter || (!local && !flags.token_stdin && !flags.invite_stdin)) {
130
+ if (flags.token_stdin) usage('--volter signs in instead of --token-stdin');
131
+ const invite = flags.invite_stdin ? inviteCode(readFileSync(0, 'utf8').trim()) : null;
132
+ const signIn = { ...(flags.volter_issuer ? { issuer: flags.volter_issuer } : {}), audience: new URL(origin).origin };
133
+ if (invite || flags.team) {
134
+ const identityToken = await volterDeviceSignIn(signIn);
135
+ return signedIn(flags, output, await loginContext({ supercodeHome: home, name: flags.as ?? null, origin, teamId: flags.team ?? null, token: null, invite, identityToken, as: flags.as ?? null, fetch: globalThis.fetch }));
136
+ }
137
+ const signedInTo = await volterDeviceSignIn({ ...signIn, finish: ({ token, ask }) => signInToTeams({ supercodeHome: home, origin, identityToken: token, ask, as: flags.as ?? null }) });
138
+ if (flags.json) return output(signedInTo.saved.length === 1 && !signedInTo.failed.length ? signedInTo.saved[0] : signedInTo);
139
+ process.stdout.write(loginSummary(signedInTo, origin));
140
+ return;
141
+ }
142
+ let token = null, invite = null;
143
+ if (flags.token_stdin || flags.invite_stdin) { const secret = readFileSync(0, 'utf8').trim(); if (!secret) usage('stdin credential is empty'); token = flags.token_stdin ? secret : null; invite = flags.invite_stdin ? inviteCode(secret) : null; }
144
+ else { const url = new URL(origin); if (!['localhost', '127.0.0.1', '::1', '[::1]'].includes(url.hostname)) usage('remote login requires --token-stdin or --invite-stdin'); try { token = JSON.parse(readFileSync(resolve(home, 'teams', 'server', 'bootstrap-credential.json'), 'utf8')).token; } catch { usage('local bootstrap credential was not found; use --token-stdin or --invite-stdin'); } }
145
+ return signedIn(flags, output, await loginContext({ supercodeHome: home, name: flags.as ?? null, origin, teamId: flags.team ?? null, token, invite, as: flags.as ?? null, fetch: globalThis.fetch }));
146
+ }
147
+
148
+ /** What a login says: the context as JSON, or to a person, the team they are in and the next step. */
149
+ function signedIn(flags, output, saved) {
150
+ if (flags.json) return output(saved);
151
+ process.stdout.write(`Signed in to team ${saved.team_name ?? saved.team_id} (${new URL(saved.origin).host}). Next, from the folder you work in: supercode teams connect\n`);
152
+ }
153
+
154
+ /** `identity link`: the signed-in member links their Volter identity, so `teams login --volter` finds them. */
155
+ async function identityCommand(sub, { flags, rest, output, client, context }) {
156
+ only(flags, ['json', 'help', 'context', 'volter_issuer']); requireArgs(rest, 0, 0, 'identity link');
157
+ if (sub !== 'link') usage('identity link');
158
+ const accessToken = await volterDeviceSignIn({ ...(flags.volter_issuer ? { issuer: flags.volter_issuer } : {}), audience: new URL(context.origin).origin });
159
+ return output(await client.request('POST', '/api/v1/me/identities', { body: { access_token: accessToken } }));
160
+ }
161
+
162
+ async function sessionsCommand(sub, command) {
163
+ const { flags, rest, output, team, context, explicitContext } = command;
164
+ if (sub === 'list' || sub === 'ls') {
165
+ only(flags, ['json', 'help', 'context', 'live', 'saved', 'watch', 'paginate', 'after', 'user', 'repo', 'harness', 'machine', 'source', 'cursor', 'limit', 'include_children', 'since', 'until', 'cwd']);
166
+ requireArgs(rest, 0, 0, 'sessions list'); if (flags.live || flags.saved) unsupported('remote contexts do not expose local --live/--saved backends');
167
+ const query = await listQuery(flags, command); if (flags.watch && flags.paginate) usage('--watch and --paginate are incompatible');
168
+ if (flags.watch) return stream(team.sessions.watch(query, { after: flags.after }), flags.json);
169
+ if (!flags.paginate) return output(await team.sessions.list(query));
170
+ const items = []; let page; do { page = await team.sessions.list({ ...query, cursor: page?.next_cursor }); items.push(...page.items); } while (page.next_cursor);
171
+ return output({ ...page, items, next_cursor: null });
172
+ }
173
+ if (sub === 'search') { only(flags, ['json', 'help', 'context', 'user', 'repo', 'harness', 'machine', 'source', 'cursor', 'limit', 'include_children', 'since', 'until', 'cwd']); requireArgs(rest, 1, Infinity, 'sessions search QUERY'); return output(await team.sessions.search({ ...await listQuery(flags, command), query: rest.join(' ') })); }
174
+ if (sub === 'access') {
175
+ const [action, reference, ...overflow] = rest;
176
+ if (!action || !reference || overflow.length) usage('sessions access list|grant|revoke REF');
177
+ const id = sessionReference(reference, context.origin, context.team_id, Boolean(explicitContext));
178
+ return sessionAccess(action, flags, team.sessions.get(id), output);
179
+ }
180
+ const reference = rest[0]; if (!reference) usage(`sessions ${sub} requires REF`);
181
+ const id = sessionReference(reference, context.origin, context.team_id, Boolean(explicitContext)); const session = team.sessions.get(id);
182
+ if (sub === 'show') { only(flags, ['json', 'help', 'context', 'tail', 'before', 'revision']); requireArgs(rest, 1, 1, 'sessions show REF'); if (flags.before && flags.revision) usage('--before and --revision are mutually exclusive'); return output(await session.read({ tail: positiveInteger(flags.tail, 100, 'tail'), ...optional(flags, ['before', 'revision']) })); }
183
+ if (sub === 'follow') { only(flags, ['json', 'help', 'context', 'tail', 'after']); requireArgs(rest, 1, 1, 'sessions follow REF'); return stream(session.follow({ tail: positiveInteger(flags.tail, 100, 'tail'), ...optional(flags, ['after']) }), flags.json); }
184
+ if (sub === 'attach') return attachSession(session, command);
185
+ if (sub === 'export') return exportSession(session, command);
186
+ if (sub === 'continue') return continueSession(session, command);
187
+ if (sub === 'url') { only(flags, ['json', 'help', 'context']); requireArgs(rest, 1, 1, 'sessions url REF'); const descriptor = await session.descriptor(); return output({ version: 1, context: scope(context), data: { url: `${context.origin}/teams/${context.team_id}/sessions/${descriptor.data.id}` } }); }
188
+ if (sub === 'rename') { only(flags, ['json', 'help', 'context']); requireArgs(rest, 2, 2, 'sessions rename REF TITLE'); const descriptor = await session.descriptor(); return output(await command.client.request('PATCH', session.path, { body: { title: rest[1] }, ifMatch: descriptor.data.revision })); }
189
+ if (sub === 'delete') { only(flags, ['json', 'help', 'context']); requireArgs(rest, 1, 1, 'sessions delete REF'); const descriptor = await session.descriptor(); await command.client.request('DELETE', session.path, { ifMatch: descriptor.data.revision }); return output({ version: 1, context: scope(context), data: null }); }
190
+ unsupported(`sessions ${sub} is reserved for a later arc`);
191
+ }
192
+
193
+ async function attachSession(session, { flags, rest, output, team }) {
194
+ only(flags, ['json', 'help', 'context', 'binding', 'take_control']);
195
+ requireArgs(rest, 1, 1, 'sessions attach REF [--binding ID] [--take-control]');
196
+ const descriptor = (await session.descriptor()).data;
197
+ const running = descriptor.bindings.filter(binding => binding.state === 'running');
198
+ let binding;
199
+ if (flags.binding) binding = running.find(candidate => candidate.id === flags.binding);
200
+ else if (running.length === 1) [binding] = running;
201
+ else if (running.length > 1) usage('--binding is required when more than one live binding is eligible');
202
+ if (!binding) unsupported(running.length ? 'the selected binding is not live' : 'this session has no live native binding');
203
+ const attached = await session.attach({ binding_id: binding.id, mode: flags.take_control ? 'control' : 'observe' });
204
+ output(attached);
205
+ try {
206
+ for await (const frame of team.attachments.stream(attached.data)) process.stdout.write(flags.json ? `${JSON.stringify(frame)}\n` : humanOutput(frame));
207
+ } finally { await team.attachments.detach(attached.data.id).catch(() => {}); }
208
+ }
209
+
210
+ async function exportSession(session, { flags, rest, output, client, context }) {
211
+ only(flags, ['json', 'help', 'context', 'format', 'fidelity', 'out', 'force', 'timeout']);
212
+ requireArgs(rest, 1, 1, 'sessions export REF --format FORMAT --out PATH');
213
+ if (!flags.format || !flags.out) usage('--format and --out are required');
214
+ const native = !['text', 'html'].includes(flags.format), fidelity = flags.fidelity ?? (native ? 'byte_lossless' : undefined);
215
+ if (native && !['claude-code', 'codex', 'gemini', 'goose', 'opencode', 'pi', 'grok'].includes(flags.format)) usage('--format must be text, html, or a supported translated harness');
216
+ if (fidelity && !['byte_lossless', 'value_lossless', 'semantic'].includes(fidelity)) usage('--fidelity must be byte_lossless, value_lossless, or semantic');
217
+ const destination = native ? reserveArtifactDirectory(flags.out, { force: flags.force }) : reserveProtected(flags.out, { force: flags.force });
218
+ try {
219
+ const descriptor = await session.descriptor();
220
+ const started = await session.export({ format: flags.format, ...(fidelity ? { fidelity } : {}), revision: descriptor.data.revision });
221
+ const operation = await waitForOperation(client, context, started.data, flags.timeout ? duration(flags.timeout) : undefined);
222
+ const result = operation.result;
223
+ if (operation.state !== 'succeeded' || result?.kind !== 'export' || !Array.isArray(result.files)) throw new Error('export operation returned no verified artifact result');
224
+ if (result.source_session_id !== descriptor.data.id || result.source_revision !== descriptor.data.revision || result.format !== flags.format) throw new Error('export operation result does not match the requested source revision and format');
225
+ if (fidelity && !fidelitySatisfies(result.fidelity, fidelity)) throw new Error(`export operation returned ${result.fidelity ?? 'unknown'} fidelity below requested ${fidelity}`);
226
+ const primaries = result.files.filter(file => file.role === 'primary'); if (primaries.length !== 1) throw new Error('export result must contain exactly one primary file');
227
+ if (!native && result.files.length !== 1) throw new Error('text and HTML exports must contain exactly one file');
228
+ for (const file of result.files) {
229
+ const bytes = await downloadArtifact(client, context, result.source_session_id, file);
230
+ if (native) destination.write(file.relative_name, bytes); else destination.writeBytes(bytes);
231
+ }
232
+ if (native) destination.publish();
233
+ return output({ version: 1, context: scope(context), data: { ...operation, written_to: native ? resolve(flags.out, primaries[0].relative_name) : resolve(flags.out), fidelity: result.fidelity } });
234
+ } catch (error) { destination.abort(); throw error; }
235
+ }
236
+
237
+ async function continueSession(session, { flags, rest, output, client, context, team, home, env, dependencies }) {
238
+ only(flags, ['json', 'help', 'context', 'plan', 'on', 'harness', 'cwd', 'root', 'fidelity', 'dry_run', 'timeout', 'workspace_from']);
239
+ requireArgs(rest, 1, 1, 'sessions continue REF (--plan ID | --on local|MACHINE --harness ID --cwd PATH [--workspace-from MACHINE:PATH])');
240
+ if (flags.plan && ['on', 'harness', 'cwd', 'root', 'fidelity'].some(key => flags[key] !== undefined)) usage('--plan cannot be combined with --on, --harness, --cwd, --root, or --fidelity because the plan already pins them');
241
+ if (flags.plan) {
242
+ const descriptor = await session.descriptor();
243
+ const continuation = (await client.request('GET', teamPath(context, `/continuations/${flags.plan}`))).data;
244
+ if (continuation.source_id !== descriptor.data.id) notFound('continuation plan does not belong to the requested session');
245
+ if (flags.dry_run || continuation.state === 'refused') return output({ version: 1, context: scope(context), data: continuation });
246
+ if (continuation.operation_id !== null) {
247
+ if (continuation.destination.machine_id !== 'local') {
248
+ if (!continuation.operation_id) throw Object.assign(new Error('consumed continuation has no operation receipt'), { code: 'conflict', exitCode: 6 });
249
+ return output({ version: 1, context: scope(context), data: await waitForOperation(client, context, { id: continuation.operation_id, state: 'pending' }, flags.timeout ? duration(flags.timeout) : undefined) });
250
+ }
251
+ const statePath = join(home, 'teams', 'workspaces', 'continuations', context.server_id, context.team_id, continuation.id, 'state.json');
252
+ if (!existsSync(statePath)) {
253
+ const completion = { operation_id: continuation.operation_id, outcome: 'unknown', native_session_id: null, error: null };
254
+ const reported = await client.request('POST', teamPath(context, `/continuations/${continuation.id}/complete-local`), { body: completion });
255
+ await waitForOperation(client, context, reported.data, flags.timeout ? duration(flags.timeout) : undefined);
256
+ throw Object.assign(new Error('local continuation was consumed without a durable local receipt; native execution will not be repeated'), { code: 'conflict', exitCode: 6 });
257
+ }
258
+ }
259
+ if (continuation.state !== 'ready' && continuation.operation_id === null) throw Object.assign(new Error(`continuation is ${continuation.state}`), { code: 'conflict', exitCode: 6 });
260
+ if (continuation.destination.machine_id === 'local') return launchLocalContinuation({ continuation, fidelity: continuation.fidelity, client, context, home, output, timeout: flags.timeout ? duration(flags.timeout) : undefined, env, json: flags.json, eventStream: dependencies.stdout ?? process.stdout, nativeContinuationFactory: dependencies.nativeContinuationFactory, coreFactory: dependencies.coreFactory });
261
+ const launched = await client.request('POST', teamPath(context, `/continuations/${continuation.id}/launch`));
262
+ return output({ version: 1, context: scope(context), data: await waitForOperation(client, context, launched.data, flags.timeout ? duration(flags.timeout) : undefined) });
263
+ }
264
+ for (const key of ['on', 'harness', 'cwd']) if (!flags[key]) usage(`--${key.replaceAll('_', '-')} is required`);
265
+ if (!CONTINUATION_HARNESSES.has(flags.harness)) usage(`--harness must be one of ${[...CONTINUATION_HARNESSES].join(', ')}`);
266
+ const fidelity = flags.fidelity ?? CONTINUATION_FIDELITY; if (fidelity !== CONTINUATION_FIDELITY) usage(`continuation fidelity is ${CONTINUATION_FIDELITY}: a continued session is a new native session`);
267
+ if (flags.workspace_from) {
268
+ // The project goes first: moved from MACHINE:PATH into --cwd on the destination, published
269
+ // there only whole, and only then is the session continued into it.
270
+ if (flags.plan || flags.dry_run) usage('--workspace-from moves a project; it runs with --on, not --plan or --dry-run');
271
+ const spec = String(flags.workspace_from), at = spec.indexOf(':');
272
+ if (at <= 0 || !spec.slice(at + 1).startsWith('/')) usage('--workspace-from is MACHINE:/absolute/path');
273
+ const moved = await moveWorkspace({ from: spec.slice(0, at), to: flags.on === 'local' ? null : String(flags.on), path: spec.slice(at + 1), toPath: flags.on === 'local' ? resolve(flags.cwd) : String(flags.cwd), context: flags.context ?? null, home: teamsHome(env) });
274
+ process.stderr.write(`moved ${moved.bytes} bytes (sha256 ${moved.sha256}) from ${moved.from} to ${moved.to}:${moved.path}\n`);
275
+ }
276
+ let machine_id, root_id, cwd;
277
+ if (flags.on === 'local') { if (flags.root) usage('--root is only valid for a remote machine'); machine_id = 'local'; root_id = null; cwd = realpathSync(resolve(flags.cwd)); }
278
+ else {
279
+ if (!flags.root) usage('--root is required for a remote machine');
280
+ const machines = await team.machines.list(), matches = machines.items.filter(machine => machine.id === flags.on || machine.name === flags.on);
281
+ if (matches.length !== 1) usage(matches.length ? `machine name ${flags.on} is ambiguous` : `machine ${flags.on} is not accessible`);
282
+ machine_id = matches[0].id; root_id = flags.root; cwd = flags.cwd;
283
+ }
284
+ const descriptor = await session.descriptor();
285
+ let continuation = (await session.prepare({ source_revision: descriptor.data.revision, destination: { machine_id, root_id, cwd }, harness: flags.harness, fidelity })).data;
286
+ continuation = await waitForContinuation(client, context, continuation, flags.timeout ? duration(flags.timeout) : undefined);
287
+ if (flags.dry_run || continuation.state === 'refused') return output({ version: 1, context: scope(context), data: continuation });
288
+ if (continuation.state !== 'ready') throw Object.assign(new Error(`continuation is ${continuation.state}`), { code: 'conflict', exitCode: 6 });
289
+ if (machine_id === 'local') return launchLocalContinuation({ continuation, fidelity, client, context, home, output, timeout: flags.timeout ? duration(flags.timeout) : undefined, env, json: flags.json, eventStream: dependencies.stdout ?? process.stdout, nativeContinuationFactory: dependencies.nativeContinuationFactory, coreFactory: dependencies.coreFactory });
290
+ const launched = await client.request('POST', teamPath(context, `/continuations/${continuation.id}/launch`));
291
+ const operation = await waitForOperation(client, context, launched.data, flags.timeout ? duration(flags.timeout) : undefined);
292
+ return output({ version: 1, context: scope(context), data: operation });
293
+ }
294
+
295
+ async function launchLocalContinuation({ continuation, fidelity, client, context, home, output, timeout, env, json, eventStream, nativeContinuationFactory, coreFactory }) {
296
+ const localRoot = join(home, 'teams', 'workspaces', 'continuations', context.server_id, context.team_id, continuation.id), statePath = join(localRoot, 'state.json'), bundlePath = join(localRoot, 'bundle');
297
+ if (existsSync(statePath)) {
298
+ const state = JSON.parse(readFileSync(statePath, 'utf8'));
299
+ if (state.version !== 1 || state.continuation_id !== continuation.id || !state.operation_id) throw Object.assign(new Error('local continuation durable state is invalid'), { code: 'conflict', exitCode: 6 });
300
+ const completion = state.phase === 'completed' && state.completion
301
+ ? state.completion
302
+ : { operation_id: state.operation_id, outcome: 'unknown', native_session_id: null, error: null };
303
+ if (state.phase !== 'completed') writeProtected(statePath, { ...state, phase: 'completed', completion }, { force: true });
304
+ const reported = await client.request('POST', teamPath(context, `/continuations/${continuation.id}/complete-local`), { body: completion });
305
+ const operation = await waitForOperation(client, context, reported.data, timeout);
306
+ if (completion.outcome !== 'running') throw Object.assign(new Error(completion.outcome === 'failed' ? completion.error.message : `native continuation outcome is unknown and will not be retried automatically${state.observed ? `: ${state.observed}` : ''}`), { code: completion.error?.code ?? 'conflict', exitCode: 6 });
307
+ return output({ version: 1, context: scope(context), data: operation });
308
+ }
309
+ let factory = nativeContinuationFactory;
310
+ if (!factory) {
311
+ try { ({ createNativeContinuationAdapter: factory } = await import('@volter/supercode-teams/connector/native-continuation')); } catch (error) { if (error.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED' && error.code !== 'ERR_MODULE_NOT_FOUND') throw error; }
312
+ }
313
+ if (!factory) unsupported('local continuation requires the reviewed connector native installer adapter');
314
+ const binary = env.SUPERCODE_TEAMS_TEST_BIN ?? env.SUPERCODE_BIN ?? 'supercode';
315
+ const core = coreFactory ? coreFactory({ command: binary, args: ['harness', 'serve'] }) : new SupercodeHarnessClient({ command: binary, args: ['harness', 'serve'] });
316
+ const adapter = factory({ core });
317
+ if (!adapter.supports({ harness: continuation.harness, fidelity })) { core.close(); unsupported(`local ${continuation.harness} continuation does not support the prepared fidelity`); }
318
+ const launched = await client.request('POST', teamPath(context, `/continuations/${continuation.id}/launch`)), ticket = launched.data.result;
319
+ if (launched.data.state !== 'running' || ticket?.kind !== 'local_launch' || ticket.continuation_id !== continuation.id || ticket.operation_id !== launched.data.id) { core.close(); throw new Error('local launch returned an invalid ticket'); }
320
+ if (Date.now() >= ticket.expires_at || ticket.destination.machine_id !== 'local' || ticket.destination.root_id !== null || ticket.destination.cwd !== continuation.destination.cwd || ticket.harness !== continuation.harness) { core.close(); throw Object.assign(new Error('local launch ticket target changed or expired'), { code: 'conflict', exitCode: 6 }); }
321
+ if (ticket.fidelity !== fidelity || ticket.artifact?.kind !== 'export' || ticket.artifact.format !== continuation.harness || !fidelitySatisfies(ticket.artifact.fidelity, fidelity) || !Array.isArray(ticket.artifact.files) || ticket.artifact.files.filter(file => file.role === 'primary').length !== 1) { core.close(); throw Object.assign(new Error('local launch ticket contains an invalid export bundle'), { code: 'conflict', exitCode: 6 }); }
322
+ const directory = reserveArtifactDirectory(bundlePath);
323
+ try {
324
+ for (const file of ticket.artifact.files) directory.write(file.relative_name, await downloadArtifact(client, context, ticket.artifact.source_session_id, file));
325
+ directory.publish();
326
+ } catch (error) { directory.abort(); core.close(); throw error; }
327
+ const current = (await client.request('GET', teamPath(context, `/continuations/${continuation.id}`))).data;
328
+ if (Date.now() >= current.expires_at || current.source_revision !== ticket.artifact.source_revision || current.source_id !== continuation.source_id || current.destination?.machine_id !== 'local' || current.destination?.cwd !== ticket.destination.cwd) { core.close(); throw Object.assign(new Error('local continuation authorization changed after artifact verification'), { code: 'conflict', exitCode: 6 }); }
329
+ writeProtected(statePath, { version: 1, phase: 'started', continuation_id: continuation.id, operation_id: ticket.operation_id, ticket, bundle: bundlePath });
330
+ let completion, runtime, observed = null;
331
+ try {
332
+ const native = await adapter.launch({ ticket, directory: bundlePath, fidelity });
333
+ if (!native?.native_session_id) throw Object.assign(new Error('native adapter returned no session identity'), { nativeOutcome: 'failed' });
334
+ runtime = native.runtime;
335
+ completion = { operation_id: ticket.operation_id, outcome: 'running', native_session_id: native.native_session_id, error: null };
336
+ } catch (error) {
337
+ completion = error.nativeOutcome === 'failed'
338
+ ? { operation_id: ticket.operation_id, outcome: 'failed', native_session_id: null, error: { code: error.code ?? 'unsupported', message: error.message, retryable: false } }
339
+ : { operation_id: ticket.operation_id, outcome: 'unknown', native_session_id: null, error: null };
340
+ // An unknown outcome is never retried, so its cause is the one thing the person can act on.
341
+ if (completion.outcome === 'unknown') observed = error.message;
342
+ }
343
+ writeProtected(statePath, { version: 1, phase: 'completed', continuation_id: continuation.id, operation_id: ticket.operation_id, ticket, bundle: bundlePath, completion, ...(observed ? { observed } : {}) }, { force: true });
344
+ let operation;
345
+ try {
346
+ const reported = await client.request('POST', teamPath(context, `/continuations/${continuation.id}/complete-local`), { body: completion });
347
+ operation = await waitForOperation(client, context, reported.data, timeout);
348
+ if (completion.outcome !== 'running') throw Object.assign(new Error(completion.outcome === 'failed' ? completion.error.message : `native continuation outcome is unknown and will not be retried automatically${observed ? `: ${observed}` : ''}`), { code: completion.error?.code ?? 'conflict', exitCode: 6 });
349
+ } catch (error) {
350
+ if (runtime) try { await runtime.close(); } catch {}
351
+ await core.close();
352
+ throw error;
353
+ }
354
+ output({ version: 1, context: scope(context), data: operation });
355
+ await ownForegroundRuntime(runtime, core, { json, eventStream, env });
356
+ }
357
+
358
+ async function ownForegroundRuntime(runtime, core, { json, eventStream, env }) {
359
+ try {
360
+ if (json) {
361
+ for await (const event of runtime.events()) eventStream.write(`${JSON.stringify(event)}\n`);
362
+ return;
363
+ }
364
+ const instructions = await runtime.terminalInstructions();
365
+ const launch = instructions?.launch;
366
+ if (!launch || typeof launch.program !== 'string' || !launch.program || !Array.isArray(launch.arguments) || launch.arguments.some(value => typeof value !== 'string') || typeof launch.cwd !== 'string' || !launch.cwd) unsupported('native runtime has no trusted terminal attachment frontend');
367
+ await new Promise((resolveExit, reject) => {
368
+ const child = spawn(launch.program, launch.arguments, { cwd: launch.cwd, env: { ...env, ...(launch.env ?? {}) }, stdio: 'inherit', shell: false });
369
+ child.once('error', reject);
370
+ child.once('close', code => code === 0 ? resolveExit() : reject(Object.assign(new Error(`native attachment frontend exited ${code}`), { code: 'conflict' })));
371
+ });
372
+ } finally {
373
+ try { await runtime.close(); } finally { await core.close(); }
374
+ }
375
+ }
376
+
377
+ async function sessionAccess(action, flags, session, output) {
378
+ const common = ['json', 'help', 'context'];
379
+ if (action === 'list') { only(flags, common); return output(await session.access()); }
380
+ if (action === 'grant') {
381
+ only(flags, [...common, 'to', 'role', 'ttl']);
382
+ if (!flags.to) usage('--to is required');
383
+ const target = /^(user|group):(.+)$/.exec(flags.to); if (!target) usage('--to must be user:ID or group:ID');
384
+ const access = await session.access();
385
+ return output(await session.grant({ subject: { kind: target[1] === 'user' ? 'principal' : 'group', id: target[2] }, role: flags.role ?? 'reader', expires_at: flags.ttl ? Date.now() + duration(flags.ttl) : null }, { ifMatch: access.data.revision }));
386
+ }
387
+ if (action === 'revoke') { only(flags, [...common, 'grant']); if (!flags.grant) usage('--grant is required'); const access = await session.access(); return output(await session.revoke(flags.grant, { ifMatch: access.data.revision })); }
388
+ usage('sessions access list|grant|revoke REF');
389
+ }
390
+
391
+ async function membersCommand(sub, { flags, rest, output, client, context }) {
392
+ const base = teamPath(context, '/members');
393
+ if (sub === 'invite') {
394
+ requireArgs(rest, 1, 1, 'teams members invite DISPLAY_NAME --out PATH'); if (!flags.out) usage('--out is required to deliver the invitation secret'); const destination = reserveProtected(flags.out, { force: flags.force });
395
+ let response; try { response = await client.request('POST', teamPath(context, '/invitations'), { body: { display_name: rest[0], role: flags.role ?? 'member', expires_in_ms: duration(flags.ttl, 86_400_000) } }); destination.write(response.data); } catch (error) { destination.abort(); throw error; }
396
+ const { enrollment_code: _secret, ...safe } = response.data;
397
+ return output({ ...response, data: { ...safe, written_to: resolve(flags.out) } });
398
+ }
399
+ if (sub === 'list') return output(await client.request('GET', base));
400
+ if (sub === 'remove' || sub === 'role') {
401
+ requireArgs(rest, 1, 1, `teams members ${sub} ID${sub === 'role' ? ' --role ROLE' : ''}`); if (sub === 'role' && !flags.role) usage('--role is required');
402
+ const membership = (await client.request('GET', base)).items.find(item => item.principal.id === rest[0]); if (!membership) notFound('member is not accessible');
403
+ if (sub === 'remove') { await client.request('DELETE', `${base}/${rest[0]}`, { ifMatch: membership.revision }); return output({ removed: rest[0] }); }
404
+ return output(await client.request('PATCH', `${base}/${rest[0]}`, { body: { role: flags.role }, ifMatch: membership.revision }));
405
+ }
406
+ usage('teams members invite|list|remove|role');
407
+ }
408
+
409
+ async function groupsCommand(sub, { rest, output, client, context }) {
410
+ const base = teamPath(context, '/groups');
411
+ if (sub === 'list') return output(await client.request('GET', base));
412
+ if (sub === 'create') { requireArgs(rest, 1, 1, 'teams groups create NAME'); return output(await client.request('POST', base, { body: { name: rest[0] } })); }
413
+ if (sub === 'remove' || sub === 'add-member' || sub === 'remove-member') {
414
+ requireArgs(rest, sub === 'remove' ? 1 : 2, sub === 'remove' ? 1 : 2, `teams groups ${sub} GROUP_ID${sub === 'remove' ? '' : ' PRINCIPAL_ID'}`);
415
+ const group = (await client.request('GET', base)).items.find(item => item.id === rest[0]); if (!group) notFound('group is not accessible');
416
+ if (sub === 'remove') { await client.request('DELETE', `${base}/${rest[0]}`, { ifMatch: group.revision }); return output({ removed: rest[0] }); }
417
+ const path = `${base}/${rest[0]}/members/${rest[1]}`, method = sub === 'add-member' ? 'PUT' : 'DELETE'; await client.request(method, path, { ...(method === 'PUT' ? { body: {} } : {}), ifMatch: group.revision }); return output({ group_id: rest[0], principal_id: rest[1], member: method === 'PUT' });
418
+ }
419
+ usage('teams groups list|create|remove|add-member|remove-member');
420
+ }
421
+
422
+ async function sourcesCommand(sub, command) {
423
+ const { flags, rest, output, client, context, team } = command;
424
+ if (sub === 'list') return output(await team.sources.list());
425
+ if (sub === 'create') { requireArgs(rest, 0, 0, 'teams sources create --type otlp --name NAME'); if (flags.type !== 'otlp' || !flags.name) usage('--type otlp and --name are required'); return output(await team.sources.create({ name: flags.name, kind: 'otlp', custodian_id: context.principal_id, visibility: flags.visibility ?? 'restricted' })); }
426
+ if (sub === 'credentials') {
427
+ requireArgs(rest, 2, 2, 'teams sources credentials create SOURCE --out PATH'); if (rest[0] !== 'create') usage('teams sources credentials create SOURCE --out PATH'); const destination = reserveProtected(flags.out, { force: flags.force });
428
+ let response; try { response = await client.request('POST', teamPath(context, `/sources/${rest[1]}/credentials`), { body: { rotate: Boolean(flags.rotate) } }); destination.write(response.data); } catch (error) { destination.abort(); throw error; }
429
+ return output({ ...response, data: { kind: response.data.kind, expires_at: response.data.expires_at, written_to: resolve(flags.out) } });
430
+ }
431
+ const source = rest[0]; if (!source) usage(`teams sources ${sub} requires SOURCE`);
432
+ if (sub === 'status') return output(await team.sources.get(source));
433
+ if (sub === 'pause' || sub === 'revoke') { const current = await team.sources.get(source); return output(await team.sources.update(source, { state: sub === 'pause' ? 'paused' : 'revoked' }, { ifMatch: current.data.revision })); }
434
+ if (sub === 'config') { requireArgs(rest, 1, 1, 'teams sources config SOURCE --format otel-collector'); if (flags.format !== 'otel-collector') usage('--format otel-collector is required'); const response = await client.request('GET', teamPath(context, `/sources/${source}/config?format=otel-collector`)); if (flags.json) return output(response); process.stdout.write(response.data.content.endsWith('\n') ? response.data.content : `${response.data.content}\n`); return; }
435
+ usage('teams sources create|list|status|pause|revoke|credentials|config');
436
+ }
437
+
438
+ async function machinesCommand(sub, { flags, rest, output, client, context, team }) {
439
+ if (sub === 'list') return output(await team.machines.list());
440
+ if (sub === 'roots') { requireArgs(rest, 1, 1, 'teams machines roots MACHINE'); return output(await client.request('GET', teamPath(context, `/machines/${rest[0]}/roots`))); }
441
+ if (sub === 'authorize-continuation') {
442
+ requireArgs(rest, 1, 1, 'teams machines authorize-continuation CONTINUATION --ttl DURATION');
443
+ if (!flags.ttl) usage('--ttl is required');
444
+ const ttl = duration(flags.ttl); if (ttl > 15 * 60_000) usage('--ttl cannot exceed the continuation plan lifetime of 15m');
445
+ // The destination custodian need not be the plan requester. GET is deliberately
446
+ // requester-only; the authorization route validates the requested expiry itself.
447
+ const expires_at = Date.now() + ttl;
448
+ return output(await client.request('POST', teamPath(context, `/continuations/${rest[0]}/authorize-destination`), { body: { expires_at } }));
449
+ }
450
+ if (sub === 'access') {
451
+ const [action, reference] = rest; if (!action || !reference) usage('teams machines access grant|list|revoke MACHINE');
452
+ const machine = reference.startsWith('mach_') ? reference : await resolveName((await team.machines.list()).items, reference, item => item.name, item => item.id, 'machine');
453
+ const machineBase = teamPath(context, `/machines/${machine}/access`), liveBase = teamPath(context, `/machines/${machine}/grants`);
454
+ if (action === 'list') {
455
+ const [machineGrants, liveGrants] = await Promise.all([client.request('GET', machineBase), client.request('GET', liveBase)]);
456
+ return output({ version: 1, context: scope(context), data: { machine_grants: machineGrants.items, live_grants: liveGrants.items } });
457
+ }
458
+ if (action === 'revoke') {
459
+ if (!flags.grant) usage('--grant is required');
460
+ // A grant id names one row in exactly one of the two tables.
461
+ for (const base of [machineBase, liveBase]) {
462
+ let grant;
463
+ try { grant = await client.request('GET', `${base}/${flags.grant}`); } catch (error) { if (error.code === 'not_found') continue; throw error; }
464
+ await client.request('DELETE', `${base}/${flags.grant}`, { ifMatch: grant.data.revision });
465
+ return output({ revoked: flags.grant });
466
+ }
467
+ notFound('grant is not accessible');
468
+ }
469
+ if (action === 'grant') {
470
+ for (const key of ['to', 'caps', 'ttl']) if (!flags[key]) usage(`--${key} is required`);
471
+ const subject = /^(user|service|group):(.+)$/.exec(flags.to)?.[2]; if (!subject) usage('--to must be user:ID, service:ID or group:ID');
472
+ const ttl = duration(flags.ttl);
473
+ if (flags.session || flags.binding) {
474
+ // One session binding's native live actions (§5).
475
+ if (!flags.session || !flags.binding) usage('--session and --binding go together');
476
+ if (ttl > 60 * 60_000) usage('--ttl cannot exceed 1h for a session live grant');
477
+ const allowed = new Set(['attach_observe', 'interact', 'approve', 'interrupt', 'terminate']);
478
+ const actions = flags.caps.split(',').map(cap => cap === 'observe' ? 'attach_observe' : cap);
479
+ if (!actions.length || actions.some(action => !allowed.has(action))) usage('--caps contains an unsupported live action');
480
+ return output(await client.request('POST', liveBase, { body: { machine_id: machine, subject_id: subject, session_id: flags.session, binding_id: flags.binding, actions, expires_at: Date.now() + ttl } }));
481
+ }
482
+ // The machine's doors (§11): operate, or view|observe|interact|control|files|ports on --on targets.
483
+ const caps = flags.caps.split(',').map(cap => cap.trim()).filter(Boolean);
484
+ // `*` carries only operate and view; any other capability names the targets it reaches.
485
+ if (!flags.on && !caps.every(cap => cap === 'operate' || cap === 'view')) usage('--on is required for ' + caps.filter(cap => cap !== 'operate' && cap !== 'view').join(',') + ' (e.g. --on pane:<ctx>, pane:*, root:/path)');
486
+ const targets = flags.on ? flags.on.split(',').map(target => target.trim()).filter(Boolean) : ['*'];
487
+ return output(await client.request('POST', machineBase, { body: { subject_id: subject, caps, targets, expires_at: Date.now() + ttl } }));
488
+ }
489
+ }
490
+ usage('teams machines list|roots|access|authorize-continuation');
491
+ }
492
+
493
+ async function syncCommand(sub, { flags, rest, output, client, context, team, home }) {
494
+ if (sub === 'list') return output(await team.syncRules.list(optional(flags, ['machine'])));
495
+ if (sub === 'add') {
496
+ requireArgs(rest, 0, 0, 'teams sync add --repo PATH'); if (!flags.repo) usage('--repo is required');
497
+ const enrollment = new WorkspaceContexts(home).connector(context.server_id, context.team_id).read();
498
+ if (!enrollment?.machine?.id) unsupported('this context has no connector enrollment');
499
+ const path = realpathSync(resolve(flags.repo)), root = enrollment.roots?.find(item => item.path === path);
500
+ if (!root) unsupported('the connector has not registered that repository root; restart connect with --cwd PATH');
501
+ if (flags.harness && !root.harnesses.includes(flags.harness)) unsupported(`the native core does not report a ${flags.harness} discovery adapter`);
502
+ return output(await team.syncRules.create({ machine_id: enrollment.machine.id, source_id: enrollment.source.id, selector: { root_id: root.id, harnesses: flags.harness ? [flags.harness] : root.harnesses }, visibility: flags.visibility ?? 'restricted', backfill: backfill(flags.backfill ?? 'none') }));
503
+ }
504
+ const id = rest[0]; if (!id) usage(`teams sync ${sub} requires RULE_ID`); const base = teamPath(context, `/sync-rules/${id}`);
505
+ if (sub === 'show') return output(await client.request('GET', base));
506
+ if (sub === 'remove' || sub === 'pause' || sub === 'resume') {
507
+ const current = await client.request('GET', base);
508
+ if (sub === 'remove') { await client.request('DELETE', base, { ifMatch: current.data.revision }); return output({ removed: id }); }
509
+ if (sub === 'resume' && !flags.backfill) usage('--backfill is required');
510
+ return output(await client.request('PATCH', base, { body: sub === 'pause' ? { state: 'paused' } : { state: 'active', backfill: backfill(flags.backfill) }, ifMatch: current.data.revision }));
511
+ }
512
+ usage('teams sync add|list|show|pause|resume|remove');
513
+ }
514
+
515
+ async function connectorCommand(verb, { flags, rest, output, client, context, team, contextName }, home) {
516
+ requireArgs(rest, 0, 0, `teams ${verb}`); const state = new WorkspaceContexts(home).connector(context.server_id, context.team_id);
517
+ if (verb === 'status') { let reachable = true; let account = null; try { account = await client.me(); } catch { reachable = false; } const saved = state.read(); const running = saved?.process?.socket ? await activeSocket(saved.process.socket) : false; return output({ version: 1, context: scope(context), data: { reachable, account, connector: saved ? { enrolled: true, running, machine: saved.machine, source: saved.source } : { enrolled: false, running: false }, sources: reachable ? (await team.sources.list()).items : [] } }); }
518
+ if (verb === 'connect') {
519
+ let saved = state.read();
520
+ if (!saved) { const enrollment_key = crypto.randomUUID(); saved = { version: 1, enrollment_key, phase: 'enrolling' }; state.write(saved); }
521
+ if (!saved.machine) {
522
+ const response = await team.machines.create({ name: flags.name ?? hostname(), enrollment_key: saved.enrollment_key });
523
+ let credential = response.data.credential;
524
+ if (!credential) credential = (await client.request('POST', teamPath(context, `/sources/${response.data.source.id}/credentials`), { body: { rotate: true } })).data;
525
+ saved = state.update(current => ({ ...current, ...response.data, credential, phase: 'enrolled' }));
526
+ }
527
+ if (!saved.credential?.token) unsupported('saved enrollment has no reusable machine credential; disconnect and enroll again');
528
+ const binary = process.env.SUPERCODE_TEAMS_TEST_BIN ?? process.env.SUPERCODE_BIN ?? 'supercode';
529
+ const core = new SupercodeHarnessClient({ command: binary, args: ['harness', 'serve'] });
530
+ let support;
531
+ try { support = await core.supportReport(); } catch (error) { core.close(); throw error; }
532
+ const harnesses = [...new Set((support.harnesses ?? []).map(item => item.id).filter(id => typeof id === 'string'))];
533
+ if (!harnesses.length) { core.close(); unsupported('the native core reported no discoverable harness adapters'); }
534
+ const path = realpathSync(resolve(flags.cwd ?? '.'));
535
+ const root = { id: `root_${createHash('sha256').update(`${saved.machine.id}\0${path}`).digest('hex').slice(0, 32)}`, machine_id: saved.machine.id, name: basename(path), harnesses, path };
536
+ saved = state.update(current => ({ ...current, roots: [root] }));
537
+ // Run from a terminal, connect installs the service that keeps this machine connected (launchd or systemd) and
538
+ // returns; the service itself runs connect without a terminal, in the foreground. --foreground runs it here.
539
+ if (flags.install || (!flags.foreground && process.stdin.isTTY && process.stdout.isTTY)) {
540
+ const serviceBin = process.env.SUPERCODE_BIN;
541
+ if (!serviceBin) unsupported('--install requires invocation through the native supercode CLI');
542
+ const service = JSON.parse(execFileSync(serviceBin, ['__teams-connector-service', 'install', contextName, path, context.server_id, context.team_id], { encoding: 'utf8' }));
543
+ saved = state.update(current => ({ ...current, service }));
544
+ core.close();
545
+ if (!flags.json) {
546
+ process.stdout.write(`Connected ${saved.machine.name} to team ${context.team_name ?? context.team_id}; it stays connected (a ${service.kind ?? 'background'} service).\n`);
547
+ for (const line of service.message_tools ?? []) process.stdout.write(` ${line}\n`);
548
+ return;
549
+ }
550
+ return output({ version: 1, context: scope(context), data: { machine: saved.machine, source: saved.source, service } });
551
+ }
552
+ let stopConnector;
553
+ let lifecycle;
554
+ try { lifecycle = await acquireLifecycle(state.directory, () => stopConnector?.()); } catch (error) { core.close(); throw error; }
555
+ saved = state.update(current => ({ ...current, process: { pid: process.pid, started_at: Date.now(), socket: lifecycle.socketPath, token: lifecycle.token } }));
556
+ // The machine daemon serves this machine's doors on its local socket and, through the link, to
557
+ // callers the server admits; its host key id rides the connector's heartbeat.
558
+ const daemonHome = teamsHome();
559
+ const hostKeyId = publicKeyId(loadOrCreateKeyPair(paths(daemonHome).machineKey).publicKeyPem);
560
+ const connector = new TeamsConnector({ server: context.origin, teamId: context.team_id, machineId: saved.machine.id, sourceId: saved.source.id, credential: saved.credential.token, stateDir: state.directory, core, hostKeyId });
561
+ const machine = new TeamsMachine({ home: daemonHome, supercode: binary, ...(flags.tmux_socket ? { tmuxSocket: String(flags.tmux_socket) } : {}), link: { server: context.origin, teamId: context.team_id, machineId: saved.machine.id, credential: saved.credential.token, incarnationId: connector.state.incarnation_id, configuration: () => connector.configuration() } });
562
+ const stopAll = async () => { await machine.stop().catch(() => {}); await connector.stop(); await core.close(); await lifecycle.close(); state.update(current => current ? ({ ...current, process: undefined }) : null); };
563
+ try { await connector.start({ roots: () => saved.roots }); await machine.start(); } catch (error) { await stopAll(); throw error; }
564
+ output({ version: 1, context: scope(context), data: { machine: saved.machine, source: saved.source, roots: saved.roots.map(({ path: _path, ...item }) => item), socket: machine.socketPath, foreground: true } });
565
+ await new Promise(resolveStop => { let stopped = false; stopConnector = async () => { if (stopped) return; stopped = true; await stopAll(); resolveStop(); }; process.once('SIGINT', stopConnector); process.once('SIGTERM', stopConnector); });
566
+ return;
567
+ }
568
+ const enrollment = state.read();
569
+ if (!enrollment?.machine?.id) return output({ version: 1, context: scope(context), data: { disconnected: false } });
570
+ if (enrollment.service) {
571
+ const serviceBin = process.env.SUPERCODE_BIN;
572
+ if (!serviceBin) unsupported('disconnecting an installed connector requires invocation through the native supercode CLI');
573
+ execFileSync(serviceBin, ['__teams-connector-service', 'uninstall', contextName, enrollment.roots?.[0]?.path ?? '.', context.server_id, context.team_id], { encoding: 'utf8' });
574
+ }
575
+ if (enrollment.process?.socket && enrollment.process?.token) await requestStop(enrollment.process.socket, enrollment.process.token);
576
+ const machine = await team.machines.get(enrollment.machine.id);
577
+ await client.request('DELETE', teamPath(context, `/machines/${enrollment.machine.id}`), { ifMatch: machine.data.revision });
578
+ state.remove();
579
+ return output({ version: 1, context: scope(context), data: { disconnected: true, machine_id: enrollment.machine.id } });
580
+ }
581
+
582
+ /**
583
+ * PUBLISH: one pane or folder of a machine of this team, put into a workplace Room as a Surface (RH2's Surface
584
+ * door). The workplace decides who in the Room may see or drive it and signs them delegated tokens; this server
585
+ * admits those if it was started with that workplace as its delegated issuer (server/delegated.mjs). The machine is
586
+ * the one `teams connect` enrolled here, or `--machine NAME`. The workplace credential is a session token, from
587
+ * --token-stdin or WORKPLACE_TOKEN. `unpublish SURFACE` retires it.
588
+ */
589
+ async function publishCommand(verb, sub, { flags, rest, output, context, team, home, env }) {
590
+ only(flags, ['json', 'help', 'context', 'room', 'workplace', 'organization', 'name', 'key', 'machine', 'token_stdin']);
591
+ if (!flags.room || !flags.workplace || !flags.organization) usage(`teams ${verb} needs --room ROOM --workplace ORIGIN --organization ORG`);
592
+ const workplace = new URL(flags.workplace).origin;
593
+ const token = flags.token_stdin ? readFileSync(0, 'utf8').trim() : env.WORKPLACE_TOKEN;
594
+ if (!token) usage('the workplace credential comes from --token-stdin or WORKPLACE_TOKEN');
595
+ const roomPath = `${workplace}/api/v3/rooms/${encodeURIComponent(flags.room)}/surfaces`;
596
+ let url, body;
597
+ if (verb === 'unpublish') {
598
+ requireArgs([sub, ...rest].filter(Boolean), 1, 1, 'teams unpublish SURFACE --room ROOM --workplace ORIGIN --organization ORG');
599
+ url = `${roomPath}/${encodeURIComponent(sub)}/retire`; body = {};
600
+ } else {
601
+ if (sub !== 'pane' && sub !== 'root') usage('teams publish pane CONTEXT_KEY|root PATH --room ROOM --workplace ORIGIN --organization ORG [--name NAME] [--key KEY] [--machine NAME]');
602
+ requireArgs(rest, 1, 1, `teams publish ${sub}`);
603
+ const machine = flags.machine
604
+ ? resolveName((await team.machines.list()).items ?? [], flags.machine, item => item.name, item => item.id, 'machine')
605
+ : new WorkspaceContexts(home).connector(context.server_id, context.team_id).read()?.machine?.id;
606
+ if (!machine) usage('this machine is not connected to the team: run teams connect, or name --machine');
607
+ // A folder on this machine is its real path; on another machine, the path as given there.
608
+ const target = sub === 'pane' ? `pane:${rest[0]}` : `root:${flags.machine ? rest[0] : realpathSync(resolve(rest[0]))}`;
609
+ if (sub === 'root' && !target.startsWith('root:/')) usage('a folder on another machine is named by its absolute path');
610
+ const label = sub === 'pane' ? rest[0] : basename(target.slice(5)) || 'root';
611
+ const key = flags.key ?? `${sub === 'pane' ? 'pane' : 'folder'}-${label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 48) || createHash('sha256').update(target).digest('hex').slice(0, 8)}`;
612
+ url = roomPath;
613
+ body = { config: { machine, target, team: context.team_id, teamsUrl: new URL(context.origin).origin }, key, kind: sub === 'pane' ? 'pane' : 'folder', name: flags.name ?? label };
614
+ }
615
+ const response = await fetch(url, { body: JSON.stringify(body), headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json', origin: workplace, 'x-rh2-organization': String(flags.organization) }, method: 'POST' });
616
+ const payload = await response.json().catch(() => null);
617
+ if (!response.ok) throw Object.assign(new Error(payload?.error?.message ?? `the workplace answered ${response.status}`), { code: response.status === 403 || response.status === 404 ? 'forbidden' : response.status === 401 ? 'authentication_required' : 'invalid_request' });
618
+ return output({ version: 1, context: scope(context), data: payload?.data ?? null });
619
+ }
620
+
621
+ async function listQuery(flags, { client, context, team }) {
622
+ const now = Date.now(), since = flags.since ? absoluteTime(flags.since, now) : undefined, until = flags.until ? absoluteTime(flags.until, now) : undefined;
623
+ if (since !== undefined && until !== undefined && since >= until) usage('--since must precede --until');
624
+ const query = { ...optional(flags, ['harness', 'cursor']), limit: positiveInteger(flags.limit, 50, 'limit'), include_children: Boolean(flags.include_children), ...(since !== undefined ? { since } : {}), ...(until !== undefined ? { until } : {}) };
625
+ if (flags.repo) query.repo = repositoryKey(flags.repo, flags.cwd);
626
+ if (flags.user) query.user = flags.user === 'me' || flags.user.startsWith('usr_') ? flags.user : await resolveName((await client.request('GET', teamPath(context, '/members'))).items, flags.user, item => item.principal?.display_name ?? item.display_name, item => item.principal?.id ?? item.principal_id ?? item.id, 'user');
627
+ if (flags.machine) query.machine = flags.machine.startsWith('mach_') ? flags.machine : await resolveName((await team.machines.list()).items, flags.machine, item => item.name, item => item.id, 'machine');
628
+ if (flags.source) query.source = flags.source.startsWith('src_') ? flags.source : await resolveName((await team.sources.list()).items, flags.source, item => item.name, item => item.id, 'source');
629
+ return query;
630
+ }
631
+ function absoluteTime(value, now) { if (/^\d{4}-\d\d-\d\dT/.test(value)) { const time = Date.parse(value); if (Number.isFinite(time)) return time; } return now - duration(value); }
632
+ function resolveName(items, name, label, id, kind) { const matches = items.filter(item => label(item) === name); if (matches.length > 1) throw Object.assign(new Error(`ambiguous ${kind} name ${name}`), { code: 'ambiguous_reference', exitCode: 2 }); if (!matches.length) notFound(`${kind} ${name} is not accessible`); return id(matches[0]); }
633
+ function repositoryKey(value, cwd) { if (value !== '.') return value; let remotes; try { remotes = execFileSync('git', ['config', '--get-all', 'remote.origin.url'], { cwd, encoding: 'utf8' }).trim().split('\n').filter(Boolean); } catch { usage('--repo . requires one origin remote'); } const keys = [...new Set(remotes.map(remoteKey))]; if (keys.length !== 1) usage('--repo . requires exactly one distinct origin remote'); return keys[0]; }
634
+ function remoteKey(value) { const scp = /^(?:[^@]+@)?([^:]+):(.+)$/.exec(value); if (scp && !value.includes('://')) return `${scp[1]}/${scp[2].replace(/\.git$/, '')}`; const url = new URL(value); return `${url.host}${url.pathname}`.replace(/\.git$/, '').replace(/\/$/, ''); }
635
+ function optional(object, keys) { return Object.fromEntries(keys.filter(key => object[key] !== undefined).map(key => [key, key === 'revision' ? positiveInteger(object[key], undefined, key) : object[key]])); }
636
+ async function waitForOperation(client, context, operation, timeoutMs = 15 * 60_000) {
637
+ let current = operation, delayMs = 250; const deadline = Date.now() + timeoutMs;
638
+ while (['pending', 'running'].includes(current.state)) {
639
+ if (Date.now() >= deadline) throw Object.assign(new Error(`operation ${current.id} did not finish before the client timeout`), { code: 'controller_busy', exitCode: 6 });
640
+ await new Promise(resolveWait => setTimeout(resolveWait, Math.min(delayMs, Math.max(1, deadline - Date.now()))));
641
+ current = (await client.request('GET', teamPath(context, `/operations/${current.id}`))).data;
642
+ delayMs = Math.min(delayMs * 2, 5_000);
643
+ }
644
+ if (current.state === 'failed') throw Object.assign(new Error(current.error?.message ?? `operation ${current.id} failed`), current.error ?? { code: 'invalid_request' });
645
+ return current;
646
+ }
647
+ async function waitForContinuation(client, context, continuation, timeoutMs = 15 * 60_000) {
648
+ let current = continuation, delayMs = 250; const deadline = Date.now() + timeoutMs;
649
+ while (current.state === 'preparing') {
650
+ if (Date.now() >= deadline) throw Object.assign(new Error(`continuation ${current.id} was not prepared before the client timeout`), { code: 'controller_busy', exitCode: 6 });
651
+ await new Promise(resolveWait => setTimeout(resolveWait, Math.min(delayMs, Math.max(1, deadline - Date.now()))));
652
+ current = (await client.request('GET', teamPath(context, `/continuations/${current.id}`))).data;
653
+ delayMs = Math.min(delayMs * 2, 5_000);
654
+ }
655
+ return current;
656
+ }
657
+ async function downloadArtifact(client, context, sessionId, file) {
658
+ if (!/^upload_[a-z0-9]+$/i.test(file.upload_id) || !Number.isSafeInteger(file.byte_length) || file.byte_length < 0 || !/^[a-f0-9]{64}$/.test(file.sha256)) throw new Error('export returned an invalid artifact manifest');
659
+ const response = await client.fetch(client.server + teamPath(context, `/sessions/${sessionId}/artifacts/${file.upload_id}`), { redirect: 'manual', headers: { Authorization: `Bearer ${client.credential}` } });
660
+ if (response.type === 'opaqueredirect' || response.status >= 300 && response.status < 400) throw new Error('cross-origin artifact redirects are refused');
661
+ if (!response.ok) { let failure; try { failure = await response.json(); } catch {} throw Object.assign(new Error(failure?.error?.message ?? `artifact download failed: HTTP ${response.status}`), failure?.error, { status: response.status }); }
662
+ const bytes = Buffer.from(await response.arrayBuffer());
663
+ if (bytes.byteLength !== file.byte_length || createHash('sha256').update(bytes).digest('hex') !== file.sha256) throw new Error(`artifact ${file.relative_name} failed length or SHA-256 verification`);
664
+ return bytes;
665
+ }
666
+ function fidelitySatisfies(actual, requested) { return ({ semantic: 0, value_lossless: 1, byte_lossless: 2 }[actual] ?? -1) >= ({ semantic: 0, value_lossless: 1, byte_lossless: 2 }[requested] ?? Infinity); }
667
+ function only(flags, allowed) { for (const key of Object.keys(flags)) if (!allowed.includes(key)) usage(`--${key.replaceAll('_', '-')} is not valid for this command`); }
668
+ function backfill(value) { const parsed = duration(value, undefined, { allowWords: true }); return parsed === 'all' || parsed === 'none' ? { mode: parsed } : { mode: 'since', since: Date.now() - parsed }; }
669
+ function teamPath(context, suffix) { return `/api/v1/teams/${context.team_id}${suffix}`; }
670
+ function scope(context) { return { server_id: context.server_id, team_id: context.team_id, principal_id: context.principal_id }; }
671
+ /** An invitation on stdin: the file `members invite --out` writes, or its bare enrollment code. */
672
+ function inviteCode(secret) { if (!secret.startsWith('{')) return secret; let parsed; try { parsed = JSON.parse(secret); } catch { usage('--invite-stdin must be an invitation file or its enrollment code'); } if (typeof parsed?.enrollment_code !== 'string') usage('the invitation file has no enrollment_code'); return parsed.enrollment_code; }
673
+ function hostPort(text) { const match = /^(.*):(\d+)$/.exec(text); if (!match || !match[1]) usage('--listen must be HOST:PORT'); return { host: match[1], port: Number(match[2]) }; }
674
+ function sessionReference(value, selectedOrigin, selectedTeam, explicit) { if (!/^https?:\/\//.test(value)) return value; const url = new URL(value), parts = url.pathname.split('/').filter(Boolean); if (explicit && url.origin !== new URL(selectedOrigin).origin) usage('session URL origin conflicts with the explicit context'); if (url.origin !== new URL(selectedOrigin).origin) unsupported('the URL origin is not authenticated by the selected context'); if (parts.length !== 4 || parts[0] !== 'teams' || parts[1] !== selectedTeam || parts[2] !== 'sessions') usage('session URL must identify the selected immutable team and session'); return parts[3]; }
675
+ function contextForUrl(document, value) { const url = new URL(value); const parts = url.pathname.split('/').filter(Boolean); const teamAt = parts.indexOf('teams'); const team = teamAt >= 0 ? parts[teamAt + 1] : null; const matches = Object.entries(document.contexts).filter(([, context]) => context.origin === url.origin && (!team || context.team_id === team)); if (matches.length > 1) usage('session URL matches more than one authenticated context'); return matches[0]?.[0] ?? null; }
676
+ function humanOutput(value) {
677
+ const context = value?.context ? `Context: ${value.context.server_id}/${value.context.team_id}\n` : '';
678
+ if (Array.isArray(value?.items)) {
679
+ if (!value.items.length) return `${context}No items.\n`;
680
+ const rows = value.items.map(humanRow), columns = [...new Set(rows.flatMap(row => Object.keys(row)))];
681
+ const widths = columns.map(column => Math.max(column.length, ...rows.map(row => String(row[column] ?? '').length)));
682
+ const line = row => columns.map((column, index) => String(row[column] ?? '').padEnd(widths[index])).join(' ').trimEnd();
683
+ return `${context}${line(Object.fromEntries(columns.map(column => [column, column])))}\n${rows.map(line).join('\n')}\n${value.next_cursor ? `Next cursor: ${value.next_cursor}\n` : ''}`;
684
+ }
685
+ // A session read: its header, then the conversation as a person reads it.
686
+ if (value?.descriptor && Array.isArray(value.messages)) return `${context}${humanTranscript(value.descriptor, value.messages)}${value.before_cursor ? `Earlier: --before ${value.before_cursor}\n` : ''}`;
687
+ const data = value?.data ?? value;
688
+ if (data === null) return `${context}Done.\n`;
689
+ if (typeof data === 'string') return `${context}${data}\n`;
690
+ const row = humanRow(data);
691
+ return `${context}${Object.entries(row).map(([key, item]) => `${key}: ${item}`).join('\n')}\n`;
692
+ }
693
+ function humanTranscript(descriptor, messages) {
694
+ const when = descriptor.recorded_activity_at ? new Date(descriptor.recorded_activity_at).toISOString() : 'unknown';
695
+ const head = `${descriptor.title ?? descriptor.id} · ${descriptor.harness ?? 'unknown harness'} · ${descriptor.user?.principal_id ?? descriptor.user?.reported_user ?? 'unknown user'} · last activity ${when}\n`;
696
+ const coverage = descriptor.coverage && descriptor.coverage.completeness !== 'complete' ? `coverage: ${descriptor.coverage.completeness}${descriptor.coverage.reasons?.length ? ` (${descriptor.coverage.reasons.join(', ')})` : ''}\n` : '';
697
+ const text = content => typeof content === 'string' ? content : Array.isArray(content) ? content.map(part => typeof part === 'string' ? part : part?.text ?? (part?.type ? `[${part.type}]` : '')).filter(Boolean).join('\n') : content == null ? '' : JSON.stringify(content);
698
+ const calls = value => (value.tool_calls ?? []).map(call => `[${call.function?.name ?? call.name ?? 'tool'}] ${String(call.function?.arguments ?? call.arguments ?? '').slice(0, 200)}`).join('\n');
699
+ const lines = messages.map(message => { const value = message.value ?? message; const body = [text(value.content ?? value.text).trim(), calls(value)].filter(Boolean).join('\n'); return `\n${value.role ?? 'message'}:\n${body || '(no text)'}\n`; });
700
+ return head + coverage + (lines.length ? lines.join('') : '\n(no messages in this window)\n');
701
+ }
702
+ function humanRow(value) { if (!value || typeof value !== 'object') return { value }; const descriptor = value.session ?? value; return Object.fromEntries(Object.entries(descriptor).filter(([, item]) => item === null || ['string', 'number', 'boolean'].includes(typeof item)).slice(0, 8)); }
703
+ async function stream(iterator, json) { for await (const event of iterator) process.stdout.write(json ? `${JSON.stringify(event)}\n` : humanOutput(event)); }
704
+ function unsupported(message) { throw Object.assign(new Error(message), { code: 'unsupported', exitCode: 5 }); }
705
+ function notFound(message) { throw Object.assign(new Error(message), { code: 'not_found', exitCode: 4 }); }
706
+ function printHelp() {
707
+ process.stdout.write(`supercode workspace commands:
708
+ context create|list|show|use|remove
709
+ teams list|create
710
+ login [ORIGIN] [--team TEAM] [--as CONTEXT] [--token-stdin|--invite-stdin] (Volter sign-in in the browser by default)
711
+ identity link
712
+ server start [--volter-issuer ORIGIN] [--delegated-issuer ORIGIN]
713
+ publish pane|root TARGET --room ROOM --workplace ORIGIN --organization ORG [--name] [--key] [--machine] [--token-stdin]
714
+ unpublish SURFACE --room ROOM --workplace ORIGIN --organization ORG [--token-stdin]
715
+ connect|disconnect|status
716
+ members invite|list|remove|role
717
+ groups list|create|remove|add-member|remove-member
718
+ sources create|list|status|pause|revoke|credentials|config
719
+ machines list|roots|access|authorize-continuation
720
+ sync add|list|show|pause|resume|remove
721
+ sessions list|search|show|follow|access|rename|delete|url|attach|export|continue
722
+ Use --help with a command for its required arguments and options.
723
+ `);
724
+ }
725
+ export function errorExit(error) { return error.exitCode ?? ERROR_EXITS[error.code] ?? 1; }
726
+ if (import.meta.url === new URL(`file://${process.argv[1]}`).href) workspaceMain().catch(error => { process.stderr.write(`${JSON.stringify({ version: 1, error: { code: error.code ?? 'invalid_request', message: error.message } })}\n`); process.exit(errorExit(error)); });