@swarm-agent/sdk 0.1.47
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/APPLICATIONS.md +133 -0
- package/HEADLESS_UI.md +97 -0
- package/LICENSE +201 -0
- package/README.md +473 -0
- package/dist/app-results.d.ts +24 -0
- package/dist/app-results.js +167 -0
- package/dist/apps.d.ts +82 -0
- package/dist/apps.js +89 -0
- package/dist/auth.d.ts +45 -0
- package/dist/auth.js +89 -0
- package/dist/automations.d.ts +74 -0
- package/dist/automations.js +159 -0
- package/dist/client.d.ts +61 -0
- package/dist/client.js +103 -0
- package/dist/deliverables.d.ts +42 -0
- package/dist/deliverables.js +83 -0
- package/dist/deploy/dockerfile.d.ts +14 -0
- package/dist/deploy/dockerfile.js +14 -0
- package/dist/deploy/index.d.ts +67 -0
- package/dist/deploy/index.js +226 -0
- package/dist/deploy/manifest.d.ts +17 -0
- package/dist/deploy/manifest.js +220 -0
- package/dist/deploy/providers/gcp-cloud-run.d.ts +9 -0
- package/dist/deploy/providers/gcp-cloud-run.js +12 -0
- package/dist/deploy/providers/gcp-compute.d.ts +9 -0
- package/dist/deploy/providers/gcp-compute.js +12 -0
- package/dist/deploy/types.d.ts +211 -0
- package/dist/deploy/types.js +4 -0
- package/dist/deploy/worker-cloud-deployer.d.ts +33 -0
- package/dist/deploy/worker-cloud-deployer.js +14 -0
- package/dist/errors.d.ts +36 -0
- package/dist/errors.js +68 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +20 -0
- package/dist/notifications.d.ts +28 -0
- package/dist/notifications.js +65 -0
- package/dist/projects.d.ts +147 -0
- package/dist/projects.js +513 -0
- package/dist/provider-auth.d.ts +160 -0
- package/dist/provider-auth.js +54 -0
- package/dist/realtime.d.ts +240 -0
- package/dist/realtime.js +312 -0
- package/dist/sessions.d.ts +60 -0
- package/dist/sessions.js +194 -0
- package/dist/settings.d.ts +85 -0
- package/dist/settings.js +26 -0
- package/dist/storage/adapters/memory.d.ts +13 -0
- package/dist/storage/adapters/memory.js +35 -0
- package/dist/storage/adapters/s3.d.ts +31 -0
- package/dist/storage/adapters/s3.js +171 -0
- package/dist/storage/client.d.ts +174 -0
- package/dist/storage/client.js +121 -0
- package/dist/storage/hub.d.ts +30 -0
- package/dist/storage/hub.js +372 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/runner.d.ts +48 -0
- package/dist/storage/runner.js +233 -0
- package/dist/storage/types.d.ts +198 -0
- package/dist/storage/types.js +1 -0
- package/dist/system.d.ts +14 -0
- package/dist/system.js +52 -0
- package/dist/transport.d.ts +17 -0
- package/dist/transport.js +243 -0
- package/dist/types.d.ts +1137 -0
- package/dist/types.js +1 -0
- package/dist/usage.d.ts +106 -0
- package/dist/usage.js +82 -0
- package/dist/workers.d.ts +146 -0
- package/dist/workers.js +822 -0
- package/dist/workspaces.d.ts +81 -0
- package/dist/workspaces.js +58 -0
- package/examples/headless-session.ts +85 -0
- package/package.json +56 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { SwarmTransport } from '../transport.js';
|
|
2
|
+
import type { DeliverableTelemetry, DeliverableReview } from './types.js';
|
|
3
|
+
export interface StorageBucketRecord {
|
|
4
|
+
id: string;
|
|
5
|
+
account_scope_id?: string;
|
|
6
|
+
name: string;
|
|
7
|
+
provider: 's3' | 'gcs' | 'mock' | 'local' | string;
|
|
8
|
+
bucket_name: string;
|
|
9
|
+
endpoint?: string;
|
|
10
|
+
region?: string;
|
|
11
|
+
prefix?: string;
|
|
12
|
+
access_key_id?: string;
|
|
13
|
+
secret_access_key?: string;
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
canonical?: boolean;
|
|
16
|
+
status?: 'active' | 'pending_approval' | 'rejected' | string;
|
|
17
|
+
proposed_by?: string;
|
|
18
|
+
proposal_reason?: string;
|
|
19
|
+
created_at?: number;
|
|
20
|
+
updated_at?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface StorageDiscoveredWorkerRecord {
|
|
23
|
+
worker_id: string;
|
|
24
|
+
bucket_id: string;
|
|
25
|
+
account_scope_id?: string;
|
|
26
|
+
name: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
version?: string;
|
|
29
|
+
tags?: string[];
|
|
30
|
+
has_base_context: boolean;
|
|
31
|
+
base_context?: Record<string, any>;
|
|
32
|
+
sessions_count?: number;
|
|
33
|
+
last_session_id?: string;
|
|
34
|
+
last_status?: string;
|
|
35
|
+
last_step?: string;
|
|
36
|
+
last_progress?: number;
|
|
37
|
+
last_activity_at?: number;
|
|
38
|
+
imported: boolean;
|
|
39
|
+
imported_at?: number;
|
|
40
|
+
discovered_at?: number;
|
|
41
|
+
updated_at?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface StorageDiscoveredDeliverableRecord {
|
|
44
|
+
deliverable_id: string;
|
|
45
|
+
worker_id: string;
|
|
46
|
+
session_id: string;
|
|
47
|
+
bucket_id: string;
|
|
48
|
+
account_scope_id?: string;
|
|
49
|
+
title: string;
|
|
50
|
+
summary?: string;
|
|
51
|
+
kind?: string;
|
|
52
|
+
status: 'pending_review' | 'accepted' | 'approved' | 'rejected' | string;
|
|
53
|
+
sha256?: string;
|
|
54
|
+
files?: Array<{
|
|
55
|
+
name: string;
|
|
56
|
+
path: string;
|
|
57
|
+
size_bytes: number;
|
|
58
|
+
sha256: string;
|
|
59
|
+
content_type?: string;
|
|
60
|
+
}>;
|
|
61
|
+
actions?: Array<{
|
|
62
|
+
id: string;
|
|
63
|
+
label: string;
|
|
64
|
+
action_type?: string;
|
|
65
|
+
endpoint: string;
|
|
66
|
+
variant?: string;
|
|
67
|
+
}>;
|
|
68
|
+
payload?: Record<string, any>;
|
|
69
|
+
telemetry?: DeliverableTelemetry;
|
|
70
|
+
review?: DeliverableReview;
|
|
71
|
+
created_at?: number;
|
|
72
|
+
updated_at?: number;
|
|
73
|
+
imported_at?: number;
|
|
74
|
+
}
|
|
75
|
+
export interface CloudConnectionProposal {
|
|
76
|
+
provider: 's3' | 'gcs' | 'r2' | 'minio' | 'local' | string;
|
|
77
|
+
bucket_name: string;
|
|
78
|
+
name?: string;
|
|
79
|
+
endpoint?: string;
|
|
80
|
+
region?: string;
|
|
81
|
+
prefix?: string;
|
|
82
|
+
access_key_id?: string;
|
|
83
|
+
secret_access_key?: string;
|
|
84
|
+
proposed_by?: string;
|
|
85
|
+
proposal_reason?: string;
|
|
86
|
+
make_canonical?: boolean;
|
|
87
|
+
}
|
|
88
|
+
export interface ScanSummary {
|
|
89
|
+
bucket_id: string;
|
|
90
|
+
workers_found: number;
|
|
91
|
+
workers_added?: string[];
|
|
92
|
+
workers_updated?: string[];
|
|
93
|
+
workers_removed?: string[];
|
|
94
|
+
sessions_found: number;
|
|
95
|
+
deliverables_new: number;
|
|
96
|
+
scanned_at: number;
|
|
97
|
+
}
|
|
98
|
+
export declare class SwarmStorageNamespace {
|
|
99
|
+
private transport;
|
|
100
|
+
constructor(transport: SwarmTransport);
|
|
101
|
+
/**
|
|
102
|
+
* Lists all configured storage buckets for this account.
|
|
103
|
+
*/
|
|
104
|
+
listBuckets(): Promise<StorageBucketRecord[]>;
|
|
105
|
+
/**
|
|
106
|
+
* Registers a new S3, GCS, or local storage bucket.
|
|
107
|
+
*/
|
|
108
|
+
registerBucket(bucket: Partial<StorageBucketRecord>): Promise<StorageBucketRecord>;
|
|
109
|
+
/**
|
|
110
|
+
* Deletes a registered storage bucket.
|
|
111
|
+
*/
|
|
112
|
+
deleteBucket(id: string): Promise<boolean>;
|
|
113
|
+
/**
|
|
114
|
+
* Triggers an immediate scan of the storage bucket for workers and deliverables.
|
|
115
|
+
*/
|
|
116
|
+
scanBucket(id: string): Promise<ScanSummary>;
|
|
117
|
+
/**
|
|
118
|
+
* Lists workers discovered across all registered buckets.
|
|
119
|
+
*/
|
|
120
|
+
listWorkers(): Promise<StorageDiscoveredWorkerRecord[]>;
|
|
121
|
+
/**
|
|
122
|
+
* Accepts and imports a discovered worker into local state.
|
|
123
|
+
*/
|
|
124
|
+
importWorker(workerId: string): Promise<StorageDiscoveredWorkerRecord>;
|
|
125
|
+
/**
|
|
126
|
+
* Lists deliverables discovered across all registered buckets.
|
|
127
|
+
*/
|
|
128
|
+
listDeliverables(): Promise<StorageDiscoveredDeliverableRecord[]>;
|
|
129
|
+
/**
|
|
130
|
+
* Imports a discovered deliverable and downloads its artifacts to the target workspace directory.
|
|
131
|
+
*/
|
|
132
|
+
importDeliverable(id: string, targetWorkspacePath?: string): Promise<{
|
|
133
|
+
deliverable: StorageDiscoveredDeliverableRecord;
|
|
134
|
+
targetDir: string;
|
|
135
|
+
}>;
|
|
136
|
+
/**
|
|
137
|
+
* Retrieves the current canonical Swarm cloud storage bucket.
|
|
138
|
+
*/
|
|
139
|
+
getCanonicalBucket(): Promise<StorageBucketRecord | null>;
|
|
140
|
+
/**
|
|
141
|
+
* Accepts and designates a storage bucket as the canonical Swarm cloud connection.
|
|
142
|
+
*/
|
|
143
|
+
setCanonicalBucket(id: string): Promise<StorageBucketRecord>;
|
|
144
|
+
/**
|
|
145
|
+
* Proposes a new cloud storage connection from an AI worker/agent for user approval.
|
|
146
|
+
*/
|
|
147
|
+
proposeConnection(proposal: CloudConnectionProposal): Promise<StorageBucketRecord>;
|
|
148
|
+
/**
|
|
149
|
+
* Lists all pending cloud storage connection proposals awaiting user approval.
|
|
150
|
+
*/
|
|
151
|
+
listProposals(): Promise<StorageBucketRecord[]>;
|
|
152
|
+
/**
|
|
153
|
+
* Accepts and approves a discovered deliverable, updating its durable state in S3/GCS
|
|
154
|
+
* and optionally triggering cloud or local execution.
|
|
155
|
+
*/
|
|
156
|
+
acceptDeliverable(id: string, options?: {
|
|
157
|
+
target?: 'cloud' | 'local';
|
|
158
|
+
note?: string;
|
|
159
|
+
}): Promise<{
|
|
160
|
+
deliverable: StorageDiscoveredDeliverableRecord;
|
|
161
|
+
status: string;
|
|
162
|
+
}>;
|
|
163
|
+
/**
|
|
164
|
+
* Rejects/dismisses a discovered deliverable.
|
|
165
|
+
*/
|
|
166
|
+
rejectDeliverable(id: string, note?: string): Promise<{
|
|
167
|
+
deliverable: StorageDiscoveredDeliverableRecord;
|
|
168
|
+
status: string;
|
|
169
|
+
}>;
|
|
170
|
+
/**
|
|
171
|
+
* Rejects/dismisses a pending connection proposal.
|
|
172
|
+
*/
|
|
173
|
+
rejectProposal(id: string): Promise<boolean>;
|
|
174
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export class SwarmStorageNamespace {
|
|
2
|
+
transport;
|
|
3
|
+
constructor(transport) {
|
|
4
|
+
this.transport = transport;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Lists all configured storage buckets for this account.
|
|
8
|
+
*/
|
|
9
|
+
async listBuckets() {
|
|
10
|
+
const res = await this.transport.request('/v1/storage/buckets', { method: 'GET' });
|
|
11
|
+
return res.data.buckets;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Registers a new S3, GCS, or local storage bucket.
|
|
15
|
+
*/
|
|
16
|
+
async registerBucket(bucket) {
|
|
17
|
+
const res = await this.transport.request('/v1/storage/buckets', { method: 'POST', body: bucket });
|
|
18
|
+
return res.data.bucket;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Deletes a registered storage bucket.
|
|
22
|
+
*/
|
|
23
|
+
async deleteBucket(id) {
|
|
24
|
+
const res = await this.transport.request(`/v1/storage/buckets/${id}`, { method: 'DELETE' });
|
|
25
|
+
return res.data.deleted;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Triggers an immediate scan of the storage bucket for workers and deliverables.
|
|
29
|
+
*/
|
|
30
|
+
async scanBucket(id) {
|
|
31
|
+
const res = await this.transport.request(`/v1/storage/buckets/${id}/scan`, { method: 'POST' });
|
|
32
|
+
return res.data.scan_summary;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Lists workers discovered across all registered buckets.
|
|
36
|
+
*/
|
|
37
|
+
async listWorkers() {
|
|
38
|
+
const res = await this.transport.request('/v1/storage/workers', { method: 'GET' });
|
|
39
|
+
return res.data.workers;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Accepts and imports a discovered worker into local state.
|
|
43
|
+
*/
|
|
44
|
+
async importWorker(workerId) {
|
|
45
|
+
const res = await this.transport.request(`/v1/storage/workers/${workerId}/import`, { method: 'POST' });
|
|
46
|
+
return res.data.worker;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Lists deliverables discovered across all registered buckets.
|
|
50
|
+
*/
|
|
51
|
+
async listDeliverables() {
|
|
52
|
+
const res = await this.transport.request('/v1/storage/deliverables', { method: 'GET' });
|
|
53
|
+
return res.data.deliverables;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Imports a discovered deliverable and downloads its artifacts to the target workspace directory.
|
|
57
|
+
*/
|
|
58
|
+
async importDeliverable(id, targetWorkspacePath) {
|
|
59
|
+
const res = await this.transport.request(`/v1/storage/deliverables/${id}/import`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
body: { target_workspace_path: targetWorkspacePath },
|
|
62
|
+
});
|
|
63
|
+
return { deliverable: res.data.deliverable, targetDir: res.data.target_dir };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Retrieves the current canonical Swarm cloud storage bucket.
|
|
67
|
+
*/
|
|
68
|
+
async getCanonicalBucket() {
|
|
69
|
+
const res = await this.transport.request('/v1/storage/canonical', { method: 'GET' });
|
|
70
|
+
return res.data.bucket;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Accepts and designates a storage bucket as the canonical Swarm cloud connection.
|
|
74
|
+
*/
|
|
75
|
+
async setCanonicalBucket(id) {
|
|
76
|
+
const res = await this.transport.request(`/v1/storage/buckets/${id}/accept-canonical`, { method: 'POST' });
|
|
77
|
+
return res.data.bucket;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Proposes a new cloud storage connection from an AI worker/agent for user approval.
|
|
81
|
+
*/
|
|
82
|
+
async proposeConnection(proposal) {
|
|
83
|
+
const res = await this.transport.request('/v1/storage/proposals', { method: 'POST', body: proposal });
|
|
84
|
+
return res.data.proposal;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Lists all pending cloud storage connection proposals awaiting user approval.
|
|
88
|
+
*/
|
|
89
|
+
async listProposals() {
|
|
90
|
+
const res = await this.transport.request('/v1/storage/proposals', { method: 'GET' });
|
|
91
|
+
return res.data.proposals;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Accepts and approves a discovered deliverable, updating its durable state in S3/GCS
|
|
95
|
+
* and optionally triggering cloud or local execution.
|
|
96
|
+
*/
|
|
97
|
+
async acceptDeliverable(id, options) {
|
|
98
|
+
const res = await this.transport.request(`/v1/storage/deliverables/${id}/accept`, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
body: options ?? { target: 'cloud' },
|
|
101
|
+
});
|
|
102
|
+
return res.data;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Rejects/dismisses a discovered deliverable.
|
|
106
|
+
*/
|
|
107
|
+
async rejectDeliverable(id, note) {
|
|
108
|
+
const res = await this.transport.request(`/v1/storage/deliverables/${id}/reject`, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
body: { note },
|
|
111
|
+
});
|
|
112
|
+
return res.data;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Rejects/dismisses a pending connection proposal.
|
|
116
|
+
*/
|
|
117
|
+
async rejectProposal(id) {
|
|
118
|
+
const res = await this.transport.request(`/v1/storage/buckets/${id}/reject`, { method: 'POST' });
|
|
119
|
+
return res.data.rejected;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { StorageHubConfig, WorkerManifest, WorkerBaseContext, WorkerSessionState, DeliverableManifest, PublishDeliverableOptions, WorkerActorSpec, WorkerActorTask, WorkerJobExecutionLog } from './types.js';
|
|
2
|
+
export declare class WorkerStorageHub {
|
|
3
|
+
private driver;
|
|
4
|
+
private workerId;
|
|
5
|
+
private prefix;
|
|
6
|
+
constructor(config: StorageHubConfig);
|
|
7
|
+
private key;
|
|
8
|
+
private sha256Hex;
|
|
9
|
+
initWorker(definition?: Partial<WorkerManifest>): Promise<WorkerManifest>;
|
|
10
|
+
getWorkerManifest(): Promise<WorkerManifest | null>;
|
|
11
|
+
loadBaseContext(): Promise<WorkerBaseContext>;
|
|
12
|
+
saveBaseContext(context: WorkerBaseContext): Promise<void>;
|
|
13
|
+
startSession(sessionId: string, initialConfig?: Record<string, any>): Promise<WorkerSessionState>;
|
|
14
|
+
updateSessionState(sessionId: string, updates: Partial<WorkerSessionState>): Promise<WorkerSessionState>;
|
|
15
|
+
appendTrace(sessionId: string, message: string, data?: any): Promise<void>;
|
|
16
|
+
publishDeliverable(options: PublishDeliverableOptions): Promise<DeliverableManifest>;
|
|
17
|
+
getSessionState(sessionId: string): Promise<WorkerSessionState | null>;
|
|
18
|
+
getDeliverableManifest(deliverableId: string): Promise<DeliverableManifest | null>;
|
|
19
|
+
listDeliverables(): Promise<DeliverableManifest[]>;
|
|
20
|
+
getWorkerActor(): Promise<WorkerActorSpec | null>;
|
|
21
|
+
saveWorkerActor(spec: WorkerActorSpec): Promise<void>;
|
|
22
|
+
updateWorkerActor(updates: Partial<WorkerActorSpec>): Promise<WorkerActorSpec>;
|
|
23
|
+
addTask(task: Omit<WorkerActorTask, 'id' | 'createdAt' | 'status'> & {
|
|
24
|
+
id?: string;
|
|
25
|
+
}): Promise<WorkerActorTask>;
|
|
26
|
+
updateTask(taskId: string, updates: Partial<WorkerActorTask>): Promise<WorkerActorTask>;
|
|
27
|
+
logJobExecution(log: WorkerJobExecutionLog): Promise<void>;
|
|
28
|
+
getJobExecutionLog(jobId: string): Promise<WorkerJobExecutionLog | null>;
|
|
29
|
+
listJobExecutionLogs(): Promise<WorkerJobExecutionLog[]>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
export class WorkerStorageHub {
|
|
2
|
+
driver;
|
|
3
|
+
workerId;
|
|
4
|
+
prefix;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.driver = config.driver;
|
|
7
|
+
this.workerId = config.workerId;
|
|
8
|
+
this.prefix = config.prefix ? config.prefix.replace(/\/+$/, '') + '/' : '';
|
|
9
|
+
}
|
|
10
|
+
key(path) {
|
|
11
|
+
return `${this.prefix}${path.replace(/^\/+/, '')}`;
|
|
12
|
+
}
|
|
13
|
+
async sha256Hex(data) {
|
|
14
|
+
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
|
|
15
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', bytes);
|
|
16
|
+
return Array.from(new Uint8Array(hashBuffer))
|
|
17
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
18
|
+
.join('');
|
|
19
|
+
}
|
|
20
|
+
async initWorker(definition = {}) {
|
|
21
|
+
const key = this.key(`workers/${this.workerId}/worker.json`);
|
|
22
|
+
const now = new Date().toISOString();
|
|
23
|
+
let existing = null;
|
|
24
|
+
try {
|
|
25
|
+
const raw = await this.driver.readString(key);
|
|
26
|
+
if (raw)
|
|
27
|
+
existing = JSON.parse(raw);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// ignore parse error on new worker
|
|
31
|
+
}
|
|
32
|
+
const manifest = {
|
|
33
|
+
id: this.workerId,
|
|
34
|
+
name: definition.name || existing?.name || this.workerId,
|
|
35
|
+
description: definition.description ?? existing?.description ?? '',
|
|
36
|
+
version: definition.version || existing?.version || '1.0.0',
|
|
37
|
+
tags: definition.tags || existing?.tags || [],
|
|
38
|
+
createdAt: existing?.createdAt || now,
|
|
39
|
+
updatedAt: now,
|
|
40
|
+
};
|
|
41
|
+
await this.driver.write(key, JSON.stringify(manifest, null, 2), 'application/json');
|
|
42
|
+
return manifest;
|
|
43
|
+
}
|
|
44
|
+
async getWorkerManifest() {
|
|
45
|
+
const key = this.key(`workers/${this.workerId}/worker.json`);
|
|
46
|
+
const raw = await this.driver.readString(key);
|
|
47
|
+
if (!raw)
|
|
48
|
+
return null;
|
|
49
|
+
return JSON.parse(raw);
|
|
50
|
+
}
|
|
51
|
+
async loadBaseContext() {
|
|
52
|
+
const basePrefix = `workers/${this.workerId}/base`;
|
|
53
|
+
const instructionsStr = await this.driver.readString(this.key(`${basePrefix}/instructions.md`));
|
|
54
|
+
const toolsStr = await this.driver.readString(this.key(`${basePrefix}/tools.json`));
|
|
55
|
+
const memoryStr = await this.driver.readString(this.key(`${basePrefix}/memory.json`));
|
|
56
|
+
return {
|
|
57
|
+
instructions: instructionsStr ?? undefined,
|
|
58
|
+
tools: toolsStr ? JSON.parse(toolsStr) : undefined,
|
|
59
|
+
memory: memoryStr ? JSON.parse(memoryStr) : undefined,
|
|
60
|
+
updatedAt: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async saveBaseContext(context) {
|
|
64
|
+
const basePrefix = `workers/${this.workerId}/base`;
|
|
65
|
+
if (context.instructions !== undefined) {
|
|
66
|
+
await this.driver.write(this.key(`${basePrefix}/instructions.md`), context.instructions, 'text/markdown');
|
|
67
|
+
}
|
|
68
|
+
if (context.tools !== undefined) {
|
|
69
|
+
await this.driver.write(this.key(`${basePrefix}/tools.json`), JSON.stringify(context.tools, null, 2), 'application/json');
|
|
70
|
+
}
|
|
71
|
+
if (context.memory !== undefined) {
|
|
72
|
+
await this.driver.write(this.key(`${basePrefix}/memory.json`), JSON.stringify(context.memory, null, 2), 'application/json');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async startSession(sessionId, initialConfig) {
|
|
76
|
+
const sessionPrefix = `workers/${this.workerId}/sessions/${sessionId}`;
|
|
77
|
+
const now = new Date().toISOString();
|
|
78
|
+
if (initialConfig) {
|
|
79
|
+
await this.driver.write(this.key(`${sessionPrefix}/run_config.json`), JSON.stringify(initialConfig, null, 2), 'application/json');
|
|
80
|
+
}
|
|
81
|
+
const state = {
|
|
82
|
+
sessionId,
|
|
83
|
+
workerId: this.workerId,
|
|
84
|
+
status: 'starting',
|
|
85
|
+
progress: 0,
|
|
86
|
+
step: 'Initialized session',
|
|
87
|
+
startedAt: now,
|
|
88
|
+
updatedAt: now,
|
|
89
|
+
};
|
|
90
|
+
await this.driver.write(this.key(`${sessionPrefix}/state.json`), JSON.stringify(state, null, 2), 'application/json');
|
|
91
|
+
return state;
|
|
92
|
+
}
|
|
93
|
+
async updateSessionState(sessionId, updates) {
|
|
94
|
+
const sessionPrefix = `workers/${this.workerId}/sessions/${sessionId}`;
|
|
95
|
+
const stateKey = this.key(`${sessionPrefix}/state.json`);
|
|
96
|
+
const now = new Date().toISOString();
|
|
97
|
+
let current = {
|
|
98
|
+
sessionId,
|
|
99
|
+
workerId: this.workerId,
|
|
100
|
+
status: 'running',
|
|
101
|
+
startedAt: now,
|
|
102
|
+
updatedAt: now,
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
const raw = await this.driver.readString(stateKey);
|
|
106
|
+
if (raw)
|
|
107
|
+
current = JSON.parse(raw);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// ignore
|
|
111
|
+
}
|
|
112
|
+
const merged = {
|
|
113
|
+
...current,
|
|
114
|
+
...updates,
|
|
115
|
+
sessionId,
|
|
116
|
+
workerId: this.workerId,
|
|
117
|
+
updatedAt: now,
|
|
118
|
+
};
|
|
119
|
+
if (merged.status === 'completed' && !merged.completedAt) {
|
|
120
|
+
merged.completedAt = now;
|
|
121
|
+
if (merged.progress === undefined || merged.progress < 100) {
|
|
122
|
+
merged.progress = 100;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
await this.driver.write(stateKey, JSON.stringify(merged, null, 2), 'application/json');
|
|
126
|
+
return merged;
|
|
127
|
+
}
|
|
128
|
+
async appendTrace(sessionId, message, data) {
|
|
129
|
+
const traceKey = this.key(`workers/${this.workerId}/sessions/${sessionId}/trace.jsonl`);
|
|
130
|
+
const entry = JSON.stringify({
|
|
131
|
+
timestamp: new Date().toISOString(),
|
|
132
|
+
message,
|
|
133
|
+
...(data ? { data } : {}),
|
|
134
|
+
}) + '\n';
|
|
135
|
+
const existing = await this.driver.readString(traceKey);
|
|
136
|
+
const combined = existing ? existing + entry : entry;
|
|
137
|
+
await this.driver.write(traceKey, combined, 'application/x-ndjson');
|
|
138
|
+
}
|
|
139
|
+
async publishDeliverable(options) {
|
|
140
|
+
const deliverableId = options.id || `deliv_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
|
141
|
+
const deliverablePrefix = `deliverables/${this.workerId}/${deliverableId}`;
|
|
142
|
+
const now = new Date().toISOString();
|
|
143
|
+
const fileRefs = [];
|
|
144
|
+
let combinedFileDigests = '';
|
|
145
|
+
if (options.files && options.files.length > 0) {
|
|
146
|
+
for (const file of options.files) {
|
|
147
|
+
const fileContent = typeof file.content === 'string'
|
|
148
|
+
? new TextEncoder().encode(file.content)
|
|
149
|
+
: file.content;
|
|
150
|
+
const fileSha256 = await this.sha256Hex(fileContent);
|
|
151
|
+
const filePath = `${deliverablePrefix}/files/${file.name}`;
|
|
152
|
+
await this.driver.write(this.key(filePath), fileContent, file.contentType || 'application/octet-stream');
|
|
153
|
+
fileRefs.push({
|
|
154
|
+
name: file.name,
|
|
155
|
+
path: filePath,
|
|
156
|
+
sizeBytes: fileContent.byteLength,
|
|
157
|
+
sha256: fileSha256,
|
|
158
|
+
contentType: file.contentType,
|
|
159
|
+
});
|
|
160
|
+
combinedFileDigests += fileSha256;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const aggregateSha256 = options.payload
|
|
164
|
+
? await this.sha256Hex(JSON.stringify(options.payload) + combinedFileDigests)
|
|
165
|
+
: await this.sha256Hex(combinedFileDigests || deliverableId);
|
|
166
|
+
const manifest = {
|
|
167
|
+
id: deliverableId,
|
|
168
|
+
workerId: this.workerId,
|
|
169
|
+
sessionId: options.sessionId,
|
|
170
|
+
title: options.title,
|
|
171
|
+
summary: options.summary,
|
|
172
|
+
kind: options.kind || 'ai_deliverable',
|
|
173
|
+
status: 'pending_review',
|
|
174
|
+
telemetry: options.telemetry,
|
|
175
|
+
files: fileRefs,
|
|
176
|
+
actions: options.actions || [],
|
|
177
|
+
payload: options.payload,
|
|
178
|
+
sha256: aggregateSha256,
|
|
179
|
+
createdAt: now,
|
|
180
|
+
updatedAt: now,
|
|
181
|
+
};
|
|
182
|
+
await this.driver.write(this.key(`${deliverablePrefix}/manifest.json`), JSON.stringify(manifest, null, 2), 'application/json');
|
|
183
|
+
// Automatically update the session state to completed
|
|
184
|
+
await this.updateSessionState(options.sessionId, {
|
|
185
|
+
status: 'completed',
|
|
186
|
+
progress: 100,
|
|
187
|
+
step: `Published deliverable: ${options.title}`,
|
|
188
|
+
});
|
|
189
|
+
return manifest;
|
|
190
|
+
}
|
|
191
|
+
async getSessionState(sessionId) {
|
|
192
|
+
const sessionPrefix = `workers/${this.workerId}/sessions/${sessionId}`;
|
|
193
|
+
const stateKey = this.key(`${sessionPrefix}/state.json`);
|
|
194
|
+
const raw = await this.driver.readString(stateKey);
|
|
195
|
+
if (!raw)
|
|
196
|
+
return null;
|
|
197
|
+
return JSON.parse(raw);
|
|
198
|
+
}
|
|
199
|
+
async getDeliverableManifest(deliverableId) {
|
|
200
|
+
const deliverablePrefix = `deliverables/${this.workerId}/${deliverableId}`;
|
|
201
|
+
const manifestKey = this.key(`${deliverablePrefix}/manifest.json`);
|
|
202
|
+
const raw = await this.driver.readString(manifestKey);
|
|
203
|
+
if (!raw)
|
|
204
|
+
return null;
|
|
205
|
+
return JSON.parse(raw);
|
|
206
|
+
}
|
|
207
|
+
async listDeliverables() {
|
|
208
|
+
const prefix = this.key(`deliverables/${this.workerId}/`);
|
|
209
|
+
const keys = await this.driver.list(prefix);
|
|
210
|
+
const manifests = [];
|
|
211
|
+
for (const k of keys) {
|
|
212
|
+
if (k.endsWith('/manifest.json')) {
|
|
213
|
+
const raw = await this.driver.readString(k);
|
|
214
|
+
if (raw) {
|
|
215
|
+
try {
|
|
216
|
+
manifests.push(JSON.parse(raw));
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// ignore
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return manifests.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
225
|
+
}
|
|
226
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
227
|
+
// WORKER ACTOR SPECIFICATION (SINGLE FILE: worker.json) & JOB LOGS
|
|
228
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
229
|
+
async getWorkerActor() {
|
|
230
|
+
const key = this.key(`workers/${this.workerId}/worker.json`);
|
|
231
|
+
const raw = await this.driver.readString(key);
|
|
232
|
+
if (!raw)
|
|
233
|
+
return null;
|
|
234
|
+
return JSON.parse(raw);
|
|
235
|
+
}
|
|
236
|
+
async saveWorkerActor(spec) {
|
|
237
|
+
const key = this.key(`workers/${this.workerId}/worker.json`);
|
|
238
|
+
const payload = {
|
|
239
|
+
...spec,
|
|
240
|
+
id: this.workerId,
|
|
241
|
+
updatedAt: new Date().toISOString(),
|
|
242
|
+
};
|
|
243
|
+
await this.driver.write(key, JSON.stringify(payload, null, 2), 'application/json');
|
|
244
|
+
}
|
|
245
|
+
async updateWorkerActor(updates) {
|
|
246
|
+
const existing = await this.getWorkerActor();
|
|
247
|
+
const now = new Date().toISOString();
|
|
248
|
+
const merged = {
|
|
249
|
+
id: this.workerId,
|
|
250
|
+
name: updates.name || existing?.name || this.workerId,
|
|
251
|
+
description: updates.description ?? existing?.description,
|
|
252
|
+
version: updates.version || existing?.version || '1.0.0',
|
|
253
|
+
status: updates.status || existing?.status || 'pending_approval',
|
|
254
|
+
cloud: updates.cloud || existing?.cloud,
|
|
255
|
+
schedule: updates.schedule || existing?.schedule,
|
|
256
|
+
brain: updates.brain || existing?.brain || {
|
|
257
|
+
model: 'gemini-3.8-flash',
|
|
258
|
+
instructions: 'Autonomous Swarm Cloud Worker',
|
|
259
|
+
},
|
|
260
|
+
tasks: updates.tasks || existing?.tasks || [],
|
|
261
|
+
createdAt: existing?.createdAt || now,
|
|
262
|
+
updatedAt: now,
|
|
263
|
+
};
|
|
264
|
+
await this.saveWorkerActor(merged);
|
|
265
|
+
return merged;
|
|
266
|
+
}
|
|
267
|
+
async addTask(task) {
|
|
268
|
+
const spec = (await this.getWorkerActor()) || {
|
|
269
|
+
id: this.workerId,
|
|
270
|
+
name: this.workerId,
|
|
271
|
+
version: '1.0.0',
|
|
272
|
+
status: 'pending_approval',
|
|
273
|
+
brain: { model: 'gemini-3.8-flash', instructions: '' },
|
|
274
|
+
tasks: [],
|
|
275
|
+
createdAt: new Date().toISOString(),
|
|
276
|
+
updatedAt: new Date().toISOString(),
|
|
277
|
+
};
|
|
278
|
+
const taskId = task.id || `task_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
|
279
|
+
const newTask = {
|
|
280
|
+
...task,
|
|
281
|
+
id: taskId,
|
|
282
|
+
status: 'pending',
|
|
283
|
+
createdAt: new Date().toISOString(),
|
|
284
|
+
};
|
|
285
|
+
const currentTasks = spec.tasks || [];
|
|
286
|
+
spec.tasks = [...currentTasks, newTask];
|
|
287
|
+
await this.saveWorkerActor(spec);
|
|
288
|
+
return newTask;
|
|
289
|
+
}
|
|
290
|
+
async updateTask(taskId, updates) {
|
|
291
|
+
const spec = await this.getWorkerActor();
|
|
292
|
+
if (!spec) {
|
|
293
|
+
throw new Error(`Worker actor not found: ${this.workerId}`);
|
|
294
|
+
}
|
|
295
|
+
const tasks = spec.tasks || [];
|
|
296
|
+
const index = tasks.findIndex((t) => t.id === taskId);
|
|
297
|
+
if (index === -1) {
|
|
298
|
+
throw new Error(`Task ${taskId} not found on worker ${this.workerId}`);
|
|
299
|
+
}
|
|
300
|
+
const current = tasks[index];
|
|
301
|
+
const updated = {
|
|
302
|
+
...current,
|
|
303
|
+
...updates,
|
|
304
|
+
id: taskId,
|
|
305
|
+
};
|
|
306
|
+
tasks[index] = updated;
|
|
307
|
+
spec.tasks = tasks;
|
|
308
|
+
await this.saveWorkerActor(spec);
|
|
309
|
+
return updated;
|
|
310
|
+
}
|
|
311
|
+
async logJobExecution(log) {
|
|
312
|
+
const jobKey = this.key(`workers/${this.workerId}/jobs/${log.jobId}/execution.json`);
|
|
313
|
+
await this.driver.write(jobKey, JSON.stringify(log, null, 2), 'application/json');
|
|
314
|
+
// Also append to session trace if sessionId is present
|
|
315
|
+
if (log.taskId) {
|
|
316
|
+
await this.appendTrace(log.taskId, `[${log.type}] ${log.status}: ${log.durationMs}ms - $${log.telemetry.cost_usd.toFixed(6)}`, {
|
|
317
|
+
tokens: log.telemetry,
|
|
318
|
+
error: log.error,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
// Update worker task if associated with a taskId
|
|
322
|
+
if (log.taskId) {
|
|
323
|
+
try {
|
|
324
|
+
await this.updateTask(log.taskId, {
|
|
325
|
+
status: log.status === 'success' ? 'completed' : 'failed',
|
|
326
|
+
completedAt: log.completedAt,
|
|
327
|
+
deliverableId: log.deliverableId,
|
|
328
|
+
error: log.error,
|
|
329
|
+
telemetry: {
|
|
330
|
+
model: log.telemetry.model,
|
|
331
|
+
thinking_level: log.telemetry.thinking_level,
|
|
332
|
+
prompt_tokens: log.telemetry.prompt_tokens,
|
|
333
|
+
candidate_tokens: log.telemetry.candidate_tokens,
|
|
334
|
+
thinking_tokens: log.telemetry.thinking_tokens,
|
|
335
|
+
total_tokens: log.telemetry.total_tokens,
|
|
336
|
+
compute_duration_ms: log.durationMs,
|
|
337
|
+
cost_usd: log.telemetry.cost_usd,
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
// Task might not have been pre-registered in worker.json
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async getJobExecutionLog(jobId) {
|
|
347
|
+
const jobKey = this.key(`workers/${this.workerId}/jobs/${jobId}/execution.json`);
|
|
348
|
+
const raw = await this.driver.readString(jobKey);
|
|
349
|
+
if (!raw)
|
|
350
|
+
return null;
|
|
351
|
+
return JSON.parse(raw);
|
|
352
|
+
}
|
|
353
|
+
async listJobExecutionLogs() {
|
|
354
|
+
const prefix = this.key(`workers/${this.workerId}/jobs/`);
|
|
355
|
+
const keys = await this.driver.list(prefix);
|
|
356
|
+
const logs = [];
|
|
357
|
+
for (const k of keys) {
|
|
358
|
+
if (k.endsWith('/execution.json')) {
|
|
359
|
+
const raw = await this.driver.readString(k);
|
|
360
|
+
if (raw) {
|
|
361
|
+
try {
|
|
362
|
+
logs.push(JSON.parse(raw));
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
// ignore
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return logs.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
371
|
+
}
|
|
372
|
+
}
|