@sekuire/sdk 0.1.11 → 0.1.12
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/dist/beacon.d.ts +21 -0
- package/dist/index.d.ts +44 -14
- package/dist/index.esm.js +198 -36
- package/dist/index.js +198 -36
- package/dist/memory/index.d.ts +1 -1
- package/dist/types/a2a-types.d.ts +7 -7
- package/dist/worker.d.ts +16 -7
- package/package.json +1 -1
package/dist/beacon.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export interface BeaconStatus {
|
|
|
35
35
|
deploymentUrl?: string;
|
|
36
36
|
lastHeartbeat?: Date;
|
|
37
37
|
failedHeartbeats: number;
|
|
38
|
+
/** True if bootstrap failed and agent is running without Sekuire features */
|
|
39
|
+
degradedMode: boolean;
|
|
38
40
|
}
|
|
39
41
|
export interface BootstrapResponse {
|
|
40
42
|
installation_id: string;
|
|
@@ -51,9 +53,13 @@ export declare class Beacon {
|
|
|
51
53
|
private refreshToken;
|
|
52
54
|
private lastHeartbeat;
|
|
53
55
|
private failedHeartbeats;
|
|
56
|
+
private degradedMode;
|
|
54
57
|
constructor(config: BeaconConfig);
|
|
55
58
|
/**
|
|
56
59
|
* Start the beacon - registers with Sekuire and begins heartbeat loop
|
|
60
|
+
*
|
|
61
|
+
* If bootstrap fails, the agent continues running in degraded mode
|
|
62
|
+
* without Sekuire features. This prevents auth failures from crashing agents.
|
|
57
63
|
*/
|
|
58
64
|
start(): Promise<void>;
|
|
59
65
|
/**
|
|
@@ -64,6 +70,21 @@ export declare class Beacon {
|
|
|
64
70
|
* Get current beacon status
|
|
65
71
|
*/
|
|
66
72
|
getStatus(): BeaconStatus;
|
|
73
|
+
/**
|
|
74
|
+
* Bootstrap with retry and exponential backoff
|
|
75
|
+
*
|
|
76
|
+
* Returns true if bootstrap succeeded, false if all retries exhausted.
|
|
77
|
+
* Does NOT throw - allows agent to continue in degraded mode.
|
|
78
|
+
*/
|
|
79
|
+
private bootstrapWithRetry;
|
|
80
|
+
private sleep;
|
|
81
|
+
/**
|
|
82
|
+
* Attempt recovery bootstrap using API key when install token fails
|
|
83
|
+
*
|
|
84
|
+
* Recovery bootstrap allows re-issuing runtime credentials without
|
|
85
|
+
* needing a new install token from the dashboard.
|
|
86
|
+
*/
|
|
87
|
+
private tryRecoveryBootstrap;
|
|
67
88
|
/**
|
|
68
89
|
* Bootstrap registration with Sekuire
|
|
69
90
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ interface BeaconStatus {
|
|
|
40
40
|
deploymentUrl?: string;
|
|
41
41
|
lastHeartbeat?: Date;
|
|
42
42
|
failedHeartbeats: number;
|
|
43
|
+
/** True if bootstrap failed and agent is running without Sekuire features */
|
|
44
|
+
degradedMode: boolean;
|
|
43
45
|
}
|
|
44
46
|
interface BootstrapResponse {
|
|
45
47
|
installation_id: string;
|
|
@@ -56,9 +58,13 @@ declare class Beacon {
|
|
|
56
58
|
private refreshToken;
|
|
57
59
|
private lastHeartbeat;
|
|
58
60
|
private failedHeartbeats;
|
|
61
|
+
private degradedMode;
|
|
59
62
|
constructor(config: BeaconConfig);
|
|
60
63
|
/**
|
|
61
64
|
* Start the beacon - registers with Sekuire and begins heartbeat loop
|
|
65
|
+
*
|
|
66
|
+
* If bootstrap fails, the agent continues running in degraded mode
|
|
67
|
+
* without Sekuire features. This prevents auth failures from crashing agents.
|
|
62
68
|
*/
|
|
63
69
|
start(): Promise<void>;
|
|
64
70
|
/**
|
|
@@ -69,6 +75,21 @@ declare class Beacon {
|
|
|
69
75
|
* Get current beacon status
|
|
70
76
|
*/
|
|
71
77
|
getStatus(): BeaconStatus;
|
|
78
|
+
/**
|
|
79
|
+
* Bootstrap with retry and exponential backoff
|
|
80
|
+
*
|
|
81
|
+
* Returns true if bootstrap succeeded, false if all retries exhausted.
|
|
82
|
+
* Does NOT throw - allows agent to continue in degraded mode.
|
|
83
|
+
*/
|
|
84
|
+
private bootstrapWithRetry;
|
|
85
|
+
private sleep;
|
|
86
|
+
/**
|
|
87
|
+
* Attempt recovery bootstrap using API key when install token fails
|
|
88
|
+
*
|
|
89
|
+
* Recovery bootstrap allows re-issuing runtime credentials without
|
|
90
|
+
* needing a new install token from the dashboard.
|
|
91
|
+
*/
|
|
92
|
+
private tryRecoveryBootstrap;
|
|
72
93
|
/**
|
|
73
94
|
* Bootstrap registration with Sekuire
|
|
74
95
|
*
|
|
@@ -2593,13 +2614,14 @@ interface TaskContext {
|
|
|
2593
2614
|
type TaskHandler = (ctx: TaskContext, input: Record<string, unknown>) => Promise<unknown>;
|
|
2594
2615
|
interface WorkerConfig {
|
|
2595
2616
|
apiBaseUrl: string;
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2617
|
+
agentId: string;
|
|
2618
|
+
installToken?: string;
|
|
2619
|
+
runtimeToken?: string;
|
|
2599
2620
|
heartbeatIntervalMs?: number;
|
|
2600
2621
|
reconnectDelayMs?: number;
|
|
2601
2622
|
maxReconnectDelayMs?: number;
|
|
2602
2623
|
deploymentUrl?: string;
|
|
2624
|
+
capabilities?: string[];
|
|
2603
2625
|
}
|
|
2604
2626
|
declare class TaskWorker {
|
|
2605
2627
|
private eventSource;
|
|
@@ -2610,6 +2632,9 @@ declare class TaskWorker {
|
|
|
2610
2632
|
private isConnected;
|
|
2611
2633
|
private isPaused;
|
|
2612
2634
|
private installationId;
|
|
2635
|
+
private runtimeToken;
|
|
2636
|
+
private refreshToken;
|
|
2637
|
+
private expiresAt;
|
|
2613
2638
|
private onCommandCallback?;
|
|
2614
2639
|
constructor(config: WorkerConfig);
|
|
2615
2640
|
/**
|
|
@@ -2640,9 +2665,9 @@ declare class TaskWorker {
|
|
|
2640
2665
|
*/
|
|
2641
2666
|
private handleCommand;
|
|
2642
2667
|
/**
|
|
2643
|
-
* Get
|
|
2668
|
+
* Get runtime token for API calls
|
|
2644
2669
|
*/
|
|
2645
|
-
private
|
|
2670
|
+
private getToken;
|
|
2646
2671
|
/**
|
|
2647
2672
|
* Connect to SSE stream
|
|
2648
2673
|
*/
|
|
@@ -2660,13 +2685,18 @@ declare class TaskWorker {
|
|
|
2660
2685
|
*/
|
|
2661
2686
|
private completeTask;
|
|
2662
2687
|
/**
|
|
2663
|
-
* Register this agent installation with Sekuire
|
|
2688
|
+
* Register this agent installation with Sekuire using install_token
|
|
2689
|
+
* Returns runtime_token for subsequent API calls
|
|
2664
2690
|
*/
|
|
2665
2691
|
private bootstrap;
|
|
2666
2692
|
/**
|
|
2667
|
-
* Start heartbeat loop
|
|
2693
|
+
* Start heartbeat/lease renewal loop
|
|
2668
2694
|
*/
|
|
2669
2695
|
private startHeartbeat;
|
|
2696
|
+
/**
|
|
2697
|
+
* Refresh the runtime token using the refresh token
|
|
2698
|
+
*/
|
|
2699
|
+
private refreshRuntimeToken;
|
|
2670
2700
|
}
|
|
2671
2701
|
declare function createWorker(config: WorkerConfig): TaskWorker;
|
|
2672
2702
|
|
|
@@ -2787,24 +2817,24 @@ interface AgentSkill {
|
|
|
2787
2817
|
/** A2A message with role and parts */
|
|
2788
2818
|
interface A2AMessage {
|
|
2789
2819
|
/** Message role: "user" or "agent" */
|
|
2790
|
-
role:
|
|
2820
|
+
role: 'user' | 'agent';
|
|
2791
2821
|
/** Message content parts */
|
|
2792
2822
|
parts: A2AMessagePart[];
|
|
2793
2823
|
}
|
|
2794
2824
|
/** Message part content */
|
|
2795
2825
|
type A2AMessagePart = {
|
|
2796
|
-
type:
|
|
2826
|
+
type: 'text';
|
|
2797
2827
|
text: string;
|
|
2798
2828
|
} | {
|
|
2799
|
-
type:
|
|
2829
|
+
type: 'file';
|
|
2800
2830
|
uri: string;
|
|
2801
2831
|
mimeType?: string;
|
|
2802
2832
|
} | {
|
|
2803
|
-
type:
|
|
2833
|
+
type: 'data';
|
|
2804
2834
|
data: unknown;
|
|
2805
2835
|
};
|
|
2806
2836
|
/** A2A task states */
|
|
2807
|
-
type A2ATaskState =
|
|
2837
|
+
type A2ATaskState = 'pending' | 'working' | 'input-required' | 'completed' | 'failed' | 'cancelled';
|
|
2808
2838
|
/** A2A task status with timestamp */
|
|
2809
2839
|
interface A2ATaskStatus {
|
|
2810
2840
|
state: A2ATaskState;
|
|
@@ -2856,14 +2886,14 @@ interface A2ARouteResponse {
|
|
|
2856
2886
|
}
|
|
2857
2887
|
/** JSON-RPC 2.0 Request */
|
|
2858
2888
|
interface JsonRpcRequest {
|
|
2859
|
-
jsonrpc:
|
|
2889
|
+
jsonrpc: '2.0';
|
|
2860
2890
|
method: string;
|
|
2861
2891
|
id: string | number;
|
|
2862
2892
|
params?: unknown;
|
|
2863
2893
|
}
|
|
2864
2894
|
/** JSON-RPC 2.0 Response */
|
|
2865
2895
|
interface JsonRpcResponse<T = unknown> {
|
|
2866
|
-
jsonrpc:
|
|
2896
|
+
jsonrpc: '2.0';
|
|
2867
2897
|
id: string | number;
|
|
2868
2898
|
result?: T;
|
|
2869
2899
|
error?: JsonRpcError;
|
package/dist/index.esm.js
CHANGED
|
@@ -94,6 +94,7 @@ class Beacon {
|
|
|
94
94
|
this.refreshToken = null;
|
|
95
95
|
this.lastHeartbeat = null;
|
|
96
96
|
this.failedHeartbeats = 0;
|
|
97
|
+
this.degradedMode = false;
|
|
97
98
|
const resolvedApiKey = config.apiKey || this.getApiKey() || '';
|
|
98
99
|
const resolvedInstallToken = config.installToken || this.getInstallToken();
|
|
99
100
|
this.config = {
|
|
@@ -109,6 +110,9 @@ class Beacon {
|
|
|
109
110
|
}
|
|
110
111
|
/**
|
|
111
112
|
* Start the beacon - registers with Sekuire and begins heartbeat loop
|
|
113
|
+
*
|
|
114
|
+
* If bootstrap fails, the agent continues running in degraded mode
|
|
115
|
+
* without Sekuire features. This prevents auth failures from crashing agents.
|
|
112
116
|
*/
|
|
113
117
|
async start() {
|
|
114
118
|
if (this.intervalId) {
|
|
@@ -121,16 +125,16 @@ class Beacon {
|
|
|
121
125
|
console.log(`[Beacon] Deployment URL: ${deploymentUrl}`);
|
|
122
126
|
}
|
|
123
127
|
// Bootstrap registration (exchanges install token for runtime credentials)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
console.
|
|
128
|
+
// On failure, continue in degraded mode - don't crash the agent
|
|
129
|
+
const bootstrapped = await this.bootstrapWithRetry(deploymentUrl);
|
|
130
|
+
if (!bootstrapped) {
|
|
131
|
+
this.degradedMode = true;
|
|
132
|
+
console.warn('[Beacon] Running in degraded mode - Sekuire features disabled');
|
|
133
|
+
console.warn('[Beacon] To re-enable: generate a new install token from the dashboard');
|
|
130
134
|
this.notifyStatusChange();
|
|
131
|
-
|
|
132
|
-
throw error;
|
|
135
|
+
return;
|
|
133
136
|
}
|
|
137
|
+
console.log(`[Beacon] Bootstrap successful, installation ID: ${this.installationId}`);
|
|
134
138
|
// Start heartbeat loop
|
|
135
139
|
const intervalMs = this.config.heartbeatIntervalSeconds * 1000;
|
|
136
140
|
this.intervalId = setInterval(() => {
|
|
@@ -157,14 +161,106 @@ class Beacon {
|
|
|
157
161
|
*/
|
|
158
162
|
getStatus() {
|
|
159
163
|
return {
|
|
160
|
-
isRunning: this.intervalId !== null,
|
|
164
|
+
isRunning: this.intervalId !== null || this.degradedMode,
|
|
161
165
|
installationId: this.installationId || undefined,
|
|
162
166
|
runtimeToken: this.runtimeToken ? '***' + this.runtimeToken.slice(-4) : undefined,
|
|
163
167
|
deploymentUrl: this.config.deploymentUrl || detectDeploymentUrl(),
|
|
164
168
|
lastHeartbeat: this.lastHeartbeat || undefined,
|
|
165
169
|
failedHeartbeats: this.failedHeartbeats,
|
|
170
|
+
degradedMode: this.degradedMode,
|
|
166
171
|
};
|
|
167
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Bootstrap with retry and exponential backoff
|
|
175
|
+
*
|
|
176
|
+
* Returns true if bootstrap succeeded, false if all retries exhausted.
|
|
177
|
+
* Does NOT throw - allows agent to continue in degraded mode.
|
|
178
|
+
*/
|
|
179
|
+
async bootstrapWithRetry(deploymentUrl, maxRetries = 3, initialDelayMs = 1000) {
|
|
180
|
+
let delay = initialDelayMs;
|
|
181
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
182
|
+
try {
|
|
183
|
+
await this.bootstrap(deploymentUrl);
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
const isLastAttempt = attempt === maxRetries;
|
|
188
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
189
|
+
// Check if error is permanent (expired/invalid token) vs transient (network)
|
|
190
|
+
const isPermanentError = errorMsg.includes('401') ||
|
|
191
|
+
errorMsg.includes('expired') ||
|
|
192
|
+
errorMsg.includes('invalid') ||
|
|
193
|
+
errorMsg.includes('not found');
|
|
194
|
+
if (isPermanentError) {
|
|
195
|
+
console.error(`[Beacon] Bootstrap failed (permanent error): ${errorMsg}`);
|
|
196
|
+
// Try recovery bootstrap if we have API key
|
|
197
|
+
if (this.config.apiKey && this.config.workspaceId) {
|
|
198
|
+
console.log('[Beacon] Attempting recovery bootstrap with API key...');
|
|
199
|
+
const recovered = await this.tryRecoveryBootstrap(deploymentUrl);
|
|
200
|
+
if (recovered) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
console.error('[Beacon] Install token expired or invalid - generate a new one from the dashboard');
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
if (isLastAttempt) {
|
|
208
|
+
console.error(`[Beacon] Bootstrap failed after ${maxRetries} attempts: ${errorMsg}`);
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
console.warn(`[Beacon] Bootstrap attempt ${attempt}/${maxRetries} failed: ${errorMsg}`);
|
|
212
|
+
console.warn(`[Beacon] Retrying in ${delay}ms...`);
|
|
213
|
+
await this.sleep(delay);
|
|
214
|
+
delay *= 2; // Exponential backoff
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
sleep(ms) {
|
|
220
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Attempt recovery bootstrap using API key when install token fails
|
|
224
|
+
*
|
|
225
|
+
* Recovery bootstrap allows re-issuing runtime credentials without
|
|
226
|
+
* needing a new install token from the dashboard.
|
|
227
|
+
*/
|
|
228
|
+
async tryRecoveryBootstrap(deploymentUrl) {
|
|
229
|
+
if (!this.config.apiKey || !this.config.workspaceId) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/installations/recovery`, {
|
|
234
|
+
method: 'POST',
|
|
235
|
+
headers: {
|
|
236
|
+
'Content-Type': 'application/json',
|
|
237
|
+
'X-API-Key': this.config.apiKey,
|
|
238
|
+
},
|
|
239
|
+
body: JSON.stringify({
|
|
240
|
+
agent_id: this.config.agentId,
|
|
241
|
+
workspace_id: this.config.workspaceId,
|
|
242
|
+
upstream_url: deploymentUrl,
|
|
243
|
+
capabilities: this.config.capabilities ?? [],
|
|
244
|
+
heartbeat_interval_seconds: this.config.heartbeatIntervalSeconds,
|
|
245
|
+
}),
|
|
246
|
+
});
|
|
247
|
+
if (!response.ok) {
|
|
248
|
+
const body = await response.text();
|
|
249
|
+
console.error(`[Beacon] Recovery bootstrap failed: ${response.status} - ${body}`);
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
const data = await response.json();
|
|
253
|
+
this.installationId = data.installation_id;
|
|
254
|
+
this.runtimeToken = data.runtime_token;
|
|
255
|
+
this.refreshToken = data.refresh_token;
|
|
256
|
+
console.log(`[Beacon] Recovery bootstrap successful, installation ID: ${this.installationId}`);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
console.error('[Beacon] Recovery bootstrap error:', error);
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
168
264
|
/**
|
|
169
265
|
* Bootstrap registration with Sekuire
|
|
170
266
|
*
|
|
@@ -223,7 +319,7 @@ class Beacon {
|
|
|
223
319
|
}),
|
|
224
320
|
});
|
|
225
321
|
if (response.status === 401) {
|
|
226
|
-
// Token expired - try to refresh
|
|
322
|
+
// Token expired - try to refresh using refresh token
|
|
227
323
|
console.warn('[Beacon] Runtime token expired, attempting refresh...');
|
|
228
324
|
await this.refreshRuntimeToken();
|
|
229
325
|
// Retry heartbeat after refresh
|
|
@@ -233,6 +329,12 @@ class Beacon {
|
|
|
233
329
|
const body = await response.text();
|
|
234
330
|
throw new Error(`Heartbeat failed: ${response.status} - ${body}`);
|
|
235
331
|
}
|
|
332
|
+
// Check if server auto-refreshed our token (sliding expiration)
|
|
333
|
+
const data = await response.json();
|
|
334
|
+
if (data.refreshed_token) {
|
|
335
|
+
this.runtimeToken = data.refreshed_token.runtime_token;
|
|
336
|
+
console.log(`[Beacon] Token auto-refreshed by server (version ${data.refreshed_token.token_version})`);
|
|
337
|
+
}
|
|
236
338
|
this.lastHeartbeat = new Date();
|
|
237
339
|
this.failedHeartbeats = 0;
|
|
238
340
|
if (prevFailed > 0) {
|
|
@@ -16054,13 +16156,21 @@ class TaskWorker {
|
|
|
16054
16156
|
this.isConnected = false;
|
|
16055
16157
|
this.isPaused = false;
|
|
16056
16158
|
this.installationId = null;
|
|
16159
|
+
this.runtimeToken = null;
|
|
16160
|
+
this.refreshToken = null;
|
|
16161
|
+
this.expiresAt = null;
|
|
16162
|
+
if (!config.installToken && !config.runtimeToken) {
|
|
16163
|
+
throw new Error("WorkerConfig requires either installToken or runtimeToken");
|
|
16164
|
+
}
|
|
16057
16165
|
this.config = {
|
|
16058
16166
|
heartbeatIntervalMs: 10000, // 10 seconds
|
|
16059
16167
|
reconnectDelayMs: 1000, // 1 second initial
|
|
16060
16168
|
maxReconnectDelayMs: 30000, // 30 seconds max
|
|
16169
|
+
capabilities: [],
|
|
16061
16170
|
...config,
|
|
16062
16171
|
};
|
|
16063
16172
|
this.reconnectDelay = this.config.reconnectDelayMs;
|
|
16173
|
+
this.runtimeToken = config.runtimeToken || null;
|
|
16064
16174
|
}
|
|
16065
16175
|
/**
|
|
16066
16176
|
* Register a handler for a specific capability
|
|
@@ -16138,10 +16248,13 @@ class TaskWorker {
|
|
|
16138
16248
|
}
|
|
16139
16249
|
}
|
|
16140
16250
|
/**
|
|
16141
|
-
* Get
|
|
16251
|
+
* Get runtime token for API calls
|
|
16142
16252
|
*/
|
|
16143
|
-
|
|
16144
|
-
|
|
16253
|
+
getToken() {
|
|
16254
|
+
if (!this.runtimeToken) {
|
|
16255
|
+
throw new Error("No runtime token available - call bootstrap first");
|
|
16256
|
+
}
|
|
16257
|
+
return this.runtimeToken;
|
|
16145
16258
|
}
|
|
16146
16259
|
/**
|
|
16147
16260
|
* Connect to SSE stream
|
|
@@ -16151,8 +16264,7 @@ class TaskWorker {
|
|
|
16151
16264
|
console.log(`[Worker] Connecting to SSE stream: ${url}`);
|
|
16152
16265
|
this.eventSource = new EventSource$1(url, {
|
|
16153
16266
|
headers: {
|
|
16154
|
-
Authorization: `Bearer ${this.
|
|
16155
|
-
...(this.getApiKey() && { "X-API-Key": this.getApiKey() }),
|
|
16267
|
+
Authorization: `Bearer ${this.getToken()}`,
|
|
16156
16268
|
},
|
|
16157
16269
|
});
|
|
16158
16270
|
this.eventSource.onopen = () => {
|
|
@@ -16244,8 +16356,7 @@ class TaskWorker {
|
|
|
16244
16356
|
method: "POST",
|
|
16245
16357
|
headers: {
|
|
16246
16358
|
"Content-Type": "application/json",
|
|
16247
|
-
Authorization: `Bearer ${this.
|
|
16248
|
-
...(this.getApiKey() && { "X-API-Key": this.getApiKey() }),
|
|
16359
|
+
Authorization: `Bearer ${this.getToken()}`,
|
|
16249
16360
|
},
|
|
16250
16361
|
body: JSON.stringify({ status, output, error }),
|
|
16251
16362
|
});
|
|
@@ -16255,57 +16366,84 @@ class TaskWorker {
|
|
|
16255
16366
|
}
|
|
16256
16367
|
}
|
|
16257
16368
|
/**
|
|
16258
|
-
* Register this agent installation with Sekuire
|
|
16369
|
+
* Register this agent installation with Sekuire using install_token
|
|
16370
|
+
* Returns runtime_token for subsequent API calls
|
|
16259
16371
|
*/
|
|
16260
16372
|
async bootstrap() {
|
|
16261
|
-
|
|
16262
|
-
if (
|
|
16373
|
+
// If we already have a runtime token, skip bootstrap
|
|
16374
|
+
if (this.runtimeToken) {
|
|
16375
|
+
console.log("[Worker] Using provided runtime token, skipping bootstrap");
|
|
16263
16376
|
return;
|
|
16264
16377
|
}
|
|
16378
|
+
if (!this.config.installToken) {
|
|
16379
|
+
throw new Error("No install token provided for bootstrap");
|
|
16380
|
+
}
|
|
16265
16381
|
const deploymentUrl = this.config.deploymentUrl || detectDeploymentUrl();
|
|
16266
16382
|
try {
|
|
16267
16383
|
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/installations/bootstrap`, {
|
|
16268
16384
|
method: "POST",
|
|
16269
16385
|
headers: {
|
|
16270
16386
|
"Content-Type": "application/json",
|
|
16271
|
-
"X-API-Key": apiKey,
|
|
16272
16387
|
},
|
|
16273
16388
|
body: JSON.stringify({
|
|
16274
|
-
|
|
16389
|
+
install_token: this.config.installToken,
|
|
16275
16390
|
upstream_url: deploymentUrl,
|
|
16391
|
+
capabilities: this.config.capabilities || [],
|
|
16392
|
+
heartbeat_interval_seconds: Math.floor((this.config.heartbeatIntervalMs || 10000) / 1000),
|
|
16276
16393
|
}),
|
|
16277
16394
|
});
|
|
16278
|
-
if (response.ok) {
|
|
16279
|
-
const
|
|
16280
|
-
|
|
16281
|
-
console.log(`[Worker] Registered installation: ${this.installationId}`);
|
|
16395
|
+
if (!response.ok) {
|
|
16396
|
+
const errorText = await response.text();
|
|
16397
|
+
throw new Error(`Bootstrap failed: ${response.status} - ${errorText}`);
|
|
16282
16398
|
}
|
|
16399
|
+
const data = await response.json();
|
|
16400
|
+
this.installationId = data.installation_id;
|
|
16401
|
+
this.runtimeToken = data.runtime_token;
|
|
16402
|
+
this.refreshToken = data.refresh_token;
|
|
16403
|
+
this.expiresAt = data.expires_at
|
|
16404
|
+
? new Date(data.expires_at).getTime()
|
|
16405
|
+
: null;
|
|
16406
|
+
console.log(`[Worker] Bootstrapped installation: ${this.installationId}`);
|
|
16283
16407
|
}
|
|
16284
16408
|
catch (e) {
|
|
16285
|
-
console.
|
|
16409
|
+
console.error("[Worker] Bootstrap registration failed:", e);
|
|
16410
|
+
throw e;
|
|
16286
16411
|
}
|
|
16287
16412
|
}
|
|
16288
16413
|
/**
|
|
16289
|
-
* Start heartbeat loop
|
|
16414
|
+
* Start heartbeat/lease renewal loop
|
|
16290
16415
|
*/
|
|
16291
16416
|
startHeartbeat() {
|
|
16292
16417
|
this.heartbeatInterval = setInterval(async () => {
|
|
16418
|
+
if (!this.installationId) {
|
|
16419
|
+
return;
|
|
16420
|
+
}
|
|
16293
16421
|
try {
|
|
16294
16422
|
const deploymentUrl = this.config.deploymentUrl || detectDeploymentUrl();
|
|
16295
|
-
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/
|
|
16423
|
+
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/installations/${this.installationId}/lease`, {
|
|
16296
16424
|
method: "POST",
|
|
16297
16425
|
headers: {
|
|
16298
16426
|
"Content-Type": "application/json",
|
|
16299
|
-
Authorization: `Bearer ${this.
|
|
16300
|
-
...(this.getApiKey() && { "X-API-Key": this.getApiKey() }),
|
|
16427
|
+
Authorization: `Bearer ${this.getToken()}`,
|
|
16301
16428
|
},
|
|
16302
16429
|
body: JSON.stringify({
|
|
16303
|
-
|
|
16304
|
-
|
|
16305
|
-
|
|
16306
|
-
load: 0.0,
|
|
16430
|
+
status: this.isPaused ? "stopped" : "running",
|
|
16431
|
+
upstream_url: deploymentUrl,
|
|
16432
|
+
heartbeat_interval_seconds: Math.floor((this.config.heartbeatIntervalMs || 10000) / 1000),
|
|
16307
16433
|
}),
|
|
16308
16434
|
});
|
|
16435
|
+
if (response.status === 401) {
|
|
16436
|
+
console.warn("[Worker] Runtime token expired, attempting refresh...");
|
|
16437
|
+
try {
|
|
16438
|
+
await this.refreshRuntimeToken();
|
|
16439
|
+
return; // Will retry on next interval
|
|
16440
|
+
}
|
|
16441
|
+
catch {
|
|
16442
|
+
console.error("[Worker] Token refresh failed, stopping worker");
|
|
16443
|
+
await this.stop();
|
|
16444
|
+
return;
|
|
16445
|
+
}
|
|
16446
|
+
}
|
|
16309
16447
|
if (response.ok) {
|
|
16310
16448
|
const data = await response.json();
|
|
16311
16449
|
if (data.command) {
|
|
@@ -16314,10 +16452,34 @@ class TaskWorker {
|
|
|
16314
16452
|
}
|
|
16315
16453
|
}
|
|
16316
16454
|
catch (e) {
|
|
16317
|
-
console.warn("[Worker] Heartbeat failed:", e);
|
|
16455
|
+
console.warn("[Worker] Heartbeat/lease failed:", e);
|
|
16318
16456
|
}
|
|
16319
16457
|
}, this.config.heartbeatIntervalMs);
|
|
16320
16458
|
}
|
|
16459
|
+
/**
|
|
16460
|
+
* Refresh the runtime token using the refresh token
|
|
16461
|
+
*/
|
|
16462
|
+
async refreshRuntimeToken() {
|
|
16463
|
+
if (!this.installationId || !this.refreshToken) {
|
|
16464
|
+
throw new Error("Cannot refresh token - missing installation ID or refresh token");
|
|
16465
|
+
}
|
|
16466
|
+
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/installations/${this.installationId}/refresh`, {
|
|
16467
|
+
method: "POST",
|
|
16468
|
+
headers: {
|
|
16469
|
+
"Content-Type": "application/json",
|
|
16470
|
+
},
|
|
16471
|
+
body: JSON.stringify({
|
|
16472
|
+
refresh_token: this.refreshToken,
|
|
16473
|
+
}),
|
|
16474
|
+
});
|
|
16475
|
+
if (!response.ok) {
|
|
16476
|
+
const body = await response.text();
|
|
16477
|
+
throw new Error(`Token refresh failed: ${response.status} - ${body}`);
|
|
16478
|
+
}
|
|
16479
|
+
const data = await response.json();
|
|
16480
|
+
this.runtimeToken = data.runtime_token;
|
|
16481
|
+
console.log("[Worker] Runtime token refreshed successfully");
|
|
16482
|
+
}
|
|
16321
16483
|
}
|
|
16322
16484
|
// ============================================================================
|
|
16323
16485
|
// Convenience Export
|
package/dist/index.js
CHANGED
|
@@ -118,6 +118,7 @@ class Beacon {
|
|
|
118
118
|
this.refreshToken = null;
|
|
119
119
|
this.lastHeartbeat = null;
|
|
120
120
|
this.failedHeartbeats = 0;
|
|
121
|
+
this.degradedMode = false;
|
|
121
122
|
const resolvedApiKey = config.apiKey || this.getApiKey() || '';
|
|
122
123
|
const resolvedInstallToken = config.installToken || this.getInstallToken();
|
|
123
124
|
this.config = {
|
|
@@ -133,6 +134,9 @@ class Beacon {
|
|
|
133
134
|
}
|
|
134
135
|
/**
|
|
135
136
|
* Start the beacon - registers with Sekuire and begins heartbeat loop
|
|
137
|
+
*
|
|
138
|
+
* If bootstrap fails, the agent continues running in degraded mode
|
|
139
|
+
* without Sekuire features. This prevents auth failures from crashing agents.
|
|
136
140
|
*/
|
|
137
141
|
async start() {
|
|
138
142
|
if (this.intervalId) {
|
|
@@ -145,16 +149,16 @@ class Beacon {
|
|
|
145
149
|
console.log(`[Beacon] Deployment URL: ${deploymentUrl}`);
|
|
146
150
|
}
|
|
147
151
|
// Bootstrap registration (exchanges install token for runtime credentials)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
console.
|
|
152
|
+
// On failure, continue in degraded mode - don't crash the agent
|
|
153
|
+
const bootstrapped = await this.bootstrapWithRetry(deploymentUrl);
|
|
154
|
+
if (!bootstrapped) {
|
|
155
|
+
this.degradedMode = true;
|
|
156
|
+
console.warn('[Beacon] Running in degraded mode - Sekuire features disabled');
|
|
157
|
+
console.warn('[Beacon] To re-enable: generate a new install token from the dashboard');
|
|
154
158
|
this.notifyStatusChange();
|
|
155
|
-
|
|
156
|
-
throw error;
|
|
159
|
+
return;
|
|
157
160
|
}
|
|
161
|
+
console.log(`[Beacon] Bootstrap successful, installation ID: ${this.installationId}`);
|
|
158
162
|
// Start heartbeat loop
|
|
159
163
|
const intervalMs = this.config.heartbeatIntervalSeconds * 1000;
|
|
160
164
|
this.intervalId = setInterval(() => {
|
|
@@ -181,14 +185,106 @@ class Beacon {
|
|
|
181
185
|
*/
|
|
182
186
|
getStatus() {
|
|
183
187
|
return {
|
|
184
|
-
isRunning: this.intervalId !== null,
|
|
188
|
+
isRunning: this.intervalId !== null || this.degradedMode,
|
|
185
189
|
installationId: this.installationId || undefined,
|
|
186
190
|
runtimeToken: this.runtimeToken ? '***' + this.runtimeToken.slice(-4) : undefined,
|
|
187
191
|
deploymentUrl: this.config.deploymentUrl || detectDeploymentUrl(),
|
|
188
192
|
lastHeartbeat: this.lastHeartbeat || undefined,
|
|
189
193
|
failedHeartbeats: this.failedHeartbeats,
|
|
194
|
+
degradedMode: this.degradedMode,
|
|
190
195
|
};
|
|
191
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Bootstrap with retry and exponential backoff
|
|
199
|
+
*
|
|
200
|
+
* Returns true if bootstrap succeeded, false if all retries exhausted.
|
|
201
|
+
* Does NOT throw - allows agent to continue in degraded mode.
|
|
202
|
+
*/
|
|
203
|
+
async bootstrapWithRetry(deploymentUrl, maxRetries = 3, initialDelayMs = 1000) {
|
|
204
|
+
let delay = initialDelayMs;
|
|
205
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
206
|
+
try {
|
|
207
|
+
await this.bootstrap(deploymentUrl);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
const isLastAttempt = attempt === maxRetries;
|
|
212
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
213
|
+
// Check if error is permanent (expired/invalid token) vs transient (network)
|
|
214
|
+
const isPermanentError = errorMsg.includes('401') ||
|
|
215
|
+
errorMsg.includes('expired') ||
|
|
216
|
+
errorMsg.includes('invalid') ||
|
|
217
|
+
errorMsg.includes('not found');
|
|
218
|
+
if (isPermanentError) {
|
|
219
|
+
console.error(`[Beacon] Bootstrap failed (permanent error): ${errorMsg}`);
|
|
220
|
+
// Try recovery bootstrap if we have API key
|
|
221
|
+
if (this.config.apiKey && this.config.workspaceId) {
|
|
222
|
+
console.log('[Beacon] Attempting recovery bootstrap with API key...');
|
|
223
|
+
const recovered = await this.tryRecoveryBootstrap(deploymentUrl);
|
|
224
|
+
if (recovered) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
console.error('[Beacon] Install token expired or invalid - generate a new one from the dashboard');
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
if (isLastAttempt) {
|
|
232
|
+
console.error(`[Beacon] Bootstrap failed after ${maxRetries} attempts: ${errorMsg}`);
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
console.warn(`[Beacon] Bootstrap attempt ${attempt}/${maxRetries} failed: ${errorMsg}`);
|
|
236
|
+
console.warn(`[Beacon] Retrying in ${delay}ms...`);
|
|
237
|
+
await this.sleep(delay);
|
|
238
|
+
delay *= 2; // Exponential backoff
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
sleep(ms) {
|
|
244
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Attempt recovery bootstrap using API key when install token fails
|
|
248
|
+
*
|
|
249
|
+
* Recovery bootstrap allows re-issuing runtime credentials without
|
|
250
|
+
* needing a new install token from the dashboard.
|
|
251
|
+
*/
|
|
252
|
+
async tryRecoveryBootstrap(deploymentUrl) {
|
|
253
|
+
if (!this.config.apiKey || !this.config.workspaceId) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/installations/recovery`, {
|
|
258
|
+
method: 'POST',
|
|
259
|
+
headers: {
|
|
260
|
+
'Content-Type': 'application/json',
|
|
261
|
+
'X-API-Key': this.config.apiKey,
|
|
262
|
+
},
|
|
263
|
+
body: JSON.stringify({
|
|
264
|
+
agent_id: this.config.agentId,
|
|
265
|
+
workspace_id: this.config.workspaceId,
|
|
266
|
+
upstream_url: deploymentUrl,
|
|
267
|
+
capabilities: this.config.capabilities ?? [],
|
|
268
|
+
heartbeat_interval_seconds: this.config.heartbeatIntervalSeconds,
|
|
269
|
+
}),
|
|
270
|
+
});
|
|
271
|
+
if (!response.ok) {
|
|
272
|
+
const body = await response.text();
|
|
273
|
+
console.error(`[Beacon] Recovery bootstrap failed: ${response.status} - ${body}`);
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
const data = await response.json();
|
|
277
|
+
this.installationId = data.installation_id;
|
|
278
|
+
this.runtimeToken = data.runtime_token;
|
|
279
|
+
this.refreshToken = data.refresh_token;
|
|
280
|
+
console.log(`[Beacon] Recovery bootstrap successful, installation ID: ${this.installationId}`);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
console.error('[Beacon] Recovery bootstrap error:', error);
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
192
288
|
/**
|
|
193
289
|
* Bootstrap registration with Sekuire
|
|
194
290
|
*
|
|
@@ -247,7 +343,7 @@ class Beacon {
|
|
|
247
343
|
}),
|
|
248
344
|
});
|
|
249
345
|
if (response.status === 401) {
|
|
250
|
-
// Token expired - try to refresh
|
|
346
|
+
// Token expired - try to refresh using refresh token
|
|
251
347
|
console.warn('[Beacon] Runtime token expired, attempting refresh...');
|
|
252
348
|
await this.refreshRuntimeToken();
|
|
253
349
|
// Retry heartbeat after refresh
|
|
@@ -257,6 +353,12 @@ class Beacon {
|
|
|
257
353
|
const body = await response.text();
|
|
258
354
|
throw new Error(`Heartbeat failed: ${response.status} - ${body}`);
|
|
259
355
|
}
|
|
356
|
+
// Check if server auto-refreshed our token (sliding expiration)
|
|
357
|
+
const data = await response.json();
|
|
358
|
+
if (data.refreshed_token) {
|
|
359
|
+
this.runtimeToken = data.refreshed_token.runtime_token;
|
|
360
|
+
console.log(`[Beacon] Token auto-refreshed by server (version ${data.refreshed_token.token_version})`);
|
|
361
|
+
}
|
|
260
362
|
this.lastHeartbeat = new Date();
|
|
261
363
|
this.failedHeartbeats = 0;
|
|
262
364
|
if (prevFailed > 0) {
|
|
@@ -16078,13 +16180,21 @@ class TaskWorker {
|
|
|
16078
16180
|
this.isConnected = false;
|
|
16079
16181
|
this.isPaused = false;
|
|
16080
16182
|
this.installationId = null;
|
|
16183
|
+
this.runtimeToken = null;
|
|
16184
|
+
this.refreshToken = null;
|
|
16185
|
+
this.expiresAt = null;
|
|
16186
|
+
if (!config.installToken && !config.runtimeToken) {
|
|
16187
|
+
throw new Error("WorkerConfig requires either installToken or runtimeToken");
|
|
16188
|
+
}
|
|
16081
16189
|
this.config = {
|
|
16082
16190
|
heartbeatIntervalMs: 10000, // 10 seconds
|
|
16083
16191
|
reconnectDelayMs: 1000, // 1 second initial
|
|
16084
16192
|
maxReconnectDelayMs: 30000, // 30 seconds max
|
|
16193
|
+
capabilities: [],
|
|
16085
16194
|
...config,
|
|
16086
16195
|
};
|
|
16087
16196
|
this.reconnectDelay = this.config.reconnectDelayMs;
|
|
16197
|
+
this.runtimeToken = config.runtimeToken || null;
|
|
16088
16198
|
}
|
|
16089
16199
|
/**
|
|
16090
16200
|
* Register a handler for a specific capability
|
|
@@ -16162,10 +16272,13 @@ class TaskWorker {
|
|
|
16162
16272
|
}
|
|
16163
16273
|
}
|
|
16164
16274
|
/**
|
|
16165
|
-
* Get
|
|
16275
|
+
* Get runtime token for API calls
|
|
16166
16276
|
*/
|
|
16167
|
-
|
|
16168
|
-
|
|
16277
|
+
getToken() {
|
|
16278
|
+
if (!this.runtimeToken) {
|
|
16279
|
+
throw new Error("No runtime token available - call bootstrap first");
|
|
16280
|
+
}
|
|
16281
|
+
return this.runtimeToken;
|
|
16169
16282
|
}
|
|
16170
16283
|
/**
|
|
16171
16284
|
* Connect to SSE stream
|
|
@@ -16175,8 +16288,7 @@ class TaskWorker {
|
|
|
16175
16288
|
console.log(`[Worker] Connecting to SSE stream: ${url}`);
|
|
16176
16289
|
this.eventSource = new EventSource$1(url, {
|
|
16177
16290
|
headers: {
|
|
16178
|
-
Authorization: `Bearer ${this.
|
|
16179
|
-
...(this.getApiKey() && { "X-API-Key": this.getApiKey() }),
|
|
16291
|
+
Authorization: `Bearer ${this.getToken()}`,
|
|
16180
16292
|
},
|
|
16181
16293
|
});
|
|
16182
16294
|
this.eventSource.onopen = () => {
|
|
@@ -16268,8 +16380,7 @@ class TaskWorker {
|
|
|
16268
16380
|
method: "POST",
|
|
16269
16381
|
headers: {
|
|
16270
16382
|
"Content-Type": "application/json",
|
|
16271
|
-
Authorization: `Bearer ${this.
|
|
16272
|
-
...(this.getApiKey() && { "X-API-Key": this.getApiKey() }),
|
|
16383
|
+
Authorization: `Bearer ${this.getToken()}`,
|
|
16273
16384
|
},
|
|
16274
16385
|
body: JSON.stringify({ status, output, error }),
|
|
16275
16386
|
});
|
|
@@ -16279,57 +16390,84 @@ class TaskWorker {
|
|
|
16279
16390
|
}
|
|
16280
16391
|
}
|
|
16281
16392
|
/**
|
|
16282
|
-
* Register this agent installation with Sekuire
|
|
16393
|
+
* Register this agent installation with Sekuire using install_token
|
|
16394
|
+
* Returns runtime_token for subsequent API calls
|
|
16283
16395
|
*/
|
|
16284
16396
|
async bootstrap() {
|
|
16285
|
-
|
|
16286
|
-
if (
|
|
16397
|
+
// If we already have a runtime token, skip bootstrap
|
|
16398
|
+
if (this.runtimeToken) {
|
|
16399
|
+
console.log("[Worker] Using provided runtime token, skipping bootstrap");
|
|
16287
16400
|
return;
|
|
16288
16401
|
}
|
|
16402
|
+
if (!this.config.installToken) {
|
|
16403
|
+
throw new Error("No install token provided for bootstrap");
|
|
16404
|
+
}
|
|
16289
16405
|
const deploymentUrl = this.config.deploymentUrl || detectDeploymentUrl();
|
|
16290
16406
|
try {
|
|
16291
16407
|
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/installations/bootstrap`, {
|
|
16292
16408
|
method: "POST",
|
|
16293
16409
|
headers: {
|
|
16294
16410
|
"Content-Type": "application/json",
|
|
16295
|
-
"X-API-Key": apiKey,
|
|
16296
16411
|
},
|
|
16297
16412
|
body: JSON.stringify({
|
|
16298
|
-
|
|
16413
|
+
install_token: this.config.installToken,
|
|
16299
16414
|
upstream_url: deploymentUrl,
|
|
16415
|
+
capabilities: this.config.capabilities || [],
|
|
16416
|
+
heartbeat_interval_seconds: Math.floor((this.config.heartbeatIntervalMs || 10000) / 1000),
|
|
16300
16417
|
}),
|
|
16301
16418
|
});
|
|
16302
|
-
if (response.ok) {
|
|
16303
|
-
const
|
|
16304
|
-
|
|
16305
|
-
console.log(`[Worker] Registered installation: ${this.installationId}`);
|
|
16419
|
+
if (!response.ok) {
|
|
16420
|
+
const errorText = await response.text();
|
|
16421
|
+
throw new Error(`Bootstrap failed: ${response.status} - ${errorText}`);
|
|
16306
16422
|
}
|
|
16423
|
+
const data = await response.json();
|
|
16424
|
+
this.installationId = data.installation_id;
|
|
16425
|
+
this.runtimeToken = data.runtime_token;
|
|
16426
|
+
this.refreshToken = data.refresh_token;
|
|
16427
|
+
this.expiresAt = data.expires_at
|
|
16428
|
+
? new Date(data.expires_at).getTime()
|
|
16429
|
+
: null;
|
|
16430
|
+
console.log(`[Worker] Bootstrapped installation: ${this.installationId}`);
|
|
16307
16431
|
}
|
|
16308
16432
|
catch (e) {
|
|
16309
|
-
console.
|
|
16433
|
+
console.error("[Worker] Bootstrap registration failed:", e);
|
|
16434
|
+
throw e;
|
|
16310
16435
|
}
|
|
16311
16436
|
}
|
|
16312
16437
|
/**
|
|
16313
|
-
* Start heartbeat loop
|
|
16438
|
+
* Start heartbeat/lease renewal loop
|
|
16314
16439
|
*/
|
|
16315
16440
|
startHeartbeat() {
|
|
16316
16441
|
this.heartbeatInterval = setInterval(async () => {
|
|
16442
|
+
if (!this.installationId) {
|
|
16443
|
+
return;
|
|
16444
|
+
}
|
|
16317
16445
|
try {
|
|
16318
16446
|
const deploymentUrl = this.config.deploymentUrl || detectDeploymentUrl();
|
|
16319
|
-
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/
|
|
16447
|
+
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/installations/${this.installationId}/lease`, {
|
|
16320
16448
|
method: "POST",
|
|
16321
16449
|
headers: {
|
|
16322
16450
|
"Content-Type": "application/json",
|
|
16323
|
-
Authorization: `Bearer ${this.
|
|
16324
|
-
...(this.getApiKey() && { "X-API-Key": this.getApiKey() }),
|
|
16451
|
+
Authorization: `Bearer ${this.getToken()}`,
|
|
16325
16452
|
},
|
|
16326
16453
|
body: JSON.stringify({
|
|
16327
|
-
|
|
16328
|
-
|
|
16329
|
-
|
|
16330
|
-
load: 0.0,
|
|
16454
|
+
status: this.isPaused ? "stopped" : "running",
|
|
16455
|
+
upstream_url: deploymentUrl,
|
|
16456
|
+
heartbeat_interval_seconds: Math.floor((this.config.heartbeatIntervalMs || 10000) / 1000),
|
|
16331
16457
|
}),
|
|
16332
16458
|
});
|
|
16459
|
+
if (response.status === 401) {
|
|
16460
|
+
console.warn("[Worker] Runtime token expired, attempting refresh...");
|
|
16461
|
+
try {
|
|
16462
|
+
await this.refreshRuntimeToken();
|
|
16463
|
+
return; // Will retry on next interval
|
|
16464
|
+
}
|
|
16465
|
+
catch {
|
|
16466
|
+
console.error("[Worker] Token refresh failed, stopping worker");
|
|
16467
|
+
await this.stop();
|
|
16468
|
+
return;
|
|
16469
|
+
}
|
|
16470
|
+
}
|
|
16333
16471
|
if (response.ok) {
|
|
16334
16472
|
const data = await response.json();
|
|
16335
16473
|
if (data.command) {
|
|
@@ -16338,10 +16476,34 @@ class TaskWorker {
|
|
|
16338
16476
|
}
|
|
16339
16477
|
}
|
|
16340
16478
|
catch (e) {
|
|
16341
|
-
console.warn("[Worker] Heartbeat failed:", e);
|
|
16479
|
+
console.warn("[Worker] Heartbeat/lease failed:", e);
|
|
16342
16480
|
}
|
|
16343
16481
|
}, this.config.heartbeatIntervalMs);
|
|
16344
16482
|
}
|
|
16483
|
+
/**
|
|
16484
|
+
* Refresh the runtime token using the refresh token
|
|
16485
|
+
*/
|
|
16486
|
+
async refreshRuntimeToken() {
|
|
16487
|
+
if (!this.installationId || !this.refreshToken) {
|
|
16488
|
+
throw new Error("Cannot refresh token - missing installation ID or refresh token");
|
|
16489
|
+
}
|
|
16490
|
+
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/installations/${this.installationId}/refresh`, {
|
|
16491
|
+
method: "POST",
|
|
16492
|
+
headers: {
|
|
16493
|
+
"Content-Type": "application/json",
|
|
16494
|
+
},
|
|
16495
|
+
body: JSON.stringify({
|
|
16496
|
+
refresh_token: this.refreshToken,
|
|
16497
|
+
}),
|
|
16498
|
+
});
|
|
16499
|
+
if (!response.ok) {
|
|
16500
|
+
const body = await response.text();
|
|
16501
|
+
throw new Error(`Token refresh failed: ${response.status} - ${body}`);
|
|
16502
|
+
}
|
|
16503
|
+
const data = await response.json();
|
|
16504
|
+
this.runtimeToken = data.runtime_token;
|
|
16505
|
+
console.log("[Worker] Runtime token refreshed successfully");
|
|
16506
|
+
}
|
|
16345
16507
|
}
|
|
16346
16508
|
// ============================================================================
|
|
16347
16509
|
// Convenience Export
|
package/dist/memory/index.d.ts
CHANGED
|
@@ -38,4 +38,4 @@ export interface MemoryFactoryConfig {
|
|
|
38
38
|
instance?: MemoryStorage;
|
|
39
39
|
}
|
|
40
40
|
export declare function createMemoryStorage(config: MemoryFactoryConfig): MemoryStorage;
|
|
41
|
-
export { registerStorage, hasStorage, listStorageTypes, getStorageInfo
|
|
41
|
+
export { registerStorage, hasStorage, listStorageTypes, getStorageInfo };
|
|
@@ -73,24 +73,24 @@ export interface AgentSkill {
|
|
|
73
73
|
/** A2A message with role and parts */
|
|
74
74
|
export interface A2AMessage {
|
|
75
75
|
/** Message role: "user" or "agent" */
|
|
76
|
-
role:
|
|
76
|
+
role: 'user' | 'agent';
|
|
77
77
|
/** Message content parts */
|
|
78
78
|
parts: A2AMessagePart[];
|
|
79
79
|
}
|
|
80
80
|
/** Message part content */
|
|
81
81
|
export type A2AMessagePart = {
|
|
82
|
-
type:
|
|
82
|
+
type: 'text';
|
|
83
83
|
text: string;
|
|
84
84
|
} | {
|
|
85
|
-
type:
|
|
85
|
+
type: 'file';
|
|
86
86
|
uri: string;
|
|
87
87
|
mimeType?: string;
|
|
88
88
|
} | {
|
|
89
|
-
type:
|
|
89
|
+
type: 'data';
|
|
90
90
|
data: unknown;
|
|
91
91
|
};
|
|
92
92
|
/** A2A task states */
|
|
93
|
-
export type A2ATaskState =
|
|
93
|
+
export type A2ATaskState = 'pending' | 'working' | 'input-required' | 'completed' | 'failed' | 'cancelled';
|
|
94
94
|
/** A2A task status with timestamp */
|
|
95
95
|
export interface A2ATaskStatus {
|
|
96
96
|
state: A2ATaskState;
|
|
@@ -142,14 +142,14 @@ export interface A2ARouteResponse {
|
|
|
142
142
|
}
|
|
143
143
|
/** JSON-RPC 2.0 Request */
|
|
144
144
|
export interface JsonRpcRequest {
|
|
145
|
-
jsonrpc:
|
|
145
|
+
jsonrpc: '2.0';
|
|
146
146
|
method: string;
|
|
147
147
|
id: string | number;
|
|
148
148
|
params?: unknown;
|
|
149
149
|
}
|
|
150
150
|
/** JSON-RPC 2.0 Response */
|
|
151
151
|
export interface JsonRpcResponse<T = unknown> {
|
|
152
|
-
jsonrpc:
|
|
152
|
+
jsonrpc: '2.0';
|
|
153
153
|
id: string | number;
|
|
154
154
|
result?: T;
|
|
155
155
|
error?: JsonRpcError;
|
package/dist/worker.d.ts
CHANGED
|
@@ -26,13 +26,14 @@ export interface TaskContext {
|
|
|
26
26
|
export type TaskHandler = (ctx: TaskContext, input: Record<string, unknown>) => Promise<unknown>;
|
|
27
27
|
export interface WorkerConfig {
|
|
28
28
|
apiBaseUrl: string;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
agentId: string;
|
|
30
|
+
installToken?: string;
|
|
31
|
+
runtimeToken?: string;
|
|
32
32
|
heartbeatIntervalMs?: number;
|
|
33
33
|
reconnectDelayMs?: number;
|
|
34
34
|
maxReconnectDelayMs?: number;
|
|
35
35
|
deploymentUrl?: string;
|
|
36
|
+
capabilities?: string[];
|
|
36
37
|
}
|
|
37
38
|
export declare class TaskWorker {
|
|
38
39
|
private eventSource;
|
|
@@ -43,6 +44,9 @@ export declare class TaskWorker {
|
|
|
43
44
|
private isConnected;
|
|
44
45
|
private isPaused;
|
|
45
46
|
private installationId;
|
|
47
|
+
private runtimeToken;
|
|
48
|
+
private refreshToken;
|
|
49
|
+
private expiresAt;
|
|
46
50
|
private onCommandCallback?;
|
|
47
51
|
constructor(config: WorkerConfig);
|
|
48
52
|
/**
|
|
@@ -73,9 +77,9 @@ export declare class TaskWorker {
|
|
|
73
77
|
*/
|
|
74
78
|
private handleCommand;
|
|
75
79
|
/**
|
|
76
|
-
* Get
|
|
80
|
+
* Get runtime token for API calls
|
|
77
81
|
*/
|
|
78
|
-
private
|
|
82
|
+
private getToken;
|
|
79
83
|
/**
|
|
80
84
|
* Connect to SSE stream
|
|
81
85
|
*/
|
|
@@ -93,12 +97,17 @@ export declare class TaskWorker {
|
|
|
93
97
|
*/
|
|
94
98
|
private completeTask;
|
|
95
99
|
/**
|
|
96
|
-
* Register this agent installation with Sekuire
|
|
100
|
+
* Register this agent installation with Sekuire using install_token
|
|
101
|
+
* Returns runtime_token for subsequent API calls
|
|
97
102
|
*/
|
|
98
103
|
private bootstrap;
|
|
99
104
|
/**
|
|
100
|
-
* Start heartbeat loop
|
|
105
|
+
* Start heartbeat/lease renewal loop
|
|
101
106
|
*/
|
|
102
107
|
private startHeartbeat;
|
|
108
|
+
/**
|
|
109
|
+
* Refresh the runtime token using the refresh token
|
|
110
|
+
*/
|
|
111
|
+
private refreshRuntimeToken;
|
|
103
112
|
}
|
|
104
113
|
export declare function createWorker(config: WorkerConfig): TaskWorker;
|