@swarm-agent/sdk 0.1.49 → 0.1.51

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 (48) hide show
  1. package/HEADLESS_UI.md +4 -6
  2. package/README.md +138 -58
  3. package/dist/agents.d.ts +114 -0
  4. package/dist/agents.js +183 -0
  5. package/dist/auth.d.ts +14 -0
  6. package/dist/auth.js +16 -0
  7. package/dist/box.d.ts +88 -0
  8. package/dist/box.js +13 -0
  9. package/dist/browser.d.ts +6 -2
  10. package/dist/browser.js +11 -3
  11. package/dist/chat.d.ts +41 -7
  12. package/dist/chat.js +148 -83
  13. package/dist/client.d.ts +12 -0
  14. package/dist/client.js +19 -6
  15. package/dist/copy-tags.d.ts +26 -0
  16. package/dist/copy-tags.js +133 -0
  17. package/dist/errors.d.ts +16 -0
  18. package/dist/errors.js +21 -0
  19. package/dist/index.d.ts +6 -0
  20. package/dist/index.js +6 -0
  21. package/dist/local-connection.d.ts +39 -0
  22. package/dist/local-connection.js +56 -0
  23. package/dist/permission-ui.d.ts +1 -0
  24. package/dist/permission-ui.js +37 -0
  25. package/dist/permissions.d.ts +4 -0
  26. package/dist/permissions.js +7 -0
  27. package/dist/projects.d.ts +25 -7
  28. package/dist/projects.js +65 -17
  29. package/dist/provider-auth.d.ts +5 -3
  30. package/dist/provider-auth.js +6 -8
  31. package/dist/realtime.d.ts +40 -6
  32. package/dist/realtime.js +156 -52
  33. package/dist/remote.d.ts +59 -0
  34. package/dist/remote.js +46 -0
  35. package/dist/sessions.d.ts +31 -1
  36. package/dist/sessions.js +49 -1
  37. package/dist/settings.d.ts +3 -0
  38. package/dist/settings.js +19 -3
  39. package/dist/signals.d.ts +148 -0
  40. package/dist/signals.js +97 -0
  41. package/dist/types.d.ts +97 -6
  42. package/dist/websocket.d.ts +3 -1
  43. package/dist/websocket.js +1 -1
  44. package/dist/workers.d.ts +6 -0
  45. package/dist/workers.js +27 -0
  46. package/dist/workspaces.d.ts +20 -0
  47. package/dist/workspaces.js +24 -0
  48. package/package.json +1 -1
