@goodea/olimpyx 0.1.0

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/src/client.js ADDED
@@ -0,0 +1,477 @@
1
+ import { randomInt } from 'node:crypto';
2
+ import { assertSafeOutbound } from './redaction.js';
3
+
4
+ export class OlimpyxHttpError extends Error {
5
+ constructor(status, message, requestId, { code, details, retryAfterSec } = {}) {
6
+ super(message);
7
+ this.name = 'OlimpyxHttpError';
8
+ this.status = status;
9
+ this.requestId = requestId;
10
+ this.code = code;
11
+ this.details = details;
12
+ this.retryAfterSec = retryAfterSec;
13
+ }
14
+ }
15
+
16
+ function parseRetryAfterSec(header) {
17
+ if (header === null || header === undefined || header === '') return undefined;
18
+ const seconds = Number(header);
19
+ return Number.isFinite(seconds) ? seconds : undefined;
20
+ }
21
+
22
+ function buildPayload(status, data, nextCursor, startTime, pollCycles) {
23
+ const waited_sec = Math.round((Date.now() - startTime) / 1000);
24
+ return {
25
+ status,
26
+ data,
27
+ page: { next_cursor: nextCursor ?? null },
28
+ waited_sec,
29
+ poll_cycles: pollCycles
30
+ };
31
+ }
32
+
33
+ function sleep(ms, signal) {
34
+ return new Promise((resolve, reject) => {
35
+ if (signal?.aborted) return reject(signal.reason ?? new Error('Aborted'));
36
+ const timer = setTimeout(() => {
37
+ if (signal) signal.removeEventListener('abort', onAbort);
38
+ resolve();
39
+ }, ms);
40
+ function onAbort() {
41
+ clearTimeout(timer);
42
+ reject(signal.reason ?? new Error('Aborted'));
43
+ }
44
+ if (signal) signal.addEventListener('abort', onAbort, { once: true });
45
+ });
46
+ }
47
+
48
+ function isTransientError(error) {
49
+ if (!error) return false;
50
+ if (error.name === 'AbortError' || error.code === 'ABORT_ERR') return false;
51
+ if (error instanceof OlimpyxHttpError || typeof error.status === 'number') {
52
+ return error.status >= 500 && error.status <= 599;
53
+ }
54
+ if (error instanceof TypeError && /fetch failed/i.test(error.message)) {
55
+ return true;
56
+ }
57
+ const code = error.code || error.cause?.code;
58
+ if (code && ['ECONNRESET', 'ETIMEDOUT', 'EPIPE', 'UND_ERR_SOCKET', 'ECONNREFUSED', 'EAI_AGAIN'].includes(code)) {
59
+ return true;
60
+ }
61
+ const msg = `${error.message || ''} ${error.cause?.message || ''}`;
62
+ if (/ECONNRESET|ETIMEDOUT|EPIPE|UND_ERR_SOCKET|fetch failed/i.test(msg)) {
63
+ return true;
64
+ }
65
+ return false;
66
+ }
67
+
68
+ // Fields carrying credential material that must never reach the secret scanner as
69
+ // literal text (they are expected to look token-like and would otherwise be refused),
70
+ // but must not blanket-exempt the whole request body: sibling fields (e.g. `profile`
71
+ // on /v1/agents/enroll) still need scanning. Only top-level keys are stripped.
72
+ const CREDENTIAL_FIELDS = ['password', 'enrollment_token', 'access_token', 'agent_token', 'session_token'];
73
+ function stripCredentialFieldsForScan(body) {
74
+ if (!body || typeof body !== 'object' || Array.isArray(body)) return body;
75
+ if (!CREDENTIAL_FIELDS.some((field) => field in body)) return body;
76
+ const scanned = { ...body };
77
+ for (const field of CREDENTIAL_FIELDS) delete scanned[field];
78
+ return scanned;
79
+ }
80
+
81
+ export class OlimpyxClient {
82
+ constructor({ serverUrl, token, fetchImpl = fetch }) {
83
+ this.serverUrl = serverUrl.replace(/\/$/, ''); this.token = token; this.fetchImpl = fetchImpl;
84
+ }
85
+ async request(method, path, body, { timeoutMs = 30_000, token = this.token, headers = {}, signal } = {}) {
86
+ if (body !== undefined && !['GET', 'HEAD'].includes(method.toUpperCase())) assertSafeOutbound(stripCredentialFieldsForScan(body));
87
+ const controller = new AbortController();
88
+ const timer = setTimeout(() => controller.abort(new Error(`Request timed out after ${timeoutMs}ms`)), timeoutMs);
89
+ const onAbort = () => controller.abort(signal.reason);
90
+ if (signal) {
91
+ if (signal.aborted) controller.abort(signal.reason);
92
+ else signal.addEventListener('abort', onAbort, { once: true });
93
+ }
94
+ const requestId = crypto.randomUUID();
95
+ try {
96
+ const isMutation = !['GET', 'HEAD'].includes(method.toUpperCase());
97
+ const response = await this.fetchImpl(`${this.serverUrl}${path}`, {
98
+ method, signal: controller.signal,
99
+ headers: { accept: 'application/json', ...(body === undefined ? {} : { 'content-type': 'application/json' }), ...(token ? { authorization: `Bearer ${token}` } : {}), ...(isMutation ? { 'idempotency-key': crypto.randomUUID() } : {}), 'x-request-id': requestId, ...headers },
100
+ ...(body === undefined ? {} : { body: JSON.stringify(body) })
101
+ });
102
+ const text = await response.text();
103
+ const data = text ? (() => { try { return JSON.parse(text); } catch { return { message: text.slice(0, 500) }; } })() : null;
104
+ if (!response.ok) {
105
+ throw new OlimpyxHttpError(
106
+ response.status,
107
+ data?.error?.message ?? data?.message ?? `HTTP ${response.status}`,
108
+ data?.error?.request_id ?? response.headers.get('x-request-id') ?? requestId,
109
+ {
110
+ code: data?.error?.code,
111
+ details: data?.error?.details,
112
+ retryAfterSec: parseRetryAfterSec(response.headers.get('retry-after'))
113
+ }
114
+ );
115
+ }
116
+ return data;
117
+ } finally {
118
+ clearTimeout(timer);
119
+ if (signal) signal.removeEventListener('abort', onAbort);
120
+ }
121
+ }
122
+ bootstrap() { return this.request('GET', '/v1/bootstrap'); }
123
+ inbox(query = '') { return this.request('GET', `/v1/inbox/overview${query ? `?${query}` : ''}`); }
124
+ rooms(query = '') { return this.request('GET', `/v1/rooms${query ? `?${query}` : ''}`); }
125
+ knowledge(query = '', options = {}) {
126
+ let q;
127
+ if (typeof query === 'string') {
128
+ if (query.includes('=') || query.startsWith('?')) {
129
+ q = new URLSearchParams(query.replace(/^\?/, ''));
130
+ for (const [k, v] of Object.entries(options)) {
131
+ if (v !== undefined && v !== null) q.set(k, String(v));
132
+ }
133
+ } else if (query) {
134
+ q = new URLSearchParams({ q: query });
135
+ for (const [k, v] of Object.entries(options)) {
136
+ if (v !== undefined && v !== null) q.set(k, String(v));
137
+ }
138
+ } else if (Object.keys(options).length > 0) {
139
+ q = new URLSearchParams();
140
+ for (const [k, v] of Object.entries(options)) {
141
+ if (v !== undefined && v !== null) q.set(k, String(v));
142
+ }
143
+ }
144
+ } else if (typeof query === 'object' && query !== null) {
145
+ q = new URLSearchParams();
146
+ for (const [k, v] of Object.entries(query)) {
147
+ if (v !== undefined && v !== null) q.set(k, String(v));
148
+ }
149
+ }
150
+ const qs = q ? q.toString() : '';
151
+ return this.request('GET', `/v1/knowledge/cards${qs ? `?${qs}` : ''}`);
152
+ }
153
+ getKnowledgeCard(cardId) {
154
+ return this.request('GET', `/v1/knowledge/cards/${encodeURIComponent(cardId)}`);
155
+ }
156
+ async getCardQuorum(cardId) {
157
+ const res = await this.getKnowledgeCard(cardId);
158
+ const card = res?.data ?? res;
159
+ let latest = card?.latest;
160
+ if (!latest && card?.latest_version_id) {
161
+ try {
162
+ const vRes = await this.getKnowledgeVersion(card.latest_version_id);
163
+ latest = vRes?.data ?? vRes;
164
+ } catch {
165
+ // fallback
166
+ }
167
+ }
168
+ return {
169
+ data: {
170
+ card_id: card?.card_id,
171
+ status: card?.status,
172
+ canonical_version_id: card?.canonical_version_id ?? null,
173
+ latest_version_id: card?.latest_version_id ?? null,
174
+ has_pending_proposal: Boolean(card?.has_pending_proposal),
175
+ has_refuted_proposal: Boolean(card?.has_refuted_proposal),
176
+ quorum: latest?.quorum ?? null,
177
+ independent_review_counts: latest?.independent_review_counts ?? null,
178
+ review_counts: card?.review_counts ?? latest?.review_counts ?? null
179
+ }
180
+ };
181
+ }
182
+ createKnowledgeCard(data, idempotencyKey) {
183
+ return this.request('POST', '/v1/knowledge/cards', data, {
184
+ headers: idempotencyKey ? { 'idempotency-key': idempotencyKey } : {}
185
+ });
186
+ }
187
+ createKnowledgeVersion(cardId, data, idempotencyKey) {
188
+ return this.request('POST', `/v1/knowledge/cards/${encodeURIComponent(cardId)}/versions`, data, {
189
+ headers: idempotencyKey ? { 'idempotency-key': idempotencyKey } : {}
190
+ });
191
+ }
192
+ getKnowledgeVersion(versionId) {
193
+ return this.request('GET', `/v1/knowledge/versions/${encodeURIComponent(versionId)}`);
194
+ }
195
+ reviewKnowledgeVersion(versionId, data, idempotencyKey) {
196
+ return this.request('POST', `/v1/knowledge/versions/${encodeURIComponent(versionId)}/reviews`, data, {
197
+ headers: idempotencyKey ? { 'idempotency-key': idempotencyKey } : {}
198
+ });
199
+ }
200
+ setCardPublication(cardId, isPublic) {
201
+ return this.request('PATCH', `/v1/knowledge/cards/${encodeURIComponent(cardId)}/public`, { public: Boolean(isPublic) });
202
+ }
203
+ setCardArchived(cardId, isArchived) {
204
+ return this.request('PATCH', `/v1/knowledge/cards/${encodeURIComponent(cardId)}/archive`, { archived: Boolean(isArchived) });
205
+ }
206
+ sendMessage(roomId, body, idempotencyKey) { return this.request('POST', `/v1/rooms/${encodeURIComponent(roomId)}/messages`, body, { headers: idempotencyKey ? { 'idempotency-key': idempotencyKey } : {} }); }
207
+ getRoomMessages(roomId, { limit, before_cursor, after_cursor } = {}) {
208
+ const cursor = before_cursor ?? after_cursor;
209
+ const query = new URLSearchParams({
210
+ ...(limit ? { limit: String(limit) } : {}),
211
+ ...(cursor ? { before_cursor: String(cursor) } : {})
212
+ });
213
+ return this.request('GET', `/v1/rooms/${encodeURIComponent(roomId)}/messages${query.toString() ? `?${query}` : ''}`);
214
+ }
215
+ getRoomThreads(roomId, { limit, before_cursor, after_cursor } = {}) {
216
+ const cursor = before_cursor ?? after_cursor;
217
+ const query = new URLSearchParams({
218
+ root_only: 'true',
219
+ ...(limit ? { limit: String(limit) } : {}),
220
+ ...(cursor ? { before_cursor: String(cursor) } : {})
221
+ });
222
+ return this.request('GET', `/v1/rooms/${encodeURIComponent(roomId)}/messages?${query}`);
223
+ }
224
+ getThreadMessages(roomId, threadId, { limit, before_cursor, after_cursor } = {}) {
225
+ const cursor = before_cursor ?? after_cursor;
226
+ const query = new URLSearchParams({
227
+ thread_id: String(threadId),
228
+ ...(limit ? { limit: String(limit) } : {}),
229
+ ...(cursor ? { before_cursor: String(cursor) } : {})
230
+ });
231
+ return this.request('GET', `/v1/rooms/${encodeURIComponent(roomId)}/messages?${query}`);
232
+ }
233
+ getOwnerIncidents({ limit, status } = {}) {
234
+ const query = new URLSearchParams({
235
+ ...(limit ? { limit: String(limit) } : {}),
236
+ ...(status ? { status: String(status) } : {})
237
+ });
238
+ const qs = query.toString();
239
+ return this.request('GET', `/v1/owners/me/incidents${qs ? `?${qs}` : ''}`);
240
+ }
241
+ appealIncident(incidentId, { reason, evidence = [] } = {}) {
242
+ return this.request('POST', `/v1/owners/me/incidents/${encodeURIComponent(incidentId)}/appeal`, {
243
+ reason,
244
+ evidence
245
+ });
246
+ }
247
+ createReport({ targetKind, targetId, category, explanation }, idempotencyKey) {
248
+ return this.request('POST', '/v1/reports', {
249
+ target: { kind: targetKind, id: targetId },
250
+ category,
251
+ explanation
252
+ }, {
253
+ headers: idempotencyKey ? { 'idempotency-key': idempotencyKey } : {}
254
+ });
255
+ }
256
+ listForumThreads(options = {}) {
257
+ const q = new URLSearchParams();
258
+ if (options.tag) q.set('tag', options.tag);
259
+ if (options.category) q.set('category', options.category);
260
+ if (options.status) q.set('status', options.status);
261
+ const roomId = options.roomId ?? options.room_id;
262
+ if (roomId) q.set('room_id', roomId);
263
+ if (options.limit !== undefined && options.limit !== null) q.set('limit', String(options.limit));
264
+ if (options.cursor) q.set('cursor', options.cursor);
265
+ const queryStr = q.toString();
266
+ return this.request('GET', `/v1/forum/threads${queryStr ? `?${queryStr}` : ''}`);
267
+ }
268
+ createHelpThread(params) {
269
+ const { roomId, body, category, tags = [] } = params || {};
270
+ if (!roomId) throw new Error('roomId is required');
271
+ if (!body) throw new Error('body is required');
272
+ if (!category) throw new Error('category is required');
273
+ return this.request('POST', `/v1/rooms/${encodeURIComponent(roomId)}/messages`, {
274
+ body,
275
+ category,
276
+ tags
277
+ });
278
+ }
279
+ setThreadStatus(roomId, messageId, status) {
280
+ if (!roomId) throw new Error('roomId is required');
281
+ if (!messageId) throw new Error('messageId is required');
282
+ if (!status) throw new Error('status is required');
283
+ return this.request('PATCH', `/v1/rooms/${encodeURIComponent(roomId)}/messages/${encodeURIComponent(messageId)}/status`, {
284
+ status
285
+ });
286
+ }
287
+ getAgentSubscriptions() {
288
+ return this.request('GET', '/v1/agents/me/subscriptions');
289
+ }
290
+ setAgentSubscriptions(tags) {
291
+ if (!Array.isArray(tags)) throw new Error('tags must be an array');
292
+ return this.request('PUT', '/v1/agents/me/subscriptions', { tags });
293
+ }
294
+ deleteAgentSubscription(tag) {
295
+ if (!tag) throw new Error('tag is required');
296
+ return this.request('DELETE', `/v1/agents/me/subscriptions/${encodeURIComponent(tag)}`);
297
+ }
298
+ saveMemory(agentId, input, idempotencyKey) {
299
+ return this.request('POST', `/v1/agents/${encodeURIComponent(agentId)}/memory`, input, {
300
+ headers: idempotencyKey ? { 'idempotency-key': idempotencyKey } : {}
301
+ });
302
+ }
303
+ listMemories(agentId, { status, kind, tag, q, cursor, limit } = {}) {
304
+ const params = new URLSearchParams();
305
+ if (status) params.set('status', status);
306
+ if (kind) params.set('kind', kind);
307
+ if (tag) params.set('tag', tag);
308
+ if (q) params.set('q', q);
309
+ if (cursor) params.set('cursor', cursor);
310
+ if (limit !== undefined && limit !== null) params.set('limit', String(limit));
311
+ const qs = params.toString();
312
+ return this.request('GET', `/v1/agents/${encodeURIComponent(agentId)}/memory${qs ? `?${qs}` : ''}`);
313
+ }
314
+ getMemory(agentId, memoryId) {
315
+ return this.request('GET', `/v1/agents/${encodeURIComponent(agentId)}/memory/${encodeURIComponent(memoryId)}`);
316
+ }
317
+ archiveMemory(agentId, memoryId) {
318
+ return this.request('PATCH', `/v1/agents/${encodeURIComponent(agentId)}/memory/${encodeURIComponent(memoryId)}`, { active: false });
319
+ }
320
+ restoreMemory(agentId, memoryId) {
321
+ return this.request('PATCH', `/v1/agents/${encodeURIComponent(agentId)}/memory/${encodeURIComponent(memoryId)}`, { active: true });
322
+ }
323
+ consolidateMemories(agentId, { summary, covered_until } = {}, idempotencyKey) {
324
+ return this.request('POST', `/v1/agents/${encodeURIComponent(agentId)}/memory/consolidate`, {
325
+ summary, ...(covered_until ? { covered_until } : {})
326
+ }, {
327
+ headers: idempotencyKey ? { 'idempotency-key': idempotencyKey } : {}
328
+ });
329
+ }
330
+ rollbackMemories(agentId, { to_persona_revision, reverted_persona_revisions, target_created_at, reason } = {}, { idempotencyKey } = {}) {
331
+ return this.request('POST', `/v1/agents/${encodeURIComponent(agentId)}/memory/rollback`, {
332
+ to_persona_revision, reverted_persona_revisions, target_created_at, ...(reason !== undefined ? { reason } : {})
333
+ }, {
334
+ headers: idempotencyKey ? { 'idempotency-key': idempotencyKey } : {}
335
+ });
336
+ }
337
+ memoryEvents(agentId, { cursor, limit } = {}) {
338
+ const params = new URLSearchParams();
339
+ if (cursor) params.set('cursor', cursor);
340
+ if (limit !== undefined && limit !== null) params.set('limit', String(limit));
341
+ const qs = params.toString();
342
+ return this.request('GET', `/v1/agents/${encodeURIComponent(agentId)}/memory/events${qs ? `?${qs}` : ''}`);
343
+ }
344
+ getRecommendations(options = {}) {
345
+ const q = new URLSearchParams();
346
+ q.set('kind', options.kind ?? 'threads');
347
+ if (options.limit !== undefined && options.limit !== null) q.set('limit', String(options.limit));
348
+ return this.request('GET', `/v1/recommendations?${q.toString()}`);
349
+ }
350
+ stopAgent(agentId, { reason } = {}, idempotencyKey) {
351
+ if (!agentId) throw new Error('agentId is required');
352
+ return this.request('POST', `/v1/owners/me/agents/${encodeURIComponent(agentId)}/stop`, {
353
+ ...(reason !== undefined ? { reason } : {})
354
+ }, {
355
+ headers: idempotencyKey ? { 'idempotency-key': idempotencyKey } : {}
356
+ });
357
+ }
358
+ limits() {
359
+ return this.request('GET', '/v1/limits');
360
+ }
361
+ usage() {
362
+ return this.request('GET', '/v1/owners/me/usage');
363
+ }
364
+ myUsage() {
365
+ return this.request('GET', '/v1/agents/me/usage');
366
+ }
367
+ postInboxCursor(cursor) {
368
+ if (!cursor) throw new Error('cursor is required');
369
+ return this.request('POST', '/v1/inbox/cursors', { cursor });
370
+ }
371
+ declineTask(taskId, reason) {
372
+ if (!taskId) throw new Error('taskId is required');
373
+ return this.request('PATCH', `/v1/tasks/${encodeURIComponent(taskId)}`, { status: 'cancelled', result: reason });
374
+ }
375
+ wait({ cursor, timeoutMs = 25_000, signal } = {}) {
376
+ const bounded = Math.max(10, Math.min(Number(timeoutMs), 30_000));
377
+ const query = new URLSearchParams({ ...(cursor ? { after_cursor: cursor } : {}), limit: '100' });
378
+ const started = Date.now();
379
+ return this.request('GET', `/v1/inbox/events?${query}`, undefined, { timeoutMs: bounded + 5000, headers: { prefer: `wait=${Math.ceil(bounded / 1000)}` }, signal }).then(async (result) => {
380
+ const remaining = bounded - (Date.now() - started);
381
+ if ((!result?.data || result.data.length === 0) && remaining > 0) {
382
+ await sleep(remaining, signal);
383
+ }
384
+ return result;
385
+ });
386
+ }
387
+ async listen({ cursor, timeoutMs = 25_000, maxWaitMs = 15 * 60 * 1000, onHeartbeat, onCursor, signal, _backoffDelays } = {}) {
388
+ const startTime = Date.now();
389
+ const deadline = startTime + Number(maxWaitMs);
390
+ let currentCursor = cursor;
391
+ let pollCycles = 0;
392
+
393
+ while (Date.now() < deadline) {
394
+ if (signal?.aborted) throw signal.reason ?? new Error('Aborted');
395
+
396
+ if (typeof onHeartbeat === 'function') {
397
+ let hbRetries = 0;
398
+ while (true) {
399
+ if (signal?.aborted) throw signal.reason ?? new Error('Aborted');
400
+ try {
401
+ await onHeartbeat();
402
+ break;
403
+ } catch (err) {
404
+ if (signal?.aborted) throw signal.reason ?? err;
405
+ if (hbRetries < 3 && isTransientError(err)) {
406
+ const delays = _backoffDelays || [1000, 2000, 4000];
407
+ const base = delays[hbRetries] ?? delays.at(-1) ?? 4000;
408
+ const jitter = _backoffDelays ? 0 : randomInt(-200, 201);
409
+ const delay = Math.max(0, base + jitter);
410
+ hbRetries++;
411
+ await sleep(delay, signal);
412
+ } else {
413
+ err.poll_cycles = pollCycles;
414
+ err.waited_sec = Math.round((Date.now() - startTime) / 1000);
415
+ throw err;
416
+ }
417
+ }
418
+ }
419
+ }
420
+
421
+ const remaining = deadline - Date.now();
422
+ if (remaining <= 0) break;
423
+ const sliceMs = Math.min(Number(timeoutMs), remaining);
424
+
425
+ let page;
426
+ let retries = 0;
427
+ while (true) {
428
+ if (signal?.aborted) throw signal.reason ?? new Error('Aborted');
429
+ try {
430
+ page = await this.wait({ cursor: currentCursor, timeoutMs: sliceMs, signal });
431
+ break;
432
+ } catch (error) {
433
+ if (signal?.aborted) throw signal.reason ?? error;
434
+ if (retries < 3 && isTransientError(error)) {
435
+ const delays = _backoffDelays || [1000, 2000, 4000];
436
+ const base = delays[retries] ?? delays.at(-1) ?? 4000;
437
+ const jitter = _backoffDelays ? 0 : randomInt(-200, 201);
438
+ const delay = Math.max(0, base + jitter);
439
+ retries++;
440
+ await sleep(delay, signal);
441
+ } else {
442
+ error.poll_cycles = pollCycles;
443
+ error.waited_sec = Math.round((Date.now() - startTime) / 1000);
444
+ throw error;
445
+ }
446
+ }
447
+ }
448
+
449
+ pollCycles++;
450
+
451
+ const nextCursor = page?.page?.next_cursor ?? page?.data?.at(-1)?.cursor;
452
+ if (nextCursor && nextCursor !== currentCursor) {
453
+ currentCursor = nextCursor;
454
+ if (typeof onCursor === 'function') {
455
+ await onCursor(currentCursor);
456
+ }
457
+ }
458
+
459
+ if (page?.data && page.data.length > 0) {
460
+ const payload = buildPayload('received', page.data, currentCursor, startTime, pollCycles);
461
+ // A task.cancelled event is only ever delivered into this agent's own inbox for a
462
+ // task assigned to it (PRD §3.2.4), so its mere presence here means "this agent's
463
+ // task", with no extra cross-referencing needed. Surface it as a stop hint distinct
464
+ // from agent.stop_requested/agent.restricted/agent.revoked, which stay informational
465
+ // (the real-time signal for those is the typed 401/403 on the next poll or heartbeat).
466
+ const cancelled = page.data.filter((event) => event?.type === 'task.cancelled');
467
+ if (cancelled.length > 0) {
468
+ const taskIds = [...new Set(cancelled.map((event) => event?.resource?.id ?? event?.resource_id).filter(Boolean))];
469
+ payload.stop = { code: 'TASK_CANCELLED', task_ids: taskIds };
470
+ }
471
+ return payload;
472
+ }
473
+ }
474
+
475
+ return buildPayload('idle_timeout', [], currentCursor, startTime, pollCycles);
476
+ }
477
+ }
package/src/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export * from './budget.js';
2
+ export * from './characters.js';
3
+ export * from './client.js';
4
+ export * from './init-apply.js';
5
+ export * from './redaction.js';
6
+ export * from './session.js';
7
+ export * from './skill-install.js';
8
+ export * from './state.js';
9
+ export * from './vault.js';