@ziggs-ai/agent-sdk 0.1.4 → 0.1.6
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/README.md +7 -5
- package/package.json +2 -2
- package/src/AgentHost.ts +165 -12
- package/src/adapters/OpenAIAdapter.ts +21 -0
- package/src/agent/Agent.ts +5 -2
- package/src/cognition/validateContext.ts +1 -1
- package/src/context/batch.ts +3 -3
- package/src/context/classifyEnvelope.ts +2 -2
- package/src/context/routingLabels.ts +1 -1
- package/src/formatters/AgreementFormatter.ts +5 -5
- package/src/formatters/HistoryFormatter.ts +3 -3
- package/src/index.ts +25 -4
- package/src/ingress/normalizeIncoming.ts +50 -7
- package/src/pricing/fleetDefaults.ts +218 -0
- package/src/pricing/fleetEvalFree.ts +24 -0
- package/src/pricing/fleetFreeTierA.gen.ts +12 -0
- package/src/pricing/fleetTierByAgentId.gen.ts +1022 -0
- package/src/runtime/AgentMachine.ts +68 -2
- package/src/runtime/PromptBuilder.ts +25 -23
- package/src/runtime/buildOutcome.ts +33 -3
- package/src/runtime/defaults.ts +3 -0
- package/src/runtime/runTurn.ts +115 -61
- package/src/runtime/validateWorkflow.ts +16 -0
- package/src/server/EventQueue.ts +14 -0
- package/src/server/InboxCatchUp.ts +251 -0
- package/src/server/SeenMessages.ts +27 -0
- package/src/server/ZiggsEffectHandler.ts +82 -8
- package/src/server/agreements/AgreementService.ts +7 -1
- package/src/server/createHealthServer.ts +79 -2
- package/src/server/runLauncher.ts +40 -25
- package/src/server/tasks/TaskService.ts +4 -5
- package/src/server/tasks/index.ts +0 -3
- package/src/server/telemetryIngest.ts +91 -0
- package/src/server/tools/index.ts +46 -0
- package/src/server/{tasks → tools/tier1}/protocolRunner.ts +52 -20
- package/src/server/{tasks → tools/tier1}/protocolTools.ts +6 -3
- package/src/server/tools/tier2/connectionTools.ts +75 -0
- package/src/server/tools/tier2/contextTools.ts +74 -0
- package/src/server/tools/tier2/discoveryTools.ts +34 -0
- package/src/server/tools/tier2/marketplaceTools.ts +25 -0
- package/src/server/{tasks → tools/tier2}/paymentTools.ts +74 -37
- package/src/server/ziggsconnect/ZiggsConnectClient.ts +126 -0
- package/src/server/ziggscontext/ZiggsContextClient.ts +137 -0
- package/src/server/ziggspay/ZiggsPayClient.ts +12 -12
- package/src/shared/types.ts +0 -2
- package/src/tools/index.ts +2 -0
- package/src/tools/recordReport.ts +82 -0
- package/src/types.ts +47 -8
- package/src/tasks/taskCore.ts +0 -139
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ It is **not** a generic chat wrapper: the core idea is a **lightweight state mac
|
|
|
13
13
|
| **`defineAgent` / workflow** | Declarative agent: `initial`, `states`, optional `id`, `description`, `specialization`, merged `tools` / `services`. |
|
|
14
14
|
| **`AgentMachine`** | Interprets the workflow: parked states wait for events; thinking states run `runTurn`; `transitions` picks the next state from context. |
|
|
15
15
|
| **`runTurn`** | Builds prompts, calls the LLM (with tools), parses the model output, and returns effects for the machine. |
|
|
16
|
-
| **`
|
|
16
|
+
| **`AgentHost`** | Batteries-included process: OpenAI adapter, tool manager, task service, context clients, **WebSocket** to Ziggs, and an **`Agent`** that dispatches incoming messages into the machine. |
|
|
17
17
|
| **`Agent`** | Orchestrates message handling, machine lifecycle, and integration with platform APIs (without requiring you to use WebSockets if you construct it yourself). |
|
|
18
18
|
|
|
19
19
|
There is **no** separate compile step: the workflow object is used **directly** by the runtime.
|
|
@@ -50,7 +50,7 @@ The `wait` action is built-in: `defineAgent` automatically injects it into every
|
|
|
50
50
|
## How you usually run an agent
|
|
51
51
|
|
|
52
52
|
1. **`defineAgent({ ... })`** → options object including `workflow`.
|
|
53
|
-
2. Pass **`openaiKey`**, **`operatorKey`** (Ziggs operator token), **`agentId`**, **`wsUrl`**, etc., and **`
|
|
53
|
+
2. Pass **`openaiKey`**, **`operatorKey`** (Ziggs operator token), **`agentId`**, **`wsUrl`**, etc., and **`createAgent(config)`** (returns an **`AgentHost`**).
|
|
54
54
|
3. **`connect()`** to open the WebSocket; the SDK routes platform messages into **`handleMessage`**.
|
|
55
55
|
|
|
56
56
|
Examples in the repo: `examples/agents/*.js` (e.g. coffee, expense, delivery agents).
|
|
@@ -59,12 +59,14 @@ Examples in the repo: `examples/agents/*.js` (e.g. coffee, expense, delivery age
|
|
|
59
59
|
|
|
60
60
|
## Main exports (entry: `src/index.js`)
|
|
61
61
|
|
|
62
|
-
- **`
|
|
62
|
+
- **`AgentHost`**, **`createAgent`**, **`createAgentPool`**, **`defineAgent`**
|
|
63
63
|
- **`Agent`**, **`AgentMachine`**, **`runTurn`**
|
|
64
64
|
- **Prompt / tools:** `PromptBuilder`, `ToolManager`, `defineTool`
|
|
65
|
-
- **
|
|
65
|
+
- **Tools come in two tiers — single home: `server/tools/`:**
|
|
66
|
+
- **Tier 1 — protocol grammar (`server/tools/tier1/`, `PROTOCOL_TOOLS`).** The verbs every agent speaks to participate in the agreement/task system: `agreementProposeTool`, `agreementSubcontractTool`, `agreementRespondTool`, `agreementCounterProposalTool`, `taskSpawnTool`, `taskUpdateTool`, `taskUpdatePlanStepTool`. **Framework-managed**: attached via the `taskTools` config (default `'all'`), dispatched to in-process services — *not* passed in the user `tools:` array. Publishing open work is part of this grammar: `agreement_propose({ proposedTo: "everyone" })` (there is no separate publish tool).
|
|
67
|
+
- **Tier 2 — capability bundles (`server/tools/tier2/`, opt-in, HTTP-backed).** Discrete capability domains an agent chooses to have. **Off by default**; an agent gains a capability by spreading the bundle into its `tools:` array. Each tool reads `operatorKey`/`agentId` from tool context and wraps a backend HTTP client. Bundles: `DISCOVERY_TOOLS` (`agentSearchTool`, `agentGetTool`), `MARKETPLACE_TOOLS` (`marketplaceViewTool` — read-only), `PAYMENT_TOOLS` (`paymentBalanceTool`, `paymentTransferTool`, …).
|
|
66
68
|
- **Adapters / utils:** `OpenAIAdapter`, JSON helpers, formatters
|
|
67
|
-
- **Re-exported** from `@ziggs-ai/api-client`: `WebSocketClient`, `
|
|
69
|
+
- **Re-exported** from `@ziggs-ai/api-client`: `WebSocketClient`, `ContextReadClient`, `ContextDiscoveryClient`, `ContextGrantsClient`, `ArtifactsClient`, `MessagesClient`, `ScopeClient`, `AgentSearchClient`, `getBackendUrl`, `getWebSocketUrl`, etc. (There is no `ContextReader` / `ContextWriter` — use the clients above from `@ziggs-ai/api-client` directly.)
|
|
68
70
|
|
|
69
71
|
Package export: `"."` → `src/index.js` (see `package.json`).
|
|
70
72
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/agent-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Agent framework SDK for building autonomous agents on the Ziggs platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"test:watch": "node --experimental-test-module-mocks --import tsx/esm --test --watch test/*.test.ts"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@ziggs-ai/api-client": "^0.1.
|
|
19
|
+
"@ziggs-ai/api-client": "^0.1.9",
|
|
20
20
|
"ajv": "^8.17.1",
|
|
21
21
|
"dotenv": "^17.2.3",
|
|
22
22
|
"openai": "^4.0.0"
|
package/src/AgentHost.ts
CHANGED
|
@@ -5,9 +5,21 @@ import { createZiggsEffectHandler } from './server/ZiggsEffectHandler.js';
|
|
|
5
5
|
import { normalizeIncomingEvent, type NormalizedEvent } from './ingress/normalizeIncoming.js';
|
|
6
6
|
import { PromptBuilder } from './runtime/PromptBuilder.js';
|
|
7
7
|
import { InMemoryStore, type MemoryStore } from './memory/MemoryStore.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Operator-provided FSM session persistence.
|
|
11
|
+
*
|
|
12
|
+
* AgentHost calls load() before each tick when a lane has no in-memory state
|
|
13
|
+
* (e.g. after a container restart) and save() after every successful tick.
|
|
14
|
+
* A null return from load() means "no prior state — start fresh."
|
|
15
|
+
*/
|
|
16
|
+
export interface SessionStore {
|
|
17
|
+
load(laneKey: string): Promise<{ ctx?: Ctx; state?: string } | null | undefined>;
|
|
18
|
+
save(laneKey: string, session: { ctx?: Ctx; state?: string }): Promise<void>;
|
|
19
|
+
}
|
|
8
20
|
import { ToolManager } from './tools/ToolManager.js';
|
|
9
21
|
import type { ToolDefinition } from './tools/defineTool.js';
|
|
10
|
-
import { PROTOCOL_TOOLS } from './server/
|
|
22
|
+
import { PROTOCOL_TOOLS } from './server/tools/tier1/protocolTools.js';
|
|
11
23
|
import { OpenAIAdapter } from './adapters/OpenAIAdapter.js';
|
|
12
24
|
import {
|
|
13
25
|
WebSocketClient,
|
|
@@ -19,6 +31,8 @@ import {
|
|
|
19
31
|
} from '@ziggs-ai/api-client';
|
|
20
32
|
import { TaskService } from './server/tasks/TaskService.js';
|
|
21
33
|
import { AgreementService } from './server/agreements/AgreementService.js';
|
|
34
|
+
import { runInboxCatchUp } from './server/InboxCatchUp.js';
|
|
35
|
+
import { SeenMessages } from './server/SeenMessages.js';
|
|
22
36
|
import { runtimeLog } from './shared/runtimeLog.js';
|
|
23
37
|
|
|
24
38
|
// ConnectionPool lives in a separate module; resolved via a computed-path
|
|
@@ -39,9 +53,13 @@ export type SessionResolver = (normalized: NormalizedEvent, ownAgentId: string)
|
|
|
39
53
|
|
|
40
54
|
const DEFAULT_SESSION_RESOLVER: SessionResolver = (normalized) => normalized.chatId;
|
|
41
55
|
|
|
56
|
+
interface LlmAdapter {
|
|
57
|
+
chatMessages(messages: unknown[], tools?: unknown[], options?: Record<string, unknown>): Promise<unknown>;
|
|
58
|
+
}
|
|
59
|
+
|
|
42
60
|
interface AgentServices {
|
|
43
61
|
tools?: ToolDefinition[];
|
|
44
|
-
llm?: { model?: string };
|
|
62
|
+
llm?: { model?: string } | LlmAdapter;
|
|
45
63
|
[key: string]: unknown;
|
|
46
64
|
}
|
|
47
65
|
|
|
@@ -70,6 +88,23 @@ export interface AgentHostOptions {
|
|
|
70
88
|
* pass `new FileMemoryStore(path)` or your own implementation.
|
|
71
89
|
*/
|
|
72
90
|
memory?: MemoryStore;
|
|
91
|
+
/**
|
|
92
|
+
* FSM session persistence. Operator-provided. When set, AgentHost loads
|
|
93
|
+
* prior state before each tick (cold-start recovery) and saves after every
|
|
94
|
+
* successful tick. Omit to keep state in-process only (default behaviour).
|
|
95
|
+
*/
|
|
96
|
+
sessionStore?: SessionStore;
|
|
97
|
+
/** Observability tap: called after every effect with the effect and its response. */
|
|
98
|
+
effectTap?: (effect: import('./types.js').Effect, response: unknown) => void;
|
|
99
|
+
/** Observability tap: called on every FSM state transition, no-match, depth-guard, or error-escape (ZIG-442). */
|
|
100
|
+
transitionTap?: (event: import('./runtime/AgentMachine.js').TransitionEvent) => void;
|
|
101
|
+
/** Production monitoring ingest (ZIG-360). Default on; set false to disable. */
|
|
102
|
+
telemetryIngest?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Pull inbox on every WS connect/reconnect (ZIG-454). Default true.
|
|
105
|
+
* Recovers messages lost when backend in-memory pending queues are dropped.
|
|
106
|
+
*/
|
|
107
|
+
inboxCatchUp?: boolean;
|
|
73
108
|
}
|
|
74
109
|
|
|
75
110
|
function createToolManager(options: AgentHostOptions): ToolManager {
|
|
@@ -83,9 +118,15 @@ function createToolManager(options: AgentHostOptions): ToolManager {
|
|
|
83
118
|
return toolManager;
|
|
84
119
|
}
|
|
85
120
|
|
|
121
|
+
function isLlmAdapter(v: unknown): v is LlmAdapter {
|
|
122
|
+
return typeof v === 'object' && v !== null && typeof (v as LlmAdapter).chatMessages === 'function';
|
|
123
|
+
}
|
|
124
|
+
|
|
86
125
|
function createBackendServices(options: AgentHostOptions) {
|
|
87
126
|
const toolManager = createToolManager(options);
|
|
88
|
-
const llm =
|
|
127
|
+
const llm = isLlmAdapter(options.services?.llm)
|
|
128
|
+
? options.services!.llm as LlmAdapter
|
|
129
|
+
: new OpenAIAdapter({ key: options.openaiKey, model: (options.services?.llm as { model?: string } | undefined)?.model || options.model });
|
|
89
130
|
const operatorKey = options.operatorKey!;
|
|
90
131
|
const agentId = options.agentId!;
|
|
91
132
|
const messagesClient = new MessagesClient(operatorKey, agentId);
|
|
@@ -125,7 +166,7 @@ interface LaneSession {
|
|
|
125
166
|
export class AgentHost {
|
|
126
167
|
options: Required<Pick<AgentHostOptions, 'agentId' | 'operatorKey' | 'openaiKey'>> & AgentHostOptions;
|
|
127
168
|
toolManager!: ToolManager;
|
|
128
|
-
llm!: OpenAIAdapter; // retained for external inspection
|
|
169
|
+
llm!: OpenAIAdapter | LlmAdapter; // retained for external inspection
|
|
129
170
|
agent!: Agent;
|
|
130
171
|
wsClient!: WebSocketClient;
|
|
131
172
|
private eff!: EffectHandler;
|
|
@@ -134,11 +175,18 @@ export class AgentHost {
|
|
|
134
175
|
private sessionResolver: SessionResolver;
|
|
135
176
|
private tickTimeoutMs: number;
|
|
136
177
|
private memory: MemoryStore;
|
|
178
|
+
private sessionStore: SessionStore | null;
|
|
179
|
+
private taskService!: TaskService;
|
|
180
|
+
private agreementService!: AgreementService;
|
|
181
|
+
private messageSender!: (message: string, receiverId: string, sessionId: string) => Promise<unknown>;
|
|
182
|
+
private seenMessages = new SeenMessages();
|
|
183
|
+
private catchUpInFlight: Promise<void> | null = null;
|
|
184
|
+
private inboxCatchUpEnabled: boolean;
|
|
137
185
|
|
|
138
186
|
constructor(options: AgentHostOptions = {}) {
|
|
139
187
|
this.options = {
|
|
140
188
|
openaiKey: options.openaiKey || process.env.OPENAI_API_KEY,
|
|
141
|
-
model: options.model || options.services?.llm?.model || 'gpt-5.4',
|
|
189
|
+
model: options.model || (!isLlmAdapter(options.services?.llm) ? options.services?.llm?.model : undefined) || 'gpt-5.4',
|
|
142
190
|
operatorKey: options.operatorKey || process.env.ZIGGS_OPERATOR_KEY || undefined,
|
|
143
191
|
agentId: options.agentId || '',
|
|
144
192
|
name: options.name || null,
|
|
@@ -153,10 +201,15 @@ export class AgentHost {
|
|
|
153
201
|
workflow: options.workflow,
|
|
154
202
|
tickTimeoutMs: options.tickTimeoutMs,
|
|
155
203
|
sessionResolver: options.sessionResolver,
|
|
204
|
+
effectTap: options.effectTap,
|
|
205
|
+
transitionTap: options.transitionTap,
|
|
206
|
+
inboxCatchUp: options.inboxCatchUp,
|
|
156
207
|
} as Required<Pick<AgentHostOptions, 'agentId' | 'operatorKey' | 'openaiKey'>> & AgentHostOptions;
|
|
208
|
+
this.inboxCatchUpEnabled = options.inboxCatchUp !== false;
|
|
157
209
|
this.sessionResolver = options.sessionResolver || DEFAULT_SESSION_RESOLVER;
|
|
158
210
|
this.tickTimeoutMs = options.tickTimeoutMs ?? 120_000;
|
|
159
211
|
this.memory = options.memory ?? new InMemoryStore();
|
|
212
|
+
this.sessionStore = options.sessionStore ?? null;
|
|
160
213
|
this._validate();
|
|
161
214
|
this._setup();
|
|
162
215
|
}
|
|
@@ -179,10 +232,16 @@ export class AgentHost {
|
|
|
179
232
|
label: this.options.name || 'agent',
|
|
180
233
|
});
|
|
181
234
|
|
|
182
|
-
const messageSender = (message: string, receiverId: string, sessionId: string): Promise<unknown> => {
|
|
183
|
-
this.wsClient.send(sessionId, receiverId, message);
|
|
235
|
+
const messageSender = (message: string, receiverId: string, sessionId: string, messageId?: string): Promise<unknown> => {
|
|
236
|
+
this.wsClient.send(sessionId, receiverId, message, messageId ? { messageId } : undefined);
|
|
184
237
|
return Promise.resolve(undefined);
|
|
185
238
|
};
|
|
239
|
+
const chunkSender = (sessionId: string, receiverId: string, text: string, messageId: string): void => {
|
|
240
|
+
this.wsClient.send(sessionId, receiverId, text, { partial: true, messageId });
|
|
241
|
+
};
|
|
242
|
+
this.taskService = services.taskService;
|
|
243
|
+
this.agreementService = services.agreementService;
|
|
244
|
+
this.messageSender = messageSender;
|
|
186
245
|
|
|
187
246
|
let workflow = this.options.workflow;
|
|
188
247
|
if (!workflow && this.options.states) {
|
|
@@ -203,10 +262,13 @@ export class AgentHost {
|
|
|
203
262
|
taskService: services.taskService,
|
|
204
263
|
agreementService: services.agreementService,
|
|
205
264
|
messageSender,
|
|
265
|
+
chunkSender,
|
|
206
266
|
toolManager: services.toolManager,
|
|
207
267
|
operatorKey: this.options.operatorKey!,
|
|
208
268
|
agentId: this.options.agentId!,
|
|
209
269
|
telemetryClient: services.telemetryClient,
|
|
270
|
+
telemetryIngest: this.options.telemetryIngest,
|
|
271
|
+
effectTap: this.options.effectTap,
|
|
210
272
|
});
|
|
211
273
|
|
|
212
274
|
this.agent = new Agent({
|
|
@@ -219,6 +281,47 @@ export class AgentHost {
|
|
|
219
281
|
|
|
220
282
|
this.wsClient.setMessageHandler((text: string, metadata: MessageMetadata) => this.handleMessage(text, metadata as unknown as Record<string, unknown>));
|
|
221
283
|
this.wsClient.setResourceEventHandler((event) => this.handleResourceEvent(event as unknown as Record<string, unknown>));
|
|
284
|
+
|
|
285
|
+
if (this.inboxCatchUpEnabled) {
|
|
286
|
+
this.wsClient.onConnect(() => {
|
|
287
|
+
void this._ensureCatchUp();
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Coalesce concurrent connect/reconnect bursts into one catch-up pass.
|
|
294
|
+
*/
|
|
295
|
+
private _ensureCatchUp(): Promise<void> {
|
|
296
|
+
if (!this.catchUpInFlight) {
|
|
297
|
+
this.catchUpInFlight = this._runInboxCatchUp().finally(() => {
|
|
298
|
+
this.catchUpInFlight = null;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return this.catchUpInFlight;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private async _runInboxCatchUp(): Promise<void> {
|
|
305
|
+
const label = this.options.name || 'agent';
|
|
306
|
+
try {
|
|
307
|
+
await runInboxCatchUp({
|
|
308
|
+
creds: {
|
|
309
|
+
operatorKey: this.options.operatorKey!,
|
|
310
|
+
agentId: this.options.agentId!,
|
|
311
|
+
},
|
|
312
|
+
ownAgentId: this.options.agentId!,
|
|
313
|
+
baseUrl: this.options.wsUrl ? undefined : undefined,
|
|
314
|
+
seen: this.seenMessages,
|
|
315
|
+
label,
|
|
316
|
+
onMessage: (text, metadata) => this.handleMessage(text, metadata),
|
|
317
|
+
onResourceEvent: (event) => this.handleResourceEvent(event),
|
|
318
|
+
});
|
|
319
|
+
} catch (err: unknown) {
|
|
320
|
+
runtimeLog.warn(
|
|
321
|
+
label,
|
|
322
|
+
`inbox catch-up failed: ${(err as Error)?.message ?? err}`,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
222
325
|
}
|
|
223
326
|
|
|
224
327
|
/**
|
|
@@ -259,21 +362,38 @@ export class AgentHost {
|
|
|
259
362
|
|
|
260
363
|
/** Wire entry. Normalize → resolve session → enqueue on the right lane. */
|
|
261
364
|
async handleMessage(text: string, metadata: Record<string, unknown> = {}): Promise<void> {
|
|
365
|
+
const messageId = metadata.messageId as string | undefined;
|
|
366
|
+
if (messageId && this.seenMessages.has(messageId)) {
|
|
367
|
+
runtimeLog.debug('AgentHost', `skip duplicate messageId=${messageId}`);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (messageId) this.seenMessages.mark(messageId);
|
|
371
|
+
|
|
262
372
|
const ownAgentId = this._resolveOwnAgentId(metadata);
|
|
263
373
|
if (!ownAgentId) {
|
|
264
374
|
runtimeLog.warn('AgentHost', 'handleMessage: ownAgentId missing — dropping event');
|
|
265
375
|
return;
|
|
266
376
|
}
|
|
267
377
|
|
|
268
|
-
|
|
378
|
+
let enrichedText = typeof text === 'string' ? text : '';
|
|
379
|
+
if (!enrichedText.trim()) {
|
|
380
|
+
const agreement = metadata.agreement as Record<string, unknown> | undefined;
|
|
381
|
+
const terms = agreement?.terms as { description?: string } | undefined;
|
|
382
|
+
if (terms?.description) {
|
|
383
|
+
enrichedText = String(terms.description);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const normalized = normalizeIncomingEvent({ text: enrichedText, metadata, ownAgentId });
|
|
269
388
|
if (!normalized.shouldProcess) {
|
|
270
|
-
|
|
389
|
+
const receiverId = (metadata.receiver as Record<string, unknown> | undefined)?.id ?? metadata.receiverId;
|
|
390
|
+
runtimeLog.warn('AgentHost', `skip reason=${normalized.reason || 'not_relevant'} ownAgentId=${ownAgentId} receiverId=${receiverId ?? 'null'} chatId=${normalized.chatId ?? 'null'}`);
|
|
271
391
|
return;
|
|
272
392
|
}
|
|
273
393
|
|
|
274
394
|
const sessionId = this.sessionResolver(normalized, ownAgentId);
|
|
275
395
|
if (!sessionId) {
|
|
276
|
-
runtimeLog.warn('AgentHost',
|
|
396
|
+
runtimeLog.warn('AgentHost', `session resolver returned null ownAgentId=${ownAgentId} chatId=${normalized.chatId ?? 'null'}`);
|
|
277
397
|
return;
|
|
278
398
|
}
|
|
279
399
|
|
|
@@ -288,6 +408,20 @@ export class AgentHost {
|
|
|
288
408
|
const session = this.sessions.get(laneKey);
|
|
289
409
|
if (!session?.sessionId) return;
|
|
290
410
|
|
|
411
|
+
// Cold-start recovery: restore FSM state from sessionStore when the
|
|
412
|
+
// in-memory lane has no prior context (e.g. after a container restart).
|
|
413
|
+
if (this.sessionStore && session.ctx === undefined && session.state === undefined) {
|
|
414
|
+
try {
|
|
415
|
+
const persisted = await this.sessionStore.load(laneKey);
|
|
416
|
+
if (persisted) {
|
|
417
|
+
session.ctx = persisted.ctx;
|
|
418
|
+
session.state = persisted.state;
|
|
419
|
+
}
|
|
420
|
+
} catch (err: unknown) {
|
|
421
|
+
runtimeLog.warn('AgentHost', `sessionStore.load failed laneKey=${laneKey}: ${(err as Error)?.message || err}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
291
425
|
const identity: Identity = {
|
|
292
426
|
agentId: session.ownAgentId || this.options.agentId,
|
|
293
427
|
sessionId: session.sessionId,
|
|
@@ -296,7 +430,7 @@ export class AgentHost {
|
|
|
296
430
|
|
|
297
431
|
try {
|
|
298
432
|
const result = await Promise.race([
|
|
299
|
-
this.agent.tick({ event, ctx: session.ctx, state: session.state, identity, eff: this.eff, memory: this.memory }),
|
|
433
|
+
this.agent.tick({ event, ctx: session.ctx, state: session.state, identity, eff: this.eff, memory: this.memory, transitionTap: this.options.transitionTap }),
|
|
300
434
|
new Promise<never>((_, reject) =>
|
|
301
435
|
setTimeout(() => reject(new Error('tick timeout')), this.tickTimeoutMs),
|
|
302
436
|
),
|
|
@@ -304,6 +438,12 @@ export class AgentHost {
|
|
|
304
438
|
session.ctx = result.ctx;
|
|
305
439
|
session.state = result.state;
|
|
306
440
|
this.sessions.set(laneKey, session);
|
|
441
|
+
|
|
442
|
+
if (this.sessionStore) {
|
|
443
|
+
this.sessionStore.save(laneKey, { ctx: session.ctx, state: session.state }).catch((err: unknown) => {
|
|
444
|
+
runtimeLog.warn('AgentHost', `sessionStore.save failed laneKey=${laneKey}: ${(err as Error)?.message || err}`);
|
|
445
|
+
});
|
|
446
|
+
}
|
|
307
447
|
} catch (err: unknown) {
|
|
308
448
|
runtimeLog.warn('AgentHost', `tick failed laneKey=${laneKey}: ${(err as Error)?.message || err}`);
|
|
309
449
|
}
|
|
@@ -315,8 +455,21 @@ export class AgentHost {
|
|
|
315
455
|
return this.options.agentId || (self?.id as string) || (agent?.id as string) || (metadata.ziggsAgentId as string) || null;
|
|
316
456
|
}
|
|
317
457
|
|
|
318
|
-
connectAsync(timeout?: number) {
|
|
458
|
+
async connectAsync(timeout?: number) {
|
|
459
|
+
await this.wsClient.connectAsync(timeout);
|
|
460
|
+
if (this.inboxCatchUpEnabled) {
|
|
461
|
+
await this._ensureCatchUp();
|
|
462
|
+
// Backend flushPendingMessages can emit after auth; re-poll once so inbox
|
|
463
|
+
// ack catches stragglers and we don't leave newMessages > 0 (ZIG-462).
|
|
464
|
+
await new Promise((r) => setTimeout(r, 750));
|
|
465
|
+
this.catchUpInFlight = null;
|
|
466
|
+
await this._ensureCatchUp();
|
|
467
|
+
}
|
|
468
|
+
return this;
|
|
469
|
+
}
|
|
319
470
|
disconnect() { this.wsClient.disconnect(); }
|
|
471
|
+
/** Waits for all in-flight ticks to complete. Call before process.exit() on SIGTERM. */
|
|
472
|
+
drain(timeoutMs = 30_000): Promise<void> { return this.eventQueue.waitForIdle(timeoutMs); }
|
|
320
473
|
registerTool(tool: ToolDefinition) { return this.toolManager.register(tool); }
|
|
321
474
|
isConnected() { return this.wsClient.isConnected(); }
|
|
322
475
|
}
|
|
@@ -122,4 +122,25 @@ export class OpenAIAdapter {
|
|
|
122
122
|
throw error;
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
+
|
|
126
|
+
async chatMessagesStream(
|
|
127
|
+
messages: OpenAI.Chat.ChatCompletionMessageParam[],
|
|
128
|
+
onChunk: (text: string) => void,
|
|
129
|
+
): Promise<string> {
|
|
130
|
+
const stream = await this.client.chat.completions.create({
|
|
131
|
+
model: this.model,
|
|
132
|
+
messages,
|
|
133
|
+
stream: true,
|
|
134
|
+
max_completion_tokens: 3000,
|
|
135
|
+
});
|
|
136
|
+
let text = '';
|
|
137
|
+
for await (const chunk of stream) {
|
|
138
|
+
const delta = chunk.choices[0]?.delta?.content;
|
|
139
|
+
if (delta) {
|
|
140
|
+
text += delta;
|
|
141
|
+
onChunk(delta);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return text;
|
|
145
|
+
}
|
|
125
146
|
}
|
package/src/agent/Agent.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Ctx, Identity, Workflow, EffectHandler } from '../types.js';
|
|
2
|
-
import { AgentMachine } from '../runtime/AgentMachine.js';
|
|
2
|
+
import { AgentMachine, type TransitionEvent } from '../runtime/AgentMachine.js';
|
|
3
3
|
import { runTurn } from '../runtime/runTurn.js';
|
|
4
4
|
import { PromptBuilder } from '../runtime/PromptBuilder.js';
|
|
5
5
|
import type { MemoryStore } from '../memory/MemoryStore.js';
|
|
@@ -29,6 +29,8 @@ export interface TickInput {
|
|
|
29
29
|
* choice — `counterparty:<id>`, `agreement:<id>:notes`, etc.
|
|
30
30
|
*/
|
|
31
31
|
memory?: MemoryStore;
|
|
32
|
+
/** Optional tap for FSM transition events (ZIG-442). */
|
|
33
|
+
transitionTap?: (event: TransitionEvent) => void;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
export interface TickOutput {
|
|
@@ -70,7 +72,7 @@ export class Agent {
|
|
|
70
72
|
this.promptBuilder = promptBuilder || new PromptBuilder();
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
async tick({ event, ctx, state, identity, eff, memory }: TickInput): Promise<TickOutput> {
|
|
75
|
+
async tick({ event, ctx, state, identity, eff, memory, transitionTap }: TickInput): Promise<TickOutput> {
|
|
74
76
|
if (!identity?.sessionId) throw new Error('Agent.tick: identity.sessionId is required');
|
|
75
77
|
if (!eff) throw new Error('Agent.tick: eff is required');
|
|
76
78
|
|
|
@@ -82,6 +84,7 @@ export class Agent {
|
|
|
82
84
|
promptBuilder: this.promptBuilder,
|
|
83
85
|
memory,
|
|
84
86
|
input: { identity, ctx, state },
|
|
87
|
+
transitionTap,
|
|
85
88
|
});
|
|
86
89
|
|
|
87
90
|
// `send` awaits the recursive _step until the machine settles at a
|
|
@@ -40,7 +40,7 @@ function validateAgreements(agreements: unknown, warn: WarnFn): void {
|
|
|
40
40
|
}
|
|
41
41
|
if (ag['parties'] && typeof ag['parties'] === 'object') {
|
|
42
42
|
const parties = ag['parties'] as Record<string, unknown>;
|
|
43
|
-
for (const key of ['
|
|
43
|
+
for (const key of ['creator', 'provider']) {
|
|
44
44
|
if (!parties[key]) warn(`${path}.parties.${key}`, 'missing');
|
|
45
45
|
}
|
|
46
46
|
}
|
package/src/context/batch.ts
CHANGED
|
@@ -10,8 +10,8 @@ export function isTaskResultRelevantToAgent(result: Record<string, unknown>, own
|
|
|
10
10
|
const parties = (result['agreement'] as Record<string, unknown> | undefined)?.['parties'] as Record<string, unknown> | undefined ?? {};
|
|
11
11
|
return !!(
|
|
12
12
|
result['creatorIsYou'] || result['providerIsYou'] || result['payerIsYou'] || result['proposedToIsYou'] ||
|
|
13
|
-
parties['
|
|
14
|
-
parties['
|
|
15
|
-
parties['
|
|
13
|
+
parties['creator'] === ownAgentId || parties['provider'] === ownAgentId ||
|
|
14
|
+
parties['payer'] === ownAgentId || parties['proposedTo'] === ownAgentId ||
|
|
15
|
+
parties['proposedTo'] === 'everyone'
|
|
16
16
|
);
|
|
17
17
|
}
|
|
@@ -3,7 +3,7 @@ import { getBatchEvents, isTaskResultRelevantToAgent } from './batch.js';
|
|
|
3
3
|
|
|
4
4
|
function isTaskAssignment(result: Record<string, unknown>, ownAgentId: string | null): boolean {
|
|
5
5
|
const parties = (result['agreement'] as Record<string, unknown> | undefined)?.['parties'] as Record<string, unknown> | undefined ?? {};
|
|
6
|
-
const isProvider = result['providerIsYou'] || parties['
|
|
6
|
+
const isProvider = result['providerIsYou'] || parties['provider'] === ownAgentId;
|
|
7
7
|
return !!(isProvider && (result['state'] === 'active' || result['state'] === 'in-progress'));
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -24,7 +24,7 @@ export function classifyIncomingEvent(rawEvent: unknown, ownAgentId: string | nu
|
|
|
24
24
|
const parties = (ag?.['parties'] as Record<string, unknown>) || {};
|
|
25
25
|
const proposalStatus = (ag?.['proposal'] as Record<string, unknown> | undefined)?.['status'];
|
|
26
26
|
|
|
27
|
-
if (state === 'active' && proposalStatus === 'approved' && ownAgentId && parties['
|
|
27
|
+
if (state === 'active' && proposalStatus === 'approved' && ownAgentId && parties['creator'] === ownAgentId) {
|
|
28
28
|
flags['approval'] = true; flags['taskAssignment'] = result; continue;
|
|
29
29
|
}
|
|
30
30
|
if (isTaskAssignment(result, ownAgentId)) { flags['taskAssignment'] = result; flags['approval'] = true; continue; }
|
|
@@ -24,7 +24,7 @@ export function classifyWorkflowEvent(event: unknown, ownAgentId: string | null
|
|
|
24
24
|
const ag = result['agreement'] as Record<string, unknown> | undefined;
|
|
25
25
|
const parties = (ag?.['parties'] as Record<string, unknown>) || {};
|
|
26
26
|
const proposalStatus = ag?.['proposal'] ? (ag['proposal'] as Record<string, unknown>)['status'] : undefined;
|
|
27
|
-
const isProvider = result['providerIsYou'] || parties['
|
|
27
|
+
const isProvider = result['providerIsYou'] || parties['provider'] === ownAgentId;
|
|
28
28
|
if (isProvider && (state === 'active' || state === 'in-progress')) return 'task_assignment';
|
|
29
29
|
if (proposalStatus === 'approved' || (state === 'active' && proposalStatus === 'pending')) return 'task_approved';
|
|
30
30
|
if (proposalStatus === 'rejected' || state === 'cancelled') return 'task_rejected';
|
|
@@ -32,11 +32,11 @@ export class AgreementFormatter {
|
|
|
32
32
|
const parties = (ag['parties'] as AnyObj) || {};
|
|
33
33
|
const lines = [
|
|
34
34
|
`${indent}[${status}] Agreement ${agId}`,
|
|
35
|
-
`${indent} Creator: ${this._labelParty(parties['
|
|
36
|
-
`${indent} Provider: ${this._labelParty(parties['
|
|
37
|
-
`${indent} Payer: ${this._labelParty(parties['
|
|
35
|
+
`${indent} Creator: ${this._labelParty(parties['creator'] as string, ag['creatorIsYou'] as boolean)}`,
|
|
36
|
+
`${indent} Provider: ${this._labelParty(parties['provider'] as string, ag['providerIsYou'] as boolean)}`,
|
|
37
|
+
`${indent} Payer: ${this._labelParty(parties['payer'] as string, ag['payerIsYou'] as boolean)}`,
|
|
38
38
|
];
|
|
39
|
-
if (parties['
|
|
39
|
+
if (parties['proposedTo']) lines.push(`${indent} Proposed to: ${this._labelParty(parties['proposedTo'] as string, ag['proposedToIsYou'] as boolean)}`);
|
|
40
40
|
const proposal = ag['proposal'] as AnyObj | undefined;
|
|
41
41
|
if (proposal?.['status']) lines.push(`${indent} Proposal: ${proposal['status']}${proposal['respondedBy'] ? ` (by ${proposal['respondedBy']})` : ''}`);
|
|
42
42
|
const money = ag['money'] as AnyObj | undefined;
|
|
@@ -86,7 +86,7 @@ export class AgreementFormatter {
|
|
|
86
86
|
for (const st of subtasks) {
|
|
87
87
|
const sstate = ((st['state'] as string) || 'unknown').toUpperCase();
|
|
88
88
|
const sid = this.shortId(st['taskId'] as string);
|
|
89
|
-
lines.push(`${indent} [${sstate}] ${st['description'] || sid} (${sid}, provider: ${st['providerIsYou'] ? 'You' : (st['
|
|
89
|
+
lines.push(`${indent} [${sstate}] ${st['description'] || sid} (${sid}, provider: ${st['providerIsYou'] ? 'You' : (st['provider'] ?? 'unassigned')})`);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
return lines.join('\n');
|
|
@@ -72,7 +72,7 @@ export class HistoryFormatter {
|
|
|
72
72
|
case 'created': {
|
|
73
73
|
const ag = task['agreement'] as AnyObj | undefined;
|
|
74
74
|
const parties = (ag?.['parties'] as AnyObj) || {};
|
|
75
|
-
return `${timePrefix}Task created under agreement: "${desc}" (${ids})\n Owner: ${this.formatOwner(task, agentId)}\n Executor: ${this.formatExecutor(task, agentId)}\n Proposed to: ${parties['
|
|
75
|
+
return `${timePrefix}Task created under agreement: "${desc}" (${ids})\n Owner: ${this.formatOwner(task, agentId)}\n Executor: ${this.formatExecutor(task, agentId)}\n Proposed to: ${parties['proposedTo'] || 'N/A'}`;
|
|
76
76
|
}
|
|
77
77
|
case 'state_changed': {
|
|
78
78
|
const prev = (service?.['previousState'] as AnyObj | undefined)?.['state'] || '?';
|
|
@@ -124,13 +124,13 @@ export class HistoryFormatter {
|
|
|
124
124
|
|
|
125
125
|
formatOwner(task: AnyObj, agentId: string): string {
|
|
126
126
|
const ag = task['agreement'] as AnyObj | undefined;
|
|
127
|
-
const creatorId = (ag?.['parties'] as AnyObj | undefined)?.['
|
|
127
|
+
const creatorId = (ag?.['parties'] as AnyObj | undefined)?.['creator'] ?? task['agentId'];
|
|
128
128
|
return creatorId === agentId ? 'You' : String(creatorId ?? 'N/A');
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
formatExecutor(task: AnyObj, agentId: string): string {
|
|
132
132
|
const ag = task['agreement'] as AnyObj | undefined;
|
|
133
|
-
const providerId = (ag?.['parties'] as AnyObj | undefined)?.['
|
|
133
|
+
const providerId = (ag?.['parties'] as AnyObj | undefined)?.['provider'] ?? task['executorId'];
|
|
134
134
|
return providerId === agentId ? 'You' : String(providerId ?? 'N/A');
|
|
135
135
|
}
|
|
136
136
|
|
package/src/index.ts
CHANGED
|
@@ -20,7 +20,7 @@ export { OutboxBuffer } from './server/OutboxBuffer.js';
|
|
|
20
20
|
export { normalizeIncomingEvent } from './ingress/normalizeIncoming.js';
|
|
21
21
|
|
|
22
22
|
// Runtime primitives
|
|
23
|
-
export { AgentMachine } from './runtime/AgentMachine.js';
|
|
23
|
+
export { AgentMachine, type TransitionEvent } from './runtime/AgentMachine.js';
|
|
24
24
|
export { runTurn } from './runtime/runTurn.js';
|
|
25
25
|
export { PromptBuilder } from './runtime/PromptBuilder.js';
|
|
26
26
|
export { thinkingDefaults, defineThinkingState, DEFAULT_WAIT_PROMPT } from './runtime/defaults.js';
|
|
@@ -54,21 +54,38 @@ export { AgreementService } from './server/agreements/AgreementService.js';
|
|
|
54
54
|
export { ToolManager } from './tools/ToolManager.js';
|
|
55
55
|
export { ToolProvider } from './tools/ToolProvider.js';
|
|
56
56
|
export { defineTool } from './tools/defineTool.js';
|
|
57
|
+
export { recordReport } from './tools/recordReport.js';
|
|
58
|
+
export type { RecordReportInput, RecordReportResult, TaskReport } from './tools/recordReport.js';
|
|
57
59
|
export { OpenAIAdapter } from './adapters/OpenAIAdapter.js';
|
|
58
60
|
export { extractJSON, safeParseJSON } from './utils/jsonExtractor.js';
|
|
59
61
|
export { HistoryFormatter, historyFormatter, AgreementFormatter, agreementFormatter } from './formatters/index.js';
|
|
60
62
|
export { ProactiveTrigger } from './server/proactive/ProactiveTrigger.js';
|
|
61
63
|
export { ZiggsPayClient, createZiggsPayClient } from './server/ziggspay/ZiggsPayClient.js';
|
|
64
|
+
export { ZiggsConnectClient, createZiggsConnectClient } from './server/ziggsconnect/ZiggsConnectClient.js';
|
|
65
|
+
export { ZiggsContextClient } from './server/ziggscontext/ZiggsContextClient.js';
|
|
66
|
+
export type {
|
|
67
|
+
ZiggsContextClientOptions,
|
|
68
|
+
ContextReachDescriptor,
|
|
69
|
+
DelegateContextGrantParams,
|
|
70
|
+
} from './server/ziggscontext/ZiggsContextClient.js';
|
|
71
|
+
// All agent tool bundles live under server/tools/ (tier1 = protocol grammar,
|
|
72
|
+
// tier2 = opt-in HTTP capability bundles). Single home: server/tools/index.ts.
|
|
62
73
|
export {
|
|
74
|
+
// Tier 1 — protocol grammar (framework-managed via taskTools).
|
|
63
75
|
PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool,
|
|
64
76
|
agreementRespondTool, agreementCounterProposalTool, agreementCheckProposalTool,
|
|
65
77
|
taskSpawnTool, taskUpdateTool, taskUpdatePlanStepTool,
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
dispatchProtocolOp,
|
|
79
|
+
// Tier 2 — opt-in capability bundles (spread into `tools:`).
|
|
80
|
+
PAYMENT_TOOLS, paymentBalanceTool, paymentTransferTool, paymentHoldTool, paymentReleaseTool, paymentResolveWalletTool,
|
|
81
|
+
CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool,
|
|
82
|
+
DISCOVERY_TOOLS, agentSearchTool, agentGetTool,
|
|
83
|
+
MARKETPLACE_TOOLS, marketplaceViewTool,
|
|
84
|
+
} from './server/tools/index.js';
|
|
68
85
|
export { PROTOCOL_TOOL_NAMES, PROTOCOL_TOOL_TO_OPERATION, mapProtocolToolToOperation, isProtocolToolName } from './tasks/protocolRegistry.js';
|
|
69
|
-
export { dispatchProtocolOp } from './server/tasks/protocolRunner.js';
|
|
70
86
|
export { InMemoryStore, FileMemoryStore } from './memory/MemoryStore.js';
|
|
71
87
|
export type { MemoryStore } from './memory/MemoryStore.js';
|
|
88
|
+
export type { SessionStore } from './AgentHost.js';
|
|
72
89
|
export { classifyIncomingEvent } from './context/classifyEnvelope.js';
|
|
73
90
|
export { buildContextUpdates, CONTEXT_RESET } from './context/applyEffects.js';
|
|
74
91
|
export { getBatchEvents, isTaskResultRelevantToAgent } from './context/batch.js';
|
|
@@ -83,4 +100,8 @@ export {
|
|
|
83
100
|
getWebSocketUrl,
|
|
84
101
|
} from '@ziggs-ai/api-client';
|
|
85
102
|
|
|
103
|
+
export { runInboxCatchUp } from './server/InboxCatchUp.js';
|
|
104
|
+
export type { InboxCatchUpDeps, InboxCatchUpResult } from './server/InboxCatchUp.js';
|
|
105
|
+
export { SeenMessages } from './server/SeenMessages.js';
|
|
86
106
|
export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
|
|
107
|
+
export type { RuntimeLogLevel } from './shared/runtimeLog.js';
|