@ours.network/fleet 0.19.0-nightly.1 → 0.19.0-nightly.3

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.
@@ -0,0 +1,15 @@
1
+ import type { RoomsConfig, TasksConfig, RoomTemplatesConfig } from './types.js';
2
+ export declare class RoomsTasksConfigError extends Error {
3
+ readonly path: string;
4
+ constructor(path: string, message: string);
5
+ }
6
+ export declare function fingerprint(invite: string): string;
7
+ export declare function validateRoomsConfig(raw: unknown, vars: Record<string, string>, path: string): RoomsConfig & {
8
+ _invite?: {
9
+ value: string;
10
+ fingerprint: string;
11
+ };
12
+ };
13
+ export declare function validateTasksConfig(raw: unknown, path: string): TasksConfig;
14
+ export declare function validateRoomTemplatesConfig(raw: unknown, path: string): RoomTemplatesConfig;
15
+ export declare const FLEET_D_ALLOWED_KEYS: readonly ["roles", "rooms", "room_templates", "tasks"];
@@ -0,0 +1,171 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { ROOMS_KEYS as RK, ROOMS_OWNER_KEYS as ROK, ROOMS_COWORK_KEYS as RCK, ROOMS_DEFAULTS_KEYS as RDK, TASKS_KEYS as TK, TEMPLATE_KEYS as TPK, TEMPLATE_MEMBER_KEYS as TMK, TEMPLATE_OVERRIDE_KEYS as TOK, } from './types.js';
4
+ import { BUILTIN_TEMPLATES } from './templates.js';
5
+ const isPlainObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
6
+ const CID_RE = /^[0-9a-fA-F]{64}$/;
7
+ export class RoomsTasksConfigError extends Error {
8
+ path;
9
+ constructor(path, message) {
10
+ super(`${path}: ${message}`);
11
+ this.path = path;
12
+ }
13
+ }
14
+ function rejectUnknown(obj, allowed, path, section) {
15
+ const bad = Object.keys(obj).filter(k => !allowed.includes(k));
16
+ if (bad.length)
17
+ throw new RoomsTasksConfigError(path, `${section} has unknown key(s) ${bad.join(', ')}; allowed: ${allowed.join(', ')}`);
18
+ }
19
+ export function fingerprint(invite) {
20
+ return createHash('sha256').update(invite.trim()).digest('hex');
21
+ }
22
+ function resolveInvite(owner, vars, path) {
23
+ const inline = owner.public_invite;
24
+ const file = owner.public_invite_file;
25
+ if (inline && file)
26
+ throw new RoomsTasksConfigError(path, 'rooms.owner: exactly one of public_invite or public_invite_file');
27
+ if (!inline && !file)
28
+ return undefined;
29
+ let raw;
30
+ if (file) {
31
+ const resolved = file.replace(/\$\{(\w+)\}/g, (m, k) => (k in vars ? String(vars[k]) : m));
32
+ if (!existsSync(resolved))
33
+ throw new RoomsTasksConfigError(path, `rooms.owner.public_invite_file: not found: ${resolved}`);
34
+ raw = readFileSync(resolved, 'utf8').trim();
35
+ }
36
+ else {
37
+ raw = inline;
38
+ }
39
+ if (!raw)
40
+ throw new RoomsTasksConfigError(path, 'rooms.owner: invite is empty');
41
+ return { value: raw, fingerprint: fingerprint(raw) };
42
+ }
43
+ export function validateRoomsConfig(raw, vars, path) {
44
+ if (!isPlainObject(raw))
45
+ throw new RoomsTasksConfigError(path, 'rooms: must be a mapping');
46
+ rejectUnknown(raw, RK, path, 'rooms');
47
+ const provider = raw.provider ?? 'cowork';
48
+ if (provider !== 'cowork')
49
+ throw new RoomsTasksConfigError(path, `rooms.provider: unsupported '${provider}'; allowed: cowork`);
50
+ let cowork;
51
+ if (raw.cowork !== undefined) {
52
+ if (!isPlainObject(raw.cowork))
53
+ throw new RoomsTasksConfigError(path, 'rooms.cowork: must be a mapping');
54
+ rejectUnknown(raw.cowork, RCK, path, 'rooms.cowork');
55
+ cowork = { config: raw.cowork.config };
56
+ }
57
+ if (!isPlainObject(raw.owner))
58
+ throw new RoomsTasksConfigError(path, 'rooms.owner: required and must be a mapping');
59
+ rejectUnknown(raw.owner, ROK, path, 'rooms.owner');
60
+ const ownerProvider = raw.owner.provider ?? 'messenger-server';
61
+ const expectedCid = raw.owner.expected_cid;
62
+ if (!expectedCid)
63
+ throw new RoomsTasksConfigError(path, 'rooms.owner.expected_cid: required');
64
+ if (!CID_RE.test(expectedCid))
65
+ throw new RoomsTasksConfigError(path, 'rooms.owner.expected_cid: must be exactly 64 hexadecimal characters');
66
+ const ownerRole = raw.owner.role ?? 'Owner';
67
+ const invite = resolveInvite(raw.owner, vars, path);
68
+ const owner = {
69
+ provider: ownerProvider,
70
+ expected_cid: expectedCid.toLowerCase(),
71
+ role: ownerRole,
72
+ ...(raw.owner.public_invite ? { public_invite: '[REDACTED]' } : {}),
73
+ ...(raw.owner.public_invite_file ? { public_invite_file: raw.owner.public_invite_file } : {}),
74
+ };
75
+ let defaults;
76
+ if (raw.defaults !== undefined) {
77
+ if (!isPlainObject(raw.defaults))
78
+ throw new RoomsTasksConfigError(path, 'rooms.defaults: must be a mapping');
79
+ rejectUnknown(raw.defaults, RDK, path, 'rooms.defaults');
80
+ defaults = {
81
+ template: raw.defaults.template,
82
+ attach_owner: raw.defaults.attach_owner,
83
+ close_when_task_done: raw.defaults.close_when_task_done,
84
+ };
85
+ }
86
+ return { provider, cowork, owner, defaults, _invite: invite };
87
+ }
88
+ export function validateTasksConfig(raw, path) {
89
+ if (!isPlainObject(raw))
90
+ throw new RoomsTasksConfigError(path, 'tasks: must be a mapping');
91
+ rejectUnknown(raw, TK, path, 'tasks');
92
+ const createMode = raw.create_mode;
93
+ if (createMode !== undefined && createMode !== 'start' && createMode !== 'backlog')
94
+ throw new RoomsTasksConfigError(path, `tasks.create_mode: must be 'start' or 'backlog'`);
95
+ if (raw.close_room_on_done !== undefined && typeof raw.close_room_on_done !== 'boolean')
96
+ throw new RoomsTasksConfigError(path, 'tasks.close_room_on_done: must be a boolean');
97
+ if (raw.retain_completed_for !== undefined && typeof raw.retain_completed_for !== 'string')
98
+ throw new RoomsTasksConfigError(path, 'tasks.retain_completed_for: must be a duration string');
99
+ return {
100
+ default_room_template: raw.default_room_template,
101
+ create_mode: createMode,
102
+ close_room_on_done: raw.close_room_on_done,
103
+ retain_completed_for: raw.retain_completed_for,
104
+ };
105
+ }
106
+ export function validateRoomTemplatesConfig(raw, path) {
107
+ if (!isPlainObject(raw))
108
+ throw new RoomsTasksConfigError(path, 'room_templates: must be a mapping');
109
+ const result = {};
110
+ for (const [name, tplRaw] of Object.entries(raw)) {
111
+ if (!/^[a-z0-9_-]+$/.test(name))
112
+ throw new RoomsTasksConfigError(path, `room_templates: invalid template name '${name}' (allowed: [a-z0-9_-])`);
113
+ if (!isPlainObject(tplRaw))
114
+ throw new RoomsTasksConfigError(path, `room_templates.${name}: must be a mapping`);
115
+ rejectUnknown(tplRaw, [...TPK, 'name'], path, `room_templates.${name}`);
116
+ const isBuiltinOverride = BUILTIN_TEMPLATES.some(b => b.name === name);
117
+ if (isBuiltinOverride && !tplRaw.override_builtin)
118
+ throw new RoomsTasksConfigError(path, `room_templates.${name}: overrides built-in template; set override_builtin: true and bump version`);
119
+ const version = tplRaw.version;
120
+ if (version === undefined || !Number.isInteger(version) || version < 1)
121
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.version: required positive integer`);
122
+ if (!tplRaw.description || typeof tplRaw.description !== 'string')
123
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.description: required string`);
124
+ if (!Array.isArray(tplRaw.members) || !tplRaw.members.length)
125
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.members: required non-empty array`);
126
+ const members = tplRaw.members.map((m, i) => {
127
+ if (!isPlainObject(m))
128
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}]: must be a mapping`);
129
+ rejectUnknown(m, TMK, path, `room_templates.${name}.members[${i}]`);
130
+ if (!m.slot || typeof m.slot !== 'string')
131
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}].slot: required string`);
132
+ if (!m.role || typeof m.role !== 'string')
133
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}].role: required string`);
134
+ if (!Number.isInteger(m.count) || m.count < 1)
135
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}].count: required positive integer`);
136
+ if (!m.role_ref || typeof m.role_ref !== 'string')
137
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}].role_ref: required string`);
138
+ if (m.overrides !== undefined) {
139
+ if (!isPlainObject(m.overrides))
140
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}].overrides: must be a mapping`);
141
+ rejectUnknown(m.overrides, TOK, path, `room_templates.${name}.members[${i}].overrides`);
142
+ }
143
+ return {
144
+ slot: m.slot,
145
+ role: m.role,
146
+ count: m.count,
147
+ role_ref: m.role_ref,
148
+ overrides: m.overrides,
149
+ };
150
+ });
151
+ let room;
152
+ if (tplRaw.room !== undefined) {
153
+ if (!isPlainObject(tplRaw.room))
154
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.room: must be a mapping`);
155
+ room = {
156
+ quiet_membership: tplRaw.room.quiet_membership,
157
+ anonymous: tplRaw.room.anonymous,
158
+ };
159
+ }
160
+ result[name] = {
161
+ name,
162
+ version: version,
163
+ description: tplRaw.description,
164
+ room,
165
+ contract: tplRaw.contract,
166
+ members,
167
+ };
168
+ }
169
+ return result;
170
+ }
171
+ export const FLEET_D_ALLOWED_KEYS = ['roles', 'rooms', 'room_templates', 'tasks'];
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Typed client for the ours-cowork v1 private management-socket protocol.
3
+ *
4
+ * Cowork owns room state. Fleet only invokes the versioned JSON RPC exposed
5
+ * by Cowork's Unix socket and projects the response into orchestration data.
6
+ */
7
+ import type { Socket } from 'node:net';
8
+ export interface CoworkRoomCreateResult {
9
+ room_id: string;
10
+ identity_name: string;
11
+ identity_cid: string;
12
+ }
13
+ export interface CoworkInviteAcceptResult {
14
+ seat_cid: string;
15
+ seat_state: 'pending' | 'active';
16
+ }
17
+ export interface CoworkInviteResult {
18
+ invite: string;
19
+ min_accepts: number;
20
+ }
21
+ export interface CoworkSeatInfo {
22
+ identity_cid: string;
23
+ role: string;
24
+ seat_state: 'pending' | 'active' | 'removed';
25
+ }
26
+ export interface CoworkRoomInfo {
27
+ room_id: string;
28
+ identity_name: string;
29
+ identity_cid: string;
30
+ room_name: string;
31
+ state: 'provisioning' | 'active' | 'closing' | 'closed';
32
+ seats: CoworkSeatInfo[];
33
+ goal?: string;
34
+ briefing?: string;
35
+ }
36
+ export interface CoworkAdapter {
37
+ available(): Promise<boolean>;
38
+ createRoom(opts: {
39
+ room_name: string;
40
+ goal: string;
41
+ briefing: string;
42
+ quiet_membership?: boolean;
43
+ anonymous?: boolean;
44
+ }): Promise<CoworkRoomCreateResult>;
45
+ acceptInvite(roomId: string, invite: string, opts: {
46
+ role: string;
47
+ expected_cid: string;
48
+ }): Promise<CoworkInviteAcceptResult>;
49
+ issueInvite(roomId: string, opts: {
50
+ role: string;
51
+ min_accepts: number;
52
+ }): Promise<CoworkInviteResult>;
53
+ getRoom(roomId: string): Promise<CoworkRoomInfo | undefined>;
54
+ listRooms(): Promise<CoworkRoomInfo[]>;
55
+ closeRoom(roomId: string): Promise<void>;
56
+ getSeats(roomId: string): Promise<CoworkSeatInfo[]>;
57
+ recoverRoom(roomId: string): Promise<CoworkRoomInfo>;
58
+ }
59
+ export declare class CoworkUnavailableError extends Error {
60
+ constructor(message?: string, options?: ErrorOptions);
61
+ }
62
+ export declare class CoworkProtocolError extends Error {
63
+ readonly operation: string;
64
+ readonly code: string;
65
+ constructor(operation: string, message: string, code?: string, options?: ErrorOptions);
66
+ }
67
+ export interface CoworkAdapterOptions {
68
+ configPath?: string;
69
+ socketPath?: string;
70
+ timeoutMs?: number;
71
+ env?: NodeJS.ProcessEnv;
72
+ home?: string;
73
+ connect?: (path: string) => Socket;
74
+ }
75
+ /** Resolve the same config/state inputs as ours-cowork and return its Unix socket. */
76
+ export declare function resolveCoworkSocketPath(options?: CoworkAdapterOptions): string;
77
+ export declare function createCoworkAdapter(options?: CoworkAdapterOptions): CoworkAdapter;
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Typed client for the ours-cowork v1 private management-socket protocol.
3
+ *
4
+ * Cowork owns room state. Fleet only invokes the versioned JSON RPC exposed
5
+ * by Cowork's Unix socket and projects the response into orchestration data.
6
+ */
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import { homedir } from 'node:os';
9
+ import { join, resolve } from 'node:path';
10
+ import { createConnection } from 'node:net';
11
+ const RPC_VERSION = 1;
12
+ const DEFAULT_TIMEOUT_MS = 10_000;
13
+ const MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
14
+ export class CoworkUnavailableError extends Error {
15
+ constructor(message = 'Cowork management socket is not reachable', options) {
16
+ super(message, options);
17
+ this.name = 'CoworkUnavailableError';
18
+ }
19
+ }
20
+ export class CoworkProtocolError extends Error {
21
+ operation;
22
+ code;
23
+ constructor(operation, message, code = 'protocol_error', options) {
24
+ super(`cowork ${operation}: ${message}`, options);
25
+ this.operation = operation;
26
+ this.code = code;
27
+ this.name = 'CoworkProtocolError';
28
+ }
29
+ }
30
+ function object(value, operation, label) {
31
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
32
+ throw new CoworkProtocolError(operation, `${label} must be an object`);
33
+ return value;
34
+ }
35
+ function string(value, operation, label) {
36
+ if (typeof value !== 'string' || value.length === 0)
37
+ throw new CoworkProtocolError(operation, `${label} must be a non-empty string`);
38
+ return value;
39
+ }
40
+ function text(value, operation, label) {
41
+ if (typeof value !== 'string')
42
+ throw new CoworkProtocolError(operation, `${label} must be a string`);
43
+ return value;
44
+ }
45
+ function roomState(value, operation) {
46
+ if (value !== 'provisioning' && value !== 'active' && value !== 'closing' && value !== 'closed')
47
+ throw new CoworkProtocolError(operation, 'room state is invalid');
48
+ return value;
49
+ }
50
+ function seatState(value, operation) {
51
+ if (value !== 'pending' && value !== 'active' && value !== 'removed')
52
+ throw new CoworkProtocolError(operation, 'seat state is invalid');
53
+ return value;
54
+ }
55
+ function projectSeat(value, operation) {
56
+ const seat = object(value, operation, 'seat');
57
+ return {
58
+ identity_cid: string(seat.identity, operation, 'seat.identity'),
59
+ role: string(seat.role, operation, 'seat.role'),
60
+ seat_state: seatState(seat.state, operation),
61
+ };
62
+ }
63
+ function projectRoom(value, operation) {
64
+ const room = object(value, operation, 'room');
65
+ if (!Array.isArray(room.seats))
66
+ throw new CoworkProtocolError(operation, 'room.seats must be an array');
67
+ const mission = room.mission === undefined ? undefined : object(room.mission, operation, 'room.mission');
68
+ return {
69
+ room_id: string(room.room_id, operation, 'room.room_id'),
70
+ identity_name: string(room.identity_name, operation, 'room.identity_name'),
71
+ // Cowork reserves an empty CID only for its durable packet_pending
72
+ // recovery sentinel; list/show must preserve that recoverable state.
73
+ identity_cid: text(room.identity_cid, operation, 'room.identity_cid'),
74
+ room_name: string(room.room_name, operation, 'room.room_name'),
75
+ state: roomState(room.state, operation),
76
+ seats: room.seats.map((seat) => projectSeat(seat, operation)),
77
+ ...(typeof mission?.goal === 'string' ? { goal: mission.goal } : {}),
78
+ ...(typeof mission?.briefing === 'string' ? { briefing: mission.briefing } : {}),
79
+ };
80
+ }
81
+ /** Resolve the same config/state inputs as ours-cowork and return its Unix socket. */
82
+ export function resolveCoworkSocketPath(options = {}) {
83
+ if (options.socketPath)
84
+ return resolve(options.socketPath);
85
+ const env = options.env ?? process.env;
86
+ const home = options.home ?? homedir();
87
+ const configuredPath = options.configPath ?? env.OURS_COWORK_CONFIG;
88
+ const configPath = resolve(configuredPath ?? join(home, '.ours-cowork', 'config.json'));
89
+ let stateDir = resolve(home, '.ours-cowork');
90
+ if (existsSync(configPath)) {
91
+ let parsed;
92
+ try {
93
+ parsed = JSON.parse(readFileSync(configPath, 'utf8'));
94
+ }
95
+ catch (error) {
96
+ throw new CoworkProtocolError('config', `malformed config at ${configPath}`, 'invalid_config', { cause: error });
97
+ }
98
+ const config = object(parsed, 'config', 'config');
99
+ if (config.version !== 1 || typeof config.stateDir !== 'string' || !config.stateDir)
100
+ throw new CoworkProtocolError('config', `invalid Cowork v1 config at ${configPath}`, 'invalid_config');
101
+ stateDir = resolve(config.stateDir);
102
+ }
103
+ else if (configuredPath !== undefined) {
104
+ throw new CoworkProtocolError('config', `configured Cowork config does not exist: ${configPath}`, 'invalid_config');
105
+ }
106
+ if (env.OURS_COWORK_STATE_DIR)
107
+ stateDir = resolve(env.OURS_COWORK_STATE_DIR);
108
+ return join(stateDir, 'management.sock');
109
+ }
110
+ function rpcCall(socketPath, method, params, timeoutMs, connect) {
111
+ const id = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
112
+ const request = `${JSON.stringify({ version: RPC_VERSION, id, method, params })}\n`;
113
+ return new Promise((resolveCall, rejectCall) => {
114
+ const socket = connect(socketPath);
115
+ let bytes = '';
116
+ let size = 0;
117
+ let settled = false;
118
+ let connected = false;
119
+ const finishError = (error) => {
120
+ if (settled)
121
+ return;
122
+ settled = true;
123
+ clearTimeout(timer);
124
+ socket.destroy();
125
+ rejectCall(error);
126
+ };
127
+ const timer = setTimeout(() => finishError(new CoworkUnavailableError(connected ? 'Cowork did not answer the management socket' : 'Cowork management socket is not reachable')), timeoutMs);
128
+ socket.setEncoding('utf8');
129
+ socket.once('connect', () => {
130
+ connected = true;
131
+ socket.write(request);
132
+ });
133
+ socket.on('data', (chunk) => {
134
+ if (settled)
135
+ return;
136
+ const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
137
+ size += Buffer.byteLength(text);
138
+ if (size > MAX_RESPONSE_BYTES) {
139
+ finishError(new CoworkProtocolError(method, 'response exceeded 4 MiB'));
140
+ return;
141
+ }
142
+ bytes += text;
143
+ const newline = bytes.indexOf('\n');
144
+ if (newline < 0)
145
+ return;
146
+ if (bytes.slice(newline + 1).trim() !== '') {
147
+ finishError(new CoworkProtocolError(method, 'daemon returned more than one response'));
148
+ return;
149
+ }
150
+ let response;
151
+ try {
152
+ response = object(JSON.parse(bytes.slice(0, newline)), method, 'RPC response');
153
+ }
154
+ catch (error) {
155
+ finishError(error instanceof CoworkProtocolError
156
+ ? error : new CoworkProtocolError(method, 'daemon returned malformed JSON', 'protocol_error', { cause: error }));
157
+ return;
158
+ }
159
+ if (response.version !== RPC_VERSION || response.id !== id
160
+ || (Object.hasOwn(response, 'error') === Object.hasOwn(response, 'result'))) {
161
+ finishError(new CoworkProtocolError(method, 'daemon returned an invalid RPC response'));
162
+ return;
163
+ }
164
+ settled = true;
165
+ clearTimeout(timer);
166
+ socket.destroy();
167
+ if (Object.hasOwn(response, 'error')) {
168
+ const error = object(response.error, method, 'RPC error');
169
+ rejectCall(new CoworkProtocolError(method, typeof error.message === 'string' ? error.message : 'unknown RPC error', typeof error.code === 'string' ? error.code : 'rpc_error'));
170
+ }
171
+ else {
172
+ resolveCall(response.result);
173
+ }
174
+ });
175
+ socket.once('end', () => {
176
+ if (!settled)
177
+ finishError(new CoworkProtocolError(method, 'daemon closed without a complete response'));
178
+ });
179
+ socket.once('error', (error) => {
180
+ if (!settled)
181
+ finishError(new CoworkUnavailableError(error.code === 'EACCES' || error.code === 'EPERM'
182
+ ? 'Cowork management socket access denied' : 'Cowork management socket is not reachable', { cause: error }));
183
+ });
184
+ });
185
+ }
186
+ export function createCoworkAdapter(options = {}) {
187
+ const socketPath = resolveCoworkSocketPath(options);
188
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
189
+ const connect = options.connect ?? ((path) => createConnection(path));
190
+ const call = (method, params) => rpcCall(socketPath, method, params, timeoutMs, connect);
191
+ return {
192
+ async available() {
193
+ try {
194
+ await call('room.list', {});
195
+ return true;
196
+ }
197
+ catch {
198
+ return false;
199
+ }
200
+ },
201
+ async createRoom(opts) {
202
+ const result = projectRoom(await call('room.create', {
203
+ name: opts.room_name,
204
+ goal: opts.goal,
205
+ briefing: opts.briefing,
206
+ ...(opts.quiet_membership === undefined ? {} : { quiet_membership: opts.quiet_membership }),
207
+ ...(opts.anonymous === undefined ? {} : { anonymous: opts.anonymous }),
208
+ }), 'room.create');
209
+ if (!result.identity_cid)
210
+ throw new CoworkProtocolError('room.create', 'created room did not establish an identity CID');
211
+ return { room_id: result.room_id, identity_name: result.identity_name, identity_cid: result.identity_cid };
212
+ },
213
+ async acceptInvite(roomId, invite, opts) {
214
+ const receipt = object(await call('room.accept', {
215
+ room_id: roomId,
216
+ role: opts.role,
217
+ invite,
218
+ expected_cid: opts.expected_cid,
219
+ }), 'room.accept', 'receipt');
220
+ const state = seatState(receipt.state, 'room.accept');
221
+ if (state === 'removed')
222
+ throw new CoworkProtocolError('room.accept', 'accepted seat cannot be removed');
223
+ return {
224
+ seat_cid: string(receipt.identity, 'room.accept', 'receipt.identity'),
225
+ seat_state: state,
226
+ };
227
+ },
228
+ async issueInvite(roomId, opts) {
229
+ const receipt = object(await call('room.invite', {
230
+ room_id: roomId,
231
+ mode: 'public',
232
+ role: opts.role,
233
+ min_accepts: opts.min_accepts,
234
+ }), 'room.invite', 'receipt');
235
+ const invite = object(receipt.invite, 'room.invite', 'receipt.invite');
236
+ return {
237
+ invite: string(receipt.blob, 'room.invite', 'receipt.blob'),
238
+ min_accepts: typeof invite.min_accepts === 'number'
239
+ ? invite.min_accepts : opts.min_accepts,
240
+ };
241
+ },
242
+ async getRoom(roomId) {
243
+ try {
244
+ return projectRoom(await call('room.show', { room_id: roomId }), 'room.show');
245
+ }
246
+ catch (error) {
247
+ if (error instanceof CoworkProtocolError && error.code === 'not_found')
248
+ return undefined;
249
+ throw error;
250
+ }
251
+ },
252
+ async listRooms() {
253
+ const result = await call('room.list', {});
254
+ if (!Array.isArray(result))
255
+ throw new CoworkProtocolError('room.list', 'result must be an array');
256
+ return result.map((room) => projectRoom(room, 'room.list'));
257
+ },
258
+ async closeRoom(roomId) { await call('room.close', { room_id: roomId }); },
259
+ async getSeats(roomId) {
260
+ const result = await call('room.participants', { room_id: roomId });
261
+ if (!Array.isArray(result))
262
+ throw new CoworkProtocolError('room.participants', 'result must be an array');
263
+ return result.map((seat) => projectSeat(seat, 'room.participants'));
264
+ },
265
+ async recoverRoom(roomId) {
266
+ // Cowork performs packet/state reconciliation during daemon recovery.
267
+ // Its `room.recover` RPC is specifically invite-receipt recovery, so a
268
+ // Fleet reconciliation pass must read `room.show`, not mutate invites.
269
+ return projectRoom(await call('room.show', { room_id: roomId }), 'room.show');
270
+ },
271
+ };
272
+ }
@@ -0,0 +1,7 @@
1
+ export * from './types.js';
2
+ export * from './templates.js';
3
+ export * from './config.js';
4
+ export * from './task-state.js';
5
+ export * from './room-state.js';
6
+ export * from './cowork-adapter.js';
7
+ export { registerTemplateCommands, registerTaskCommands, registerRoomCommands } from './cli.js';
@@ -0,0 +1,7 @@
1
+ export * from './types.js';
2
+ export * from './templates.js';
3
+ export * from './config.js';
4
+ export * from './task-state.js';
5
+ export * from './room-state.js';
6
+ export * from './cowork-adapter.js';
7
+ export { registerTemplateCommands, registerTaskCommands, registerRoomCommands } from './cli.js';
@@ -0,0 +1,23 @@
1
+ import type { RoomOrchestrationRecord, RoomOrchestrationState, SagaPhase, RoomMemberSeat, ProvisioningDetail } from './types.js';
2
+ export declare const roomsDir: () => string;
3
+ export declare class RoomStateError extends Error {
4
+ }
5
+ export interface CreateRoomInput {
6
+ room_id: string;
7
+ room_name: string;
8
+ room_identity_cid?: string;
9
+ task_id?: string;
10
+ template_snapshot?: import('./types.js').TemplateSnapshot;
11
+ }
12
+ export declare function createRoomRecord(input: CreateRoomInput): RoomOrchestrationRecord;
13
+ export declare function getRoomRecord(id: string): RoomOrchestrationRecord | undefined;
14
+ export declare function listRoomRecords(filter?: {
15
+ state?: RoomOrchestrationState | RoomOrchestrationState[];
16
+ }): RoomOrchestrationRecord[];
17
+ export declare function advanceSaga(id: string, phase: SagaPhase, stepIndex?: number, detail?: ProvisioningDetail): RoomOrchestrationRecord;
18
+ export declare function setSagaError(id: string, error: string, hint?: string, detail?: ProvisioningDetail): RoomOrchestrationRecord;
19
+ export declare function setRoomIdentity(id: string, roomIdentityCid: string): RoomOrchestrationRecord;
20
+ export declare function setOwnerSeat(id: string, ownerSeatCid: string, inviteFingerprint: string): RoomOrchestrationRecord;
21
+ export declare function updateMemberSeats(id: string, seats: RoomMemberSeat[]): RoomOrchestrationRecord;
22
+ export declare function activateRoom(id: string): RoomOrchestrationRecord;
23
+ export declare function closeRoom(id: string): RoomOrchestrationRecord;