package/dist/remote.js ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Owner administration of the machine's relay connection (`/v1/remote*`).
3
+ * The daemon refuses scoped tokens here: call it only from a trusted backend
4
+ * on the private local transport, never from a browser.
5
+ */
6
+ export class SwarmRemoteNamespace {
7
+ transport;
8
+ constructor(transport) {
9
+ this.transport = transport;
10
+ }
11
+ async call(path, method, body) {
12
+ const res = await this.transport.request(`/v1/remote${path}`, { method, ...(method === 'POST' ? { body: body ?? {} } : {}) });
13
+ return res.data.remote;
14
+ }
15
+ status() {
16
+ return this.call('', 'GET');
17
+ }
18
+ /** Creates this machine's device key for one relay. Does not connect. */
19
+ init(input) {
20
+ return this.call('/init', 'POST', {
21
+ relay_url: input.relay_url, device_name: input.device_name,
22
+ allow_write: input.allow_write === true, allow_approve: input.allow_approve === true, allow_manage: input.allow_manage === true,
23
+ });
24
+ }
25
+ enable() {
26
+ return this.call('/enable', 'POST');
27
+ }
28
+ disable() {
29
+ return this.call('/disable', 'POST');
30
+ }
31
+ /**
32
+ * Approves or denies an AI client's authorization request shown on the
33
+ * relay's consent page. Approved scopes are clamped to the request and to
34
+ * this machine's ceiling by the daemon.
35
+ */
36
+ async decideConsent(code, approve, scopes) {
37
+ const res = await this.transport.request(`/v1/remote/consents/${approve ? 'approve' : 'deny'}`, {
38
+ method: 'POST', body: approve && scopes ? { code, scopes } : { code },
39
+ });
40
+ return res.data.consent;
41
+ }
42
+ /** Disconnects, revokes the device token and deletes the device key. */
43
+ reset() {
44
+ return this.call('/reset', 'POST');
45
+ }
46
+ }
@@ -35,14 +35,44 @@ export declare class SwarmSessionsNamespace {
35
35
  * Deletes a session and triggers cascading cancellation of associated runs and worktrees.
36
36
  */
37
37
  delete(sessionId: string): Promise<boolean>;
38
+ /**
39
+ * Renames a session or project conversation you own (`POST /v3/sessions/{id}/title`).
40
+ * The title is trimmed and must be 1–200 characters. A renamed conversation keeps its
41
+ * title: Swarm no longer titles it automatically. (Untitled conversations are named
42
+ * from their first message.) Retrying with the same `clientRequestId` is idempotent.
43
+ */
44
+ rename(sessionId: string, title: string, options?: {
45
+ clientRequestId?: string;
46
+ }): Promise<{
47
+ ok: boolean;
48
+ session_id: string;
49
+ title: string;
50
+ }>;
38
51
  /**
39
52
  * Appends a message to a session conversation.
40
53
  */
41
54
  sendMessage(sessionId: string, params: {
42
55
  content: string;
43
- role?: 'user' | 'assistant' | 'system';
56
+ role?: 'user';
44
57
  client_request_id?: string;
45
58
  }): Promise<any>;
59
+ /**
60
+ * Starts a fresh model context in the same conversation (`POST /v3/sessions/{id}/context/clear`).
61
+ * History stays visible and the session ID is unchanged; the next turn starts without the
62
+ * earlier provider context. The daemon accepts this only for an idle project conversation
63
+ * without an unresolved plan, and only when `expectedLastEventSeq` equals the session's current
64
+ * durable sequence (`state.snapshot.projections_by_session[id].last_event_seq`); otherwise it
65
+ * rejects with `SwarmConflictError` and changes nothing. A standalone chat session cannot be
66
+ * cleared: create a new session instead. Retrying with the same `clientRequestId` is idempotent.
67
+ */
68
+ clearContext(sessionId: string, params: {
69
+ expectedLastEventSeq: number;
70
+ clientRequestId?: string;
71
+ }): Promise<{
72
+ ok: boolean;
73
+ session_id: string;
74
+ mutation: Record<string, unknown>;
75
+ }>;
46
76
  /**
47
77
  * Stops an active execution run for a session.
48
78
  */
package/dist/sessions.js CHANGED
@@ -84,10 +84,14 @@ export class SwarmSessionsNamespace {
84
84
  const data = res.data;
85
85
  if (data && typeof data === 'object') {
86
86
  const sessionObj = data.session || data;
87
+ // V3 sessions carry their source workspace as the primary grant.
88
+ const primaryGrant = Array.isArray(sessionObj.workspace_grants)
89
+ ? sessionObj.workspace_grants.find((grant) => grant?.kind === 'primary')
90
+ : undefined;
87
91
  return {
88
92
  id: sessionObj.id || sessionObj.session_id || sessionId,
89
93
  title: sessionObj.title || '',
90
- workspace_id: sessionObj.workspace_id,
94
+ workspace_id: sessionObj.workspace_id || primaryGrant?.workspace_id,
91
95
  workspace_path: sessionObj.workspace_path,
92
96
  state: sessionObj.state || data.projection?.state || 'idle',
93
97
  mode: sessionObj.mode,
@@ -145,6 +149,27 @@ export class SwarmSessionsNamespace {
145
149
  });
146
150
  return res.data?.ok === true;
147
151
  }
152
+ /**
153
+ * Renames a session or project conversation you own (`POST /v3/sessions/{id}/title`).
154
+ * The title is trimmed and must be 1–200 characters. A renamed conversation keeps its
155
+ * title: Swarm no longer titles it automatically. (Untitled conversations are named
156
+ * from their first message.) Retrying with the same `clientRequestId` is idempotent.
157
+ */
158
+ async rename(sessionId, title, options = {}) {
159
+ const id = sessionId.trim();
160
+ if (!id)
161
+ throw new Error('sessionId is required');
162
+ const trimmed = typeof title === 'string' ? title.trim() : '';
163
+ if (!trimmed)
164
+ throw new Error('title is required');
165
+ if ([...trimmed].length > 200)
166
+ throw new Error('title must be 200 characters or fewer');
167
+ const res = await this.transport.request(`/v3/sessions/${encodeURIComponent(id)}/title`, {
168
+ method: 'POST',
169
+ body: { title: trimmed, client_request_id: options.clientRequestId || generateRequestId() },
170
+ });
171
+ return res.data;
172
+ }
148
173
  /**
149
174
  * Appends a message to a session conversation.
150
175
  */
@@ -161,6 +186,29 @@ export class SwarmSessionsNamespace {
161
186
  });
162
187
  return res.data;
163
188
  }
