@ziggs-ai/agent-sdk 0.1.12 → 0.2.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.
Files changed (218) 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/claudeSdkTools.d.ts +19 -0
  20. package/dist/agent/claudeSdkTools.js +57 -0
  21. package/dist/agent/hostedAgentConnection.d.ts +15 -0
  22. package/dist/agent/hostedAgentConnection.js +29 -0
  23. package/dist/agent/toolManagerToSdkMcp.d.ts +12 -0
  24. package/dist/agent/toolManagerToSdkMcp.js +123 -0
  25. package/dist/brain/index.d.ts +50 -0
  26. package/dist/brain/index.js +45 -0
  27. package/dist/cognition/validateContext.d.ts +11 -0
  28. package/dist/cognition/validateContext.js +117 -0
  29. package/dist/context/applyEffects.d.ts +2 -0
  30. package/dist/context/applyEffects.js +89 -0
  31. package/dist/context/batch.d.ts +2 -0
  32. package/dist/context/batch.js +18 -0
  33. package/dist/context/classifyEnvelope.d.ts +1 -0
  34. package/dist/context/classifyEnvelope.js +58 -0
  35. package/dist/context/routingLabels.d.ts +4 -0
  36. package/dist/context/routingLabels.js +48 -0
  37. package/dist/defineAgent.d.ts +14 -0
  38. package/dist/defineAgent.js +68 -0
  39. package/dist/formatters/AgreementFormatter.d.ts +18 -0
  40. package/dist/formatters/AgreementFormatter.js +130 -0
  41. package/dist/formatters/HistoryFormatter.d.ts +26 -0
  42. package/dist/formatters/HistoryFormatter.js +173 -0
  43. package/dist/formatters/index.js +2 -0
  44. package/{src/index.ts → dist/index.d.ts} +7 -53
  45. package/dist/index.js +66 -0
  46. package/dist/ingress/normalizeIncoming.d.ts +65 -0
  47. package/dist/ingress/normalizeIncoming.js +88 -0
  48. package/dist/memory/MemoryStore.d.ts +55 -0
  49. package/dist/memory/MemoryStore.js +78 -0
  50. package/dist/pricing/fleetDefaults.d.ts +46 -0
  51. package/dist/pricing/fleetDefaults.js +168 -0
  52. package/dist/pricing/fleetEvalFree.d.ts +7 -0
  53. package/dist/pricing/fleetEvalFree.js +24 -0
  54. package/dist/pricing/fleetFreeTierA.gen.d.ts +6 -0
  55. package/{src/pricing/fleetFreeTierA.gen.ts → dist/pricing/fleetFreeTierA.gen.js} +5 -6
  56. package/dist/pricing/fleetTierByAgentId.gen.d.ts +7 -0
  57. package/dist/pricing/fleetTierByAgentId.gen.js +768 -0
  58. package/dist/runtime/AgentMachine.d.ts +143 -0
  59. package/dist/runtime/AgentMachine.js +400 -0
  60. package/dist/runtime/PromptBuilder.d.ts +96 -0
  61. package/dist/runtime/PromptBuilder.js +439 -0
  62. package/{src/runtime/bootstrapCtx.ts → dist/runtime/bootstrapCtx.d.ts} +1 -22
  63. package/dist/runtime/bootstrapCtx.js +37 -0
  64. package/dist/runtime/buildOutcome.d.ts +68 -0
  65. package/dist/runtime/buildOutcome.js +464 -0
  66. package/dist/runtime/defaults.d.ts +54 -0
  67. package/dist/runtime/defaults.js +47 -0
  68. package/dist/runtime/deriveRespondsToProposals.d.ts +9 -0
  69. package/dist/runtime/deriveRespondsToProposals.js +38 -0
  70. package/dist/runtime/kickoff.d.ts +15 -0
  71. package/dist/runtime/kickoff.js +112 -0
  72. package/dist/runtime/runMechanical.d.ts +25 -0
  73. package/dist/runtime/runMechanical.js +74 -0
  74. package/dist/runtime/runTurn.d.ts +63 -0
  75. package/dist/runtime/runTurn.js +800 -0
  76. package/dist/runtime/validateWorkflow.d.ts +17 -0
  77. package/dist/runtime/validateWorkflow.js +209 -0
  78. package/dist/server/ConnectionPool.d.ts +68 -0
  79. package/dist/server/ConnectionPool.js +166 -0
  80. package/dist/server/EventQueue.d.ts +33 -0
  81. package/dist/server/EventQueue.js +98 -0
  82. package/dist/server/InboxCatchUp.d.ts +24 -0
  83. package/dist/server/InboxCatchUp.js +214 -0
  84. package/dist/server/OutboxBuffer.d.ts +33 -0
  85. package/dist/server/OutboxBuffer.js +68 -0
  86. package/dist/server/SeenMessages.d.ts +13 -0
  87. package/dist/server/SeenMessages.js +31 -0
  88. package/dist/server/StateTimers.d.ts +34 -0
  89. package/dist/server/StateTimers.js +70 -0
  90. package/dist/server/ZiggsEffectHandler.d.ts +79 -0
  91. package/dist/server/ZiggsEffectHandler.js +319 -0
  92. package/dist/server/agreements/AgreementService.d.ts +50 -0
  93. package/dist/server/agreements/AgreementService.js +85 -0
  94. package/dist/server/createHealthServer.d.ts +7 -0
  95. package/dist/server/createHealthServer.js +75 -0
  96. package/dist/server/proactive/ProactiveTrigger.d.ts +33 -0
  97. package/dist/server/proactive/ProactiveTrigger.js +71 -0
  98. package/dist/server/runLauncher.d.ts +28 -0
  99. package/dist/server/runLauncher.js +93 -0
  100. package/dist/server/tasks/TaskService.d.ts +25 -0
  101. package/dist/server/tasks/TaskService.js +103 -0
  102. package/dist/server/tasks/index.js +1 -0
  103. package/dist/server/telemetryIngest.d.ts +24 -0
  104. package/dist/server/telemetryIngest.js +62 -0
  105. package/dist/server/tools/index.d.ts +24 -0
  106. package/{src/server/tools/index.ts → dist/server/tools/index.js} +4 -24
  107. package/dist/server/tools/tier1/protocolRunner.d.ts +39 -0
  108. package/dist/server/tools/tier1/protocolRunner.js +288 -0
  109. package/dist/server/tools/tier1/protocolTools.d.ts +12 -0
  110. package/dist/server/tools/tier1/protocolTools.js +37 -0
  111. package/dist/server/tools/tier2/connectionTools.d.ts +4 -0
  112. package/dist/server/tools/tier2/connectionTools.js +63 -0
  113. package/dist/server/tools/tier2/contextTools.d.ts +4 -0
  114. package/dist/server/tools/tier2/contextTools.js +46 -0
  115. package/dist/server/tools/tier2/discoveryTools.d.ts +4 -0
  116. package/dist/server/tools/tier2/discoveryTools.js +25 -0
  117. package/dist/server/tools/tier2/marketplaceTools.d.ts +3 -0
  118. package/dist/server/tools/tier2/marketplaceTools.js +22 -0
  119. package/dist/server/tools/tier2/paymentTools.d.ts +12 -0
  120. package/dist/server/tools/tier2/paymentTools.js +165 -0
  121. package/dist/server/ziggsconnect/ZiggsConnectClient.d.ts +35 -0
  122. package/dist/server/ziggsconnect/ZiggsConnectClient.js +95 -0
  123. package/dist/server/ziggscontext/ZiggsContextClient.d.ts +53 -0
  124. package/dist/server/ziggscontext/ZiggsContextClient.js +124 -0
  125. package/dist/server/ziggspay/ZiggsPayClient.d.ts +82 -0
  126. package/dist/server/ziggspay/ZiggsPayClient.js +203 -0
  127. package/dist/shared/ids.d.ts +1 -0
  128. package/dist/shared/ids.js +3 -0
  129. package/dist/shared/runtimeLog.d.ts +18 -0
  130. package/dist/shared/runtimeLog.js +68 -0
  131. package/dist/shared/types.d.ts +22 -0
  132. package/dist/shared/types.js +23 -0
  133. package/dist/tasks/protocolRegistry.d.ts +16 -0
  134. package/dist/tasks/protocolRegistry.js +20 -0
  135. package/dist/tools/ToolManager.d.ts +12 -0
  136. package/dist/tools/ToolManager.js +74 -0
  137. package/dist/tools/ToolProvider.d.ts +6 -0
  138. package/dist/tools/ToolProvider.js +21 -0
  139. package/dist/tools/defineTool.d.ts +27 -0
  140. package/dist/tools/defineTool.js +62 -0
  141. package/{src/tools/index.ts → dist/tools/index.d.ts} +0 -1
  142. package/dist/tools/index.js +6 -0
  143. package/dist/tools/recordReport.d.ts +41 -0
  144. package/dist/tools/recordReport.js +30 -0
  145. package/dist/types.d.ts +466 -0
  146. package/dist/types.js +7 -0
  147. package/dist/utils/jsonExtractor.d.ts +2 -0
  148. package/dist/utils/jsonExtractor.js +111 -0
  149. package/package.json +14 -6
  150. package/src/AgentHost.ts +0 -814
  151. package/src/adapters/AnthropicAdapter.ts +0 -238
  152. package/src/adapters/McpAdapter.ts +0 -678
  153. package/src/adapters/OpenAIAdapter.ts +0 -194
  154. package/src/agent/Agent.ts +0 -112
  155. package/src/agent/ClaudeSdkAgent.ts +0 -401
  156. package/src/agent/claudeCognition.ts +0 -139
  157. package/src/agent/claudeSdkTools.ts +0 -81
  158. package/src/agent/hostedAgentConnection.ts +0 -41
  159. package/src/agent/toolManagerToSdkMcp.ts +0 -136
  160. package/src/cognition/validateContext.ts +0 -95
  161. package/src/context/applyEffects.ts +0 -80
  162. package/src/context/batch.ts +0 -19
  163. package/src/context/classifyEnvelope.ts +0 -38
  164. package/src/context/routingLabels.ts +0 -46
  165. package/src/defineAgent.ts +0 -77
  166. package/src/formatters/AgreementFormatter.ts +0 -111
  167. package/src/formatters/HistoryFormatter.ts +0 -172
  168. package/src/ingress/normalizeIncoming.ts +0 -162
  169. package/src/memory/MemoryStore.ts +0 -104
  170. package/src/pricing/fleetDefaults.ts +0 -225
  171. package/src/pricing/fleetEvalFree.ts +0 -24
  172. package/src/pricing/fleetTierByAgentId.gen.ts +0 -775
  173. package/src/runtime/AgentMachine.ts +0 -484
  174. package/src/runtime/PromptBuilder.ts +0 -522
  175. package/src/runtime/buildOutcome.ts +0 -563
  176. package/src/runtime/defaults.ts +0 -86
  177. package/src/runtime/deriveRespondsToProposals.ts +0 -35
  178. package/src/runtime/kickoff.ts +0 -117
  179. package/src/runtime/runMechanical.ts +0 -106
  180. package/src/runtime/runTurn.ts +0 -870
  181. package/src/runtime/validateWorkflow.ts +0 -221
  182. package/src/server/ConnectionPool.ts +0 -206
  183. package/src/server/EventQueue.ts +0 -133
  184. package/src/server/InboxCatchUp.ts +0 -291
  185. package/src/server/OutboxBuffer.ts +0 -90
  186. package/src/server/SeenMessages.ts +0 -27
  187. package/src/server/StateTimers.ts +0 -85
  188. package/src/server/ZiggsEffectHandler.ts +0 -427
  189. package/src/server/agreements/AgreementService.ts +0 -118
  190. package/src/server/createHealthServer.ts +0 -85
  191. package/src/server/proactive/ProactiveTrigger.ts +0 -83
  192. package/src/server/runLauncher.ts +0 -146
  193. package/src/server/tasks/TaskService.ts +0 -111
  194. package/src/server/telemetryIngest.ts +0 -91
  195. package/src/server/tools/tier1/protocolRunner.ts +0 -353
  196. package/src/server/tools/tier1/protocolTools.ts +0 -114
  197. package/src/server/tools/tier2/connectionTools.ts +0 -75
  198. package/src/server/tools/tier2/contextTools.ts +0 -63
  199. package/src/server/tools/tier2/discoveryTools.ts +0 -34
  200. package/src/server/tools/tier2/marketplaceTools.ts +0 -27
  201. package/src/server/tools/tier2/paymentTools.ts +0 -170
  202. package/src/server/ziggsconnect/ZiggsConnectClient.ts +0 -126
  203. package/src/server/ziggscontext/ZiggsContextClient.ts +0 -129
  204. package/src/server/ziggspay/ZiggsPayClient.ts +0 -193
  205. package/src/shared/ids.ts +0 -3
  206. package/src/shared/runtimeLog.ts +0 -72
  207. package/src/shared/types.ts +0 -29
  208. package/src/tasks/protocolRegistry.ts +0 -25
  209. package/src/tools/ToolManager.ts +0 -95
  210. package/src/tools/ToolProvider.ts +0 -19
  211. package/src/tools/defineTool.ts +0 -90
  212. package/src/tools/recordReport.ts +0 -82
  213. package/src/types.ts +0 -483
  214. package/src/utils/jsonExtractor.ts +0 -100
  215. /package/{src/adapters/index.ts → dist/adapters/index.d.ts} +0 -0
  216. /package/{src/agent/claudeSdkProtocol.ts → dist/agent/claudeSdkProtocol.js} +0 -0
  217. /package/{src/formatters/index.ts → dist/formatters/index.d.ts} +0 -0
  218. /package/{src/server/tasks/index.ts → dist/server/tasks/index.d.ts} +0 -0
