@ziggs-ai/agent-sdk 0.1.11 → 0.2.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.
Files changed (211) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +41 -2
  3. package/dist/AgentHost.d.ts +201 -0
  4. package/dist/AgentHost.js +652 -0
  5. package/dist/adapters/AnthropicAdapter.d.ts +39 -0
  6. package/dist/adapters/AnthropicAdapter.js +200 -0
  7. package/dist/adapters/McpAdapter.d.ts +199 -0
  8. package/dist/adapters/McpAdapter.js +468 -0
  9. package/dist/adapters/OpenAIAdapter.d.ts +26 -0
  10. package/dist/adapters/OpenAIAdapter.js +158 -0
  11. package/dist/adapters/index.js +2 -0
  12. package/dist/agent/Agent.d.ts +78 -0
  13. package/dist/agent/Agent.js +58 -0
  14. package/dist/agent/ClaudeSdkAgent.d.ts +38 -0
  15. package/dist/agent/ClaudeSdkAgent.js +322 -0
  16. package/dist/agent/claudeCognition.d.ts +67 -0
  17. package/dist/agent/claudeCognition.js +103 -0
  18. package/dist/agent/claudeSdkProtocol.d.ts +11 -0
  19. package/dist/agent/claudeSdkProtocol.js +27 -0
  20. package/dist/agent/claudeSdkTools.d.ts +19 -0
  21. package/dist/agent/claudeSdkTools.js +57 -0
  22. package/dist/agent/hostedAgentConnection.d.ts +15 -0
  23. package/dist/agent/hostedAgentConnection.js +29 -0
  24. package/dist/agent/toolManagerToSdkMcp.d.ts +12 -0
  25. package/dist/agent/toolManagerToSdkMcp.js +123 -0
  26. package/dist/brain/index.d.ts +50 -0
  27. package/dist/brain/index.js +45 -0
  28. package/dist/cognition/validateContext.d.ts +11 -0
  29. package/dist/cognition/validateContext.js +117 -0
  30. package/dist/context/applyEffects.d.ts +2 -0
  31. package/dist/context/applyEffects.js +89 -0
  32. package/dist/context/batch.d.ts +2 -0
  33. package/dist/context/batch.js +18 -0
  34. package/dist/context/classifyEnvelope.d.ts +1 -0
  35. package/dist/context/classifyEnvelope.js +58 -0
  36. package/dist/context/routingLabels.d.ts +4 -0
  37. package/dist/context/routingLabels.js +48 -0
  38. package/dist/defineAgent.d.ts +14 -0
  39. package/dist/defineAgent.js +68 -0
  40. package/dist/formatters/AgreementFormatter.d.ts +18 -0
  41. package/dist/formatters/AgreementFormatter.js +130 -0
  42. package/dist/formatters/HistoryFormatter.d.ts +26 -0
  43. package/dist/formatters/HistoryFormatter.js +173 -0
  44. package/dist/formatters/index.js +2 -0
  45. package/{src/index.ts → dist/index.d.ts} +13 -53
  46. package/dist/index.js +66 -0
  47. package/dist/ingress/normalizeIncoming.d.ts +65 -0
  48. package/dist/ingress/normalizeIncoming.js +88 -0
  49. package/dist/memory/MemoryStore.d.ts +55 -0
  50. package/dist/memory/MemoryStore.js +78 -0
  51. package/dist/pricing/fleetDefaults.d.ts +46 -0
  52. package/dist/pricing/fleetDefaults.js +168 -0
  53. package/dist/pricing/fleetEvalFree.d.ts +7 -0
  54. package/dist/pricing/fleetEvalFree.js +24 -0
  55. package/dist/pricing/fleetFreeTierA.gen.d.ts +6 -0
  56. package/{src/pricing/fleetFreeTierA.gen.ts → dist/pricing/fleetFreeTierA.gen.js} +5 -6
  57. package/dist/pricing/fleetTierByAgentId.gen.d.ts +7 -0
  58. package/dist/pricing/fleetTierByAgentId.gen.js +768 -0
  59. package/dist/runtime/AgentMachine.d.ts +143 -0
  60. package/dist/runtime/AgentMachine.js +400 -0
  61. package/dist/runtime/PromptBuilder.d.ts +96 -0
  62. package/dist/runtime/PromptBuilder.js +439 -0
  63. package/dist/runtime/bootstrapCtx.d.ts +18 -0
  64. package/dist/runtime/bootstrapCtx.js +37 -0
  65. package/dist/runtime/buildOutcome.d.ts +68 -0
  66. package/dist/runtime/buildOutcome.js +464 -0
  67. package/dist/runtime/defaults.d.ts +54 -0
  68. package/dist/runtime/defaults.js +47 -0
  69. package/dist/runtime/deriveRespondsToProposals.d.ts +9 -0
  70. package/dist/runtime/deriveRespondsToProposals.js +38 -0
  71. package/dist/runtime/kickoff.d.ts +15 -0
  72. package/dist/runtime/kickoff.js +112 -0
  73. package/dist/runtime/runMechanical.d.ts +25 -0
  74. package/dist/runtime/runMechanical.js +74 -0
  75. package/dist/runtime/runTurn.d.ts +63 -0
  76. package/dist/runtime/runTurn.js +800 -0
  77. package/dist/runtime/validateWorkflow.d.ts +17 -0
  78. package/dist/runtime/validateWorkflow.js +209 -0
  79. package/dist/server/ConnectionPool.d.ts +68 -0
  80. package/dist/server/ConnectionPool.js +166 -0
  81. package/dist/server/EventQueue.d.ts +33 -0
  82. package/dist/server/EventQueue.js +98 -0
  83. package/dist/server/InboxCatchUp.d.ts +24 -0
  84. package/dist/server/InboxCatchUp.js +213 -0
  85. package/dist/server/OutboxBuffer.d.ts +33 -0
  86. package/dist/server/OutboxBuffer.js +68 -0
  87. package/dist/server/SeenMessages.d.ts +13 -0
  88. package/dist/server/SeenMessages.js +31 -0
  89. package/dist/server/StateTimers.d.ts +34 -0
  90. package/dist/server/StateTimers.js +70 -0
  91. package/dist/server/ZiggsEffectHandler.d.ts +79 -0
  92. package/dist/server/ZiggsEffectHandler.js +319 -0
  93. package/dist/server/agreements/AgreementService.d.ts +50 -0
  94. package/dist/server/agreements/AgreementService.js +85 -0
  95. package/dist/server/createHealthServer.d.ts +7 -0
  96. package/dist/server/createHealthServer.js +75 -0
  97. package/dist/server/proactive/ProactiveTrigger.d.ts +33 -0
  98. package/dist/server/proactive/ProactiveTrigger.js +71 -0
  99. package/dist/server/runLauncher.d.ts +28 -0
  100. package/dist/server/runLauncher.js +93 -0
  101. package/dist/server/tasks/TaskService.d.ts +25 -0
  102. package/dist/server/tasks/TaskService.js +103 -0
  103. package/dist/server/tasks/index.js +1 -0
  104. package/dist/server/telemetryIngest.d.ts +24 -0
  105. package/dist/server/telemetryIngest.js +62 -0
  106. package/dist/server/tools/index.d.ts +24 -0
  107. package/{src/server/tools/index.ts → dist/server/tools/index.js} +4 -24
  108. package/dist/server/tools/tier1/protocolRunner.d.ts +39 -0
  109. package/dist/server/tools/tier1/protocolRunner.js +288 -0
  110. package/dist/server/tools/tier1/protocolTools.d.ts +12 -0
  111. package/dist/server/tools/tier1/protocolTools.js +37 -0
  112. package/dist/server/tools/tier2/connectionTools.d.ts +4 -0
  113. package/dist/server/tools/tier2/connectionTools.js +63 -0
  114. package/dist/server/tools/tier2/contextTools.d.ts +4 -0
  115. package/dist/server/tools/tier2/contextTools.js +46 -0
  116. package/dist/server/tools/tier2/discoveryTools.d.ts +4 -0
  117. package/dist/server/tools/tier2/discoveryTools.js +25 -0
  118. package/dist/server/tools/tier2/marketplaceTools.d.ts +3 -0
  119. package/dist/server/tools/tier2/marketplaceTools.js +22 -0
  120. package/dist/server/tools/tier2/paymentTools.d.ts +12 -0
  121. package/dist/server/tools/tier2/paymentTools.js +165 -0
  122. package/dist/server/ziggsconnect/ZiggsConnectClient.d.ts +35 -0
  123. package/dist/server/ziggsconnect/ZiggsConnectClient.js +95 -0
  124. package/dist/server/ziggscontext/ZiggsContextClient.d.ts +48 -0
  125. package/dist/server/ziggscontext/ZiggsContextClient.js +90 -0
  126. package/dist/server/ziggspay/ZiggsPayClient.d.ts +82 -0
  127. package/dist/server/ziggspay/ZiggsPayClient.js +188 -0
  128. package/dist/shared/ids.d.ts +1 -0
  129. package/dist/shared/ids.js +3 -0
  130. package/dist/shared/runtimeLog.d.ts +18 -0
  131. package/dist/shared/runtimeLog.js +68 -0
  132. package/dist/shared/types.d.ts +22 -0
  133. package/dist/shared/types.js +23 -0
  134. package/dist/tasks/protocolRegistry.d.ts +16 -0
  135. package/dist/tasks/protocolRegistry.js +20 -0
  136. package/dist/tools/ToolManager.d.ts +12 -0
  137. package/dist/tools/ToolManager.js +74 -0
  138. package/dist/tools/ToolProvider.d.ts +6 -0
  139. package/dist/tools/ToolProvider.js +21 -0
  140. package/dist/tools/defineTool.d.ts +27 -0
  141. package/dist/tools/defineTool.js +62 -0
  142. package/{src/tools/index.ts → dist/tools/index.d.ts} +0 -1
  143. package/dist/tools/index.js +6 -0
  144. package/dist/tools/recordReport.d.ts +41 -0
  145. package/dist/tools/recordReport.js +30 -0
  146. package/dist/types.d.ts +466 -0
  147. package/dist/types.js +7 -0
  148. package/dist/utils/jsonExtractor.d.ts +2 -0
  149. package/dist/utils/jsonExtractor.js +111 -0
  150. package/package.json +17 -7
  151. package/src/AgentHost.ts +0 -545
  152. package/src/adapters/AnthropicAdapter.ts +0 -206
  153. package/src/adapters/McpAdapter.ts +0 -348
  154. package/src/adapters/OpenAIAdapter.ts +0 -187
  155. package/src/agent/Agent.ts +0 -104
  156. package/src/cognition/validateContext.ts +0 -95
  157. package/src/context/applyEffects.ts +0 -80
  158. package/src/context/batch.ts +0 -19
  159. package/src/context/classifyEnvelope.ts +0 -38
  160. package/src/context/routingLabels.ts +0 -46
  161. package/src/defineAgent.ts +0 -70
  162. package/src/formatters/AgreementFormatter.ts +0 -111
  163. package/src/formatters/HistoryFormatter.ts +0 -172
  164. package/src/ingress/normalizeIncoming.ts +0 -162
  165. package/src/memory/MemoryStore.ts +0 -104
  166. package/src/pricing/fleetDefaults.ts +0 -225
  167. package/src/pricing/fleetEvalFree.ts +0 -24
  168. package/src/pricing/fleetTierByAgentId.gen.ts +0 -775
  169. package/src/runtime/AgentMachine.ts +0 -402
  170. package/src/runtime/PromptBuilder.ts +0 -485
  171. package/src/runtime/buildOutcome.ts +0 -563
  172. package/src/runtime/defaults.ts +0 -75
  173. package/src/runtime/kickoff.ts +0 -117
  174. package/src/runtime/runTurn.ts +0 -813
  175. package/src/runtime/validateWorkflow.ts +0 -202
  176. package/src/server/ConnectionPool.ts +0 -206
  177. package/src/server/EventQueue.ts +0 -133
  178. package/src/server/InboxCatchUp.ts +0 -254
  179. package/src/server/OutboxBuffer.ts +0 -90
  180. package/src/server/SeenMessages.ts +0 -27
  181. package/src/server/StateTimers.ts +0 -85
  182. package/src/server/ZiggsEffectHandler.ts +0 -418
  183. package/src/server/agreements/AgreementService.ts +0 -118
  184. package/src/server/createHealthServer.ts +0 -85
  185. package/src/server/proactive/ProactiveTrigger.ts +0 -83
  186. package/src/server/runLauncher.ts +0 -146
  187. package/src/server/tasks/TaskService.ts +0 -110
  188. package/src/server/telemetryIngest.ts +0 -91
  189. package/src/server/tools/tier1/protocolRunner.ts +0 -144
  190. package/src/server/tools/tier1/protocolTools.ts +0 -99
  191. package/src/server/tools/tier2/connectionTools.ts +0 -75
  192. package/src/server/tools/tier2/contextTools.ts +0 -63
  193. package/src/server/tools/tier2/discoveryTools.ts +0 -34
  194. package/src/server/tools/tier2/marketplaceTools.ts +0 -27
  195. package/src/server/tools/tier2/paymentTools.ts +0 -170
  196. package/src/server/ziggsconnect/ZiggsConnectClient.ts +0 -126
  197. package/src/server/ziggscontext/ZiggsContextClient.ts +0 -129
  198. package/src/server/ziggspay/ZiggsPayClient.ts +0 -193
  199. package/src/shared/ids.ts +0 -3
  200. package/src/shared/runtimeLog.ts +0 -72
  201. package/src/shared/types.ts +0 -29
  202. package/src/tasks/protocolRegistry.ts +0 -25
  203. package/src/tools/ToolManager.ts +0 -95
  204. package/src/tools/ToolProvider.ts +0 -19
  205. package/src/tools/defineTool.ts +0 -90
  206. package/src/tools/recordReport.ts +0 -82
  207. package/src/types.ts +0 -456
  208. package/src/utils/jsonExtractor.ts +0 -100
  209. /package/{src/adapters/index.ts → dist/adapters/index.d.ts} +0 -0
  210. /package/{src/formatters/index.ts → dist/formatters/index.d.ts} +0 -0
  211. /package/{src/server/tasks/index.ts → dist/server/tasks/index.d.ts} +0 -0