189
+ /**
190
+ * Starts a fresh model context in the same conversation (`POST /v3/sessions/{id}/context/clear`).
191
+ * History stays visible and the session ID is unchanged; the next turn starts without the
192
+ * earlier provider context. The daemon accepts this only for an idle project conversation
193
+ * without an unresolved plan, and only when `expectedLastEventSeq` equals the session's current
194
+ * durable sequence (`state.snapshot.projections_by_session[id].last_event_seq`); otherwise it
195
+ * rejects with `SwarmConflictError` and changes nothing. A standalone chat session cannot be
196
+ * cleared: create a new session instead. Retrying with the same `clientRequestId` is idempotent.
197
+ */
198
+ async clearContext(sessionId, params) {
199
+ if (!Number.isSafeInteger(params.expectedLastEventSeq) || params.expectedLastEventSeq < 0) {
200
+ throw new Error('expectedLastEventSeq must be the current non-negative session event sequence');
201
+ }
202
+ const id = encodeURIComponent(sessionId.trim());
203
+ const res = await this.transport.request(`/v3/sessions/${id}/context/clear`, {
204
+ method: 'POST',
205
+ body: {
206
+ client_request_id: params.clientRequestId || generateRequestId(),
207
+ expected_last_event_seq: params.expectedLastEventSeq,
208
+ },
209
+ });
210
+ return res.data;
211
+ }
164
212
  /**
165
213
  * Stops an active execution run for a session.
166
214
  */
