@swarm-agent/sdk 0.1.47 → 0.1.48
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 +2 -0
- package/README.md +270 -7
- package/WORKER_CONTROL.md +77 -0
- package/dist/auth.d.ts +26 -0
- package/dist/auth.js +81 -0
- package/dist/browser.d.ts +94 -0
- package/dist/browser.js +408 -0
- package/dist/chat.d.ts +134 -0
- package/dist/chat.js +307 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.js +8 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/permission-ui.d.ts +50 -0
- package/dist/permission-ui.js +167 -0
- package/dist/permissions.d.ts +126 -0
- package/dist/permissions.js +131 -0
- package/dist/projects.d.ts +36 -1
- package/dist/projects.js +96 -1
- package/dist/provider-auth.d.ts +38 -0
- package/dist/provider-auth.js +87 -0
- package/dist/sessions.d.ts +2 -0
- package/dist/sessions.js +34 -7
- package/dist/settings.d.ts +29 -1
- package/dist/settings.js +69 -0
- package/dist/types.d.ts +13 -0
- package/dist/websocket.d.ts +60 -0
- package/dist/websocket.js +207 -0
- package/dist/worker-control.d.ts +133 -0
- package/dist/worker-control.js +93 -0
- package/dist/workers.d.ts +2 -0
- package/dist/workers.js +3 -0
- package/examples/headless-worker-control.ts +28 -0
- package/package.json +8 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { SwarmTransport } from './transport.js';
|
|
2
|
+
import type { PermissionAction, ResolvePermissionOptions } from './permission-ui.js';
|
|
3
|
+
export * from './permission-ui.js';
|
|
4
|
+
export interface PermissionPolicy {
|
|
5
|
+
version: number;
|
|
6
|
+
bash_profile: string;
|
|
7
|
+
rules?: PermissionRule[];
|
|
8
|
+
updated_at?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface PermissionRule {
|
|
11
|
+
id: string;
|
|
12
|
+
kind: string;
|
|
13
|
+
decision: string;
|
|
14
|
+
tool?: string;
|
|
15
|
+
pattern?: string;
|
|
16
|
+
created_at?: number;
|
|
17
|
+
updated_at?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface PermissionRuleInput {
|
|
20
|
+
kind: string;
|
|
21
|
+
decision: 'allow' | 'deny';
|
|
22
|
+
tool?: string;
|
|
23
|
+
pattern?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface PendingPermissionRecord {
|
|
26
|
+
id: string;
|
|
27
|
+
session_id: string;
|
|
28
|
+
run_id: string;
|
|
29
|
+
step?: number;
|
|
30
|
+
call_id?: string;
|
|
31
|
+
tool_name: string;
|
|
32
|
+
tool_arguments?: string;
|
|
33
|
+
tool_call_arguments?: string;
|
|
34
|
+
approved_arguments?: string;
|
|
35
|
+
requirement: string;
|
|
36
|
+
mode: string;
|
|
37
|
+
status: 'pending' | 'approved' | 'denied' | string;
|
|
38
|
+
decision?: string;
|
|
39
|
+
reason?: string;
|
|
40
|
+
execution_status?: string;
|
|
41
|
+
permission_requested_at?: number;
|
|
42
|
+
resolved_at?: number;
|
|
43
|
+
created_at: number;
|
|
44
|
+
updated_at: number;
|
|
45
|
+
}
|
|
46
|
+
export interface ResolvePermissionResult {
|
|
47
|
+
ok: boolean;
|
|
48
|
+
session_id: string;
|
|
49
|
+
permission: PendingPermissionRecord;
|
|
50
|
+
saved_rule?: PermissionRule | null;
|
|
51
|
+
}
|
|
52
|
+
export interface ResolveAllPermissionsResult {
|
|
53
|
+
ok: boolean;
|
|
54
|
+
session_id: string;
|
|
55
|
+
count: number;
|
|
56
|
+
resolved: PendingPermissionRecord[];
|
|
57
|
+
}
|
|
58
|
+
export interface PermissionExplainResult {
|
|
59
|
+
decision: string;
|
|
60
|
+
source: string;
|
|
61
|
+
reason: string;
|
|
62
|
+
tool_name?: string;
|
|
63
|
+
command?: string;
|
|
64
|
+
rule_preview?: string;
|
|
65
|
+
bash_profile?: string;
|
|
66
|
+
profile_decision?: string;
|
|
67
|
+
profile_reason?: string;
|
|
68
|
+
}
|
|
69
|
+
export declare class SwarmPermissionsNamespace {
|
|
70
|
+
private readonly transport;
|
|
71
|
+
constructor(transport: SwarmTransport);
|
|
72
|
+
private readonly resolving;
|
|
73
|
+
/**
|
|
74
|
+
* Retrieves the current account-wide permission policy, including bash profile and persistent rules.
|
|
75
|
+
*/
|
|
76
|
+
getPolicy(): Promise<PermissionPolicy>;
|
|
77
|
+
/**
|
|
78
|
+
* Toggles permissionless mode (bypass permissions).
|
|
79
|
+
* When enabled, tool executions run autonomously without pausing for manual approvals.
|
|
80
|
+
*/
|
|
81
|
+
setBypass(enabled: boolean): Promise<boolean>;
|
|
82
|
+
/**
|
|
83
|
+
* Updates the bash approval profile ('strict' | 'onboarding' | 'permissive').
|
|
84
|
+
*/
|
|
85
|
+
setBashProfile(profile: 'strict' | 'onboarding' | 'permissive'): Promise<string>;
|
|
86
|
+
/**
|
|
87
|
+
* Adds a persistent permission rule (e.g. allow tool 'search' or allow specific command patterns).
|
|
88
|
+
*/
|
|
89
|
+
addRule(rule: PermissionRuleInput): Promise<PermissionRule>;
|
|
90
|
+
/**
|
|
91
|
+
* Removes a persistent permission rule by ID.
|
|
92
|
+
*/
|
|
93
|
+
removeRule(ruleId: string): Promise<boolean>;
|
|
94
|
+
/**
|
|
95
|
+
* Resets the permission policy to default system state.
|
|
96
|
+
*/
|
|
97
|
+
resetPolicy(): Promise<PermissionPolicy>;
|
|
98
|
+
/**
|
|
99
|
+
* Explains how the permission policy evaluates a given mode, tool, and arguments.
|
|
100
|
+
*/
|
|
101
|
+
explain(mode: string, tool: string, args?: string): Promise<PermissionExplainResult>;
|
|
102
|
+
/**
|
|
103
|
+
* Lists pending permissions for a specific session that are blocking tool execution.
|
|
104
|
+
*/
|
|
105
|
+
listSessionPending(sessionId: string, limit?: number): Promise<PendingPermissionRecord[]>;
|
|
106
|
+
/**
|
|
107
|
+
* Lists all permissions (pending and resolved) for a session.
|
|
108
|
+
*/
|
|
109
|
+
listSessionAll(sessionId: string, limit?: number): Promise<PendingPermissionRecord[]>;
|
|
110
|
+
/**
|
|
111
|
+
* Resolves a single pending permission on a session.
|
|
112
|
+
* Actions:
|
|
113
|
+
* - 'allow_once': Approve this exact tool invocation once.
|
|
114
|
+
* - 'deny' / 'deny_once': Deny this exact tool invocation once.
|
|
115
|
+
* - 'allow_always': Approve this tool call and create a persistent allow rule.
|
|
116
|
+
* - 'deny_always': Deny this tool call and create a persistent deny rule.
|
|
117
|
+
*/
|
|
118
|
+
resolve(sessionId: string, permissionId: string, action: PermissionAction, options?: ResolvePermissionOptions): Promise<ResolvePermissionResult>;
|
|
119
|
+
/**
|
|
120
|
+
* Resolves all pending permissions on a session in a single call.
|
|
121
|
+
*/
|
|
122
|
+
resolveAll(sessionId: string, action: 'allow_once' | 'deny', options?: {
|
|
123
|
+
reason?: string;
|
|
124
|
+
limit?: number;
|
|
125
|
+
}): Promise<ResolveAllPermissionsResult>;
|
|
126
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { confirmPermissionResolution, permissionId as validatePermissionId, permissionResolutionBody } from './permission-ui.js';
|
|
2
|
+
export * from './permission-ui.js';
|
|
3
|
+
export class SwarmPermissionsNamespace {
|
|
4
|
+
transport;
|
|
5
|
+
constructor(transport) {
|
|
6
|
+
this.transport = transport;
|
|
7
|
+
}
|
|
8
|
+
resolving = new Set();
|
|
9
|
+
/**
|
|
10
|
+
* Retrieves the current account-wide permission policy, including bash profile and persistent rules.
|
|
11
|
+
*/
|
|
12
|
+
async getPolicy() {
|
|
13
|
+
const res = await this.transport.request('/v1/permissions');
|
|
14
|
+
return res.data.policy;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Toggles permissionless mode (bypass permissions).
|
|
18
|
+
* When enabled, tool executions run autonomously without pausing for manual approvals.
|
|
19
|
+
*/
|
|
20
|
+
async setBypass(enabled) {
|
|
21
|
+
const res = await this.transport.request('/v1/permissions/bypass', { method: 'POST', body: { enabled } });
|
|
22
|
+
return res.data.bypass_permissions === true;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Updates the bash approval profile ('strict' | 'onboarding' | 'permissive').
|
|
26
|
+
*/
|
|
27
|
+
async setBashProfile(profile) {
|
|
28
|
+
const res = await this.transport.request('/v1/permissions/bash-profile', { method: 'POST', body: { bash_profile: profile.trim() } });
|
|
29
|
+
return res.data.bash_profile;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Adds a persistent permission rule (e.g. allow tool 'search' or allow specific command patterns).
|
|
33
|
+
*/
|
|
34
|
+
async addRule(rule) {
|
|
35
|
+
const res = await this.transport.request('/v1/permissions', { method: 'POST', body: rule });
|
|
36
|
+
return res.data.rule;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Removes a persistent permission rule by ID.
|
|
40
|
+
*/
|
|
41
|
+
async removeRule(ruleId) {
|
|
42
|
+
const res = await this.transport.request(`/v1/permissions/${encodeURIComponent(ruleId.trim())}`, { method: 'DELETE' });
|
|
43
|
+
return res.data.removed === true;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resets the permission policy to default system state.
|
|
47
|
+
*/
|
|
48
|
+
async resetPolicy() {
|
|
49
|
+
const res = await this.transport.request('/v1/permissions/reset', { method: 'POST', body: {} });
|
|
50
|
+
return res.data.policy;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Explains how the permission policy evaluates a given mode, tool, and arguments.
|
|
54
|
+
*/
|
|
55
|
+
async explain(mode, tool, args = '') {
|
|
56
|
+
const query = new URLSearchParams({ mode, tool, arguments: args });
|
|
57
|
+
const res = await this.transport.request(`/v1/permissions/explain?${query}`);
|
|
58
|
+
return res.data.explain;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Lists pending permissions for a specific session that are blocking tool execution.
|
|
62
|
+
*/
|
|
63
|
+
async listSessionPending(sessionId, limit = 50) {
|
|
64
|
+
sessionId = validatePermissionId(sessionId, 'sessionId');
|
|
65
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 1000)
|
|
66
|
+
throw new Error('limit must be an integer from 1 to 1000');
|
|
67
|
+
const encId = encodeURIComponent(sessionId);
|
|
68
|
+
const res = await this.transport.request(`/v3/sessions/${encId}/permissions?status=pending&limit=${limit}`);
|
|
69
|
+
if (res.data?.ok === true && res.data.count === 0 && res.data.permissions === null)
|
|
70
|
+
return [];
|
|
71
|
+
if (res.data?.ok !== true || !Array.isArray(res.data.permissions) || res.data.permissions.some(p => !p || p.session_id !== sessionId || typeof p.id !== 'string' || !p.id)) {
|
|
72
|
+
throw new Error('Invalid permission list response; refresh the conversation');
|
|
73
|
+
}
|
|
74
|
+
return res.data.permissions;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Lists all permissions (pending and resolved) for a session.
|
|
78
|
+
*/
|
|
79
|
+
async listSessionAll(sessionId, limit = 100) {
|
|
80
|
+
sessionId = validatePermissionId(sessionId, 'sessionId');
|
|
81
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 1000)
|
|
82
|
+
throw new Error('limit must be an integer from 1 to 1000');
|
|
83
|
+
const encId = encodeURIComponent(sessionId);
|
|
84
|
+
const res = await this.transport.request(`/v3/sessions/${encId}/permissions?status=all&limit=${limit}`);
|
|
85
|
+
if (res.data?.ok === true && res.data.count === 0 && res.data.permissions === null)
|
|
86
|
+
return [];
|
|
87
|
+
if (res.data?.ok !== true || !Array.isArray(res.data.permissions) || res.data.permissions.some(p => !p || p.session_id !== sessionId || typeof p.id !== 'string' || !p.id)) {
|
|
88
|
+
throw new Error('Invalid permission list response; refresh the conversation');
|
|
89
|
+
}
|
|
90
|
+
return res.data.permissions;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolves a single pending permission on a session.
|
|
94
|
+
* Actions:
|
|
95
|
+
* - 'allow_once': Approve this exact tool invocation once.
|
|
96
|
+
* - 'deny' / 'deny_once': Deny this exact tool invocation once.
|
|
97
|
+
* - 'allow_always': Approve this tool call and create a persistent allow rule.
|
|
98
|
+
* - 'deny_always': Deny this tool call and create a persistent deny rule.
|
|
99
|
+
*/
|
|
100
|
+
async resolve(sessionId, permissionId, action, options = {}) {
|
|
101
|
+
const session = validatePermissionId(sessionId, 'sessionId');
|
|
102
|
+
const id = validatePermissionId(permissionId);
|
|
103
|
+
const body = permissionResolutionBody(action, options);
|
|
104
|
+
const key = JSON.stringify([session, id]);
|
|
105
|
+
if (this.resolving.has(key))
|
|
106
|
+
throw new Error('Permission resolution already in flight');
|
|
107
|
+
this.resolving.add(key);
|
|
108
|
+
try {
|
|
109
|
+
const res = await this.transport.request(`/v3/sessions/${encodeURIComponent(session)}/permissions/${encodeURIComponent(id)}/resolve`, { method: 'POST', body });
|
|
110
|
+
return confirmPermissionResolution(res.data, session, id, action, options);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
this.resolving.delete(key);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolves all pending permissions on a session in a single call.
|
|
118
|
+
*/
|
|
119
|
+
async resolveAll(sessionId, action, options = {}) {
|
|
120
|
+
const encSession = encodeURIComponent(sessionId.trim());
|
|
121
|
+
const res = await this.transport.request(`/v3/sessions/${encSession}/permissions/resolve_all`, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
body: {
|
|
124
|
+
action,
|
|
125
|
+
reason: options.reason ?? '',
|
|
126
|
+
limit: options.limit ?? 50,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
return res.data;
|
|
130
|
+
}
|
|
131
|
+
}
|
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, SyncStreamParams, SyncStreamResult, SynthesizeProjectContextParams, UpdateProjectParams, UpdateProjectTaskParams } from './types.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, SynthesizeProjectContextParams, UpdateProjectParams, UpdateProjectTaskParams } from './types.js';
|
|
3
3
|
export declare class SwarmProjectsNamespace {
|
|
4
4
|
private readonly transport;
|
|
5
5
|
constructor(transport: SwarmTransport);
|
|
@@ -11,10 +11,43 @@ export declare class SwarmProjectsNamespace {
|
|
|
11
11
|
* Creates a new project in the active account.
|
|
12
12
|
*/
|
|
13
13
|
create(params: CreateProjectParams, options?: RequestOptions): Promise<ProjectRecord>;
|
|
14
|
+
/** Retry the same project's context generation using its latest attempt receipt. */
|
|
15
|
+
retryContext(projectId: string, expectedAttempt: number, options?: RequestOptions): Promise<ProjectRecord>;
|
|
14
16
|
/**
|
|
15
17
|
* Retrieves a single project by ID.
|
|
16
18
|
*/
|
|
17
19
|
get(projectId: string, options?: RequestOptions): Promise<ProjectRecord>;
|
|
20
|
+
/**
|
|
21
|
+
* Ensures a project exists by name. If found, returns the existing record;
|
|
22
|
+
* otherwise creates a new project with the specified name and optional workspace binding.
|
|
23
|
+
*/
|
|
24
|
+
ensureProject(name: string, options?: {
|
|
25
|
+
description?: string;
|
|
26
|
+
workspacePath?: string;
|
|
27
|
+
workspaceId?: string;
|
|
28
|
+
}, reqOptions?: RequestOptions): Promise<ProjectRecord>;
|
|
29
|
+
/**
|
|
30
|
+
* Retrieves an active orchestrator session for this project, or creates one if none exists.
|
|
31
|
+
*/
|
|
32
|
+
getOrchestratorSession(projectId: string, options?: {
|
|
33
|
+
title?: string;
|
|
34
|
+
mode?: 'auto' | 'plan';
|
|
35
|
+
agentName?: string;
|
|
36
|
+
}, reqOptions?: RequestOptions): Promise<SessionRecord>;
|
|
37
|
+
/** Project orchestrator conversations, not delegated task sessions or workspace chats. */
|
|
38
|
+
listConversations(projectId: string, params?: {
|
|
39
|
+
limit?: number;
|
|
40
|
+
}, reqOptions?: RequestOptions): Promise<SessionRecord[]>;
|
|
41
|
+
/** Create an independent project conversation without archiving existing conversations.
|
|
42
|
+
* Models/context resolve on the daemon. Project conversations never bind a workspace.
|
|
43
|
+
* Reuse clientRequestId when retrying an uncertain creation result.
|
|
44
|
+
*/
|
|
45
|
+
createConversation(projectId: string, options?: {
|
|
46
|
+
title?: string;
|
|
47
|
+
mode?: 'auto' | 'plan';
|
|
48
|
+
agentName?: string;
|
|
49
|
+
clientRequestId?: string;
|
|
50
|
+
}, reqOptions?: RequestOptions): Promise<SessionRecord>;
|
|
18
51
|
/**
|
|
19
52
|
* Updates an existing project's metadata, context, or bound workspaces.
|
|
20
53
|
*/
|
|
@@ -61,6 +94,8 @@ export declare class SwarmProjectsNamespace {
|
|
|
61
94
|
updateTask(projectId: string, taskId: string, params: UpdateProjectTaskParams, options?: RequestOptions): Promise<ProjectTaskRecord>;
|
|
62
95
|
/** Archives a task only at the supplied revision. */
|
|
63
96
|
archiveTask(projectId: string, taskId: string, revision: number, options?: RequestOptions): Promise<ProjectTaskRecord>;
|
|
97
|
+
/** Restores task visibility without changing execution or linked sessions. */
|
|
98
|
+
unarchiveTask(projectId: string, taskId: string, revision: number, options?: RequestOptions): Promise<ProjectTaskRecord>;
|
|
64
99
|
/** Deletes only an archived, unlaunched task at the supplied revision. */
|
|
65
100
|
deleteTask(projectId: string, taskId: string, revision: number, options?: RequestOptions): Promise<DeleteProjectTaskResult>;
|
|
66
101
|
/**
|
package/dist/projects.js
CHANGED
|
@@ -29,9 +29,10 @@ export class SwarmProjectsNamespace {
|
|
|
29
29
|
* Creates a new project in the active account.
|
|
30
30
|
*/
|
|
31
31
|
async create(params, options) {
|
|
32
|
+
const clientRequestId = params.client_request_id ?? `sdk-project-${crypto.randomUUID()}`;
|
|
32
33
|
const res = await this.transport.request('/v3/projects', {
|
|
33
34
|
method: 'POST',
|
|
34
|
-
body: params,
|
|
35
|
+
body: { ...params, client_request_id: clientRequestId },
|
|
35
36
|
...options,
|
|
36
37
|
});
|
|
37
38
|
const data = res.data;
|
|
@@ -43,6 +44,14 @@ export class SwarmProjectsNamespace {
|
|
|
43
44
|
}
|
|
44
45
|
throw new SwarmApiError('Malformed response: missing project record', { status: res.status, details: data });
|
|
45
46
|
}
|
|
47
|
+
/** Retry the same project's context generation using its latest attempt receipt. */
|
|
48
|
+
async retryContext(projectId, expectedAttempt, options) {
|
|
49
|
+
const res = await this.transport.request(`/v3/projects/${encodeURIComponent(projectId.trim())}/context:retry`, { ...options, method: 'POST', body: { expected_attempt: expectedAttempt } });
|
|
50
|
+
if (!res.data?.project || res.data.project.id !== projectId.trim()) {
|
|
51
|
+
throw new SwarmApiError('Malformed response: project retry identity mismatch', { status: res.status, details: res.data });
|
|
52
|
+
}
|
|
53
|
+
return res.data.project;
|
|
54
|
+
}
|
|
46
55
|
/**
|
|
47
56
|
* Retrieves a single project by ID.
|
|
48
57
|
*/
|
|
@@ -58,6 +67,84 @@ export class SwarmProjectsNamespace {
|
|
|
58
67
|
}
|
|
59
68
|
throw new SwarmApiError('Malformed response: missing project record', { status: res.status, details: data });
|
|
60
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Ensures a project exists by name. If found, returns the existing record;
|
|
72
|
+
* otherwise creates a new project with the specified name and optional workspace binding.
|
|
73
|
+
*/
|
|
74
|
+
async ensureProject(name, options = {}, reqOptions) {
|
|
75
|
+
const trimmed = name.trim();
|
|
76
|
+
const existingList = await this.list({ limit: 50 }, reqOptions);
|
|
77
|
+
const existing = existingList.find((p) => p.name === trimmed);
|
|
78
|
+
if (existing)
|
|
79
|
+
return existing;
|
|
80
|
+
const workspaces = options.workspacePath
|
|
81
|
+
? [{ path: options.workspacePath, workspace_id: options.workspaceId }]
|
|
82
|
+
: undefined;
|
|
83
|
+
return this.create({
|
|
84
|
+
name: trimmed,
|
|
85
|
+
description: options.description ?? `Project ${trimmed}`,
|
|
86
|
+
workspaces,
|
|
87
|
+
}, reqOptions);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Retrieves an active orchestrator session for this project, or creates one if none exists.
|
|
91
|
+
*/
|
|
92
|
+
async getOrchestratorSession(projectId, options = {}, reqOptions) {
|
|
93
|
+
const sessions = await this.listConversations(projectId, {}, reqOptions);
|
|
94
|
+
const active = sessions.find((s) => !s.archived_at && !s.archived);
|
|
95
|
+
if (active)
|
|
96
|
+
return active;
|
|
97
|
+
return this.createConversation(projectId, options, reqOptions);
|
|
98
|
+
}
|
|
99
|
+
/** Project orchestrator conversations, not delegated task sessions or workspace chats. */
|
|
100
|
+
async listConversations(projectId, params = {}, reqOptions) {
|
|
101
|
+
if (!projectId.trim())
|
|
102
|
+
throw new Error('projectId is required');
|
|
103
|
+
const q = new URLSearchParams();
|
|
104
|
+
if (params.limit)
|
|
105
|
+
q.set('limit', String(params.limit));
|
|
106
|
+
const suffix = q.size ? `?${q}` : '';
|
|
107
|
+
const res = await this.transport.request(`/v3/projects/${encodeURIComponent(projectId.trim())}/sessions${suffix}`, { method: 'GET', ...reqOptions });
|
|
108
|
+
const items = Array.isArray(res.data) ? res.data : res.data?.sessions;
|
|
109
|
+
if (!Array.isArray(items))
|
|
110
|
+
throw new SwarmApiError('Malformed conversation list', { status: res.status });
|
|
111
|
+
return items.map((item) => {
|
|
112
|
+
const session = item.session ? { ...item.session, projection: item.projection } : item;
|
|
113
|
+
if (!session?.id)
|
|
114
|
+
throw new SwarmApiError('Conversation is missing session identity', { status: res.status });
|
|
115
|
+
return session;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/** Create an independent project conversation without archiving existing conversations.
|
|
119
|
+
* Models/context resolve on the daemon. Project conversations never bind a workspace.
|
|
120
|
+
* Reuse clientRequestId when retrying an uncertain creation result.
|
|
121
|
+
*/
|
|
122
|
+
async createConversation(projectId, options = {}, reqOptions) {
|
|
123
|
+
const id = projectId.trim();
|
|
124
|
+
if (!id)
|
|
125
|
+
throw new Error('projectId is required');
|
|
126
|
+
if (options.agentName && options.agentName !== 'system-orchestrator') {
|
|
127
|
+
throw new Error('Project conversations require system-orchestrator; use chat.createSession for standalone chat');
|
|
128
|
+
}
|
|
129
|
+
const encId = encodeURIComponent(id);
|
|
130
|
+
const createRes = await this.transport.request(`/v3/projects/${encId}/sessions`, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
body: {
|
|
133
|
+
client_request_id: options.clientRequestId ?? `proj-orch-${globalThis.crypto.randomUUID()}`,
|
|
134
|
+
agent_name: options.agentName ?? 'system-orchestrator',
|
|
135
|
+
title: options.title ?? 'Orchestrator AI',
|
|
136
|
+
mode: options.mode ?? 'auto',
|
|
137
|
+
},
|
|
138
|
+
...reqOptions,
|
|
139
|
+
});
|
|
140
|
+
const data = createRes.data;
|
|
141
|
+
if (data && typeof data === 'object' && 'session' in data && data.session) {
|
|
142
|
+
return data.session;
|
|
143
|
+
}
|
|
144
|
+
if (data && 'id' in data && data.id)
|
|
145
|
+
return data;
|
|
146
|
+
throw new SwarmApiError('Malformed conversation creation response', { status: createRes.status });
|
|
147
|
+
}
|
|
61
148
|
/**
|
|
62
149
|
* Updates an existing project's metadata, context, or bound workspaces.
|
|
63
150
|
*/
|
|
@@ -239,6 +326,14 @@ export class SwarmProjectsNamespace {
|
|
|
239
326
|
throw new SwarmApiError('Malformed response: missing archived task', { status: res.status, details: res.data });
|
|
240
327
|
return res.data.task;
|
|
241
328
|
}
|
|
329
|
+
/** Restores task visibility without changing execution or linked sessions. */
|
|
330
|
+
async unarchiveTask(projectId, taskId, revision, options) {
|
|
331
|
+
const path = `/v3/projects/${encodeURIComponent(projectId.trim())}/tasks/${encodeURIComponent(taskId.trim())}/unarchive`;
|
|
332
|
+
const res = await this.transport.request(path, { method: 'POST', body: { revision }, ...options });
|
|
333
|
+
if (!res.data?.task)
|
|
334
|
+
throw new SwarmApiError('Malformed response: missing unarchived task', { status: res.status, details: res.data });
|
|
335
|
+
return res.data.task;
|
|
336
|
+
}
|
|
242
337
|
/** Deletes only an archived, unlaunched task at the supplied revision. */
|
|
243
338
|
async deleteTask(projectId, taskId, revision, options) {
|
|
244
339
|
const pId = encodeURIComponent(projectId.trim());
|
package/dist/provider-auth.d.ts
CHANGED
|
@@ -92,6 +92,21 @@ export declare class SwarmCredentialsNamespace {
|
|
|
92
92
|
cleanup: Record<string, unknown>;
|
|
93
93
|
}>;
|
|
94
94
|
}
|
|
95
|
+
export interface CodexDeviceCode {
|
|
96
|
+
verification_url: string;
|
|
97
|
+
user_code: string;
|
|
98
|
+
session_id: string;
|
|
99
|
+
expires_at?: number;
|
|
100
|
+
}
|
|
101
|
+
export interface CodexDeviceLoginOptions {
|
|
102
|
+
label?: string;
|
|
103
|
+
active?: boolean;
|
|
104
|
+
intervalMs?: number;
|
|
105
|
+
timeoutMs?: number;
|
|
106
|
+
signal?: AbortSignal;
|
|
107
|
+
onCode?: (code: CodexDeviceCode) => void;
|
|
108
|
+
onStatus?: (status: CodexLogin) => void;
|
|
109
|
+
}
|
|
95
110
|
export declare class SwarmCodexAuthNamespace {
|
|
96
111
|
private readonly transport;
|
|
97
112
|
constructor(transport: SwarmTransport);
|
|
@@ -102,6 +117,20 @@ export declare class SwarmCodexAuthNamespace {
|
|
|
102
117
|
}): Promise<CodexLogin>;
|
|
103
118
|
status(session_id: string): Promise<CodexLogin>;
|
|
104
119
|
complete(session_id: string, callback_input: string): Promise<CodexLogin>;
|
|
120
|
+
/**
|
|
121
|
+
* High-level Codex device login flow.
|
|
122
|
+
* Starts the OAuth device authorization session, invokes onCode with verification URL
|
|
123
|
+
* and user code, and polls status until completion, timeout, or abort.
|
|
124
|
+
*/
|
|
125
|
+
loginDevice(options?: CodexDeviceLoginOptions): Promise<CodexLogin>;
|
|
126
|
+
/**
|
|
127
|
+
* Checks whether Codex is already connected and active.
|
|
128
|
+
*/
|
|
129
|
+
isAuthenticated(): Promise<boolean>;
|
|
130
|
+
/**
|
|
131
|
+
* Retrieves the active Codex credential status if one exists.
|
|
132
|
+
*/
|
|
133
|
+
getCredential(): Promise<CredentialStatus | null>;
|
|
105
134
|
}
|
|
106
135
|
export interface OnboardingStatus {
|
|
107
136
|
ok: boolean;
|
|
@@ -155,6 +184,15 @@ export declare class SwarmOnboardingNamespace {
|
|
|
155
184
|
constructor(transport: SwarmTransport);
|
|
156
185
|
get(): Promise<OnboardingStatus>;
|
|
157
186
|
update(body: OnboardingUpdate): Promise<OnboardingStatus>;
|
|
187
|
+
/**
|
|
188
|
+
* Ensures the headless daemon identity is bootstrapped.
|
|
189
|
+
* If the daemon requires onboarding, it automatically sets default identity parameters
|
|
190
|
+
* so provider authentication and sessions can immediately proceed without manual wizard steps.
|
|
191
|
+
*/
|
|
192
|
+
ensureBootstrapped(options?: {
|
|
193
|
+
username?: string;
|
|
194
|
+
swarm_name?: string;
|
|
195
|
+
}): Promise<OnboardingStatus>;
|
|
158
196
|
/** First credential only. Use auth.credentials.save for subsequent credentials. */
|
|
159
197
|
credential(body: Omit<CredentialInput, 'id'>): Promise<CredentialStatus>;
|
|
160
198
|
}
|
package/dist/provider-auth.js
CHANGED
|
@@ -37,6 +37,74 @@ export class SwarmCodexAuthNamespace {
|
|
|
37
37
|
async complete(session_id, callback_input) {
|
|
38
38
|
return (await this.transport.request('/v1/auth/codex/oauth/complete', { method: 'POST', body: { session_id, callback_input } })).data;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* High-level Codex device login flow.
|
|
42
|
+
* Starts the OAuth device authorization session, invokes onCode with verification URL
|
|
43
|
+
* and user code, and polls status until completion, timeout, or abort.
|
|
44
|
+
*/
|
|
45
|
+
async loginDevice(options = {}) {
|
|
46
|
+
const startRes = await this.start({
|
|
47
|
+
method: 'device',
|
|
48
|
+
label: options.label ?? 'Codex Device Login',
|
|
49
|
+
active: options.active ?? true,
|
|
50
|
+
});
|
|
51
|
+
if (options.onCode && startRes.verification_url && startRes.user_code) {
|
|
52
|
+
options.onCode({
|
|
53
|
+
verification_url: startRes.verification_url,
|
|
54
|
+
user_code: startRes.user_code,
|
|
55
|
+
session_id: startRes.session_id,
|
|
56
|
+
expires_at: startRes.expires_at,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const intervalMs = Math.max(500, options.intervalMs ?? 2000);
|
|
60
|
+
const timeoutMs = options.timeoutMs ?? 15 * 60 * 1000;
|
|
61
|
+
const deadline = Date.now() + timeoutMs;
|
|
62
|
+
while (Date.now() < deadline) {
|
|
63
|
+
if (options.signal?.aborted) {
|
|
64
|
+
throw new Error('Codex device login aborted by caller');
|
|
65
|
+
}
|
|
66
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
67
|
+
if (options.signal?.aborted) {
|
|
68
|
+
throw new Error('Codex device login aborted by caller');
|
|
69
|
+
}
|
|
70
|
+
const current = await this.status(startRes.session_id);
|
|
71
|
+
options.onStatus?.(current);
|
|
72
|
+
if (current.status === 'success') {
|
|
73
|
+
return current;
|
|
74
|
+
}
|
|
75
|
+
if (current.status === 'error' || current.status === 'failed' || current.status === 'expired') {
|
|
76
|
+
throw new Error(current.error || `Codex login failed with status: ${current.status}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
throw new Error(`Codex device login timed out after ${timeoutMs}ms`);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Checks whether Codex is already connected and active.
|
|
83
|
+
*/
|
|
84
|
+
async isAuthenticated() {
|
|
85
|
+
try {
|
|
86
|
+
const res = await (await this.transport.request('/v1/auth/credentials?provider=codex')).data;
|
|
87
|
+
if (!res?.records?.length)
|
|
88
|
+
return false;
|
|
89
|
+
return res.records.some((r) => r.provider === 'codex' && r.active && r.connection?.connected !== false);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Retrieves the active Codex credential status if one exists.
|
|
97
|
+
*/
|
|
98
|
+
async getCredential() {
|
|
99
|
+
try {
|
|
100
|
+
const res = await (await this.transport.request('/v1/auth/credentials?provider=codex')).data;
|
|
101
|
+
const match = res?.records?.find((r) => r.provider === 'codex' && r.active);
|
|
102
|
+
return match ?? null;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
40
108
|
}
|
|
41
109
|
export class SwarmOnboardingNamespace {
|
|
42
110
|
transport;
|
|
@@ -47,6 +115,25 @@ export class SwarmOnboardingNamespace {
|
|
|
47
115
|
async update(body) {
|
|
48
116
|
return (await this.transport.request('/v1/onboarding', { method: 'POST', body })).data;
|
|
49
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Ensures the headless daemon identity is bootstrapped.
|
|
120
|
+
* If the daemon requires onboarding, it automatically sets default identity parameters
|
|
121
|
+
* so provider authentication and sessions can immediately proceed without manual wizard steps.
|
|
122
|
+
*/
|
|
123
|
+
async ensureBootstrapped(options = {}) {
|
|
124
|
+
const status = await this.get();
|
|
125
|
+
if (!status.needs_onboarding && status.identity?.bootstrapped) {
|
|
126
|
+
return status;
|
|
127
|
+
}
|
|
128
|
+
if (status.identity?.bootstrapped) {
|
|
129
|
+
return this.update({ desktop_onboarding_complete: true });
|
|
130
|
+
}
|
|
131
|
+
return this.update({
|
|
132
|
+
username: options.username ?? 'owner',
|
|
133
|
+
swarm_name: options.swarm_name ?? 'Headless Swarm',
|
|
134
|
+
desktop_onboarding_complete: true,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
50
137
|
/** First credential only. Use auth.credentials.save for subsequent credentials. */
|
|
51
138
|
async credential(body) {
|
|
52
139
|
return (await this.transport.request('/v1/onboarding/provider/credential', { method: 'POST', body })).data;
|
package/dist/sessions.d.ts
CHANGED
|
@@ -52,9 +52,11 @@ export declare class SwarmSessionsNamespace {
|
|
|
52
52
|
}): Promise<boolean>;
|
|
53
53
|
/**
|
|
54
54
|
* Helper that polls a session until its active run completes, or times out.
|
|
55
|
+
* Explicit autoApprovePermissions applies only to ordinary tools; questions still require user input.
|
|
55
56
|
*/
|
|
56
57
|
waitForRun(sessionId: string, options?: {
|
|
57
58
|
timeoutMs?: number;
|
|
58
59
|
pollIntervalMs?: number;
|
|
60
|
+
autoApprovePermissions?: boolean;
|
|
59
61
|
}): Promise<SessionDetail>;
|
|
60
62
|
}
|