@ziggs-ai/agent-sdk 0.3.0 → 0.4.1
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/CHANGELOG.md +18 -0
- package/dist/AgentHost.js +10 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/ingress/normalizeIncoming.d.ts +6 -5
- package/dist/ingress/normalizeIncoming.js +10 -6
- package/dist/runtime/PromptBuilder.js +2 -2
- package/dist/runtime/runTurn.js +2 -2
- package/dist/server/ConnectionPool.d.ts +24 -5
- package/dist/server/ConnectionPool.js +10 -10
- package/dist/server/agreements/AgreementService.d.ts +6 -0
- package/dist/server/agreements/AgreementService.js +6 -0
- package/dist/server/createHealthServer.js +5 -5
- package/dist/server/inboxLoop.js +1 -29
- package/dist/server/runLauncher.d.ts +1 -1
- package/dist/server/runLauncher.js +19 -6
- package/dist/server/staleBacklogGate.d.ts +53 -0
- package/dist/server/staleBacklogGate.js +120 -0
- package/dist/server/tools/tier2/contextTools.js +1 -1
- package/dist/shared/operatorKeyClaims.d.ts +22 -0
- package/dist/shared/operatorKeyClaims.js +35 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 — 2026-07-23
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- New exports: `createStaleBacklogGate`, `resolveReplayPolicy`, `newestActionableAt`, `parseReplayMaxAge` (stale-backlog gating), and `decodeOperatorKeyClaims`, `boundKeyFleetWarning` (operator-key claims).
|
|
8
|
+
|
|
9
|
+
### Changed (breaking)
|
|
10
|
+
|
|
11
|
+
- Removed legacy socket-frame aliases; wire frames use canonical names only (ZIG-913, ZIG-915).
|
|
12
|
+
- Delivery-law: classify inbound messages from the `service` wire shape (ZIG-936).
|
|
13
|
+
- Bumped `@ziggs-ai/api-client` to `^0.3.0`.
|
|
14
|
+
|
|
15
|
+
## 0.3.0 — 2026-07-22
|
|
16
|
+
|
|
17
|
+
### Changed (breaking)
|
|
18
|
+
|
|
19
|
+
- Agent delivery law: backend targeting policy, idempotent writes, and long-poll / multiplexed inbox.
|
|
20
|
+
|
|
3
21
|
## 0.2.0 — 2026-07-16
|
|
4
22
|
|
|
5
23
|
### Added (ZIG-784)
|
package/dist/AgentHost.js
CHANGED
|
@@ -306,14 +306,17 @@ export class AgentHost {
|
|
|
306
306
|
}
|
|
307
307
|
if (messageId)
|
|
308
308
|
this.seenMessages.mark(messageId);
|
|
309
|
-
const ownAgentId = this._resolveOwnAgentId(
|
|
309
|
+
const ownAgentId = this._resolveOwnAgentId();
|
|
310
310
|
if (!ownAgentId) {
|
|
311
311
|
runtimeLog.warn('AgentHost', 'handleMessage: ownAgentId missing — dropping event');
|
|
312
312
|
return;
|
|
313
313
|
}
|
|
314
314
|
let enrichedText = typeof text === 'string' ? text : '';
|
|
315
315
|
if (!enrichedText.trim()) {
|
|
316
|
-
|
|
316
|
+
// ZIG-936: agreement rides under `service` on the inbox-pull wire; fall
|
|
317
|
+
// back to top-level for hand-built metadata.
|
|
318
|
+
const svc = metadata.service;
|
|
319
|
+
const agreement = (svc?.agreement ?? metadata.agreement);
|
|
317
320
|
const terms = agreement?.terms;
|
|
318
321
|
if (terms?.description) {
|
|
319
322
|
enrichedText = String(terms.description);
|
|
@@ -449,10 +452,11 @@ export class AgentHost {
|
|
|
449
452
|
return;
|
|
450
453
|
this.stateTimers.arm(laneKey, stateName, sd.timeoutMs);
|
|
451
454
|
}
|
|
452
|
-
_resolveOwnAgentId(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
455
|
+
_resolveOwnAgentId() {
|
|
456
|
+
// The host is constructed for exactly one agent; its own id is the only
|
|
457
|
+
// source. (The socket-frame-era metadata fallbacks — self/agent/ziggsAgentId
|
|
458
|
+
// — are gone: nothing on the inbox wire ever carried them.)
|
|
459
|
+
return this.options.agentId || null;
|
|
456
460
|
}
|
|
457
461
|
async connectAsync(_timeout) {
|
|
458
462
|
// Bootstrap active assigned tasks once (HTTP pull → lane), then start the
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export { default } from './AgentHost.js';
|
|
|
40
40
|
export { validateContext, ContextWarning } from './cognition/validateContext.js';
|
|
41
41
|
export { runLauncher } from './server/runLauncher.js';
|
|
42
42
|
export { ConnectionPool } from './server/ConnectionPool.js';
|
|
43
|
+
export { createStaleBacklogGate, resolveReplayPolicy, newestActionableAt, parseReplayMaxAge, } from './server/staleBacklogGate.js';
|
|
44
|
+
export { decodeOperatorKeyClaims, boundKeyFleetWarning } from './shared/operatorKeyClaims.js';
|
|
43
45
|
export { createHealthServer } from './server/createHealthServer.js';
|
|
44
46
|
export { TaskService } from './server/tasks/TaskService.js';
|
|
45
47
|
export { AgreementService } from './server/agreements/AgreementService.js';
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,8 @@ export { default } from './AgentHost.js';
|
|
|
34
34
|
export { validateContext, ContextWarning } from './cognition/validateContext.js';
|
|
35
35
|
export { runLauncher } from './server/runLauncher.js';
|
|
36
36
|
export { ConnectionPool } from './server/ConnectionPool.js';
|
|
37
|
+
export { createStaleBacklogGate, resolveReplayPolicy, newestActionableAt, parseReplayMaxAge, } from './server/staleBacklogGate.js';
|
|
38
|
+
export { decodeOperatorKeyClaims, boundKeyFleetWarning } from './shared/operatorKeyClaims.js';
|
|
37
39
|
export { createHealthServer } from './server/createHealthServer.js';
|
|
38
40
|
export { TaskService } from './server/tasks/TaskService.js';
|
|
39
41
|
export { AgreementService } from './server/agreements/AgreementService.js';
|
|
@@ -40,12 +40,10 @@ interface WireTask {
|
|
|
40
40
|
}
|
|
41
41
|
interface WireMetadata {
|
|
42
42
|
chatId?: string;
|
|
43
|
-
chat_id?: string;
|
|
44
43
|
messageId?: string;
|
|
45
44
|
entryType?: string;
|
|
46
45
|
content_type?: string;
|
|
47
46
|
contentType?: string;
|
|
48
|
-
receiverId?: string;
|
|
49
47
|
operation?: string;
|
|
50
48
|
agreementId?: string;
|
|
51
49
|
sender?: {
|
|
@@ -55,10 +53,13 @@ interface WireMetadata {
|
|
|
55
53
|
receiver?: {
|
|
56
54
|
id?: string;
|
|
57
55
|
};
|
|
58
|
-
to?: {
|
|
59
|
-
id?: string;
|
|
60
|
-
};
|
|
61
56
|
task?: WireTask;
|
|
57
|
+
service?: {
|
|
58
|
+
task?: WireTask;
|
|
59
|
+
operation?: string;
|
|
60
|
+
agreement?: unknown;
|
|
61
|
+
agreementId?: string;
|
|
62
|
+
};
|
|
62
63
|
[key: string]: unknown;
|
|
63
64
|
}
|
|
64
65
|
interface NormalizeArgs {
|
|
@@ -5,10 +5,10 @@ const PROPOSAL_RESOLVED_OPERATIONS = new Set([
|
|
|
5
5
|
'proposal_approved_execute',
|
|
6
6
|
]);
|
|
7
7
|
function normalizeChatId(metadata) {
|
|
8
|
-
return metadata.chatId ||
|
|
8
|
+
return metadata.chatId || null;
|
|
9
9
|
}
|
|
10
10
|
function normalizeReceiverId(metadata, taskData) {
|
|
11
|
-
return metadata.receiver?.id ||
|
|
11
|
+
return metadata.receiver?.id || taskData?.executorId || taskData?.proposedTo || null;
|
|
12
12
|
}
|
|
13
13
|
function isTaskRelated(metadata, taskData) {
|
|
14
14
|
if (!taskData?.taskId)
|
|
@@ -21,7 +21,7 @@ function isTaskRelated(metadata, taskData) {
|
|
|
21
21
|
}
|
|
22
22
|
function isAgreementLifecycleNotification(metadata) {
|
|
23
23
|
const entryType = metadata.entryType || 'message';
|
|
24
|
-
const operation = metadata.operation;
|
|
24
|
+
const operation = metadata.service?.operation ?? metadata.operation;
|
|
25
25
|
return (entryType === 'notification' &&
|
|
26
26
|
typeof operation === 'string' &&
|
|
27
27
|
PROPOSAL_RESOLVED_OPERATIONS.has(operation));
|
|
@@ -36,7 +36,9 @@ function isAgreementLifecycleNotification(metadata) {
|
|
|
36
36
|
* but no longer gates anything.
|
|
37
37
|
*/
|
|
38
38
|
export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
39
|
-
|
|
39
|
+
// ZIG-936: classifier data lives under `service` on the inbox-pull wire;
|
|
40
|
+
// fall back to top-level for hand-built (direct-injection) metadata.
|
|
41
|
+
const taskData = (metadata.service?.task ?? metadata.task) || null;
|
|
40
42
|
const chatId = normalizeChatId(metadata);
|
|
41
43
|
const receiverId = normalizeReceiverId(metadata, taskData);
|
|
42
44
|
const senderType = metadata.sender?.type ? String(metadata.sender.type).toUpperCase() : undefined;
|
|
@@ -48,13 +50,15 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
48
50
|
timestamp: Date.now(),
|
|
49
51
|
};
|
|
50
52
|
if (isAgreementLifecycleNotification(metadata)) {
|
|
51
|
-
const
|
|
53
|
+
const operation = metadata.service?.operation ?? metadata.operation;
|
|
54
|
+
const rawAgreementId = metadata.service?.agreementId ?? metadata.agreementId;
|
|
55
|
+
const agreementId = typeof rawAgreementId === 'string' ? rawAgreementId : '';
|
|
52
56
|
return {
|
|
53
57
|
chatId,
|
|
54
58
|
event: {
|
|
55
59
|
...base,
|
|
56
60
|
type: 'agreement_lifecycle',
|
|
57
|
-
operation
|
|
61
|
+
operation,
|
|
58
62
|
agreementId,
|
|
59
63
|
text,
|
|
60
64
|
},
|
|
@@ -421,8 +421,8 @@ ${this._renderDecisionSchemaFromActions(actions, senderMeta, ctxObj, omitMessage
|
|
|
421
421
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
422
422
|
const row = history[i];
|
|
423
423
|
const sender = row.sender;
|
|
424
|
-
const id = String(sender?.id ||
|
|
425
|
-
const type = String(sender?.type ||
|
|
424
|
+
const id = String(sender?.id || '');
|
|
425
|
+
const type = String(sender?.type || '').toUpperCase();
|
|
426
426
|
if (id && id !== myId && !isNonRoutableSenderType(type)) {
|
|
427
427
|
defaultReplyReceiverId = id;
|
|
428
428
|
break;
|
package/dist/runtime/runTurn.js
CHANGED
|
@@ -809,8 +809,8 @@ function pickPrimaryUserIdFromContext(context) {
|
|
|
809
809
|
for (let i = hist.length - 1; i >= 0; i--) {
|
|
810
810
|
const row = hist[i];
|
|
811
811
|
const sender = row?.sender;
|
|
812
|
-
const type = String(sender?.type ||
|
|
813
|
-
const id =
|
|
812
|
+
const type = String(sender?.type || '').toUpperCase();
|
|
813
|
+
const id = sender?.id;
|
|
814
814
|
if (type === 'USER' && id)
|
|
815
815
|
return id;
|
|
816
816
|
}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConnectionPool — multi-agent fleet manager.
|
|
3
|
+
*
|
|
4
|
+
* Holds N `AgentHostOptions` configs but only constructs/wakes an
|
|
5
|
+
* `AgentHost` (one WS connection) when needed. Idle hosts are slept after
|
|
6
|
+
* `idleTimeoutMs`. At most `maxActive` hosts are connected at a time; LRU
|
|
7
|
+
* eviction drops the oldest if the cap is reached. With `startControl`, a
|
|
8
|
+
* separate "launcher" socket receives backend-driven wake hints so the pool
|
|
9
|
+
* spins up the right host on demand.
|
|
10
|
+
*
|
|
11
|
+
* This is how a single Node process serves a fleet of agents without
|
|
12
|
+
* holding 1000 open sockets — typical config: 1000+ registered, ~50 live.
|
|
13
|
+
*/
|
|
14
|
+
import { type InboxOperatorAgentEntry } from '@ziggs-ai/api-client';
|
|
1
15
|
import { AgentHost, type AgentHostOptions } from '../AgentHost.js';
|
|
2
16
|
interface ConnectionManagerMeta {
|
|
3
17
|
domain?: string;
|
|
@@ -13,6 +27,11 @@ interface QueryFilter {
|
|
|
13
27
|
export interface PoolOptions {
|
|
14
28
|
maxActive?: number;
|
|
15
29
|
idleTimeoutMs?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Gate on the operator-poll lazy start (ZIG-910): return false to leave an
|
|
32
|
+
* actionable agent's host down this pass. Explicit startAgent()s are not gated.
|
|
33
|
+
*/
|
|
34
|
+
shouldLazyStart?: (entry: InboxOperatorAgentEntry) => boolean;
|
|
16
35
|
}
|
|
17
36
|
export interface SendToOptions {
|
|
18
37
|
timeoutMs?: number;
|
|
@@ -25,17 +44,17 @@ export declare class ConnectionPool {
|
|
|
25
44
|
private readonly manager;
|
|
26
45
|
private readonly configs;
|
|
27
46
|
private readonly responseListeners;
|
|
28
|
-
constructor({ maxActive, idleTimeoutMs }?: PoolOptions);
|
|
47
|
+
constructor({ maxActive, idleTimeoutMs, shouldLazyStart }?: PoolOptions);
|
|
29
48
|
/**
|
|
30
|
-
* Register N agent configs. Each will be lazily
|
|
49
|
+
* Register N agent configs. Each will be lazily started (a fresh
|
|
31
50
|
* `AgentHost` constructed + connected) the first time it's referenced.
|
|
32
51
|
*/
|
|
33
52
|
register(configs: AgentHostOptions[], metaArr?: ConnectionManagerMeta[]): void;
|
|
34
|
-
|
|
53
|
+
startAgent(agentId: string): Promise<AgentHost>;
|
|
35
54
|
sleep(agentId: string): Promise<void>;
|
|
36
55
|
disconnectAll(): Promise<void>;
|
|
37
56
|
/**
|
|
38
|
-
*
|
|
57
|
+
* Start the named agent, deliver a message, and wait for its first outbound
|
|
39
58
|
* `send-message` effect — i.e. the agent's reply. Returns the reply text.
|
|
40
59
|
*
|
|
41
60
|
* The effectTap injected in `register` drains a per-agent listener queue;
|
|
@@ -44,7 +63,7 @@ export declare class ConnectionPool {
|
|
|
44
63
|
*/
|
|
45
64
|
requestFrom(agentId: string, text: string, metadata?: Record<string, unknown>, { timeoutMs }?: SendToOptions): Promise<string>;
|
|
46
65
|
/**
|
|
47
|
-
*
|
|
66
|
+
* Start the named agent and deliver one wire frame to it. Returns once the
|
|
48
67
|
* host has finished processing.
|
|
49
68
|
*
|
|
50
69
|
* Note: does not return the agent's response. For request/response
|
|
@@ -19,11 +19,11 @@ export class ConnectionPool {
|
|
|
19
19
|
configs = new Map();
|
|
20
20
|
// Listeners enqueued by requestFrom — drained on outbound send-message effects.
|
|
21
21
|
responseListeners = new Map();
|
|
22
|
-
constructor({ maxActive = 50, idleTimeoutMs = 60_000 } = {}) {
|
|
23
|
-
this.manager = new ConnectionManager({ maxActive, idleTimeoutMs });
|
|
22
|
+
constructor({ maxActive = 50, idleTimeoutMs = 60_000, shouldLazyStart } = {}) {
|
|
23
|
+
this.manager = new ConnectionManager({ maxActive, idleTimeoutMs, shouldLazyStart });
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* Register N agent configs. Each will be lazily
|
|
26
|
+
* Register N agent configs. Each will be lazily started (a fresh
|
|
27
27
|
* `AgentHost` constructed + connected) the first time it's referenced.
|
|
28
28
|
*/
|
|
29
29
|
register(configs, metaArr = []) {
|
|
@@ -56,7 +56,7 @@ export class ConnectionPool {
|
|
|
56
56
|
},
|
|
57
57
|
});
|
|
58
58
|
await host.connectAsync();
|
|
59
|
-
runtimeLog.debug('ConnectionPool', `
|
|
59
|
+
runtimeLog.debug('ConnectionPool', `started "${agentId}" (active: ${this.manager.listActive().length}/${this.maxActive})`);
|
|
60
60
|
return host;
|
|
61
61
|
}, async (host) => {
|
|
62
62
|
host.disconnect();
|
|
@@ -64,8 +64,8 @@ export class ConnectionPool {
|
|
|
64
64
|
}, metaByAgentId.get(agentId));
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
async
|
|
68
|
-
return await this.manager.
|
|
67
|
+
async startAgent(agentId) {
|
|
68
|
+
return await this.manager.startAgent(agentId);
|
|
69
69
|
}
|
|
70
70
|
async sleep(agentId) {
|
|
71
71
|
return this.manager.sleep(agentId);
|
|
@@ -74,7 +74,7 @@ export class ConnectionPool {
|
|
|
74
74
|
return this.manager.sleepAll();
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
|
-
*
|
|
77
|
+
* Start the named agent, deliver a message, and wait for its first outbound
|
|
78
78
|
* `send-message` effect — i.e. the agent's reply. Returns the reply text.
|
|
79
79
|
*
|
|
80
80
|
* The effectTap injected in `register` drains a per-agent listener queue;
|
|
@@ -82,7 +82,7 @@ export class ConnectionPool {
|
|
|
82
82
|
* does not reply within `timeoutMs` (default 60 s for LLM agents).
|
|
83
83
|
*/
|
|
84
84
|
async requestFrom(agentId, text, metadata = {}, { timeoutMs = 60_000 } = {}) {
|
|
85
|
-
const host = await this.
|
|
85
|
+
const host = await this.startAgent(agentId);
|
|
86
86
|
return new Promise((resolve, reject) => {
|
|
87
87
|
let settled = false;
|
|
88
88
|
const timer = setTimeout(() => {
|
|
@@ -123,14 +123,14 @@ export class ConnectionPool {
|
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
126
|
-
*
|
|
126
|
+
* Start the named agent and deliver one wire frame to it. Returns once the
|
|
127
127
|
* host has finished processing.
|
|
128
128
|
*
|
|
129
129
|
* Note: does not return the agent's response. For request/response
|
|
130
130
|
* semantics use `requestFrom`.
|
|
131
131
|
*/
|
|
132
132
|
async sendTo(agentId, text, metadata = {}, { timeoutMs = 30_000 } = {}) {
|
|
133
|
-
const host = await this.
|
|
133
|
+
const host = await this.startAgent(agentId);
|
|
134
134
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`sendTo timeout for "${agentId}" after ${timeoutMs}ms`)), timeoutMs));
|
|
135
135
|
await Promise.race([host.handleMessage(text, metadata), timeoutPromise]);
|
|
136
136
|
this.manager.touch(agentId);
|
|
@@ -22,6 +22,12 @@ export declare class AgreementService {
|
|
|
22
22
|
counter(agreementId: string, counter: CounterAgreementData): Promise<Agreement>;
|
|
23
23
|
getStatus(agreementId: string): Promise<unknown | null>;
|
|
24
24
|
list(filters?: ListAgreementsFilters): Promise<Agreement[]>;
|
|
25
|
+
/**
|
|
26
|
+
* ZIG-942: despite the name, the default returns every agreement the
|
|
27
|
+
* caller's grant can read in the active org — including ones the caller is
|
|
28
|
+
* not a party to (`isYou` all-false). Pass `{ partyOnly: true }` for only
|
|
29
|
+
* agreements where the caller is a party.
|
|
30
|
+
*/
|
|
25
31
|
mine(filters?: GetMyAgreementsFilters): Promise<Agreement[]>;
|
|
26
32
|
get(agreementId: string): Promise<Agreement | null>;
|
|
27
33
|
create(body: CreateAgreementBody): Promise<{
|
|
@@ -46,6 +46,12 @@ export class AgreementService {
|
|
|
46
46
|
list(filters = {}) {
|
|
47
47
|
return listAgreements(filters, this.creds);
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* ZIG-942: despite the name, the default returns every agreement the
|
|
51
|
+
* caller's grant can read in the active org — including ones the caller is
|
|
52
|
+
* not a party to (`isYou` all-false). Pass `{ partyOnly: true }` for only
|
|
53
|
+
* agreements where the caller is a party.
|
|
54
|
+
*/
|
|
49
55
|
mine(filters = {}) {
|
|
50
56
|
return getMyAgreements(filters, this.creds);
|
|
51
57
|
}
|
|
@@ -19,11 +19,11 @@ function readJsonBody(req) {
|
|
|
19
19
|
export function createHealthServer({ port = Number(process.env.PORT) || 8080, label = 'agents', pool, } = {}) {
|
|
20
20
|
const server = createServer(async (req, res) => {
|
|
21
21
|
const url = req.url ?? '/';
|
|
22
|
-
const
|
|
23
|
-
if (req.method === 'POST' &&
|
|
24
|
-
const agentId = decodeURIComponent(
|
|
22
|
+
const startMatch = url.match(/^\/fleet-wake\/([^/?]+)/);
|
|
23
|
+
if (req.method === 'POST' && startMatch && pool) {
|
|
24
|
+
const agentId = decodeURIComponent(startMatch[1]);
|
|
25
25
|
try {
|
|
26
|
-
await pool.
|
|
26
|
+
await pool.startAgent(agentId);
|
|
27
27
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
28
28
|
res.end(JSON.stringify({ ok: true, agentId, active: pool.listActive().includes(agentId) }));
|
|
29
29
|
}
|
|
@@ -45,7 +45,7 @@ export function createHealthServer({ port = Number(process.env.PORT) || 8080, la
|
|
|
45
45
|
res.end(JSON.stringify({ ok: false, error: 'agentId, chatId, text, senderId required' }));
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
|
-
const host = (await pool.
|
|
48
|
+
const host = (await pool.startAgent(agentId));
|
|
49
49
|
await host.handleMessage(text, {
|
|
50
50
|
chatId,
|
|
51
51
|
entryType: 'message',
|
package/dist/server/inboxLoop.js
CHANGED
|
@@ -7,33 +7,6 @@ function log(label, msg) {
|
|
|
7
7
|
function warn(label, msg) {
|
|
8
8
|
runtimeLog.warn(label, msg);
|
|
9
9
|
}
|
|
10
|
-
/** Map Mongo message row → wire metadata for normalizeIncomingEvent. */
|
|
11
|
-
function rowToMetadata(row, chatId) {
|
|
12
|
-
const senderRaw = row.sender;
|
|
13
|
-
const senderId = String(senderRaw?.agentId ?? senderRaw?.id ?? '');
|
|
14
|
-
const receiver = row.receiver;
|
|
15
|
-
const senderType = senderRaw?.type ??
|
|
16
|
-
(receiver?.type === 'agent' ? 'agent' : 'user');
|
|
17
|
-
return {
|
|
18
|
-
chatId,
|
|
19
|
-
chat_id: chatId,
|
|
20
|
-
messageId: row.messageId,
|
|
21
|
-
sender: { id: senderId, type: senderType },
|
|
22
|
-
senderId,
|
|
23
|
-
senderType: String(senderType).toUpperCase(),
|
|
24
|
-
receiver: receiver ?? null,
|
|
25
|
-
receiverId: receiver?.id ?? null,
|
|
26
|
-
entryType: row.entryType ?? 'message',
|
|
27
|
-
content_type: row.content_type ?? row.contentType ?? 'text',
|
|
28
|
-
contentType: row.content_type ?? row.contentType ?? 'text',
|
|
29
|
-
task: row.task ?? null,
|
|
30
|
-
agreement: row.agreement ?? null,
|
|
31
|
-
operation: row.operation ?? null,
|
|
32
|
-
agreementId: row.agreementId ?? null,
|
|
33
|
-
timestamp: row.timestamp,
|
|
34
|
-
text: row.text,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
10
|
async function chatIdsForScope(scope, creds, grants) {
|
|
38
11
|
const { kind, id } = scope.scope;
|
|
39
12
|
switch (kind) {
|
|
@@ -82,8 +55,7 @@ async function deliverMessagesForScope(deps, scope, chatIds, reader, countCap) {
|
|
|
82
55
|
const text = typeof row.text === 'string' ? row.text : '';
|
|
83
56
|
if (!text.trim())
|
|
84
57
|
continue;
|
|
85
|
-
|
|
86
|
-
await deps.onMessage(text, metadata);
|
|
58
|
+
await deps.onMessage(text, { ...row, chatId });
|
|
87
59
|
delivered++;
|
|
88
60
|
if (remaining !== Number.POSITIVE_INFINITY)
|
|
89
61
|
remaining--;
|
|
@@ -18,7 +18,7 @@ interface FleetOptions extends SingleOptions {
|
|
|
18
18
|
wsUrl?: string;
|
|
19
19
|
agentMeta?: Record<string, unknown>[];
|
|
20
20
|
primary?: AgentConfig | ((pool: ConnectionPool) => AgentConfig);
|
|
21
|
-
|
|
21
|
+
preStart?: string[];
|
|
22
22
|
poolOptions?: {
|
|
23
23
|
maxActive?: number;
|
|
24
24
|
idleTimeoutMs?: number;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ConnectionPool } from './ConnectionPool.js';
|
|
2
2
|
import { AgentHost } from '../AgentHost.js';
|
|
3
3
|
import { createHealthServer } from './createHealthServer.js';
|
|
4
|
+
import { createStaleBacklogGate, resolveReplayPolicy } from './staleBacklogGate.js';
|
|
5
|
+
import { boundKeyFleetWarning } from '../shared/operatorKeyClaims.js';
|
|
4
6
|
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
5
7
|
export async function runLauncher(configOrConfigs, opts = {}) {
|
|
6
8
|
if (Array.isArray(configOrConfigs))
|
|
@@ -34,8 +36,14 @@ async function runSingle(config, { operatorKey, label = 'agent', port = process.
|
|
|
34
36
|
});
|
|
35
37
|
return { agent, healthServer: server ?? undefined };
|
|
36
38
|
}
|
|
37
|
-
async function runFleet(configs, { operatorKey, wsUrl = process.env.WS_URL, port = process.env.PORT || 8080, healthServer = true, label = 'launcher', agentMeta, primary: primaryConfig,
|
|
38
|
-
|
|
39
|
+
async function runFleet(configs, { operatorKey, wsUrl = process.env.WS_URL, port = process.env.PORT || 8080, healthServer = true, label = 'launcher', agentMeta, primary: primaryConfig, preStart = [], poolOptions, onShutdown, } = {}) {
|
|
40
|
+
// ZIG-910: don't resurrect stale backlogs at bootstrap. Env-driven default
|
|
41
|
+
// (FLEET_REPLAY_MAX_AGE / FLEET_REPLAY_STALE); an explicit
|
|
42
|
+
// poolOptions.shouldLazyStart wins.
|
|
43
|
+
const pool = new ConnectionPool({
|
|
44
|
+
shouldLazyStart: createStaleBacklogGate({ ...resolveReplayPolicy(), label }),
|
|
45
|
+
...poolOptions,
|
|
46
|
+
});
|
|
39
47
|
const effectiveKey = operatorKey ?? process.env.ZIGGS_OPERATOR_KEY;
|
|
40
48
|
const decoratedConfigs = effectiveKey
|
|
41
49
|
? configs.map(c => (c.operatorKey ? c : { ...c, operatorKey: effectiveKey }))
|
|
@@ -50,6 +58,11 @@ async function runFleet(configs, { operatorKey, wsUrl = process.env.WS_URL, port
|
|
|
50
58
|
}
|
|
51
59
|
const controlKey = effectiveKey ?? configs.find(c => c.operatorKey)?.operatorKey ?? resolvedPrimaryConfig?.operatorKey;
|
|
52
60
|
if (controlKey) {
|
|
61
|
+
// ZIG-911: an agent-bound key half-works in a confusing way — say the big
|
|
62
|
+
// thing once at startup instead of letting N-1 per-agent loops 403.
|
|
63
|
+
const boundWarning = boundKeyFleetWarning(controlKey, pool.size);
|
|
64
|
+
if (boundWarning)
|
|
65
|
+
runtimeLog.warn(label, boundWarning);
|
|
53
66
|
pool.startControl({ wsUrl, operatorKey: controlKey });
|
|
54
67
|
}
|
|
55
68
|
else {
|
|
@@ -68,10 +81,10 @@ async function runFleet(configs, { operatorKey, wsUrl = process.env.WS_URL, port
|
|
|
68
81
|
runtimeLog.warn(label, `Primary agent "${resolvedPrimaryConfig.name || resolvedPrimaryConfig.ownAgentId || 'primary'}" failed to connect at startup: ${err.message} — will retry`);
|
|
69
82
|
}
|
|
70
83
|
}
|
|
71
|
-
if (
|
|
72
|
-
await Promise.all(
|
|
73
|
-
.then(() => runtimeLog.debug(label, `Pre-
|
|
74
|
-
.catch(err => runtimeLog.warn(label, `Could not pre-
|
|
84
|
+
if (preStart.length > 0) {
|
|
85
|
+
await Promise.all(preStart.map(id => pool.startAgent(id)
|
|
86
|
+
.then(() => runtimeLog.debug(label, `Pre-started "${id}"`))
|
|
87
|
+
.catch(err => runtimeLog.warn(label, `Could not pre-start "${id}": ${err.message}`))));
|
|
75
88
|
}
|
|
76
89
|
runtimeLog.info(label, `Ready — ${pool.size} agent(s) registered, ${pool.listActive().length} connected`);
|
|
77
90
|
process.on('SIGTERM', async () => {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launcher startup backlog policy (ZIG-910).
|
|
3
|
+
*
|
|
4
|
+
* "Work waits" has no statute of limitations on the backend — the inbox never
|
|
5
|
+
* expires anything. But when a fleet comes up after a dark period, replaying
|
|
6
|
+
* every weeks-old actionable slice means a mass of LLM turns on stale work at
|
|
7
|
+
* real token cost. The consumer chooses what to pick up: this gate keeps the
|
|
8
|
+
* operator-poll lazy start from resurrecting agents whose newest actionable
|
|
9
|
+
* item is older than a cutoff. Skips are never silent — one loud aggregated
|
|
10
|
+
* summary line announces what was left down and how to opt back in.
|
|
11
|
+
*
|
|
12
|
+
* Env:
|
|
13
|
+
* FLEET_REPLAY_MAX_AGE cutoff, e.g. "3d" | "12h" | "30m" | bare number =
|
|
14
|
+
* days (default "3d")
|
|
15
|
+
* FLEET_REPLAY_STALE=1 replay everything (disable the gate)
|
|
16
|
+
*
|
|
17
|
+
* Scope is the launcher only. The backend inbox is untouched, explicit
|
|
18
|
+
* `pool.startAgent()` / preStart are untouched, and an agent with ANY fresh item
|
|
19
|
+
* starts normally (its own inbox loop then drains the stale backlog too).
|
|
20
|
+
*/
|
|
21
|
+
import type { InboxEnvelope, InboxOperatorAgentEntry } from '@ziggs-ai/api-client';
|
|
22
|
+
export declare const DEFAULT_REPLAY_MAX_AGE_MS: number;
|
|
23
|
+
export interface ReplayPolicy {
|
|
24
|
+
/** Skip slices whose newest actionable item is older than this. */
|
|
25
|
+
maxAgeMs: number;
|
|
26
|
+
/** FLEET_REPLAY_STALE=1 — replay everything, gate disabled. */
|
|
27
|
+
replayAll: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* "3d" | "12h" | "30m" | "45s"; a bare number means days (the knob's natural
|
|
31
|
+
* granularity). Returns null on garbage so the caller can warn and fall back.
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseReplayMaxAge(raw: string): number | null;
|
|
34
|
+
export declare function resolveReplayPolicy(env?: NodeJS.ProcessEnv): ReplayPolicy;
|
|
35
|
+
/**
|
|
36
|
+
* Newest actionable timestamp across an agent's slice: scope activity,
|
|
37
|
+
* proposals, and connection/MCP requests. Null when nothing carries a
|
|
38
|
+
* timestamp (e.g. a joined-but-idle chat) — age unprovable, treated as fresh.
|
|
39
|
+
*/
|
|
40
|
+
export declare function newestActionableAt(inbox: InboxEnvelope): number | null;
|
|
41
|
+
export declare function formatAge(ms: number): string;
|
|
42
|
+
export interface StaleBacklogGateOptions extends ReplayPolicy {
|
|
43
|
+
label?: string;
|
|
44
|
+
now?: () => number;
|
|
45
|
+
log?: (line: string) => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build the `shouldLazyStart` gate for the operator poll. Skipped agents
|
|
49
|
+
* resurface in every envelope (reading never acks), so announcements are
|
|
50
|
+
* per-agent-once: each poll pass that skips agents not yet announced emits one
|
|
51
|
+
* aggregated warn line covering them.
|
|
52
|
+
*/
|
|
53
|
+
export declare function createStaleBacklogGate({ maxAgeMs, replayAll, label, now, log, }: StaleBacklogGateOptions): (entry: InboxOperatorAgentEntry) => boolean;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
2
|
+
export const DEFAULT_REPLAY_MAX_AGE_MS = 3 * 24 * 60 * 60 * 1000;
|
|
3
|
+
/**
|
|
4
|
+
* "3d" | "12h" | "30m" | "45s"; a bare number means days (the knob's natural
|
|
5
|
+
* granularity). Returns null on garbage so the caller can warn and fall back.
|
|
6
|
+
*/
|
|
7
|
+
export function parseReplayMaxAge(raw) {
|
|
8
|
+
const m = /^\s*(\d+(?:\.\d+)?)\s*([dhms]?)\s*$/i.exec(raw);
|
|
9
|
+
if (!m)
|
|
10
|
+
return null;
|
|
11
|
+
const value = Number(m[1]);
|
|
12
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
13
|
+
return null;
|
|
14
|
+
const unitMs = { d: 86_400_000, h: 3_600_000, m: 60_000, s: 1_000 }[(m[2] || 'd').toLowerCase()];
|
|
15
|
+
return value * unitMs;
|
|
16
|
+
}
|
|
17
|
+
export function resolveReplayPolicy(env = process.env) {
|
|
18
|
+
const replayAll = env.FLEET_REPLAY_STALE === '1' || env.FLEET_REPLAY_STALE === 'true';
|
|
19
|
+
const raw = env.FLEET_REPLAY_MAX_AGE?.trim();
|
|
20
|
+
let maxAgeMs = DEFAULT_REPLAY_MAX_AGE_MS;
|
|
21
|
+
if (raw) {
|
|
22
|
+
const parsed = parseReplayMaxAge(raw);
|
|
23
|
+
if (parsed == null) {
|
|
24
|
+
runtimeLog.warn('staleBacklogGate', `FLEET_REPLAY_MAX_AGE="${raw}" is not a duration (use e.g. "3d", "12h", "30m") — using default ${formatAge(DEFAULT_REPLAY_MAX_AGE_MS)}`);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
maxAgeMs = parsed;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { maxAgeMs, replayAll };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Newest actionable timestamp across an agent's slice: scope activity,
|
|
34
|
+
* proposals, and connection/MCP requests. Null when nothing carries a
|
|
35
|
+
* timestamp (e.g. a joined-but-idle chat) — age unprovable, treated as fresh.
|
|
36
|
+
*/
|
|
37
|
+
export function newestActionableAt(inbox) {
|
|
38
|
+
let newest = null;
|
|
39
|
+
const consider = (iso) => {
|
|
40
|
+
if (!iso)
|
|
41
|
+
return;
|
|
42
|
+
const t = Date.parse(iso);
|
|
43
|
+
if (!Number.isNaN(t) && (newest == null || t > newest))
|
|
44
|
+
newest = t;
|
|
45
|
+
};
|
|
46
|
+
for (const scope of inbox.scopes ?? [])
|
|
47
|
+
consider(scope.latestAt);
|
|
48
|
+
for (const p of inbox.proposalsAwaitingMe ?? [])
|
|
49
|
+
consider(p.proposedAt);
|
|
50
|
+
for (const r of inbox.connectionRequestsAwaitingMe ?? [])
|
|
51
|
+
consider(r.requestedAt);
|
|
52
|
+
for (const r of inbox.mcpServerRequestsAwaitingMe ?? [])
|
|
53
|
+
consider(r.requestedAt);
|
|
54
|
+
return newest;
|
|
55
|
+
}
|
|
56
|
+
/** Unread items in a slice, for the summary line. Counts are backend-capped. */
|
|
57
|
+
function countItems(inbox) {
|
|
58
|
+
let items = 0;
|
|
59
|
+
for (const scope of inbox.scopes ?? []) {
|
|
60
|
+
items += (scope.newMessages ?? 0) + (scope.newArtifacts ?? 0);
|
|
61
|
+
}
|
|
62
|
+
items += (inbox.proposalsAwaitingMe ?? []).length;
|
|
63
|
+
items += (inbox.connectionRequestsAwaitingMe ?? []).length;
|
|
64
|
+
items += (inbox.mcpServerRequestsAwaitingMe ?? []).length;
|
|
65
|
+
return items;
|
|
66
|
+
}
|
|
67
|
+
export function formatAge(ms) {
|
|
68
|
+
if (ms >= 86_400_000)
|
|
69
|
+
return `${trimNum(ms / 86_400_000)}d`;
|
|
70
|
+
if (ms >= 3_600_000)
|
|
71
|
+
return `${trimNum(ms / 3_600_000)}h`;
|
|
72
|
+
if (ms >= 60_000)
|
|
73
|
+
return `${trimNum(ms / 60_000)}m`;
|
|
74
|
+
return `${trimNum(ms / 1_000)}s`;
|
|
75
|
+
}
|
|
76
|
+
function trimNum(n) {
|
|
77
|
+
return Number(n.toFixed(1)).toString();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build the `shouldLazyStart` gate for the operator poll. Skipped agents
|
|
81
|
+
* resurface in every envelope (reading never acks), so announcements are
|
|
82
|
+
* per-agent-once: each poll pass that skips agents not yet announced emits one
|
|
83
|
+
* aggregated warn line covering them.
|
|
84
|
+
*/
|
|
85
|
+
export function createStaleBacklogGate({ maxAgeMs, replayAll, label = 'launcher', now = Date.now, log, }) {
|
|
86
|
+
const emit = log ?? ((line) => runtimeLog.warn(label, line));
|
|
87
|
+
const announced = new Set();
|
|
88
|
+
let pendingAgents = [];
|
|
89
|
+
let pendingItems = 0;
|
|
90
|
+
let flushScheduled = false;
|
|
91
|
+
// One envelope's entries are gated synchronously; a microtask-deferred
|
|
92
|
+
// flush turns a whole boot flood into a single summary line.
|
|
93
|
+
const flush = () => {
|
|
94
|
+
flushScheduled = false;
|
|
95
|
+
if (pendingAgents.length === 0)
|
|
96
|
+
return;
|
|
97
|
+
const preview = pendingAgents.slice(0, 5).join(', ');
|
|
98
|
+
const more = pendingAgents.length > 5 ? `, +${pendingAgents.length - 5} more` : '';
|
|
99
|
+
emit(`startup backlog policy: skipped ${pendingItems} stale item(s) across ${pendingAgents.length} agent(s) older than ${formatAge(maxAgeMs)} (${preview}${more}) — set FLEET_REPLAY_STALE=1 to replay them`);
|
|
100
|
+
pendingAgents = [];
|
|
101
|
+
pendingItems = 0;
|
|
102
|
+
};
|
|
103
|
+
return (entry) => {
|
|
104
|
+
if (replayAll)
|
|
105
|
+
return true;
|
|
106
|
+
const newest = newestActionableAt(entry.inbox);
|
|
107
|
+
if (newest == null || now() - newest <= maxAgeMs)
|
|
108
|
+
return true;
|
|
109
|
+
if (!announced.has(entry.agentId)) {
|
|
110
|
+
announced.add(entry.agentId);
|
|
111
|
+
pendingAgents.push(entry.agentId);
|
|
112
|
+
pendingItems += countItems(entry.inbox);
|
|
113
|
+
if (!flushScheduled) {
|
|
114
|
+
flushScheduled = true;
|
|
115
|
+
queueMicrotask(flush);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -129,7 +129,7 @@ export const contextDiscoverGrantableTool = defineTool('context_discover_grantab
|
|
|
129
129
|
const items = await new ContextDiscoveryClient(operatorKey, agentId).discoverGrantable();
|
|
130
130
|
return { count: items.length, items };
|
|
131
131
|
}, {
|
|
132
|
-
description: 'See what context EXISTS in your orgs that you CANNOT read yet — the inverse of list_grants. Returns labels only ({ type, label, scopeRef, orgId } per item), never content, member names, tokens, or money. Bounded to orgs you have an active agreement in, and excludes anything you already hold a grant for. Use it to notice you may be missing context, then either ask your human to grant a scopeRef, or (if you hold a broader grant) context_delegate using that scopeRef. Pair with list_grants (what you hold) and context_expand_reach (what a held grant covers).',
|
|
132
|
+
description: 'See what context EXISTS in your orgs that you CANNOT read yet — the inverse of list_grants. Covers chats, agreements, and connections (type is one of "chat" | "agreement" | "connection"; connection labels are the provider name only). Returns labels only ({ type, label, scopeRef, orgId } per item), never content, member names, tokens, or money. Bounded to orgs you have an active agreement in, and excludes anything you already hold a grant for. Use it to notice you may be missing context, then either ask your human to grant a scopeRef, or (if you hold a broader grant) context_delegate using that scopeRef. Pair with list_grants (what you hold) and context_expand_reach (what a held grant covers).',
|
|
133
133
|
});
|
|
134
134
|
export const CONTEXT_TOOLS = [
|
|
135
135
|
contextReadTool,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side peek at an operator key's JWT payload (signature NOT verified —
|
|
3
|
+
* identity hint only, the backend remains the authority). Used by the fleet
|
|
4
|
+
* launcher to warn when the key is agent-bound (ZIG-911): a bound key
|
|
5
|
+
* collapses the operator multiplex to its one agent and 403s every other
|
|
6
|
+
* per-agent loop, so a large fleet quietly watches a single agent.
|
|
7
|
+
*/
|
|
8
|
+
export interface OperatorKeyClaims {
|
|
9
|
+
type?: string;
|
|
10
|
+
keyId?: string;
|
|
11
|
+
ownerId?: string;
|
|
12
|
+
boundAgentId?: string | null;
|
|
13
|
+
exp?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function decodeOperatorKeyClaims(token: string): OperatorKeyClaims | null;
|
|
16
|
+
/**
|
|
17
|
+
* The ZIG-911 startup check: warn (never refuse — a one-agent launcher on a
|
|
18
|
+
* bound key is legitimate) when the fleet's control key is bound to a single
|
|
19
|
+
* agent while more than one agent is registered. Returns the warning line, or
|
|
20
|
+
* null when the setup is coherent.
|
|
21
|
+
*/
|
|
22
|
+
export declare function boundKeyFleetWarning(operatorKey: string, registeredAgents: number): string | null;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function decodeOperatorKeyClaims(token) {
|
|
2
|
+
const parts = token.trim().split('.');
|
|
3
|
+
if (parts.length !== 3)
|
|
4
|
+
return null;
|
|
5
|
+
try {
|
|
6
|
+
const json = Buffer.from(parts[1], 'base64url').toString('utf8');
|
|
7
|
+
const payload = JSON.parse(json);
|
|
8
|
+
return {
|
|
9
|
+
type: payload.type,
|
|
10
|
+
keyId: payload.keyId,
|
|
11
|
+
ownerId: payload.ownerId,
|
|
12
|
+
boundAgentId: payload.boundAgentId ?? null,
|
|
13
|
+
exp: payload.exp,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The ZIG-911 startup check: warn (never refuse — a one-agent launcher on a
|
|
22
|
+
* bound key is legitimate) when the fleet's control key is bound to a single
|
|
23
|
+
* agent while more than one agent is registered. Returns the warning line, or
|
|
24
|
+
* null when the setup is coherent.
|
|
25
|
+
*/
|
|
26
|
+
export function boundKeyFleetWarning(operatorKey, registeredAgents) {
|
|
27
|
+
if (registeredAgents <= 1)
|
|
28
|
+
return null;
|
|
29
|
+
const bound = decodeOperatorKeyClaims(operatorKey)?.boundAgentId;
|
|
30
|
+
if (!bound)
|
|
31
|
+
return null;
|
|
32
|
+
return (`fleet key is bound to agent "${bound}": the operator multiplex covers 1 of ${registeredAgents} registered agents, ` +
|
|
33
|
+
`and per-agent inbox loops for the rest will 403 ("cannot impersonate"). ` +
|
|
34
|
+
`Use an unbound owner key with agents:impersonate to run a fleet.`);
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Agent framework SDK for building autonomous agents on the Ziggs platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@anthropic-ai/claude-agent-sdk": "^0.3.204",
|
|
27
27
|
"@anthropic-ai/sdk": "^0.110.0",
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
|
-
"@ziggs-ai/api-client": "^0.
|
|
29
|
+
"@ziggs-ai/api-client": "^0.3.1",
|
|
30
30
|
"ajv": "^8.17.1",
|
|
31
31
|
"dotenv": "^17.2.3",
|
|
32
32
|
"openai": "^4.0.0",
|