@@ -1,221 +0,0 @@
1
- import type { Ctx, OutcomeKind, Workflow, ThinkingState, ParkedState, MechanicalState } from '../types.js';
2
- import { bootstrapCtx } from './bootstrapCtx.js';
3
-
4
- /** Static probe ctx for evaluating transition `when` guards during validation. */
5
- export const PROBE_CTX: Ctx = bootstrapCtx({ agentId: '', sessionId: '', laneKey: '' });
6
-
7
- // 'state-timeout' is intentionally absent: it is a runtime-generated liveness
8
- // outcome, never something an action's `produces` may declare.
9
- const KNOWN_OUTCOME_KINDS = new Set<OutcomeKind>([
10
- 'enter',
11
- 'wait',
12
- 'message-sent',
13
- 'message-received',
14
- 'task-assigned',
15
- 'task-delegated',
16
- 'task-closed',
17
- 'subtask-finished',
18
- 'proposal-made',
19
- 'proposal-resolved',
20
- 'tool-result',
21
- 'error',
22
- 'extension',
23
- ]);
24
-
25
- export class WorkflowValidationError extends Error {
26
- readonly issues: string[];
27
- constructor(issues: string[]) {
28
- super(`Workflow validation failed:\n - ${issues.join('\n - ')}`);
29
- this.name = 'WorkflowValidationError';
30
- this.issues = issues;
31
- }
32
- }
33
-
34
- export interface ValidationResult {
35
- errors: string[];
36
- warnings: string[];
37
- }
38
-
39
- /**
40
- * Synchronous structural validation. Errors throw; warnings are returned for
41
- * the caller to log. defineAgent runs this on construction, so misconfigured
42
- * workflows fail loudly at load instead of mysteriously misbehaving at runtime.
43
- */
44
- export function validateWorkflow(workflow: Workflow): ValidationResult {
45
- const errors: string[] = [];
46
- const warnings: string[] = [];
47
-
48
- if (!workflow || typeof workflow !== 'object') {
49
- throw new WorkflowValidationError(['workflow must be an object']);
50
- }
51
- if (!workflow.id) errors.push('workflow.id is required');
52
- if (!workflow.initial) errors.push('workflow.initial is required');
53
- if (!workflow.states || typeof workflow.states !== 'object') {
54
- errors.push('workflow.states is required');
55
- }
56
-
57
- if (errors.length) throw new WorkflowValidationError(errors);
58
-
59
- const stateNames = new Set(Object.keys(workflow.states));
60
- if (!stateNames.has(workflow.initial)) {
61
- errors.push(`workflow.initial "${workflow.initial}" is not a defined state`);
62
- }
63
-
64
- for (const [name, state] of Object.entries(workflow.states)) {
65
- if (!state || typeof state !== 'object') {
66
- errors.push(`state "${name}" is not an object`);
67
- continue;
68
- }
69
- if (state.kind !== 'parked' && state.kind !== 'thinking' && state.kind !== 'mechanical') {
70
- errors.push(`state "${name}" must declare kind: 'parked' | 'thinking' | 'mechanical'`);
71
- continue;
72
- }
73
-
74
- // Shape rejects mixing
75
- if (state.kind === 'parked') {
76
- if ((state as unknown as ThinkingState).prompt) {
77
- errors.push(`state "${name}" is parked but has a prompt — parked states are event-driven only`);
78
- }
79
- if ((state as unknown as ThinkingState).actions) {
80
- errors.push(`state "${name}" is parked but has actions — parked states are event-driven only`);
81
- }
82
- const tm = (state as ParkedState).timeoutMs;
83
- if (tm !== undefined && (typeof tm !== 'number' || !Number.isFinite(tm) || tm <= 0)) {
84
- errors.push(`state "${name}" timeoutMs must be a finite number > 0 (got ${String(tm)})`);
85
- }
86
- if (typeof tm === 'number' && Number.isFinite(tm) && tm > 0) {
87
- const handled = state.transitions.some(t => {
88
- if (!t.when) return true;
89
- try { return t.when({ kind: 'state-timeout', state: name, waitedMs: tm }, PROBE_CTX); }
90
- catch { return false; }
91
- });
92
- if (!handled) {
93
- warnings.push(`parked state "${name}" declares timeoutMs=${tm} but no transition matches { kind: 'state-timeout' } — the timeout will park loudly (runtimeLog.error) instead of routing to a recovery state.`);
94
- }
95
- }
96
- }
97
- if (state.kind === 'thinking') {
98
- if (!state.prompt) errors.push(`thinking state "${name}" requires a prompt`);
99
- if (!state.actions || Object.keys(state.actions).length === 0) {
100
- errors.push(`thinking state "${name}" requires at least one action`);
101
- }
102
- if ((state as unknown as ParkedState).timeoutMs !== undefined) {
103
- errors.push(`state "${name}" is thinking but declares timeoutMs — liveness timers apply to parked states only (thinking turns are bounded by AgentHost tickTimeoutMs)`);
104
- }
105
- }
106
- if (state.kind === 'mechanical') {
107
- const mech = state as MechanicalState;
108
- if (!mech.tool || typeof mech.tool !== 'string') {
109
- errors.push(`mechanical state "${name}" requires a tool name`);
110
- }
111
- if (mech.buildArgs !== undefined && typeof mech.buildArgs !== 'function') {
112
- errors.push(`mechanical state "${name}" buildArgs must be a function`);
113
- }
114
- if ((mech as unknown as ThinkingState).prompt) {
115
- errors.push(`state "${name}" is mechanical but has a prompt — use kind: 'thinking' instead`);
116
- }
117
- if ((mech as unknown as ThinkingState).actions) {
118
- errors.push(`state "${name}" is mechanical but has actions — use kind: 'thinking' instead`);
119
- }
120
- const p = mech.produces;
121
- if (p === 'state-timeout') {
122
- errors.push(`mechanical state "${name}" produces "state-timeout" — reserved for runtime liveness timers`);
123
- } else if (typeof p === 'string') {
124
- if (!KNOWN_OUTCOME_KINDS.has(p as OutcomeKind)) {
125
- errors.push(
126
- `mechanical state "${name}" produces unknown outcome kind "${p}" — use one of: ${[...KNOWN_OUTCOME_KINDS].join(', ')}`,
127
- );
128
- }
129
- } else if (typeof p !== 'function') {
130
- errors.push(`mechanical state "${name}" requires produces (OutcomeKind or fn)`);
131
- }
132
- }
133
-
134
- if (!Array.isArray(state.transitions)) {
135
- errors.push(`state "${name}" requires a transitions array`);
136
- continue;
137
- }
138
-
139
- for (let i = 0; i < state.transitions.length; i++) {
140
- const t = state.transitions[i];
141
- if (!t || typeof t !== 'object') {
142
- errors.push(`state "${name}" transition[${i}] is not an object`);
143
- continue;
144
- }
145
- if (typeof t.to !== 'string') {
146
- errors.push(`state "${name}" transition[${i}] is missing string "to"`);
147
- continue;
148
- }
149
- if (!stateNames.has(t.to)) {
150
- errors.push(`state "${name}" transition[${i}] points to unknown state "${t.to}"`);
151
- }
152
- if (t.when !== undefined && typeof t.when !== 'function') {
153
- errors.push(`state "${name}" transition[${i}].when must be a function or omitted`);
154
- }
155
- }
156
-
157
- if (state.kind === 'thinking') {
158
- for (const [actionName, action] of Object.entries(state.actions)) {
159
- if (!action || typeof action !== 'object') {
160
- errors.push(`state "${name}" action "${actionName}" is not an object`);
161
- continue;
162
- }
163
- if (!action.prompt || !action.prompt.instruction) {
164
- errors.push(`state "${name}" action "${actionName}" requires prompt.instruction`);
165
- }
166
- const p = action.produces;
167
- if (p === 'state-timeout') {
168
- errors.push(`state "${name}" action "${actionName}" produces "state-timeout" — reserved for runtime liveness timers; actions cannot produce it`);
169
- } else if (typeof p === 'string') {
170
- if (!KNOWN_OUTCOME_KINDS.has(p as OutcomeKind)) {
171
- errors.push(
172
- `state "${name}" action "${actionName}" produces unknown outcome kind "${p}" — use one of: ${[...KNOWN_OUTCOME_KINDS].join(', ')}`,
173
- );
174
- }
175
- } else if (typeof p !== 'function') {
176
- errors.push(`state "${name}" action "${actionName}" requires produces (OutcomeKind or fn)`);
177
- }
178
- }
179
-
180
- // Soft check: thinking state should have a wait action and a fallthrough.
181
- // Authors can opt out via allowNoWait when intentionally building a
182
- // looping or always-progressing state.
183
- if (!state.allowNoWait) {
184
- if (!state.actions?.wait) {
185
- warnings.push(
186
- `thinking state "${name}" has no "wait" action — did you forget to spread thinkingDefaults({ initial: '${workflow.initial}' })? Set allowNoWait: true to silence.`,
187
- );
188
- }
189
- const hasWaitFallthrough = state.transitions.some(t => {
190
- if (!t.when) return true;
191
- try { return t.when({ kind: 'wait' }, PROBE_CTX); }
192
- catch { return false; }
193
- });
194
- if (!hasWaitFallthrough) {
195
- warnings.push(
196
- `thinking state "${name}" has no fallthrough for { kind: 'wait' } — agent may stall when LLM picks wait. Spread thinkingDefaults().transitions or add explicitly.`,
197
- );
198
- }
199
- const hasErrorFallthrough = state.transitions.some(t => {
200
- if (!t.when) return true;
201
- try {
202
- return t.when(
203
- { kind: 'error', source: 'unknown', cause: 'probe', retryable: false },
204
- PROBE_CTX,
205
- );
206
- } catch {
207
- return false;
208
- }
209
- });
210
- if (!hasErrorFallthrough) {
211
- warnings.push(
212
- `thinking state "${name}" has no fallthrough for { kind: 'error' } — empty/failed turns may silently recurse until the depth guard. Add an error transition or spread thinkingDefaults().transitions.`,
213
- );
214
- }
215
- }
216
- }
217
- }
218
-
219
- if (errors.length) throw new WorkflowValidationError(errors);
220
- return { errors, warnings };
221
- }
@@ -1,206 +0,0 @@
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 { ConnectionManager } from '@ziggs-ai/api-client';
15
- import { AgentHost, type AgentHostOptions } from '../AgentHost.js';
16
- import { runtimeLog } from '../shared/runtimeLog.js';
17
-
18
- // Inlined from api-client (not re-exported there). Sync if the upstream
19
- // types change.
20
- interface ConnectionManagerMeta {
21
- domain?: string;
22
- expertise?: string[];
23
- tags?: string[];
24
- [key: string]: unknown;
25
- }
26
-
27
- interface QueryFilter {
28
- domain?: string;
29
- expertise?: string[];
30
- tags?: string[];
31
- }
32
-
33
- export interface PoolOptions {
34
- maxActive?: number;
35
- idleTimeoutMs?: number;
36
- }
37
-
38
- export interface SendToOptions {
39
- timeoutMs?: number;
40
- }
41
-
42
- export interface ControlOptions {
43
- wsUrl?: string;
44
- operatorKey?: string;
45
- }
46
-
47
- export class ConnectionPool {
48
- private readonly manager: ConnectionManager;
49
- private readonly configs = new Map<string, AgentHostOptions>();
50
- // Listeners enqueued by requestFrom — drained on outbound send-message effects.
51
- private readonly responseListeners = new Map<string, Array<(text: string) => void>>();
52
-
53
- constructor({ maxActive = 50, idleTimeoutMs = 60_000 }: PoolOptions = {}) {
54
- this.manager = new ConnectionManager({ maxActive, idleTimeoutMs });
55
- }
56
-
57
- /**
58
- * Register N agent configs. Each will be lazily woken (a fresh
59
- * `AgentHost` constructed + connected) the first time it's referenced.
60
- */
61
- register(configs: AgentHostOptions[], metaArr: ConnectionManagerMeta[] = []): void {
62
- const metaByAgentId = new Map<string, ConnectionManagerMeta>();
63
- for (const m of metaArr) {
64
- const id = m['agentId'] as string | undefined;
65
- if (id) metaByAgentId.set(id, m);
66
- }
67
- for (const config of configs) {
68
- const agentId = config.agentId ?? config.workflow?.id;
69
- if (!agentId) {
70
- runtimeLog.warn('ConnectionPool', 'skipping config with no resolvable agentId');
71
- continue;
72
- }
73
- this.configs.set(agentId, config);
74
- this.manager.register(
75
- agentId,
76
- async () => {
77
- const cfg = this.configs.get(agentId)!;
78
- const originalTap = cfg.effectTap;
79
- const pool = this;
80
- const host = new AgentHost({
81
- ...cfg,
82
- effectTap(effect, response) {
83
- if (effect.kind === 'send-message') {
84
- const listeners = pool.responseListeners.get(agentId);
85
- if (listeners?.length) listeners.shift()!(effect.text ?? '');
86
- }
87
- originalTap?.(effect, response);
88
- },
89
- });
90
- await host.connectAsync();
91
- runtimeLog.debug(
92
- 'ConnectionPool',
93
- `woke "${agentId}" (active: ${this.manager.listActive().length}/${this.maxActive})`,
94
- );
95
- return host;
96
- },
97
- async (host) => {
98
- (host as AgentHost).disconnect();
99
- runtimeLog.debug('ConnectionPool', `slept "${agentId}"`);
100
- },
101
- metaByAgentId.get(agentId),
102
- );
103
- }
104
- }
105
-
106
- async wake(agentId: string): Promise<AgentHost> {
107
- return await this.manager.wake(agentId) as AgentHost;
108
- }
109
-
110
- async sleep(agentId: string): Promise<void> {
111
- return this.manager.sleep(agentId);
112
- }
113
-
114
- async disconnectAll(): Promise<void> {
115
- return this.manager.sleepAll();
116
- }
117
-
118
- /**
119
- * Wake the named agent, deliver a message, and wait for its first outbound
120
- * `send-message` effect — i.e. the agent's reply. Returns the reply text.
121
- *
122
- * The effectTap injected in `register` drains a per-agent listener queue;
123
- * this method enqueues one resolver and awaits it. Times out if the agent
124
- * does not reply within `timeoutMs` (default 60 s for LLM agents).
125
- */
126
- async requestFrom(agentId: string, text: string, metadata: Record<string, unknown> = {}, { timeoutMs = 60_000 }: SendToOptions = {}): Promise<string> {
127
- const host = await this.wake(agentId);
128
- return new Promise<string>((resolve, reject) => {
129
- let settled = false;
130
- const timer = setTimeout(() => {
131
- if (settled) return;
132
- settled = true;
133
- const ls = this.responseListeners.get(agentId);
134
- if (ls) { const i = ls.indexOf(listener); if (i !== -1) ls.splice(i, 1); }
135
- reject(new Error(`requestFrom timeout for "${agentId}" after ${timeoutMs}ms`));
136
- }, timeoutMs);
137
- const listener = (responseText: string) => {
138
- if (settled) return;
139
- settled = true;
140
- clearTimeout(timer);
141
- resolve(responseText);
142
- };
143
- const ls = this.responseListeners.get(agentId) ?? [];
144
- ls.push(listener);
145
- this.responseListeners.set(agentId, ls);
146
- host.handleMessage(text, metadata).catch((err: unknown) => {
147
- if (settled) return;
148
- settled = true;
149
- clearTimeout(timer);
150
- const ls2 = this.responseListeners.get(agentId);
151
- if (ls2) { const i = ls2.indexOf(listener); if (i !== -1) ls2.splice(i, 1); }
152
- reject(err);
153
- });
154
- });
155
- }
156
-
157
- /**
158
- * Wake the named agent and deliver one wire frame to it. Returns once the
159
- * host has finished processing.
160
- *
161
- * Note: does not return the agent's response. For request/response
162
- * semantics use `requestFrom`.
163
- */
164
- async sendTo(agentId: string, text: string, metadata: Record<string, unknown> = {}, { timeoutMs = 30_000 }: SendToOptions = {}): Promise<null> {
165
- const host = await this.wake(agentId);
166
- const timeoutPromise = new Promise<never>((_, reject) =>
167
- setTimeout(() => reject(new Error(`sendTo timeout for "${agentId}" after ${timeoutMs}ms`)), timeoutMs),
168
- );
169
- await Promise.race([host.handleMessage(text, metadata), timeoutPromise]);
170
- this.manager.touch(agentId);
171
- return null;
172
- }
173
-
174
- /**
175
- * Open a launcher (control) socket that the backend uses to push wake
176
- * hints for any of the registered agents. Pool spins up the right host
177
- * lazily when a hint arrives.
178
- */
179
- startControl({ wsUrl, operatorKey }: ControlOptions = {}): void {
180
- if (!wsUrl || !operatorKey) {
181
- runtimeLog.warn(
182
- 'ConnectionPool',
183
- 'startControl: wsUrl and operatorKey are required — skipping',
184
- );
185
- return;
186
- }
187
- // ConnectionManager only accepts `control` at construction; mutating its
188
- // private field is the smallest way to add it later. (Cleaner fix lives
189
- // in api-client: expose a `setControl` setter.)
190
- (this.manager as unknown as Record<string, unknown>)['_controlOpts'] = { wsUrl, operatorKey };
191
- this.manager.start();
192
- }
193
-
194
- stopControl(): void {
195
- const m = this.manager as unknown as Record<string, unknown>;
196
- (m['_controlHandle'] as { close?: () => void } | null)?.close?.();
197
- m['_controlHandle'] = null;
198
- }
199
-
200
- query(filter: QueryFilter): string[] { return this.manager.query(filter); }
201
- list(): string[] { return this.manager.list(); }
202
- listActive(): string[] { return this.manager.listActive(); }
203
- get size(): number { return this.manager.size; }
204
- get maxActive(): number { return (this.manager as unknown as { maxActive: number }).maxActive; }
205
- getMeta(id: string): ConnectionManagerMeta | undefined { return this.manager.getMeta(id); }
206
- }
@@ -1,133 +0,0 @@
1
- import { runtimeLog } from '../shared/runtimeLog.js';
2
-
3
- export type ProcessEventFn = (event: unknown, laneKey: string) => Promise<void>;
4
-
5
- interface QueuedEntry {
6
- event: unknown;
7
- resolve: () => void;
8
- }
9
-
10
- interface LaneState {
11
- events: QueuedEntry[];
12
- processing: boolean;
13
- }
14
-
15
- interface QueueMetrics {
16
- enqueued: number;
17
- dropped: number;
18
- processedBatches: number;
19
- processedEvents: number;
20
- errors: number;
21
- }
22
-
23
- export interface EventQueueOptions {
24
- maxQueueLengthPerLane?: number;
25
- }
26
-
27
- /**
28
- * Serializes events per lane and coalesces multiple queued events into one
29
- * batch dispatch. The runtime sees at most one in-flight processEvent call
30
- * per laneKey at a time; multiple events arriving while busy are merged
31
- * into a single { type: 'batch', events: [...] } frame on the next pass.
32
- */
33
- export class EventQueue {
34
- private _processEvent: ProcessEventFn;
35
- private _maxQueueLengthPerLane: number;
36
- private _state: Map<string, LaneState> = new Map();
37
- private _metrics: QueueMetrics = {
38
- enqueued: 0, dropped: 0, processedBatches: 0, processedEvents: 0, errors: 0,
39
- };
40
-
41
- constructor(processEventFn: ProcessEventFn, options: EventQueueOptions = {}) {
42
- this._processEvent = processEventFn;
43
- this._maxQueueLengthPerLane = Number.isFinite(options.maxQueueLengthPerLane)
44
- ? Math.max(1, options.maxQueueLengthPerLane!)
45
- : 100;
46
- }
47
-
48
- enqueue(event: unknown, laneKey: string): Promise<void> {
49
- if (!laneKey) {
50
- runtimeLog.warn('EventQueue', 'enqueue called without laneKey, skipping');
51
- return Promise.resolve();
52
- }
53
-
54
- let state = this._state.get(laneKey);
55
- if (!state) {
56
- state = { events: [], processing: false };
57
- this._state.set(laneKey, state);
58
- }
59
-
60
- return new Promise<void>((resolve) => {
61
- this._metrics.enqueued += 1;
62
- state!.events.push({ event, resolve });
63
- this._trimQueueIfNeeded(state!, laneKey);
64
- this._processIfNeeded(laneKey);
65
- });
66
- }
67
-
68
- private _processIfNeeded(laneKey: string): void {
69
- const state = this._state.get(laneKey);
70
- if (!state || state.processing || state.events.length === 0) return;
71
- state.processing = true;
72
- this._processLoop(laneKey);
73
- }
74
-
75
- private async _processLoop(laneKey: string): Promise<void> {
76
- const state = this._state.get(laneKey);
77
- if (!state) return;
78
-
79
- while (state.events.length > 0) {
80
- const batch = state.events.splice(0, state.events.length);
81
- const events = batch.map(e => e.event);
82
- const resolvers = batch.map(e => e.resolve);
83
-
84
- const coalesced = events.length === 1
85
- ? events[0]
86
- : { type: 'batch', events };
87
-
88
- try {
89
- await this._processEvent(coalesced, laneKey);
90
- } catch (error: unknown) {
91
- this._metrics.errors += 1;
92
- runtimeLog.error('EventQueue', `processEvent error for laneKey=${laneKey}: ${(error as Error)?.message || error}`);
93
- }
94
- this._metrics.processedBatches += 1;
95
- this._metrics.processedEvents += events.length;
96
-
97
- for (const r of resolvers) r();
98
- }
99
-
100
- state.processing = false;
101
- }
102
-
103
- private _trimQueueIfNeeded(state: LaneState, laneKey: string): void {
104
- while (state.events.length > this._maxQueueLengthPerLane) {
105
- const dropped = state.events.shift();
106
- this._metrics.dropped += 1;
107
- dropped?.resolve();
108
- runtimeLog.warn('EventQueue', `dropped oldest event laneKey=${laneKey} max=${this._maxQueueLengthPerLane}`);
109
- }
110
- }
111
-
112
- get isIdle(): boolean {
113
- return [...this._state.values()].every(s => !s.processing && s.events.length === 0);
114
- }
115
-
116
- /** Resolves when all lanes finish processing, or after timeoutMs. Best-effort — never throws. */
117
- async waitForIdle(timeoutMs = 30_000): Promise<void> {
118
- const deadline = Date.now() + timeoutMs;
119
- while (Date.now() < deadline) {
120
- if (this.isIdle) return;
121
- await new Promise<void>(r => setTimeout(r, 50));
122
- }
123
- runtimeLog.warn('EventQueue', `waitForIdle timed out after ${timeoutMs}ms`);
124
- }
125
-
126
- getMetrics() {
127
- return {
128
- ...this._metrics,
129
- lanes: this._state.size,
130
- queuedEvents: [...this._state.values()].reduce((sum, lane) => sum + lane.events.length, 0),
131
- };
132
- }
133
- }