@swarm-agent/sdk 0.1.51 → 0.1.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -10
- package/dist/activity.d.ts +33 -0
- package/dist/activity.js +83 -0
- package/dist/agents.d.ts +432 -17
- package/dist/agents.js +529 -37
- package/dist/auth.d.ts +21 -5
- package/dist/auth.js +33 -4
- package/dist/client.d.ts +11 -0
- package/dist/client.js +14 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/job-automations.d.ts +343 -0
- package/dist/job-automations.js +74 -0
- package/dist/limits.d.ts +16 -0
- package/dist/limits.js +12 -0
- package/dist/projects.d.ts +11 -1
- package/dist/projects.js +30 -0
- package/dist/sessions.js +9 -1
- package/dist/types.d.ts +158 -0
- package/dist/workers.js +2 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -21,8 +21,8 @@ Requires Node 22 or newer (ESM). The package is public on npm:
|
|
|
21
21
|
npm install @swarm-agent/sdk
|
|
22
22
|
</copy>
|
|
23
23
|
|
|
24
|
-
You need a running Swarm
|
|
25
|
-
`curl -fsSL https://swarmagent.dev/install
|
|
24
|
+
You need a running Swarm. Install the Swarm box (Ubuntu 24.04 x86_64) with
|
|
25
|
+
`curl -fsSL https://swarmagent.dev/install | sudo bash`.
|
|
26
26
|
|
|
27
27
|
## Connect to your Swarm
|
|
28
28
|
|
|
@@ -58,10 +58,14 @@ example, not source tests or node_modules. `prepack` builds locally; there are n
|
|
|
58
58
|
install/prepare hooks. TypeScript/tsx are build/test tools, not runtime dependencies.
|
|
59
59
|
CommonJS `require` and non-Node runtimes are not validated by this packaging step.
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
`
|
|
64
|
-
|
|
61
|
+
On the Swarm box, `http://127.0.0.1:7783` is the scoped-token listener. The AI
|
|
62
|
+
key the installer wrote to `~/.config/swarm/ai-key` (a new one:
|
|
63
|
+
`sudo bash /var/lib/swarm-headless/install.sh connect-ai`)
|
|
64
|
+
works only through its MCP, `http://127.0.0.1:7783/mcp`, with any MCP client;
|
|
65
|
+
it reaches no SDK route. For the session routes there, use a client key (a full
|
|
66
|
+
AI key's `swarm_create_client_key` mints one); **do not** use Desktop bootstrap
|
|
67
|
+
or a privileged socket. A client key reaches session operations only, not the
|
|
68
|
+
complete API below.
|
|
65
69
|
The packaged `examples/headless-session.ts` imports `@swarm-agent/sdk` and can be copied
|
|
66
70
|
into your consumer and run with an explicitly installed `tsx` runner. Protect its
|
|
67
71
|
`SWARM_SDK_TOKEN_FILE` (0600); the example asks before each tool approval and uses
|
|
@@ -69,9 +73,18 @@ explicit refresh, not timer polling. Live agent verification is a separate step.
|
|
|
69
73
|
|
|
70
74
|
## Quick Start
|
|
71
75
|
|
|
72
|
-
###
|
|
76
|
+
### Workers and jobs
|
|
73
77
|
|
|
74
|
-
|
|
78
|
+
Start with a project and its authorized folders, then give jobs to workers:
|
|
79
|
+
coder for code changes, designer for UI/design, finder for research, or reviewer
|
|
80
|
+
for independent read-only review of another job. Reviewers require `review_job`,
|
|
81
|
+
use detached disposable checkouts, and report without delivering. Worker models
|
|
82
|
+
follow account role settings unless pinned. Read each job's result before
|
|
83
|
+
delivery (local merge, configured pull request, or queued landing).
|
|
84
|
+
|
|
85
|
+
### Advanced: sessions, plans and orchestration
|
|
86
|
+
|
|
87
|
+
For conversational interfaces or structured multi-stage work:
|
|
75
88
|
|
|
76
89
|
1. **Interactive Chat Flow (`client.chat` / `client.sessions`)**:
|
|
77
90
|
- Best for conversational assistants, interactive Q&A, and direct tool calling.
|
|
@@ -80,7 +93,7 @@ Swarm supports two distinct modes of execution depending on your application goa
|
|
|
80
93
|
|
|
81
94
|
2. **Autonomous Orchestration Flow (`client.projects` / `client.workers` / `client.deploy`)**:
|
|
82
95
|
- Best for multi-agent background tasks, codebase modifications, and durable pipelines.
|
|
83
|
-
-
|
|
96
|
+
- Organizes worker jobs across authorized folders with structured plans and checkpoint contracts.
|
|
84
97
|
- **Project → orchestrator conversations → delegated tasks → task sessions.** Project Markdown and linked workspaces provide high-level context; each deployed task resolves its own authorized execution workspace.
|
|
85
98
|
- Use `client.projects.listConversations(projectId)` and `createConversation(projectId)` for the session picker and New Conversation button. These call canonical `/v3/projects/{id}/sessions`, not generic workspace session creation. `getOrchestratorSession` reuses an existing conversation or creates one; it does not mean there can only be one.
|
|
86
99
|
- Use `client.projects.createTask()` / `listTasks()` for custom task organization. Task-linked sessions must be continued through `reopenTask` with exact revision guards, not ordinary chat messages.
|
|
@@ -386,8 +399,11 @@ console.log(`Authenticated as user ${session.user_id}`);
|
|
|
386
399
|
const { token, record } = await client.auth.createScopedToken({
|
|
387
400
|
name: 'GitHub Actions Trigger',
|
|
388
401
|
scopes: ['automations:trigger'],
|
|
389
|
-
expires_in_seconds: 86400, // 24 hours
|
|
390
402
|
});
|
|
403
|
+
// Every key works while it is used and stops after 90 days unused; nothing to
|
|
404
|
+
// renew. Pass expires_in_seconds only for a fixed end date (at most 365 days).
|
|
405
|
+
// client.auth.createKey({ kind: 'app' | 'bot' | 'ai', ... }), listKeys() and
|
|
406
|
+
// revokeKey(id) cover all three kinds.
|
|
391
407
|
console.log(`Created deploy token: ${token} (hint: ${record.token_hint})`);
|
|
392
408
|
|
|
393
409
|
// List active tokens
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SwarmTransport } from './transport.js';
|
|
2
|
+
export interface ActivityEvent {
|
|
3
|
+
seq: number;
|
|
4
|
+
at: number;
|
|
5
|
+
kind: string;
|
|
6
|
+
session_id: string;
|
|
7
|
+
job_id: string;
|
|
8
|
+
worker_id: string;
|
|
9
|
+
worker_name: string;
|
|
10
|
+
project_id: string;
|
|
11
|
+
state: string;
|
|
12
|
+
summary: string;
|
|
13
|
+
tool_name?: string;
|
|
14
|
+
file_path?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ActivityGap {
|
|
17
|
+
kind: 'gap';
|
|
18
|
+
from: number;
|
|
19
|
+
to: number;
|
|
20
|
+
cursor: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ActivityStreamOptions {
|
|
23
|
+
cursor?: string;
|
|
24
|
+
project?: string;
|
|
25
|
+
worker?: string;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
}
|
|
28
|
+
export declare class SwarmActivityNamespace {
|
|
29
|
+
private readonly transport;
|
|
30
|
+
constructor(transport: SwarmTransport);
|
|
31
|
+
/** Metadata only. Gap records must be surfaced to users; they are not activity events. */
|
|
32
|
+
stream(options?: ActivityStreamOptions): AsyncGenerator<ActivityEvent | ActivityGap>;
|
|
33
|
+
}
|
package/dist/activity.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export class SwarmActivityNamespace {
|
|
2
|
+
transport;
|
|
3
|
+
constructor(transport) {
|
|
4
|
+
this.transport = transport;
|
|
5
|
+
}
|
|
6
|
+
/** Metadata only. Gap records must be surfaced to users; they are not activity events. */
|
|
7
|
+
async *stream(options = {}) {
|
|
8
|
+
let cursor = options.cursor ?? '0';
|
|
9
|
+
while (!options.signal?.aborted) {
|
|
10
|
+
const config = this.transport.getConfig();
|
|
11
|
+
if (config.socketPath)
|
|
12
|
+
throw new Error('Activity SSE requires an HTTP connection');
|
|
13
|
+
const query = new URLSearchParams({ cursor });
|
|
14
|
+
if (options.project)
|
|
15
|
+
query.set('project', options.project);
|
|
16
|
+
if (options.worker)
|
|
17
|
+
query.set('worker', options.worker);
|
|
18
|
+
const headers = { ...config.defaultHeaders, Accept: 'text/event-stream' };
|
|
19
|
+
if (config.token && !headers.Authorization && !headers.authorization)
|
|
20
|
+
headers.Authorization = `Bearer ${config.token}`;
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch(`${config.baseUrl.replace(/\/+$/, '')}/v3/activity/stream?${query}`, { headers, signal: options.signal, redirect: 'error' });
|
|
23
|
+
if (!response.ok || !response.body)
|
|
24
|
+
throw new Error(`Activity stream failed: ${response.status}`);
|
|
25
|
+
const reader = response.body.getReader();
|
|
26
|
+
const decoder = new TextDecoder();
|
|
27
|
+
let pending = '';
|
|
28
|
+
try {
|
|
29
|
+
for (;;) {
|
|
30
|
+
const chunk = await reader.read();
|
|
31
|
+
if (chunk.done)
|
|
32
|
+
break;
|
|
33
|
+
pending += decoder.decode(chunk.value, { stream: true }).replace(/\r\n/g, '\n');
|
|
34
|
+
if (pending.length > 1024 * 1024)
|
|
35
|
+
throw new Error('Activity frame too large');
|
|
36
|
+
let end;
|
|
37
|
+
while ((end = pending.indexOf('\n\n')) >= 0) {
|
|
38
|
+
const frame = pending.slice(0, end);
|
|
39
|
+
pending = pending.slice(end + 2);
|
|
40
|
+
const kind = frame.split('\n').find(line => line.startsWith('event:'))?.slice(6).trim();
|
|
41
|
+
const data = frame.split('\n').filter(line => line.startsWith('data:')).map(line => line.slice(5).trimStart()).join('\n');
|
|
42
|
+
if (!data)
|
|
43
|
+
continue;
|
|
44
|
+
const value = JSON.parse(data);
|
|
45
|
+
if (kind === 'cursor')
|
|
46
|
+
cursor = value.cursor;
|
|
47
|
+
else if (kind === 'gap') {
|
|
48
|
+
cursor = value.cursor;
|
|
49
|
+
yield { ...value, kind: 'gap' };
|
|
50
|
+
}
|
|
51
|
+
else if (kind === 'activity') {
|
|
52
|
+
cursor = String(value.seq);
|
|
53
|
+
yield value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
try {
|
|
60
|
+
await reader.cancel();
|
|
61
|
+
}
|
|
62
|
+
catch { /* The disconnected transport is already closed. */ }
|
|
63
|
+
reader.releaseLock();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (options.signal?.aborted)
|
|
68
|
+
return;
|
|
69
|
+
// fetch and stream transport failures are TypeErrors; malformed frames
|
|
70
|
+
// and HTTP authorization failures are not retried.
|
|
71
|
+
if (!(error instanceof TypeError))
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
// Bound reconnect frequency; this is reconnection, not polling for activity.
|
|
75
|
+
if (!options.signal?.aborted)
|
|
76
|
+
await new Promise(resolve => {
|
|
77
|
+
const finish = () => { clearTimeout(timer); options.signal?.removeEventListener('abort', finish); resolve(); };
|
|
78
|
+
const timer = setTimeout(finish, 1000);
|
|
79
|
+
options.signal?.addEventListener('abort', finish, { once: true });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|