@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.
- package/HEADLESS_UI.md +4 -6
- package/README.md +138 -58
- package/dist/agents.d.ts +114 -0
- package/dist/agents.js +183 -0
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +16 -0
- package/dist/box.d.ts +88 -0
- package/dist/box.js +13 -0
- package/dist/browser.d.ts +6 -2
- package/dist/browser.js +11 -3
- package/dist/chat.d.ts +41 -7
- package/dist/chat.js +148 -83
- package/dist/client.d.ts +12 -0
- package/dist/client.js +19 -6
- package/dist/copy-tags.d.ts +26 -0
- package/dist/copy-tags.js +133 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +21 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/local-connection.d.ts +39 -0
- package/dist/local-connection.js +56 -0
- package/dist/permission-ui.d.ts +1 -0
- package/dist/permission-ui.js +37 -0
- package/dist/permissions.d.ts +4 -0
- package/dist/permissions.js +7 -0
- package/dist/projects.d.ts +25 -7
- package/dist/projects.js +65 -17
- package/dist/provider-auth.d.ts +5 -3
- package/dist/provider-auth.js +6 -8
- package/dist/realtime.d.ts +40 -6
- package/dist/realtime.js +156 -52
- package/dist/remote.d.ts +59 -0
- package/dist/remote.js +46 -0
- package/dist/sessions.d.ts +31 -1
- package/dist/sessions.js +49 -1
- package/dist/settings.d.ts +3 -0
- package/dist/settings.js +19 -3
- package/dist/signals.d.ts +148 -0
- package/dist/signals.js +97 -0
- package/dist/types.d.ts +97 -6
- package/dist/websocket.d.ts +3 -1
- package/dist/websocket.js +1 -1
- package/dist/workers.d.ts +6 -0
- package/dist/workers.js +27 -0
- package/dist/workspaces.d.ts +20 -0
- package/dist/workspaces.js +24 -0
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SwarmAgentsNamespace } from './agents.js';
|
|
1
2
|
import { SwarmAppsNamespace } from './apps.js';
|
|
2
3
|
import { SwarmChatNamespace } from './chat.js';
|
|
3
4
|
import { SwarmPermissionsNamespace } from './permissions.js';
|
|
@@ -11,10 +12,14 @@ import { SwarmWorkersNamespace } from './workers.js';
|
|
|
11
12
|
import { SwarmDeliverablesNamespace } from './deliverables.js';
|
|
12
13
|
import { SwarmDeployNamespace } from './deploy/index.js';
|
|
13
14
|
import { SwarmNotificationsNamespace } from './notifications.js';
|
|
15
|
+
import { SwarmSignalsNamespace } from './signals.js';
|
|
16
|
+
import { SwarmBoxNamespace } from './box.js';
|
|
14
17
|
import { SwarmProjectsNamespace } from './projects.js';
|
|
18
|
+
import { SwarmRemoteNamespace } from './remote.js';
|
|
15
19
|
import { SwarmSessionsNamespace } from './sessions.js';
|
|
16
20
|
import { SwarmSystemNamespace } from './system.js';
|
|
17
21
|
import { SwarmStorageNamespace } from './storage/index.js';
|
|
22
|
+
import { resolveSwarmConnection } from './local-connection.js';
|
|
18
23
|
import { SwarmTransport } from './transport.js';
|
|
19
24
|
import { SwarmWorkspacesNamespace } from './workspaces.js';
|
|
20
25
|
export class SwarmClient {
|
|
@@ -51,19 +56,23 @@ export class SwarmClient {
|
|
|
51
56
|
notifications;
|
|
52
57
|
/** Convenient alias for notifications namespace: inbox */
|
|
53
58
|
inbox;
|
|
59
|
+
/** Machine signal feed: blocked agents, failures, refused keys, reported events. */
|
|
60
|
+
signals;
|
|
61
|
+
/** Machine state for monitors: sandbox, blocked sessions, failing workers, usage. */
|
|
62
|
+
box;
|
|
54
63
|
storage;
|
|
64
|
+
/** Owner administration of the relay connection (private transport only) */
|
|
65
|
+
remote;
|
|
66
|
+
/** Sealed agents and the client tools your application answers */
|
|
67
|
+
agents;
|
|
55
68
|
/** Convenient alias for cloud storage connections: cloud */
|
|
56
69
|
cloud;
|
|
57
70
|
constructor(config = {}) {
|
|
58
71
|
const env = typeof process !== 'undefined' && process?.env ? process.env : {};
|
|
59
|
-
const baseUrl = config
|
|
60
|
-
env.SWARM_API_URL ||
|
|
61
|
-
env.SWARM_DESKTOP_URL ||
|
|
62
|
-
'http://127.0.0.1:18080';
|
|
63
|
-
const socketPath = config.socketPath || env.SWARM_SOCKET_PATH;
|
|
72
|
+
const { baseUrl, socketPath } = resolveSwarmConnection(config, { env });
|
|
64
73
|
const token = config.token || env.SWARM_AUTH_TOKEN || env.SWARM_DEPLOY_TOKEN;
|
|
65
74
|
const resolved = {
|
|
66
|
-
baseUrl
|
|
75
|
+
baseUrl,
|
|
67
76
|
token,
|
|
68
77
|
socketPath,
|
|
69
78
|
defaultHeaders: config.defaultHeaders ?? {},
|
|
@@ -92,8 +101,12 @@ export class SwarmClient {
|
|
|
92
101
|
this.deploy = new SwarmDeployNamespace(this.transport);
|
|
93
102
|
this.notifications = new SwarmNotificationsNamespace(this.transport);
|
|
94
103
|
this.inbox = this.notifications;
|
|
104
|
+
this.signals = new SwarmSignalsNamespace(this.transport);
|
|
105
|
+
this.box = new SwarmBoxNamespace(this.transport);
|
|
106
|
+
this.agents = new SwarmAgentsNamespace(this.transport);
|
|
95
107
|
this.storage = new SwarmStorageNamespace(this.transport);
|
|
96
108
|
this.cloud = this.storage;
|
|
109
|
+
this.remote = new SwarmRemoteNamespace(this.transport);
|
|
97
110
|
}
|
|
98
111
|
/**
|
|
99
112
|
* Updates the bearer authentication token for all subsequent client requests.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm agents wrap copyable snippets (commands, config, file payloads) in
|
|
3
|
+
* `<copy label="...">…</copy>` tags. Swarm's own desktop and terminal UIs render
|
|
4
|
+
* them as copy buttons; everywhere else they are noise. `copyTagsToMarkdown`
|
|
5
|
+
* turns each tag into a plain Markdown code block (with its label, if any, as a
|
|
6
|
+
* bold line above it), so any Markdown renderer shows it correctly.
|
|
7
|
+
*
|
|
8
|
+
* Rules (same parsing as Swarm's own renderers):
|
|
9
|
+
* - Tags inside fenced code blocks (``` or ~~~) are left untouched.
|
|
10
|
+
* - The label comes from `label`, `title` or `name`.
|
|
11
|
+
* - A tag that is never closed becomes a code block running to the end.
|
|
12
|
+
* - Text without copy tags is returned unchanged.
|
|
13
|
+
*
|
|
14
|
+
* Pass `{ streaming: true }` for text that is still arriving: a half-written tag
|
|
15
|
+
* at the very end is hidden, so converting the growing text only ever appends.
|
|
16
|
+
*
|
|
17
|
+
* The SDK applies it by default to assistant text from `chat.run`,
|
|
18
|
+
* `chat.listMessages` and `chat.stream` (`onText`, `onComplete`); pass
|
|
19
|
+
* `rawCopyTags: true` there to get the original text. Lower-level reads
|
|
20
|
+
* (`sessions.get`, `realtime.watchSession`) return the stored text unchanged.
|
|
21
|
+
*/
|
|
22
|
+
export declare function copyTagsToMarkdown(text: string, options?: {
|
|
23
|
+
streaming?: boolean;
|
|
24
|
+
}): string;
|
|
25
|
+
/** True when `text` contains at least one `<copy>` tag outside fenced code. */
|
|
26
|
+
export declare function hasCopyTags(text: string): boolean;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm agents wrap copyable snippets (commands, config, file payloads) in
|
|
3
|
+
* `<copy label="...">…</copy>` tags. Swarm's own desktop and terminal UIs render
|
|
4
|
+
* them as copy buttons; everywhere else they are noise. `copyTagsToMarkdown`
|
|
5
|
+
* turns each tag into a plain Markdown code block (with its label, if any, as a
|
|
6
|
+
* bold line above it), so any Markdown renderer shows it correctly.
|
|
7
|
+
*
|
|
8
|
+
* Rules (same parsing as Swarm's own renderers):
|
|
9
|
+
* - Tags inside fenced code blocks (``` or ~~~) are left untouched.
|
|
10
|
+
* - The label comes from `label`, `title` or `name`.
|
|
11
|
+
* - A tag that is never closed becomes a code block running to the end.
|
|
12
|
+
* - Text without copy tags is returned unchanged.
|
|
13
|
+
*
|
|
14
|
+
* Pass `{ streaming: true }` for text that is still arriving: a half-written tag
|
|
15
|
+
* at the very end is hidden, so converting the growing text only ever appends.
|
|
16
|
+
*
|
|
17
|
+
* The SDK applies it by default to assistant text from `chat.run`,
|
|
18
|
+
* `chat.listMessages` and `chat.stream` (`onText`, `onComplete`); pass
|
|
19
|
+
* `rawCopyTags: true` there to get the original text. Lower-level reads
|
|
20
|
+
* (`sessions.get`, `realtime.watchSession`) return the stored text unchanged.
|
|
21
|
+
*/
|
|
22
|
+
export function copyTagsToMarkdown(text, options = {}) {
|
|
23
|
+
if (typeof text !== 'string' || !text)
|
|
24
|
+
return text;
|
|
25
|
+
const streaming = options.streaming === true;
|
|
26
|
+
if (!(streaming ? /</.test(text) : /<copy[\s>]/i.test(text)))
|
|
27
|
+
return text;
|
|
28
|
+
const source = text.replace(/\r\n?/g, '\n');
|
|
29
|
+
const protectedRanges = fencedRanges(source);
|
|
30
|
+
let out = '';
|
|
31
|
+
let cursor = 0;
|
|
32
|
+
while (cursor < source.length) {
|
|
33
|
+
const open = nextOpenTag(source, cursor, protectedRanges);
|
|
34
|
+
if (!open) {
|
|
35
|
+
out += source.slice(cursor);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
out += source.slice(cursor, open.start);
|
|
39
|
+
const afterOpen = source.slice(open.end);
|
|
40
|
+
const closeIndex = afterOpen.toLowerCase().indexOf('</copy>');
|
|
41
|
+
if (closeIndex < 0) {
|
|
42
|
+
let body = afterOpen;
|
|
43
|
+
if (streaming)
|
|
44
|
+
body = body.replace(PARTIAL_CLOSE_TAIL, '');
|
|
45
|
+
return out + renderBlock(out, tagLabel(open.tag), body.replace(/^\n+/, ''), false, streaming);
|
|
46
|
+
}
|
|
47
|
+
// Streaming keeps trailing newlines it may already have delivered.
|
|
48
|
+
const body = afterOpen.slice(0, closeIndex).replace(streaming ? /^\n+/ : /^\n+|\n+$/g, '');
|
|
49
|
+
out += renderBlock(out, tagLabel(open.tag), body, true, streaming);
|
|
50
|
+
cursor = open.end + closeIndex + '</copy>'.length;
|
|
51
|
+
if (cursor < source.length && source[cursor] !== '\n')
|
|
52
|
+
out += '\n';
|
|
53
|
+
}
|
|
54
|
+
if (streaming && !inside(out.length - 1, fencedRanges(out)))
|
|
55
|
+
out = out.replace(PARTIAL_OPEN_TAIL, '');
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/** True when `text` contains at least one `<copy>` tag outside fenced code. */
|
|
59
|
+
export function hasCopyTags(text) {
|
|
60
|
+
if (typeof text !== 'string' || !/<copy[\s>]/i.test(text))
|
|
61
|
+
return false;
|
|
62
|
+
const source = text.replace(/\r\n?/g, '\n');
|
|
63
|
+
return nextOpenTag(source, 0, fencedRanges(source)) !== undefined;
|
|
64
|
+
}
|
|
65
|
+
const OPEN_TAG = /<copy(?:\s+[^>]*)?>/gi;
|
|
66
|
+
const LABEL_ATTR = /\b(?:label|title|name)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i;
|
|
67
|
+
// Streaming only: a tag cut off at the very end (`<`, `<co`, `<copy label="x`).
|
|
68
|
+
const PARTIAL_OPEN_TAIL = /<(?:c(?:o(?:p(?:y(?:\s[^<>]*)?)?)?)?)?$/i;
|
|
69
|
+
const PARTIAL_CLOSE_TAIL = /<\/?(?:c(?:o(?:p(?:y)?)?)?)?$/i;
|
|
70
|
+
function renderBlock(before, label, body, closed, streaming) {
|
|
71
|
+
let longest = 0;
|
|
72
|
+
for (const run of body.match(/`+/g) ?? [])
|
|
73
|
+
longest = Math.max(longest, run.length);
|
|
74
|
+
// Streaming uses a 4-backtick fence so a payload that later shows a ``` line does
|
|
75
|
+
// not force re-fencing text that was already delivered.
|
|
76
|
+
const fence = '`'.repeat(Math.max(streaming ? 4 : 3, longest + 1));
|
|
77
|
+
const lead = before === '' || before.endsWith('\n') ? '' : '\n';
|
|
78
|
+
const title = label ? `**${label}**\n` : '';
|
|
79
|
+
// An open (still streaming) block gets no trailing newline, so later text only appends.
|
|
80
|
+
return closed
|
|
81
|
+
? `${lead}${title}${fence}\n${body}${body && !body.endsWith('\n') ? '\n' : ''}${fence}`
|
|
82
|
+
: `${lead}${title}${fence}\n${body}`;
|
|
83
|
+
}
|
|
84
|
+
function tagLabel(tag) {
|
|
85
|
+
const match = LABEL_ATTR.exec(tag);
|
|
86
|
+
if (!match)
|
|
87
|
+
return '';
|
|
88
|
+
return (match[1] ?? match[2] ?? match[3] ?? '').trim();
|
|
89
|
+
}
|
|
90
|
+
function inside(index, ranges) {
|
|
91
|
+
return ranges.some((range) => index >= range.start && index < range.end);
|
|
92
|
+
}
|
|
93
|
+
function nextOpenTag(text, from, ranges) {
|
|
94
|
+
OPEN_TAG.lastIndex = from;
|
|
95
|
+
for (let match = OPEN_TAG.exec(text); match; match = OPEN_TAG.exec(text)) {
|
|
96
|
+
const start = match.index;
|
|
97
|
+
const range = ranges.find((r) => start >= r.start && start < r.end);
|
|
98
|
+
if (range) {
|
|
99
|
+
OPEN_TAG.lastIndex = Math.max(range.end, start + match[0].length);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
return { start, end: start + match[0].length, tag: match[0] };
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
function fencedRanges(text) {
|
|
107
|
+
const ranges = [];
|
|
108
|
+
let fence;
|
|
109
|
+
let lineStart = 0;
|
|
110
|
+
while (lineStart < text.length) {
|
|
111
|
+
const newline = text.indexOf('\n', lineStart);
|
|
112
|
+
const lineEnd = newline < 0 ? text.length : newline;
|
|
113
|
+
const next = newline < 0 ? text.length : newline + 1;
|
|
114
|
+
const line = text.slice(lineStart, lineEnd).trim();
|
|
115
|
+
const marker = line[0];
|
|
116
|
+
if (marker === '`' || marker === '~') {
|
|
117
|
+
let count = 0;
|
|
118
|
+
while (line[count] === marker)
|
|
119
|
+
count++;
|
|
120
|
+
const info = line.slice(count).trim();
|
|
121
|
+
if (!fence && count >= 3)
|
|
122
|
+
fence = { marker, count, start: lineStart };
|
|
123
|
+
else if (fence && marker === fence.marker && count >= fence.count && info === '') {
|
|
124
|
+
ranges.push({ start: fence.start, end: next });
|
|
125
|
+
fence = undefined;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
lineStart = next;
|
|
129
|
+
}
|
|
130
|
+
if (fence)
|
|
131
|
+
ranges.push({ start: fence.start, end: text.length });
|
|
132
|
+
return ranges;
|
|
133
|
+
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -34,3 +34,19 @@ export declare class SwarmValidationError extends SwarmError {
|
|
|
34
34
|
readonly details?: unknown;
|
|
35
35
|
constructor(message: string, details?: unknown);
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* `chat_reconnecting`: the connection to this conversation is being restored; retry shortly.
|
|
39
|
+
* `chat_disconnected`: the connection is down and will not come back on its own; call `connect()`.
|
|
40
|
+
* `chat_not_subscribed`: the conversation is not open in this client; `subscribe()` to it first.
|
|
41
|
+
*/
|
|
42
|
+
export type SwarmChatNotReadyCode = 'chat_reconnecting' | 'chat_disconnected' | 'chat_not_subscribed';
|
|
43
|
+
/**
|
|
44
|
+
* A browser chat send refused before anything reached Swarm. The message is plain enough
|
|
45
|
+
* to show people; apps branch on `code` (or `retryable`) instead of matching text.
|
|
46
|
+
*/
|
|
47
|
+
export declare class SwarmChatNotReadyError extends SwarmError {
|
|
48
|
+
readonly code: SwarmChatNotReadyCode;
|
|
49
|
+
/** True when retrying the same send shortly is expected to work without app action. */
|
|
50
|
+
readonly retryable: boolean;
|
|
51
|
+
constructor(code: SwarmChatNotReadyCode);
|
|
52
|
+
}
|
package/dist/errors.js
CHANGED
|
@@ -66,3 +66,24 @@ export class SwarmValidationError extends SwarmError {
|
|
|
66
66
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
const chatNotReadyMessages = {
|
|
70
|
+
chat_reconnecting: 'Not sent: reconnecting to Swarm, try again in a moment',
|
|
71
|
+
chat_disconnected: 'Not sent: disconnected from Swarm, reconnect and try again',
|
|
72
|
+
chat_not_subscribed: 'Not sent: this conversation is not open yet, open it and try again',
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* A browser chat send refused before anything reached Swarm. The message is plain enough
|
|
76
|
+
* to show people; apps branch on `code` (or `retryable`) instead of matching text.
|
|
77
|
+
*/
|
|
78
|
+
export class SwarmChatNotReadyError extends SwarmError {
|
|
79
|
+
code;
|
|
80
|
+
/** True when retrying the same send shortly is expected to work without app action. */
|
|
81
|
+
retryable;
|
|
82
|
+
constructor(code) {
|
|
83
|
+
super(chatNotReadyMessages[code]);
|
|
84
|
+
this.name = 'SwarmChatNotReadyError';
|
|
85
|
+
this.code = code;
|
|
86
|
+
this.retryable = code === 'chat_reconnecting';
|
|
87
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
88
|
+
}
|
|
89
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './client.js';
|
|
2
|
+
export * from './local-connection.js';
|
|
2
3
|
export * from './auth.js';
|
|
3
4
|
export * from './automations.js';
|
|
4
5
|
export * from './workers.js';
|
|
@@ -6,12 +7,17 @@ export * from './worker-control.js';
|
|
|
6
7
|
export * from './deliverables.js';
|
|
7
8
|
export * from './deploy/index.js';
|
|
8
9
|
export * from './notifications.js';
|
|
10
|
+
export * from './signals.js';
|
|
11
|
+
export * from './box.js';
|
|
9
12
|
export * from './sessions.js';
|
|
10
13
|
export * from './projects.js';
|
|
11
14
|
export * from './permissions.js';
|
|
12
15
|
export * from './chat.js';
|
|
16
|
+
export * from './copy-tags.js';
|
|
13
17
|
export * from './workspaces.js';
|
|
14
18
|
export * from './system.js';
|
|
19
|
+
export * from './remote.js';
|
|
20
|
+
export * from './agents.js';
|
|
15
21
|
export * from './transport.js';
|
|
16
22
|
export * from './errors.js';
|
|
17
23
|
export * from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './client.js';
|
|
2
|
+
export * from './local-connection.js';
|
|
2
3
|
export * from './auth.js';
|
|
3
4
|
export * from './automations.js';
|
|
4
5
|
export * from './workers.js';
|
|
@@ -6,12 +7,17 @@ export * from './worker-control.js';
|
|
|
6
7
|
export * from './deliverables.js';
|
|
7
8
|
export * from './deploy/index.js';
|
|
8
9
|
export * from './notifications.js';
|
|
10
|
+
export * from './signals.js';
|
|
11
|
+
export * from './box.js';
|
|
9
12
|
export * from './sessions.js';
|
|
10
13
|
export * from './projects.js';
|
|
11
14
|
export * from './permissions.js';
|
|
12
15
|
export * from './chat.js';
|
|
16
|
+
export * from './copy-tags.js';
|
|
13
17
|
export * from './workspaces.js';
|
|
14
18
|
export * from './system.js';
|
|
19
|
+
export * from './remote.js';
|
|
20
|
+
export * from './agents.js';
|
|
15
21
|
export * from './transport.js';
|
|
16
22
|
export * from './errors.js';
|
|
17
23
|
export * from './types.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a client with no explicit address finds this machine's Swarm.
|
|
3
|
+
*
|
|
4
|
+
* Order: an explicit `socketPath`/`baseUrl`, then `SWARM_SOCKET_PATH`, then
|
|
5
|
+
* `SWARM_API_URL`/`SWARM_DESKTOP_URL`, then (Node only) the owner socket of a
|
|
6
|
+
* system install when it exists and this process may connect to it, then the
|
|
7
|
+
* daemon's default loopback API at http://127.0.0.1:7781.
|
|
8
|
+
*
|
|
9
|
+
* Browsers never touch the filesystem: the probe uses Node's synchronous
|
|
10
|
+
* `process.getBuiltinModule`, which no bundler resolves and no browser has.
|
|
11
|
+
*/
|
|
12
|
+
/** Owner socket of a Linux system install (`/var/lib/swarmd` data root, main lane). */
|
|
13
|
+
export declare const SWARM_OWNER_SOCKET_PATH = "/var/lib/swarmd/local-transport/api.sock";
|
|
14
|
+
/** The daemon's default loopback API address. */
|
|
15
|
+
export declare const SWARM_DEFAULT_API_URL = "http://127.0.0.1:7781";
|
|
16
|
+
export interface SwarmConnectionInput {
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
socketPath?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface SwarmConnectionOptions {
|
|
21
|
+
/** Environment to read; defaults to `process.env` in Node, `{}` elsewhere. */
|
|
22
|
+
env?: Record<string, string | undefined>;
|
|
23
|
+
/** Owner socket to probe; defaults to {@link SWARM_OWNER_SOCKET_PATH}. */
|
|
24
|
+
ownerSocketPath?: string;
|
|
25
|
+
/** Returns true when `path` is a socket this process may connect to. */
|
|
26
|
+
isUsableSocket?: (path: string) => boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface SwarmConnection {
|
|
29
|
+
baseUrl: string;
|
|
30
|
+
socketPath?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* True when `path` exists, is a Unix socket and is readable and writable by
|
|
34
|
+
* this process. Any error (missing, no permission on a parent directory, not
|
|
35
|
+
* Node) means "not usable"; it never throws.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isUsableSwarmSocket(path: string): boolean;
|
|
38
|
+
/** Resolves where a client with the given explicit settings connects. */
|
|
39
|
+
export declare function resolveSwarmConnection(input?: SwarmConnectionInput, options?: SwarmConnectionOptions): SwarmConnection;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a client with no explicit address finds this machine's Swarm.
|
|
3
|
+
*
|
|
4
|
+
* Order: an explicit `socketPath`/`baseUrl`, then `SWARM_SOCKET_PATH`, then
|
|
5
|
+
* `SWARM_API_URL`/`SWARM_DESKTOP_URL`, then (Node only) the owner socket of a
|
|
6
|
+
* system install when it exists and this process may connect to it, then the
|
|
7
|
+
* daemon's default loopback API at http://127.0.0.1:7781.
|
|
8
|
+
*
|
|
9
|
+
* Browsers never touch the filesystem: the probe uses Node's synchronous
|
|
10
|
+
* `process.getBuiltinModule`, which no bundler resolves and no browser has.
|
|
11
|
+
*/
|
|
12
|
+
/** Owner socket of a Linux system install (`/var/lib/swarmd` data root, main lane). */
|
|
13
|
+
export const SWARM_OWNER_SOCKET_PATH = '/var/lib/swarmd/local-transport/api.sock';
|
|
14
|
+
/** The daemon's default loopback API address. */
|
|
15
|
+
export const SWARM_DEFAULT_API_URL = 'http://127.0.0.1:7781';
|
|
16
|
+
function processEnv() {
|
|
17
|
+
return typeof process !== 'undefined' && process?.env ? process.env : {};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* True when `path` exists, is a Unix socket and is readable and writable by
|
|
21
|
+
* this process. Any error (missing, no permission on a parent directory, not
|
|
22
|
+
* Node) means "not usable"; it never throws.
|
|
23
|
+
*/
|
|
24
|
+
export function isUsableSwarmSocket(path) {
|
|
25
|
+
if (typeof process === 'undefined' || typeof process?.getBuiltinModule !== 'function')
|
|
26
|
+
return false;
|
|
27
|
+
try {
|
|
28
|
+
const fs = process.getBuiltinModule('node:fs');
|
|
29
|
+
if (!fs?.statSync(path)?.isSocket())
|
|
30
|
+
return false;
|
|
31
|
+
fs.accessSync(path, fs.constants.R_OK | fs.constants.W_OK);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Resolves where a client with the given explicit settings connects. */
|
|
39
|
+
export function resolveSwarmConnection(input = {}, options = {}) {
|
|
40
|
+
const env = options.env ?? processEnv();
|
|
41
|
+
const explicitUrl = input.baseUrl || env.SWARM_API_URL || env.SWARM_DESKTOP_URL;
|
|
42
|
+
const baseUrl = (explicitUrl || SWARM_DEFAULT_API_URL).replace(/\/+$/, '');
|
|
43
|
+
if (input.socketPath)
|
|
44
|
+
return { baseUrl, socketPath: input.socketPath };
|
|
45
|
+
if (input.baseUrl)
|
|
46
|
+
return { baseUrl };
|
|
47
|
+
if (env.SWARM_SOCKET_PATH)
|
|
48
|
+
return { baseUrl, socketPath: env.SWARM_SOCKET_PATH };
|
|
49
|
+
if (explicitUrl)
|
|
50
|
+
return { baseUrl };
|
|
51
|
+
const ownerSocket = options.ownerSocketPath ?? SWARM_OWNER_SOCKET_PATH;
|
|
52
|
+
const usable = options.isUsableSocket ?? isUsableSwarmSocket;
|
|
53
|
+
if (usable(ownerSocket))
|
|
54
|
+
return { baseUrl, socketPath: ownerSocket };
|
|
55
|
+
return { baseUrl };
|
|
56
|
+
}
|
package/dist/permission-ui.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface PermissionRequestView {
|
|
|
27
27
|
arguments: Record<string, unknown> | null;
|
|
28
28
|
rawArguments: string;
|
|
29
29
|
parseError?: string;
|
|
30
|
+
/** Plain-text heading: the request's own title, else a fallback (bash command, first ask-user question, tool + action). Never HTML. */
|
|
30
31
|
title?: string;
|
|
31
32
|
context?: string;
|
|
32
33
|
structured: boolean;
|
package/dist/permission-ui.js
CHANGED
|
@@ -15,6 +15,11 @@ export function isAskUserPermission(permission) {
|
|
|
15
15
|
}
|
|
16
16
|
/** Parse untrusted request arguments for text rendering. Never render these fields as HTML. */
|
|
17
17
|
export function describePermission(permission) {
|
|
18
|
+
const view = describePermissionRequest(permission);
|
|
19
|
+
view.title = view.title || permissionFallbackTitle(view);
|
|
20
|
+
return view;
|
|
21
|
+
}
|
|
22
|
+
function describePermissionRequest(permission) {
|
|
18
23
|
const id = permissionId(permission?.id);
|
|
19
24
|
const sessionId = permissionId(permission?.session_id, 'sessionId');
|
|
20
25
|
const raw = permission.tool_arguments ?? permission.tool_call_arguments ?? '';
|
|
@@ -165,3 +170,35 @@ export function confirmPermissionResolution(result, sessionId, id, action, optio
|
|
|
165
170
|
}
|
|
166
171
|
return result;
|
|
167
172
|
}
|
|
173
|
+
const TITLE_MAX = 160;
|
|
174
|
+
/** One plain-text line: control characters and runs of whitespace collapse, long text is cut. */
|
|
175
|
+
function titleLine(value) {
|
|
176
|
+
const line = value.replace(/[\u0000-\u001f\u007f\s]+/g, ' ').trim();
|
|
177
|
+
return line.length > TITLE_MAX ? `${line.slice(0, TITLE_MAX - 1).trimEnd()}…` : line;
|
|
178
|
+
}
|
|
179
|
+
function humanize(value) {
|
|
180
|
+
const words = value.trim().split('.').at(-1).replace(/[_-]+/g, ' ').replace(/([a-z])([A-Z])/g, '$1 $2').trim().toLowerCase();
|
|
181
|
+
return words ? words[0].toUpperCase() + words.slice(1) : '';
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Plain-text fallback title when the request carries none: bash shows its command,
|
|
185
|
+
* ask-user its first question, any other tool its humanized name plus action/subject.
|
|
186
|
+
* Built only from untrusted strings; render it as text, never as HTML.
|
|
187
|
+
*/
|
|
188
|
+
function permissionFallbackTitle(view) {
|
|
189
|
+
const args = view.arguments ?? {};
|
|
190
|
+
const tool = text(view.toolName).toLowerCase().split('.').at(-1) ?? '';
|
|
191
|
+
if ((tool === 'bash' || tool === 'shell') && text(args.command))
|
|
192
|
+
return titleLine(text(args.command));
|
|
193
|
+
if (view.kind === 'ask-user') {
|
|
194
|
+
const first = view.questions[0]?.question
|
|
195
|
+
|| (Array.isArray(args.questions) && object(args.questions[0]) ? text(args.questions[0].question) : '')
|
|
196
|
+
|| text(args.question);
|
|
197
|
+
if (first)
|
|
198
|
+
return titleLine(first);
|
|
199
|
+
}
|
|
200
|
+
const name = humanize(text(view.toolName)) || 'Tool request';
|
|
201
|
+
const action = text(args.action) ? humanize(text(args.action)).toLowerCase() : '';
|
|
202
|
+
const subject = [args.title, args.name, args.path, args.file_path, args.url, args.query].map(text).find(Boolean) ?? '';
|
|
203
|
+
return titleLine(`${name}${action ? `: ${action}` : ''}${subject ? ` (${subject})` : ''}`);
|
|
204
|
+
}
|
package/dist/permissions.d.ts
CHANGED
|
@@ -74,6 +74,10 @@ export declare class SwarmPermissionsNamespace {
|
|
|
74
74
|
* Retrieves the current account-wide permission policy, including bash profile and persistent rules.
|
|
75
75
|
*/
|
|
76
76
|
getPolicy(): Promise<PermissionPolicy>;
|
|
77
|
+
/**
|
|
78
|
+
* Whether permissionless mode (bypass permissions) is on.
|
|
79
|
+
*/
|
|
80
|
+
bypass(): Promise<boolean>;
|
|
77
81
|
/**
|
|
78
82
|
* Toggles permissionless mode (bypass permissions).
|
|
79
83
|
* When enabled, tool executions run autonomously without pausing for manual approvals.
|
package/dist/permissions.js
CHANGED
|
@@ -13,6 +13,13 @@ export class SwarmPermissionsNamespace {
|
|
|
13
13
|
const res = await this.transport.request('/v1/permissions');
|
|
14
14
|
return res.data.policy;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Whether permissionless mode (bypass permissions) is on.
|
|
18
|
+
*/
|
|
19
|
+
async bypass() {
|
|
20
|
+
const res = await this.transport.request('/v1/permissions');
|
|
21
|
+
return res.data.bypass_permissions === true;
|
|
22
|
+
}
|
|
16
23
|
/**
|
|
17
24
|
* Toggles permissionless mode (bypass permissions).
|
|
18
25
|
* When enabled, tool executions run autonomously without pausing for manual approvals.
|
package/dist/projects.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SwarmTransport } from './transport.js';
|
|
2
|
-
import type { ApproveProjectTaskResult, ClearProjectContextResult, CompleteProjectTaskResult, CreateProjectParams, CreateProjectTaskParams, CreateProjectTaskResult, DeleteProjectMediaResult, DeleteProjectResult, DeleteProjectTaskResult, DeployProjectTaskProgramResult, GetProjectTaskProgramResult, GetProjectTaskResult, IntegrateProjectTaskResult, ListProjectsParams, PreviewProjectTaskParams, ProjectRecord, ProjectTaskApprovalGuards, ProjectTaskMediaRef, ProjectTaskModelPreviewResult, ProjectTaskPreviewResult, ProjectTaskRecord, RedeployProjectTaskJobResult, RefineProjectTaskParams, RefineProjectTaskResult, RejectProjectTaskResult, ReopenProjectTaskResult, RequestOptions, SessionRecord, SyncStreamParams, SyncStreamResult,
|
|
2
|
+
import type { ApproveProjectTaskResult, ClearProjectContextResult, CompleteProjectTaskResult, CreateProjectParams, CreateProjectTaskParams, CreateProjectTaskResult, DeleteProjectMediaResult, DeleteProjectResult, DeleteProjectTaskResult, DeployProjectTaskProgramResult, GetProjectTaskProgramResult, GetProjectTaskResult, IntegrateProjectTaskOptions, IntegrateProjectTaskResult, IntegrateProjectTaskSelection, ListProjectsParams, PreviewProjectTaskParams, ProjectRecord, ProjectTaskApprovalGuards, ProjectTaskMediaRef, ProjectTaskModelPreviewResult, ProjectTaskPreviewResult, ProjectTaskRecord, RedeployProjectTaskJobResult, RefineProjectTaskParams, RefineProjectTaskResult, RejectProjectTaskResult, ReopenProjectTaskResult, RequestOptions, SessionRecord, SyncStreamParams, SyncStreamResult, UpdateProjectParams, UpdateProjectTaskParams } from './types.js';
|
|
3
3
|
export declare class SwarmProjectsNamespace {
|
|
4
4
|
private readonly transport;
|
|
5
5
|
constructor(transport: SwarmTransport);
|
|
@@ -38,9 +38,18 @@ export declare class SwarmProjectsNamespace {
|
|
|
38
38
|
listConversations(projectId: string, params?: {
|
|
39
39
|
limit?: number;
|
|
40
40
|
}, reqOptions?: RequestOptions): Promise<SessionRecord[]>;
|
|
41
|
+
/** Rename a project conversation (a conversation ID is its session ID); see sessions.rename. */
|
|
42
|
+
renameConversation(conversationId: string, title: string, options?: {
|
|
43
|
+
clientRequestId?: string;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
ok: boolean;
|
|
46
|
+
session_id: string;
|
|
47
|
+
title: string;
|
|
48
|
+
}>;
|
|
41
49
|
/** Create an independent project conversation without archiving existing conversations.
|
|
42
50
|
* Models/context resolve on the daemon. Project conversations never bind a workspace.
|
|
43
|
-
* Reuse clientRequestId when retrying an uncertain creation result.
|
|
51
|
+
* Reuse clientRequestId when retrying an uncertain creation result. Without a title the
|
|
52
|
+
* conversation is named from its first message; rename it with renameConversation.
|
|
44
53
|
*/
|
|
45
54
|
createConversation(projectId: string, options?: {
|
|
46
55
|
title?: string;
|
|
@@ -56,10 +65,6 @@ export declare class SwarmProjectsNamespace {
|
|
|
56
65
|
* Deletes a project by ID.
|
|
57
66
|
*/
|
|
58
67
|
delete(projectId: string, options?: RequestOptions): Promise<DeleteProjectResult>;
|
|
59
|
-
/**
|
|
60
|
-
* Synthesizes Markdown project context from project name and linked workspaces.
|
|
61
|
-
*/
|
|
62
|
-
synthesizeContext(params: SynthesizeProjectContextParams, options?: RequestOptions): Promise<string>;
|
|
63
68
|
/**
|
|
64
69
|
* Clears orchestrator context for a project, archiving old session and provisioning replacement.
|
|
65
70
|
*/
|
|
@@ -136,8 +141,14 @@ export declare class SwarmProjectsNamespace {
|
|
|
136
141
|
refineTask(projectId: string, taskId: string, params: RefineProjectTaskParams, options?: RequestOptions): Promise<RefineProjectTaskResult>;
|
|
137
142
|
/**
|
|
138
143
|
* Integrates task commits into the parent workspace repository.
|
|
144
|
+
*
|
|
145
|
+
* The daemon only integrates an exact selection: the task's current session and its
|
|
146
|
+
* captured source/target branches. With no options the SDK fetches the task
|
|
147
|
+
* (`getTask`, which runs a fresh delivery assessment) and builds that selection from
|
|
148
|
+
* it; pass `options.selection` to override individual fields. A stale selection is
|
|
149
|
+
* refused by the daemon with a conflict error rather than integrating the wrong lane.
|
|
139
150
|
*/
|
|
140
|
-
integrateTask(projectId: string, taskId: string, options?:
|
|
151
|
+
integrateTask(projectId: string, taskId: string, options?: IntegrateProjectTaskOptions): Promise<IntegrateProjectTaskResult>;
|
|
141
152
|
/**
|
|
142
153
|
* Deploys a task program for staged multi-agent execution.
|
|
143
154
|
*/
|
|
@@ -180,3 +191,10 @@ export declare class SwarmProjectsNamespace {
|
|
|
180
191
|
timeoutMs?: number;
|
|
181
192
|
}): AsyncIterableIterator<TEvent>;
|
|
182
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Builds the integrate selection the daemon checks from a fresh task record.
|
|
196
|
+
* Branch precedence: the detail GET's delivery assessment (the daemon's own view of
|
|
197
|
+
* the captured lane), then the task's recorded worktree/base branch, then the active
|
|
198
|
+
* attempt's. The daemon still verifies every field against the owned session.
|
|
199
|
+
*/
|
|
200
|
+
export declare function integrationSelectionFromTask(task: ProjectTaskRecord): Partial<IntegrateProjectTaskSelection>;
|