@@ -98,6 +98,9 @@ export declare class SwarmSettingsNamespace {
98
98
  /**
99
99
  * Restores verified recommended defaults across all system agent and swarm core roles.
100
100
  * If expectedUpdatedAt is omitted, automatically reads the current setting's revision first.
101
+ * An account with no model settings yet (a provider is connected but its
102
+ * models were never set up) restores from revision 0: the daemon then sets
103
+ * up the recommended models from a ready provider.
101
104
  */
102
105
  restoreDefaults(expectedUpdatedAt?: number): Promise<AgentModelSettingsResponse>;
103
106
  /**
package/dist/settings.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { SwarmNotFoundError } from './errors.js';
1
2
  /** Account-scoped settings. Requires the local administrative connection, not a session token. */
2
3
  export class SwarmSettingsNamespace {
3
4
  transport;
@@ -23,12 +24,22 @@ export class SwarmSettingsNamespace {
23
24
  /**
24
25
  * Restores verified recommended defaults across all system agent and swarm core roles.
25
26
  * If expectedUpdatedAt is omitted, automatically reads the current setting's revision first.
27
+ * An account with no model settings yet (a provider is connected but its
28
+ * models were never set up) restores from revision 0: the daemon then sets
29
+ * up the recommended models from a ready provider.
26
30
  */
27
31
  async restoreDefaults(expectedUpdatedAt) {
28
32
  let updatedAt = expectedUpdatedAt;
29
33
  if (typeof updatedAt !== 'number') {
30
- const current = await this.agentModels();
31
- updatedAt = current.agent_model_settings.updated_at;
34
+ try {
35
+ const current = await this.agentModels();
36
+ updatedAt = current.agent_model_settings.updated_at;
37
+ }
38
+ catch (error) {
39
+ if (!(error instanceof SwarmNotFoundError))
40
+ throw error;
41
+ updatedAt = 0;
42
+ }
32
43
  }
33
44
  return (await this.transport.request('/v1/agent-model-settings/restore-defaults', {
34
45
  method: 'POST',
@@ -87,7 +98,12 @@ export class SwarmSettingsNamespace {
87
98
  */
88
99
  async applyProviderFleet(provider) {
89
100
  const fleet = await this.getRecommendationsForProvider(provider);
90
- return this.patch(fleet);
101
+ // The daemon takes one target per PATCH: the swarm pair, then each system agent.
102
+ let result = await this.patch({ swarm: fleet.swarm });
103
+ for (const [slot, assignment] of Object.entries(fleet.system_agents)) {
104
+ result = await this.patch({ system_agents: { [slot]: assignment } });
105
+ }
106
+ return result;
91
107
  }
92
108
  async patch(body) {
93
109
  return (await this.transport.request('/v1/agent-model-settings', { method: 'PATCH', body })).data;
@@ -0,0 +1,148 @@
1
+ import type { SwarmTransport } from './transport.js';
2
+ export type SignalSeverity = 'info' | 'warning' | 'critical';
3
+ /**
4
+ * One entry in a machine's signal feed: a high-level event such as an agent
5
+ * waiting on a person, a failed run, a refused key or an event an outside
6
+ * source reported. Signals carry ids and a one-line summary, never transcripts,
7
+ * command arguments or secret values.
8
+ */
9
+ export interface Signal {
10
+ /** Position in this machine's feed; pass the last one back as `after`. */
11
+ seq: number;
12
+ /** Unique id, stable across forwarding (use it to drop duplicates). */
13
+ id: string;
14
+ /** Unix milliseconds. */
15
+ at: number;
16
+ /** Dotted kind, e.g. `agent.blocked`, `run.failed`, `external.falco`. */
17
+ kind: string;
18
+ severity: SignalSeverity;
19
+ /** `swarmd` for Swarm's own signals, `external:<name>` for reported ones. */
20
+ source: string;
21
+ /** Empty for machine-wide signals. */
22
+ account?: string;
23
+ summary: string;
24
+ /** Signals with the same key describe the same ongoing condition. */
25
+ dedup_key?: string;
26
+ refs?: Record<string, string>;
27
+ attrs?: Record<string, string>;
28
+ }
29
+ export interface SignalPage {
30
+ signals: Signal[];
31
+ /** The last position examined; pass it as `after` to continue. */
32
+ next_after: number;
33
+ oldest_seq: number;
34
+ latest_seq: number;
35
+ /**
36
+ * True when signals after your cursor were already dropped (the feed keeps
37
+ * the newest entries only) or your cursor is ahead of the feed. Resync
38
+ * from `next_after` and treat the missed range as unknown.
39
+ */
40
+ gap: boolean;
41
+ }
42
+ export interface ListSignalsParams {
43
+ /** Return signals numbered after this position (default 0: from the oldest kept). */
44
+ after?: number;
45
+ /** At most this many signals (default 100, maximum 500). */
46
+ limit?: number;
47
+ /** Kind prefixes, e.g. `['agent', 'run.failed']`. */
48
+ kinds?: string[];
49
+ /** Only signals at least this severe. */
50
+ minSeverity?: SignalSeverity;
51
+ }
52
+ export interface ReportSignalParams {
53
+ /** Must start with `external.`, e.g. `external.falco`. */
54
+ kind: string;
55
+ /** One line; longer text is cut to 300 characters. */
56
+ summary: string;
57
+ severity?: SignalSeverity;
58
+ /** Lowercase name of the reporting source, e.g. `falco`. */
59
+ source?: string;
60
+ dedup_key?: string;
61
+ refs?: Record<string, string>;
62
+ attrs?: Record<string, string>;
63
+ }
64
+ /**
65
+ * The machine signal feed. Reading needs a key with `signals:read`; reporting
66
+ * an outside event needs `signals:write`. Readers see their own account's
67
+ * signals and machine-wide ones.
68
+ */
69
+ export declare class SwarmSignalsNamespace {
70
+ private readonly transport;
71
+ /** Where this machine forwards its feed (owner only). */
72
+ readonly sinks: SwarmSignalSinksNamespace;
73
+ constructor(transport: SwarmTransport);
74
+ /** Lists signals after a cursor, oldest first. */
75
+ list(params?: ListSignalsParams): Promise<SignalPage>;
76
+ /**
77
+ * Reports an outside event (a host security monitor, a backup script) into
78
+ * the feed. Swarm records the source as `external:<source>` and, for a
79
+ * scoped key, which key reported it.
80
+ */
81
+ report(params: ReportSignalParams): Promise<Signal>;
82
+ }
83
+ /** Where a machine forwards its signal feed (owner only). */
84
+ export interface SignalSink {
85
+ id: string;
86
+ account: string;
87
+ name: string;
88
+ url: string;
89
+ kinds?: string[];
90
+ min_severity?: SignalSeverity;
91
+ heartbeat_seconds: number;
92
+ /** Last signal position the sink accepted. */
93
+ cursor: number;
94
+ created_at: number;
95
+ last_delivered_at?: number;
96
+ last_attempt_at?: number;
97
+ last_error?: string;
98
+ failures?: number;
99
+ }
100
+ export interface CreateSignalSinkParams {
101
+ name: string;
102
+ /** https URL (http only to this machine's loopback). */
103
+ url: string;
104
+ /** Kind prefixes to forward, e.g. `['agent', 'worker.run.failed']`; empty forwards all. */
105
+ kinds?: string[];
106
+ minSeverity?: SignalSeverity;
107
+ /** Idle heartbeat interval, 30 to 3600 seconds (default 60). */
108
+ heartbeatSeconds?: number;
109
+ }
110
+ /**
111
+ * Signal sinks: the machine POSTs batches of its feed to each sink, signed
112
+ * with the sink's secret (see `verifySignalDelivery`). Owner only: scoped
113
+ * keys, including admin keys, are refused.
114
+ */
115
+ export declare class SwarmSignalSinksNamespace {
116
+ private readonly transport;
117
+ constructor(transport: SwarmTransport);
118
+ list(): Promise<SignalSink[]>;
119
+ /** Creates a sink. The signing secret is returned only here; store it in the receiver. */
120
+ create(params: CreateSignalSinkParams): Promise<{
121
+ sink: SignalSink;
122
+ secret: string;
123
+ }>;
124
+ delete(id: string): Promise<void>;
125
+ }
126
+ /** Body of one signal delivery (version 1). */
127
+ export interface SignalDelivery {
128
+ version: 1;
129
+ sink_id: string;
130
+ machine?: string;
131
+ sent_at: number;
132
+ /** True for an idle heartbeat with no signals. */
133
+ heartbeat: boolean;
134
+ signals: Signal[];
135
+ next_after: number;
136
+ latest_seq: number;
137
+ gap: boolean;
138
+ }
139
+ /**
140
+ * Checks a delivery's `X-Swarm-Signature` for a receiver: HMAC-SHA256 of
141
+ * `${X-Swarm-Timestamp}.${rawBody}` with the sink secret, and a timestamp
142
+ * within `maxSkewMs` of now (rejects replays). Pass the raw request body,
143
+ * not re-serialized JSON. Works with WebCrypto (Node 18+, Workers, browsers).
144
+ */
145
+ export declare function verifySignalDelivery(secret: string, timestamp: string | number, rawBody: string | Uint8Array, signature: string, { now, maxSkewMs }?: {
146
+ now?: number;
147
+ maxSkewMs?: number;
148
+ }): Promise<boolean>;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The machine signal feed. Reading needs a key with `signals:read`; reporting
3
+ * an outside event needs `signals:write`. Readers see their own account's
4
+ * signals and machine-wide ones.
5
+ */
6
+ export class SwarmSignalsNamespace {
7
+ transport;
8
+ /** Where this machine forwards its feed (owner only). */
9
+ sinks;
10
+ constructor(transport) {
11
+ this.transport = transport;
12
+ this.sinks = new SwarmSignalSinksNamespace(transport);
13
+ }
14
+ /** Lists signals after a cursor, oldest first. */
15
+ async list(params = {}) {
16
+ const q = new URLSearchParams();
17
+ if (typeof params.after === 'number')
18
+ q.set('after', String(params.after));
19
+ if (typeof params.limit === 'number')
20
+ q.set('limit', String(params.limit));
21
+ if (params.kinds?.length)
22
+ q.set('kind', params.kinds.join(','));
23
+ if (params.minSeverity)
24
+ q.set('min_severity', params.minSeverity);
25
+ const path = q.toString() ? `/v3/signals?${q.toString()}` : '/v3/signals';
26
+ const res = await this.transport.request(path, { method: 'GET' });
27
+ const { signals, next_after, oldest_seq, latest_seq, gap } = res.data;
28
+ return { signals: signals ?? [], next_after, oldest_seq, latest_seq, gap };
29
+ }
30
+ /**
31
+ * Reports an outside event (a host security monitor, a backup script) into
32
+ * the feed. Swarm records the source as `external:<source>` and, for a
33
+ * scoped key, which key reported it.
34
+ */
35
+ async report(params) {
36
+ const res = await this.transport.request('/v3/signals', {
37
+ method: 'POST',
38
+ body: params,
39
+ });
40
+ return res.data.signal;
41
+ }
42
+ }
43
+ /**
44
+ * Signal sinks: the machine POSTs batches of its feed to each sink, signed
45
+ * with the sink's secret (see `verifySignalDelivery`). Owner only: scoped
46
+ * keys, including admin keys, are refused.
47
+ */
48
+ export class SwarmSignalSinksNamespace {
49
+ transport;
50
+ constructor(transport) {
51
+ this.transport = transport;
52
+ }
53
+ async list() {
54
+ const res = await this.transport.request('/v3/signals/sinks', { method: 'GET' });
55
+ return res.data.sinks ?? [];
56
+ }
57
+ /** Creates a sink. The signing secret is returned only here; store it in the receiver. */
58
+ async create(params) {
59
+ const res = await this.transport.request('/v3/signals/sinks', {
60
+ method: 'POST',
61
+ body: {
62
+ name: params.name,
63
+ url: params.url,
64
+ kinds: params.kinds,
65
+ min_severity: params.minSeverity,
66
+ heartbeat_seconds: params.heartbeatSeconds,
67
+ },
68
+ });
69
+ return { sink: res.data.sink, secret: res.data.secret };
70
+ }
71
+ async delete(id) {
72
+ await this.transport.request(`/v3/signals/sinks/${encodeURIComponent(id)}`, { method: 'DELETE' });
73
+ }
74
+ }
75
+ /**
76
+ * Checks a delivery's `X-Swarm-Signature` for a receiver: HMAC-SHA256 of
77
+ * `${X-Swarm-Timestamp}.${rawBody}` with the sink secret, and a timestamp
78
+ * within `maxSkewMs` of now (rejects replays). Pass the raw request body,
79
+ * not re-serialized JSON. Works with WebCrypto (Node 18+, Workers, browsers).
80
+ */
81
+ export async function verifySignalDelivery(secret, timestamp, rawBody, signature, { now = Date.now(), maxSkewMs = 5 * 60 * 1000 } = {}) {
82
+ const ts = Number(timestamp);
83
+ if (!Number.isFinite(ts) || Math.abs(now - ts) > maxSkewMs || !signature.startsWith('v1='))
84
+ return false;
85
+ const enc = new TextEncoder();
86
+ const body = typeof rawBody === 'string' ? enc.encode(rawBody) : rawBody;
87
+ const message = new Uint8Array([...enc.encode(`${ts}.`), ...body]);
88
+ const key = await crypto.subtle.importKey('raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
89
+ const mac = new Uint8Array(await crypto.subtle.sign('HMAC', key, message));
90
+ const expected = 'v1=' + Array.from(mac, (b) => b.toString(16).padStart(2, '0')).join('');
91
+ if (expected.length !== signature.length)
92
+ return false;
93
+ let diff = 0;
94
+ for (let i = 0; i < expected.length; i++)
95
+ diff |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
96
+ return diff === 0;
97
+ }
package/dist/types.d.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  export type TokenScope = 'automations:trigger' | 'automations:read' | 'automations:write' | 'sessions:read' | 'sessions:write' | 'projects:read' | 'projects:write' | 'admin' | string;
2
2
  export interface SwarmClientConfig {
3
- /** Base URL for the Swarm API/Desktop daemon (e.g. http://127.0.0.1:18080 or http://127.0.0.1:7781) */
3
+ /**
4
+ * Base URL of the Swarm daemon API (default http://127.0.0.1:7781). With no
5
+ * `baseUrl`, `socketPath` or environment override, Node clients use the
6
+ * owner socket of a system install when this process may connect to it.
7
+ */
4
8
  baseUrl?: string;
5
9
  /** Bearer authentication token (attach token or scoped token starting with 'swk_') */
6
10
  token?: string;
7
- /** Unix domain socket path for zero-conf local IPC (e.g. /run/swarmd/swarmd.sock) */
11
+ /** Unix socket for local IPC (owner socket: /var/lib/swarmd/local-transport/api.sock) */
8
12
  socketPath?: string;
9
13
  /** Custom default HTTP headers */
10
14
  defaultHeaders?: Record<string, string>;
@@ -496,10 +500,6 @@ export interface UpdateProjectParams {
496
500
  export interface ListProjectsParams {
497
501
  limit?: number;
498
502
  }
499
- export interface SynthesizeProjectContextParams {
500
- name: string;
501
- workspaces: string[];
502
- }
503
503
  export interface DeleteProjectResult {
504
504
  status: string;
505
505
  id: string;
@@ -535,6 +535,43 @@ export interface ProjectTaskPlanBinding {
535
535
  session_id?: string;
536
536
  receipt?: string;
537
537
  }
538
+ /**
539
+ * Backend-inspected delivery facts for a task's captured worktree lane. Only the
540
+ * task-detail GET (`getTask`) observes Git; collection rows carry `freshness: 'stale'`.
541
+ */
542
+ export interface ProjectTaskDeliveryAssessment {
543
+ task_id?: string;
544
+ task_revision?: number;
545
+ attempt_id?: string;
546
+ session_id?: string;
547
+ base_oid?: string;
548
+ source_oid?: string;
549
+ target_oid?: string;
550
+ source_branch?: string;
551
+ target_branch?: string;
552
+ state?: 'candidate_work' | 'integrated' | 'unavailable' | string;
553
+ reason_code?: string;
554
+ reason?: string;
555
+ freshness?: 'observed' | 'stale' | 'unknown' | string;
556
+ source_dirty?: boolean;
557
+ target_dirty?: boolean;
558
+ candidate_commits?: number;
559
+ files?: string[];
560
+ allowed_actions?: string[];
561
+ [key: string]: unknown;
562
+ }
563
+ /** One coordinator attempt (session reservation) on a project task. */
564
+ export interface ProjectTaskAttempt {
565
+ id: string;
566
+ session_id?: string;
567
+ status?: string;
568
+ role?: string;
569
+ workspace_path?: string;
570
+ worktree_branch?: string;
571
+ base_branch?: string;
572
+ base_commit?: string;
573
+ [key: string]: unknown;
574
+ }
538
575
  export interface ProjectTaskRecord {
539
576
  id: string;
540
577
  project_id: string;
@@ -543,6 +580,9 @@ export interface ProjectTaskRecord {
543
580
  description?: string;
544
581
  status: 'queued' | 'in_progress' | 'needs_review' | 'completed' | 'failed' | 'pending_approval' | 'planning' | 'rejected' | string;
545
582
  session_id?: string;
583
+ active_attempt_id?: string;
584
+ attempts?: ProjectTaskAttempt[];
585
+ delivery_assessment?: ProjectTaskDeliveryAssessment;
546
586
  agent?: string;
547
587
  worker_name?: string;
548
588
  outcome_type?: string;
@@ -744,6 +784,30 @@ export interface RefineProjectTaskResult {
744
784
  status: 'refined' | string;
745
785
  task: ProjectTaskRecord;
746
786
  }
787
+ /**
788
+ * Selection body for POST /v3/projects/{id}/tasks/{taskId}/integrate. The daemon
789
+ * requires `session_id` (must equal the task's current session) plus the captured
790
+ * `source_branch`/`target_branch` (must equal the session worktree branch and its
791
+ * base branch). The remaining fields are the stale-state guards the daemon's
792
+ * `recover-integrate` path checks; plain integrate accepts and ignores them.
793
+ */
794
+ export interface IntegrateProjectTaskSelection {
795
+ session_id: string;
796
+ source_branch: string;
797
+ target_branch: string;
798
+ revision?: number;
799
+ attempt_id?: string;
800
+ source_head?: string;
801
+ target_head?: string;
802
+ }
803
+ export interface IntegrateProjectTaskOptions extends RequestOptions {
804
+ /**
805
+ * Overrides merged over the selection the SDK derives from a fresh `getTask`.
806
+ * When `session_id`, `source_branch` and `target_branch` are all supplied, the
807
+ * SDK skips the task fetch and sends them as given.
808
+ */
809
+ selection?: Partial<IntegrateProjectTaskSelection>;
810
+ }
747
811
  export interface IntegrateProjectTaskResult {
748
812
  status: 'integrated' | 'already_integrated' | string;
749
813
  message?: string;
@@ -914,10 +978,18 @@ export interface WorkerAutomationDefinition {
914
978
  plan_document: SessionPlanDocument;
915
979
  input_requirements?: WorkerInputRequirement[] | null;
916
980
  deliverable_requirements?: WorkerDeliverableRequirement[] | null;
981
+ /** Registered workspace this automation's runs work in; absent means the worker's primary binding. */
982
+ workspace_id?: string | null;
917
983
  revision: number;
918
984
  created_at: number;
919
985
  updated_at: number;
920
986
  }
987
+ /**
988
+ * Code-owned system agent that runs a worker's jobs. Absent means `swarm`.
989
+ * `swarm` executes jobs as accepted plans; `coder`, `designer` and `finder`
990
+ * run their compiled agent in an auto session.
991
+ */
992
+ export type WorkerRole = 'swarm' | 'coder' | 'designer' | 'finder';
921
993
  export interface WorkerAutomationInput {
922
994
  name: string;
923
995
  description?: string;
@@ -933,10 +1005,13 @@ export interface WorkerAutomationInput {
933
1005
  plan_document: SessionPlanDocument;
934
1006
  input_requirements?: WorkerInputRequirement[];
935
1007
  deliverable_requirements?: WorkerDeliverableRequirement[];
1008
+ /** Registered workspace this automation's runs work in. Required for schedules of a worker with no primary binding. */
1009
+ workspace_id?: string;
936
1010
  }
937
1011
  export interface WorkerRecord {
938
1012
  id: string;
939
1013
  account_scope_id: string;
1014
+ role?: WorkerRole | null;
940
1015
  name: string;
941
1016
  description?: string | null;
942
1017
  instructions: string;
@@ -970,6 +1045,12 @@ export interface WorkerRunRecord {
970
1045
  automation_id?: string;
971
1046
  automation_revision?: number;
972
1047
  occurrence_id?: string;
1048
+ /** Workspace pinned at admission. */
1049
+ workspace_id?: string;
1050
+ /** `worker_job` when the request or automation named the workspace, `worker_binding` when it came from the primary binding. */
1051
+ workspace_source?: 'worker_job' | 'worker_binding' | string;
1052
+ /** Worker role pinned at admission; absent means `swarm`. */
1053
+ role?: WorkerRole | string;
973
1054
  session_id?: string;
974
1055
  request_source: 'direct' | 'schedule' | 'trigger' | 'test_run' | 'orchestrator' | string;
975
1056
  input?: Record<string, unknown>;
@@ -1013,6 +1094,7 @@ export interface PortableWorkerDefinition {
1013
1094
  provenance?: PortableWorkerProvenance | null;
1014
1095
  }
1015
1096
  export interface CreateWorkerParams {
1097
+ role?: WorkerRole;
1016
1098
  name: string;
1017
1099
  description?: string;
1018
1100
  instructions?: string;
@@ -1023,6 +1105,7 @@ export interface CreateWorkerParams {
1023
1105
  idempotency_key: string;
1024
1106
  }
1025
1107
  export interface UpdateWorkerParams {
1108
+ role?: WorkerRole;
1026
1109
  name?: string;
1027
1110
  description?: string;
1028
1111
  instructions?: string;
@@ -1106,6 +1189,10 @@ export interface DirectWorkerRequestParams {
1106
1189
  prompt?: string;
1107
1190
  input?: Record<string, unknown>;
1108
1191
  idempotency_key?: string;
1192
+ /** Workspace this job works in (registered workspace ID). Required when the worker has no primary binding. */
1193
+ workspace_id?: string;
1194
+ /** Alternative to workspace_id: the exact root path of a registered workspace. */
1195
+ workspace_path?: string;
1109
1196
  }
1110
1197
  export interface TestWorkerRunParams {
1111
1198
  worker_id: string;
@@ -1113,6 +1200,10 @@ export interface TestWorkerRunParams {
1113
1200
  prompt?: string;
1114
1201
  input?: Record<string, unknown>;
1115
1202
  idempotency_key?: string;
1203
+ /** Workspace this job works in (registered workspace ID). */
1204
+ workspace_id?: string;
1205
+ /** Alternative to workspace_id: the exact root path of a registered workspace. */
1206
+ workspace_path?: string;
1116
1207
  }
1117
1208
  export interface TriggerWorkerParams {
1118
1209
  worker_id: string;
@@ -3,7 +3,7 @@ import type { Server as HttpsServer } from 'node:https';
3
3
  import { WebSocket } from 'ws';
4
4
  import type { SwarmChatNamespace, ToolStreamItem } from './chat.js';
5
5
  import type { PendingPermissionRecord, SwarmPermissionsNamespace, PermissionResolver, ResolvePermissionOptions, ResolvePermissionResult } from './permissions.js';
6
- import type { SessionWatchState, V3Message } from './realtime.js';
6
+ import type { SessionWatchState, V3Message, V3RunState } from './realtime.js';
7
7
  export interface SwarmWebSocketBridgeOptions {
8
8
  path?: string;
9
9
  /** Server-owned policy. Browser frames cannot enable auto approval. Questions always require a reply. */
@@ -37,6 +37,8 @@ export interface SwarmWebSocketServerMessage {
37
37
  result?: ResolvePermissionResult;
38
38
  permissionId?: string;
39
39
  messages?: V3Message[];
40
+ /** Finished run on `done`: `status` is completed, failed, cancelled, ... */
41
+ run?: V3RunState;
40
42
  error?: string;
41
43
  }
42
44
  /** Application-owned bridge to canonical V3 realtime. A subscription survives every turn.