package/src/AgentHost.ts DELETED
@@ -1,545 +0,0 @@
1
- import type { AgentConfig, Ctx, EffectHandler, Identity, Workflow } from './types.js';
2
- import { Agent } from './agent/Agent.js';
3
- import { EventQueue } from './server/EventQueue.js';
4
- import { StateTimers } from './server/StateTimers.js';
5
- import { createZiggsEffectHandler } from './server/ZiggsEffectHandler.js';
6
- import { normalizeIncomingEvent, type NormalizedEvent } from './ingress/normalizeIncoming.js';
7
- import { PromptBuilder } from './runtime/PromptBuilder.js';
8
- import { InMemoryStore, type MemoryStore } from './memory/MemoryStore.js';
9
-
10
- /**
11
- * Operator-provided FSM session persistence.
12
- *
13
- * AgentHost calls load() before each tick when a lane has no in-memory state
14
- * (e.g. after a container restart) and save() after every successful tick.
15
- * A null return from load() means "no prior state — start fresh."
16
- */
17
- export interface SessionStore {
18
- load(laneKey: string): Promise<{ ctx?: Ctx; state?: string } | null | undefined>;
19
- save(laneKey: string, session: { ctx?: Ctx; state?: string }): Promise<void>;
20
- }
21
- import { ToolManager } from './tools/ToolManager.js';
22
- import type { ToolDefinition } from './tools/defineTool.js';
23
- import { PROTOCOL_TOOLS } from './server/tools/tier1/protocolTools.js';
24
- import { OpenAIAdapter } from './adapters/OpenAIAdapter.js';
25
- import { AnthropicAdapter } from './adapters/AnthropicAdapter.js';
26
- import {
27
- WebSocketClient,
28
- MessagesClient,
29
- ArtifactsClient,
30
- ScopeClient,
31
- TelemetryClient,
32
- type MessageMetadata,
33
- } from '@ziggs-ai/api-client';
34
- import { TaskService } from './server/tasks/TaskService.js';
35
- import { AgreementService } from './server/agreements/AgreementService.js';
36
- import { runInboxCatchUp } from './server/InboxCatchUp.js';
37
- import { SeenMessages } from './server/SeenMessages.js';
38
- import { runtimeLog } from './shared/runtimeLog.js';
39
-
40
- // ConnectionPool lives in a separate module; resolved via a computed-path
41
- // dynamic import to avoid a circular import with AgentHost.
42
- async function loadConnectionPool(): Promise<new (opts?: { maxActive?: number; idleTimeoutMs?: number }) => unknown> {
43
- const modPath = './server/ConnectionPool.js';
44
- const mod = await import(/* @vite-ignore */ modPath) as { ConnectionPool: new (opts?: { maxActive?: number; idleTimeoutMs?: number }) => unknown };
45
- return mod.ConnectionPool;
46
- }
47
-
48
- /**
49
- * Given a normalized event, return the sessionId for this agent. Return
50
- * `null` to skip processing. Lets each Agent define what "session" means —
51
- * chat-scoped agents bucket by chatId (the default), task-scoped agents
52
- * could bucket by taskId, cron agents by schedule id, etc.
53
- */
54
- export type SessionResolver = (normalized: NormalizedEvent, ownAgentId: string) => string | null;
55
-
56
- const DEFAULT_SESSION_RESOLVER: SessionResolver = (normalized) => normalized.chatId;
57
-
58
- interface LlmAdapter {
59
- chatMessages(messages: unknown[], tools?: unknown[], options?: Record<string, unknown>): Promise<unknown>;
60
- }
61
-
62
- interface AgentServices {
63
- tools?: ToolDefinition[];
64
- llm?: { model?: string } | LlmAdapter;
65
- [key: string]: unknown;
66
- }
67
-
68
- export interface AgentHostOptions {
69
- openaiKey?: string;
70
- anthropicKey?: string;
71
- model?: string;
72
- operatorKey?: string;
73
- agentId?: string;
74
- name?: string | null;
75
- wsUrl?: string;
76
- description?: string;
77
- specialization?: string | null;
78
- tools?: ToolDefinition[];
79
- taskTools?: 'all' | 'none' | string[];
80
- services?: AgentServices;
81
- workflow?: Workflow;
82
- // Convenience: states + initial without an explicit workflow.
83
- states?: Workflow['states'];
84
- initial?: string;
85
- tickTimeoutMs?: number;
86
- /** Per-agent session keying. Defaults to `(n) => n.chatId`. */
87
- sessionResolver?: SessionResolver;
88
- /**
89
- * Agent-private long-term memory. Operator-provided. Default: a fresh
90
- * `InMemoryStore` (non-durable; lost on process restart). For durability,
91
- * pass `new FileMemoryStore(path)` or your own implementation.
92
- */
93
- memory?: MemoryStore;
94
- /**
95
- * FSM session persistence. Operator-provided. When set, AgentHost loads
96
- * prior state before each tick (cold-start recovery) and saves after every
97
- * successful tick. Omit to keep state in-process only (default behaviour).
98
- */
99
- sessionStore?: SessionStore;
100
- /** Observability tap: called after every effect with the effect and its response. */
101
- effectTap?: (effect: import('./types.js').Effect, response: unknown) => void;
102
- /** Observability tap: called on every FSM state transition, no-match, depth-guard, or error-escape (ZIG-442). */
103
- transitionTap?: (event: import('./runtime/AgentMachine.js').TransitionEvent) => void;
104
- /** Production monitoring ingest (ZIG-360). Default on; set false to disable. */
105
- telemetryIngest?: boolean;
106
- /**
107
- * Pull inbox on every WS connect/reconnect (ZIG-454). Default true.
108
- * Recovers messages lost when backend in-memory pending queues are dropped.
109
- */
110
- inboxCatchUp?: boolean;
111
- }
112
-
113
- function createToolManager(options: AgentHostOptions): ToolManager {
114
- const toolManager = new ToolManager();
115
- const userTools = options.tools || options.services?.tools || [];
116
- let taskTools: ToolDefinition[];
117
- if (options.taskTools === 'none') taskTools = [];
118
- else if (Array.isArray(options.taskTools)) taskTools = PROTOCOL_TOOLS.filter((t: ToolDefinition) => (options.taskTools as string[]).includes(t.schema.function.name as string));
119
- else taskTools = PROTOCOL_TOOLS; // 'all' or undefined
120
- toolManager.registerAll([...taskTools, ...(Array.isArray(userTools) ? userTools : [])]);
121
- return toolManager;
122
- }
123
-
124
- function isLlmAdapter(v: unknown): v is LlmAdapter {
125
- return typeof v === 'object' && v !== null && typeof (v as LlmAdapter).chatMessages === 'function';
126
- }
127
-
128
- function createBackendServices(options: AgentHostOptions) {
129
- const toolManager = createToolManager(options);
130
- const model = (options.services?.llm as { model?: string } | undefined)?.model || options.model;
131
- const llm = isLlmAdapter(options.services?.llm)
132
- ? options.services!.llm as LlmAdapter
133
- : model?.startsWith('claude')
134
- ? new AnthropicAdapter({ key: options.anthropicKey, model })
135
- : new OpenAIAdapter({ key: options.openaiKey, model });
136
- const operatorKey = options.operatorKey!;
137
- const agentId = options.agentId!;
138
- const messagesClient = new MessagesClient(operatorKey, agentId);
139
- const artifactsClient = new ArtifactsClient(operatorKey, agentId);
140
- const scopeClient = new ScopeClient(operatorKey, agentId);
141
- const promptBuilder = new PromptBuilder({ description: options.description, specialization: options.specialization });
142
- const taskService = new TaskService(operatorKey, agentId);
143
- const agreementService = new AgreementService(operatorKey, agentId);
144
- const telemetryClient = new TelemetryClient(operatorKey, agentId);
145
- return {
146
- toolManager,
147
- llm,
148
- messagesClient,
149
- artifactsClient,
150
- scopeClient,
151
- promptBuilder,
152
- taskService,
153
- agreementService,
154
- telemetryClient,
155
- };
156
- }
157
-
158
- interface LaneSession {
159
- sessionId: string;
160
- ownAgentId: string;
161
- ctx?: Ctx;
162
- state?: string;
163
- }
164
-
165
- /**
166
- * AgentHost — the Server. Owns a pure {@link Agent}, an {@link EffectHandler},
167
- * a transport (Ziggs WebSocket), and the lane router (per-session FIFO queue
168
- * + in-memory session state).
169
- *
170
- * transport (WS) → handleMessage → agent.tick(... , eff) → ...
171
- */
172
- export class AgentHost {
173
- options: Required<Pick<AgentHostOptions, 'agentId' | 'operatorKey' | 'openaiKey'>> & AgentHostOptions;
174
- toolManager!: ToolManager;
175
- llm!: OpenAIAdapter | LlmAdapter; // retained for external inspection
176
- agent!: Agent;
177
- wsClient!: WebSocketClient;
178
- private eff!: EffectHandler;
179
- private eventQueue!: EventQueue;
180
- private sessions = new Map<string, LaneSession>();
181
- private sessionResolver: SessionResolver;
182
- private tickTimeoutMs: number;
183
- private memory: MemoryStore;
184
- private sessionStore: SessionStore | null;
185
- private taskService!: TaskService;
186
- private agreementService!: AgreementService;
187
- private messageSender!: (message: string, receiverId: string, sessionId: string) => Promise<unknown>;
188
- private seenMessages = new SeenMessages();
189
- private stateTimers!: StateTimers;
190
- private catchUpInFlight: Promise<void> | null = null;
191
- private inboxCatchUpEnabled: boolean;
192
-
193
- constructor(options: AgentHostOptions = {}) {
194
- this.options = {
195
- openaiKey: options.openaiKey || process.env.OPENAI_API_KEY,
196
- anthropicKey: options.anthropicKey || process.env.ANTHROPIC_API_KEY,
197
- model: options.model || (!isLlmAdapter(options.services?.llm) ? options.services?.llm?.model : undefined) || 'gpt-5.4',
198
- operatorKey: options.operatorKey || process.env.ZIGGS_OPERATOR_KEY || undefined,
199
- agentId: options.agentId || '',
200
- name: options.name || null,
201
- wsUrl: options.wsUrl,
202
- description: options.description || 'A helpful assistant',
203
- specialization: options.specialization ?? null,
204
- tools: options.tools || [],
205
- taskTools: options.taskTools,
206
- states: options.states,
207
- initial: options.initial,
208
- services: options.services,
209
- workflow: options.workflow,
210
- tickTimeoutMs: options.tickTimeoutMs,
211
- sessionResolver: options.sessionResolver,
212
- effectTap: options.effectTap,
213
- transitionTap: options.transitionTap,
214
- inboxCatchUp: options.inboxCatchUp,
215
- } as Required<Pick<AgentHostOptions, 'agentId' | 'operatorKey' | 'openaiKey'>> & AgentHostOptions;
216
- this.inboxCatchUpEnabled = options.inboxCatchUp !== false;
217
- this.sessionResolver = options.sessionResolver || DEFAULT_SESSION_RESOLVER;
218
- this.tickTimeoutMs = options.tickTimeoutMs ?? 120_000;
219
- this.memory = options.memory ?? new InMemoryStore();
220
- this.sessionStore = options.sessionStore ?? null;
221
- this._validate();
222
- this._setup();
223
- }
224
-
225
- private _validate(): void {
226
- if (!isLlmAdapter(this.options.services?.llm)) {
227
- if (this.options.model?.startsWith('claude')) {
228
- if (!this.options.anthropicKey) throw new Error('anthropicKey is required for claude-* models — pass { anthropicKey } or set ANTHROPIC_API_KEY');
229
- } else if (!this.options.openaiKey) {
230
- throw new Error('openaiKey is required');
231
- }
232
- }
233
- if (!this.options.operatorKey) throw new Error('operatorKey is required — pass { operatorKey } or set ZIGGS_OPERATOR_KEY');
234
- if (!this.options.agentId) throw new Error('agentId is required');
235
- }
236
-
237
- private _setup(): void {
238
- const services = createBackendServices(this.options);
239
- this.toolManager = services.toolManager;
240
- this.llm = services.llm;
241
-
242
- this.wsClient = new WebSocketClient({
243
- wsUrl: this.options.wsUrl,
244
- operatorKey: this.options.operatorKey,
245
- agentId: this.options.agentId,
246
- label: this.options.name || 'agent',
247
- });
248
-
249
- const messageSender = (message: string, receiverId: string, sessionId: string, messageId?: string): Promise<unknown> => {
250
- this.wsClient.send(sessionId, receiverId, message, messageId ? { messageId } : undefined);
251
- return Promise.resolve(undefined);
252
- };
253
- const chunkSender = (sessionId: string, receiverId: string, text: string, messageId: string): void => {
254
- this.wsClient.send(sessionId, receiverId, text, { partial: true, messageId });
255
- };
256
- this.taskService = services.taskService;
257
- this.agreementService = services.agreementService;
258
- this.messageSender = messageSender;
259
-
260
- let workflow = this.options.workflow;
261
- if (!workflow && this.options.states) {
262
- workflow = {
263
- id: this.options.agentId,
264
- description: this.options.description!,
265
- initial: this.options.initial || 'idle',
266
- states: this.options.states,
267
- };
268
- }
269
- if (!workflow?.states) throw new Error('AgentHost: workflow (or states + initial) is required');
270
-
271
- this.eff = createZiggsEffectHandler({
272
- llm: services.llm as unknown as import('./server/ZiggsEffectHandler.js').ZiggsEffectDeps['llm'],
273
- messagesClient: services.messagesClient,
274
- artifactsClient: services.artifactsClient,
275
- scopeClient: services.scopeClient,
276
- taskService: services.taskService,
277
- agreementService: services.agreementService,
278
- messageSender,
279
- chunkSender,
280
- toolManager: services.toolManager,
281
- operatorKey: this.options.operatorKey!,
282
- agentId: this.options.agentId!,
283
- telemetryClient: services.telemetryClient,
284
- telemetryIngest: this.options.telemetryIngest,
285
- effectTap: this.options.effectTap,
286
- });
287
-
288
- this.agent = new Agent({
289
- workflow,
290
- toolManager: services.toolManager,
291
- promptBuilder: services.promptBuilder,
292
- });
293
-
294
- this.eventQueue = new EventQueue((event, laneKey) => this._processEvent(event, laneKey));
295
- this.stateTimers = new StateTimers((ev, laneKey) => { void this.eventQueue.enqueue(ev, laneKey); });
296
-
297
- this.wsClient.setMessageHandler((text: string, metadata: MessageMetadata) => this.handleMessage(text, metadata as unknown as Record<string, unknown>));
298
- this.wsClient.setResourceEventHandler((event) => this.handleResourceEvent(event as unknown as Record<string, unknown>));
299
-
300
- if (this.inboxCatchUpEnabled) {
301
- this.wsClient.onConnect(() => {
302
- void this._ensureCatchUp();
303
- });
304
- }
305
- }
306
-
307
- /**
308
- * Coalesce concurrent connect/reconnect bursts into one catch-up pass.
309
- */
310
- private _ensureCatchUp(): Promise<void> {
311
- if (!this.catchUpInFlight) {
312
- this.catchUpInFlight = this._runInboxCatchUp().finally(() => {
313
- this.catchUpInFlight = null;
314
- });
315
- }
316
- return this.catchUpInFlight;
317
- }
318
-
319
- private async _runInboxCatchUp(): Promise<void> {
320
- const label = this.options.name || 'agent';
321
- try {
322
- await runInboxCatchUp({
323
- creds: {
324
- operatorKey: this.options.operatorKey!,
325
- agentId: this.options.agentId!,
326
- },
327
- ownAgentId: this.options.agentId!,
328
- baseUrl: this.options.wsUrl ? undefined : undefined,
329
- seen: this.seenMessages,
330
- label,
331
- onMessage: (text, metadata) => this.handleMessage(text, metadata),
332
- onResourceEvent: (event) => this.handleResourceEvent(event),
333
- });
334
- } catch (err: unknown) {
335
- runtimeLog.warn(
336
- label,
337
- `inbox catch-up failed: ${(err as Error)?.message ?? err}`,
338
- );
339
- }
340
- }
341
-
342
- /**
343
- * Push notification handler — invoked when the backend sees a relevant
344
- * resource change (artifact created, task state transition, agreement
345
- * updated). The agent decides which session/lane to wake based on the
346
- * event payload; it pulls the actual data via the primitive endpoints
347
- * from inside the tick.
348
- *
349
- * Lightweight: defers all work to a tick by enqueueing a synthetic event
350
- * carrying just enough context for the agent to route it.
351
- */
352
- async handleResourceEvent(event: Record<string, unknown>): Promise<void> {
353
- if (!event || typeof event !== 'object') return;
354
- const ownAgentId = this.options.agentId;
355
- if (!ownAgentId) return;
356
-
357
- // Map the event to a candidate session key using the configured resolver
358
- // on a synthetic normalized-event shape. Default resolver buckets by
359
- // chatId; agents using a different sessionResolver (agreementId, taskId,
360
- // counterpartyId) will get the right lane here as long as the wire
361
- // event carries the corresponding id.
362
- const fakeNormalized = {
363
- chatId: event.chatId as string | null,
364
- event: { type: 'message' as const, timestamp: Date.now(), text: '' },
365
- shouldProcess: true,
366
- reason: null,
367
- } as NormalizedEvent;
368
- const sessionId = this.sessionResolver(fakeNormalized, ownAgentId);
369
- if (!sessionId) return;
370
-
371
- const laneKey = `${ownAgentId}:${sessionId}`;
372
- if (!this.sessions.has(laneKey)) {
373
- this.sessions.set(laneKey, { sessionId, ownAgentId });
374
- }
375
- return this.eventQueue.enqueue({ type: 'resource_changed', resource: event, chatId: event.chatId, taskId: event.taskId, agreementId: event.agreementId }, laneKey);
376
- }
377
-
378
- /** Wire entry. Normalize → resolve session → enqueue on the right lane. */
379
- async handleMessage(text: string, metadata: Record<string, unknown> = {}): Promise<void> {
380
- const messageId = metadata.messageId as string | undefined;
381
- if (messageId && this.seenMessages.has(messageId)) {
382
- runtimeLog.debug('AgentHost', `skip duplicate messageId=${messageId}`);
383
- return;
384
- }
385
- if (messageId) this.seenMessages.mark(messageId);
386
-
387
- const ownAgentId = this._resolveOwnAgentId(metadata);
388
- if (!ownAgentId) {
389
- runtimeLog.warn('AgentHost', 'handleMessage: ownAgentId missing — dropping event');
390
- return;
391
- }
392
-
393
- let enrichedText = typeof text === 'string' ? text : '';
394
- if (!enrichedText.trim()) {
395
- const agreement = metadata.agreement as Record<string, unknown> | undefined;
396
- const terms = agreement?.terms as { description?: string } | undefined;
397
- if (terms?.description) {
398
- enrichedText = String(terms.description);
399
- }
400
- }
401
-
402
- const normalized = normalizeIncomingEvent({ text: enrichedText, metadata, ownAgentId });
403
- if (!normalized.shouldProcess) {
404
- const receiverId = (metadata.receiver as Record<string, unknown> | undefined)?.id ?? metadata.receiverId;
405
- runtimeLog.warn('AgentHost', `skip reason=${normalized.reason || 'not_relevant'} ownAgentId=${ownAgentId} receiverId=${receiverId ?? 'null'} chatId=${normalized.chatId ?? 'null'}`);
406
- return;
407
- }
408
-
409
- const sessionId = this.sessionResolver(normalized, ownAgentId);
410
- if (!sessionId) {
411
- runtimeLog.warn('AgentHost', `session resolver returned null ownAgentId=${ownAgentId} chatId=${normalized.chatId ?? 'null'}`);
412
- return;
413
- }
414
-
415
- const laneKey = `${ownAgentId}:${sessionId}`;
416
- if (!this.sessions.has(laneKey)) {
417
- this.sessions.set(laneKey, { sessionId, ownAgentId });
418
- }
419
- return this.eventQueue.enqueue(normalized.event, laneKey);
420
- }
421
-
422
- private async _processEvent(event: unknown, laneKey: string): Promise<void> {
423
- const session = this.sessions.get(laneKey);
424
- if (!session?.sessionId) return;
425
-
426
- // Stale timer frames (superseded epoch) are dropped BEFORE touching the
427
- // live timer — a pure-stale frame must not reset a freshly armed countdown.
428
- const frame = this.stateTimers.stripStale(event, laneKey);
429
- if (frame === null) return;
430
-
431
- // Any event reaching the machine invalidates the countdown ("clear on any
432
- // event for the lane"); re-armed below if the tick settles parked+timed.
433
- this.stateTimers.clear(laneKey);
434
-
435
- // Cold-start recovery: restore FSM state from sessionStore when the
436
- // in-memory lane has no prior context (e.g. after a container restart).
437
- if (this.sessionStore && session.ctx === undefined && session.state === undefined) {
438
- try {
439
- const persisted = await this.sessionStore.load(laneKey);
440
- if (persisted) {
441
- session.ctx = persisted.ctx;
442
- session.state = persisted.state;
443
- }
444
- } catch (err: unknown) {
445
- runtimeLog.warn('AgentHost', `sessionStore.load failed laneKey=${laneKey}: ${(err as Error)?.message || err}`);
446
- }
447
- }
448
-
449
- const identity: Identity = {
450
- agentId: session.ownAgentId || this.options.agentId,
451
- sessionId: session.sessionId,
452
- laneKey,
453
- };
454
-
455
- try {
456
- const result = await Promise.race([
457
- this.agent.tick({ event: frame, ctx: session.ctx, state: session.state, identity, eff: this.eff, memory: this.memory, transitionTap: this.options.transitionTap }),
458
- new Promise<never>((_, reject) =>
459
- setTimeout(() => reject(new Error('tick timeout')), this.tickTimeoutMs),
460
- ),
461
- ]);
462
- session.ctx = result.ctx;
463
- session.state = result.state;
464
- this.sessions.set(laneKey, session);
465
-
466
- if (this.sessionStore) {
467
- this.sessionStore.save(laneKey, { ctx: session.ctx, state: session.state }).catch((err: unknown) => {
468
- runtimeLog.warn('AgentHost', `sessionStore.save failed laneKey=${laneKey}: ${(err as Error)?.message || err}`);
469
- });
470
- }
471
-
472
- // Arm only when the timeout was handled: an unhandled state-timeout must
473
- // not re-arm, or the lane would runtimeLog.error every timeoutMs forever.
474
- if (!result.stateTimeoutUnhandled) this._armIfTimedParked(laneKey, result.state);
475
- } catch (err: unknown) {
476
- runtimeLog.warn('AgentHost', `tick failed laneKey=${laneKey}: ${(err as Error)?.message || err}`);
477
- // Deliberate re-arm on failure. Exact scenario: the tick timed out at
478
- // tickTimeoutMs (or sessionStore.load threw) leaving session.state at a
479
- // timed parked state — without re-arming, that lane would never wake
480
- // again unless a real event happens to arrive.
481
- this._armIfTimedParked(laneKey, session.state ?? this.agent.workflow.initial);
482
- // Best-effort: also notify the user so the conversation isn't silently
483
- // stuck (ZIG-607). Complements the timer re-arm above — that recovers a
484
- // parked lane; this tells a waiting human.
485
- const ev = event as Record<string, unknown> | undefined;
486
- const senderType = String(ev?.senderType ?? '').toUpperCase();
487
- const senderId = ev?.senderId as string | undefined;
488
- if (senderId && senderType === 'USER') {
489
- this.eff({ kind: 'send-message', sessionId: session.sessionId, receiverId: senderId, text: 'Something went wrong. Please try again.' }).catch(() => {});
490
- }
491
- }
492
- }
493
-
494
- /** Arms the lane's liveness timer iff stateName is a parked state declaring timeoutMs. */
495
- private _armIfTimedParked(laneKey: string, stateName: string | undefined): void {
496
- if (!stateName) return;
497
- const sd = this.agent.workflow.states[stateName];
498
- if (sd?.kind !== 'parked' || sd.timeoutMs === undefined) return;
499
- this.stateTimers.arm(laneKey, stateName, sd.timeoutMs);
500
- }
501
-
502
- private _resolveOwnAgentId(metadata: Record<string, unknown> = {}): string | null {
503
- const self = metadata.self as Record<string, unknown> | undefined;
504
- const agent = metadata.agent as Record<string, unknown> | undefined;
505
- return this.options.agentId || (self?.id as string) || (agent?.id as string) || (metadata.ziggsAgentId as string) || null;
506
- }
507
-
508
- async connectAsync(timeout?: number) {
509
- await this.wsClient.connectAsync(timeout);
510
- if (this.inboxCatchUpEnabled) {
511
- await this._ensureCatchUp();
512
- // Backend flushPendingMessages can emit after auth; re-poll once so inbox
513
- // ack catches stragglers and we don't leave newMessages > 0 (ZIG-462).
514
- await new Promise((r) => setTimeout(r, 750));
515
- this.catchUpInFlight = null;
516
- await this._ensureCatchUp();
517
- }
518
- return this;
519
- }
520
- disconnect() { this.stateTimers.clearAll(); this.wsClient.disconnect(); }
521
- /** Waits for all in-flight ticks to complete. Call before process.exit() on SIGTERM. */
522
- drain(timeoutMs = 30_000): Promise<void> { return this.eventQueue.waitForIdle(timeoutMs); }
523
- registerTool(tool: ToolDefinition) { return this.toolManager.register(tool); }
524
- isConnected() { return this.wsClient.isConnected(); }
525
- }
526
-
527
- /**
528
- * createAgent(config) → AgentHost
529
- * createAgentPool([cfg, ...]) → ConnectionPool (lazy fleet, async-loaded)
530
- */
531
- export function createAgent(options: AgentHostOptions | AgentConfig = {}): AgentHost {
532
- return new AgentHost(options as AgentHostOptions);
533
- }
534
-
535
- export async function createAgentPool(
536
- configs: AgentHostOptions[] | AgentConfig[],
537
- opts: { poolOptions?: { maxActive?: number; idleTimeoutMs?: number } } = {},
538
- ): Promise<unknown> {
539
- const ConnectionPool = await loadConnectionPool();
540
- const pool = new ConnectionPool(opts.poolOptions);
541
- (pool as { register: (configs: unknown[]) => void }).register(configs);
542
- return pool;
543
- }
544
-
545
- export default AgentHost;