@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
@@ -0,0 +1,17 @@
1
+ import type { Ctx, Workflow } from '../types.js';
2
+ /** Static probe ctx for evaluating transition `when` guards during validation. */
3
+ export declare const PROBE_CTX: Ctx;
4
+ export declare class WorkflowValidationError extends Error {
5
+ readonly issues: string[];
6
+ constructor(issues: string[]);
7
+ }
8
+ export interface ValidationResult {
9
+ errors: string[];
10
+ warnings: string[];
11
+ }
12
+ /**
13
+ * Synchronous structural validation. Errors throw; warnings are returned for
14
+ * the caller to log. defineAgent runs this on construction, so misconfigured
15
+ * workflows fail loudly at load instead of mysteriously misbehaving at runtime.
16
+ */
17
+ export declare function validateWorkflow(workflow: Workflow): ValidationResult;
@@ -0,0 +1,209 @@
1
+ import { bootstrapCtx } from './bootstrapCtx.js';
2
+ /** Static probe ctx for evaluating transition `when` guards during validation. */
3
+ export const PROBE_CTX = bootstrapCtx({ agentId: '', sessionId: '', laneKey: '' });
4
+ // 'state-timeout' is intentionally absent: it is a runtime-generated liveness
5
+ // outcome, never something an action's `produces` may declare.
6
+ const KNOWN_OUTCOME_KINDS = new Set([
7
+ 'enter',
8
+ 'wait',
9
+ 'message-sent',
10
+ 'message-received',
11
+ 'task-assigned',
12
+ 'task-delegated',
13
+ 'task-closed',
14
+ 'subtask-finished',
15
+ 'proposal-made',
16
+ 'proposal-resolved',
17
+ 'tool-result',
18
+ 'error',
19
+ 'extension',
20
+ ]);
21
+ export class WorkflowValidationError extends Error {
22
+ issues;
23
+ constructor(issues) {
24
+ super(`Workflow validation failed:\n - ${issues.join('\n - ')}`);
25
+ this.name = 'WorkflowValidationError';
26
+ this.issues = issues;
27
+ }
28
+ }
29
+ /**
30
+ * Synchronous structural validation. Errors throw; warnings are returned for
31
+ * the caller to log. defineAgent runs this on construction, so misconfigured
32
+ * workflows fail loudly at load instead of mysteriously misbehaving at runtime.
33
+ */
34
+ export function validateWorkflow(workflow) {
35
+ const errors = [];
36
+ const warnings = [];
37
+ if (!workflow || typeof workflow !== 'object') {
38
+ throw new WorkflowValidationError(['workflow must be an object']);
39
+ }
40
+ if (!workflow.id)
41
+ errors.push('workflow.id is required');
42
+ if (!workflow.initial)
43
+ errors.push('workflow.initial is required');
44
+ if (!workflow.states || typeof workflow.states !== 'object') {
45
+ errors.push('workflow.states is required');
46
+ }
47
+ if (errors.length)
48
+ throw new WorkflowValidationError(errors);
49
+ const stateNames = new Set(Object.keys(workflow.states));
50
+ if (!stateNames.has(workflow.initial)) {
51
+ errors.push(`workflow.initial "${workflow.initial}" is not a defined state`);
52
+ }
53
+ for (const [name, state] of Object.entries(workflow.states)) {
54
+ if (!state || typeof state !== 'object') {
55
+ errors.push(`state "${name}" is not an object`);
56
+ continue;
57
+ }
58
+ if (state.kind !== 'parked' && state.kind !== 'thinking' && state.kind !== 'mechanical') {
59
+ errors.push(`state "${name}" must declare kind: 'parked' | 'thinking' | 'mechanical'`);
60
+ continue;
61
+ }
62
+ // Shape rejects mixing
63
+ if (state.kind === 'parked') {
64
+ if (state.prompt) {
65
+ errors.push(`state "${name}" is parked but has a prompt — parked states are event-driven only`);
66
+ }
67
+ if (state.actions) {
68
+ errors.push(`state "${name}" is parked but has actions — parked states are event-driven only`);
69
+ }
70
+ const tm = state.timeoutMs;
71
+ if (tm !== undefined && (typeof tm !== 'number' || !Number.isFinite(tm) || tm <= 0)) {
72
+ errors.push(`state "${name}" timeoutMs must be a finite number > 0 (got ${String(tm)})`);
73
+ }
74
+ if (typeof tm === 'number' && Number.isFinite(tm) && tm > 0) {
75
+ const handled = state.transitions.some(t => {
76
+ if (!t.when)
77
+ return true;
78
+ try {
79
+ return t.when({ kind: 'state-timeout', state: name, waitedMs: tm }, PROBE_CTX);
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ });
85
+ if (!handled) {
86
+ 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.`);
87
+ }
88
+ }
89
+ }
90
+ if (state.kind === 'thinking') {
91
+ if (!state.prompt)
92
+ errors.push(`thinking state "${name}" requires a prompt`);
93
+ if (!state.actions || Object.keys(state.actions).length === 0) {
94
+ errors.push(`thinking state "${name}" requires at least one action`);
95
+ }
96
+ if (state.timeoutMs !== undefined) {
97
+ errors.push(`state "${name}" is thinking but declares timeoutMs — liveness timers apply to parked states only (thinking turns are bounded by AgentHost tickTimeoutMs)`);
98
+ }
99
+ }
100
+ if (state.kind === 'mechanical') {
101
+ const mech = state;
102
+ if (!mech.tool || typeof mech.tool !== 'string') {
103
+ errors.push(`mechanical state "${name}" requires a tool name`);
104
+ }
105
+ if (mech.buildArgs !== undefined && typeof mech.buildArgs !== 'function') {
106
+ errors.push(`mechanical state "${name}" buildArgs must be a function`);
107
+ }
108
+ if (mech.prompt) {
109
+ errors.push(`state "${name}" is mechanical but has a prompt — use kind: 'thinking' instead`);
110
+ }
111
+ if (mech.actions) {
112
+ errors.push(`state "${name}" is mechanical but has actions — use kind: 'thinking' instead`);
113
+ }
114
+ const p = mech.produces;
115
+ if (p === 'state-timeout') {
116
+ errors.push(`mechanical state "${name}" produces "state-timeout" — reserved for runtime liveness timers`);
117
+ }
118
+ else if (typeof p === 'string') {
119
+ if (!KNOWN_OUTCOME_KINDS.has(p)) {
120
+ errors.push(`mechanical state "${name}" produces unknown outcome kind "${p}" — use one of: ${[...KNOWN_OUTCOME_KINDS].join(', ')}`);
121
+ }
122
+ }
123
+ else if (typeof p !== 'function') {
124
+ errors.push(`mechanical state "${name}" requires produces (OutcomeKind or fn)`);
125
+ }
126
+ }
127
+ if (!Array.isArray(state.transitions)) {
128
+ errors.push(`state "${name}" requires a transitions array`);
129
+ continue;
130
+ }
131
+ for (let i = 0; i < state.transitions.length; i++) {
132
+ const t = state.transitions[i];
133
+ if (!t || typeof t !== 'object') {
134
+ errors.push(`state "${name}" transition[${i}] is not an object`);
135
+ continue;
136
+ }
137
+ if (typeof t.to !== 'string') {
138
+ errors.push(`state "${name}" transition[${i}] is missing string "to"`);
139
+ continue;
140
+ }
141
+ if (!stateNames.has(t.to)) {
142
+ errors.push(`state "${name}" transition[${i}] points to unknown state "${t.to}"`);
143
+ }
144
+ if (t.when !== undefined && typeof t.when !== 'function') {
145
+ errors.push(`state "${name}" transition[${i}].when must be a function or omitted`);
146
+ }
147
+ }
148
+ if (state.kind === 'thinking') {
149
+ for (const [actionName, action] of Object.entries(state.actions)) {
150
+ if (!action || typeof action !== 'object') {
151
+ errors.push(`state "${name}" action "${actionName}" is not an object`);
152
+ continue;
153
+ }
154
+ if (!action.prompt || !action.prompt.instruction) {
155
+ errors.push(`state "${name}" action "${actionName}" requires prompt.instruction`);
156
+ }
157
+ const p = action.produces;
158
+ if (p === 'state-timeout') {
159
+ errors.push(`state "${name}" action "${actionName}" produces "state-timeout" — reserved for runtime liveness timers; actions cannot produce it`);
160
+ }
161
+ else if (typeof p === 'string') {
162
+ if (!KNOWN_OUTCOME_KINDS.has(p)) {
163
+ errors.push(`state "${name}" action "${actionName}" produces unknown outcome kind "${p}" — use one of: ${[...KNOWN_OUTCOME_KINDS].join(', ')}`);
164
+ }
165
+ }
166
+ else if (typeof p !== 'function') {
167
+ errors.push(`state "${name}" action "${actionName}" requires produces (OutcomeKind or fn)`);
168
+ }
169
+ }
170
+ // Soft check: thinking state should have a wait action and a fallthrough.
171
+ // Authors can opt out via allowNoWait when intentionally building a
172
+ // looping or always-progressing state.
173
+ if (!state.allowNoWait) {
174
+ if (!state.actions?.wait) {
175
+ warnings.push(`thinking state "${name}" has no "wait" action — did you forget to spread thinkingDefaults({ initial: '${workflow.initial}' })? Set allowNoWait: true to silence.`);
176
+ }
177
+ const hasWaitFallthrough = state.transitions.some(t => {
178
+ if (!t.when)
179
+ return true;
180
+ try {
181
+ return t.when({ kind: 'wait' }, PROBE_CTX);
182
+ }
183
+ catch {
184
+ return false;
185
+ }
186
+ });
187
+ if (!hasWaitFallthrough) {
188
+ warnings.push(`thinking state "${name}" has no fallthrough for { kind: 'wait' } — agent may stall when LLM picks wait. Spread thinkingDefaults().transitions or add explicitly.`);
189
+ }
190
+ const hasErrorFallthrough = state.transitions.some(t => {
191
+ if (!t.when)
192
+ return true;
193
+ try {
194
+ return t.when({ kind: 'error', source: 'unknown', cause: 'probe', retryable: false }, PROBE_CTX);
195
+ }
196
+ catch {
197
+ return false;
198
+ }
199
+ });
200
+ if (!hasErrorFallthrough) {
201
+ warnings.push(`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.`);
202
+ }
203
+ }
204
+ }
205
+ }
206
+ if (errors.length)
207
+ throw new WorkflowValidationError(errors);
208
+ return { errors, warnings };
209
+ }
@@ -0,0 +1,68 @@
1
+ import { AgentHost, type AgentHostOptions } from '../AgentHost.js';
2
+ interface ConnectionManagerMeta {
3
+ domain?: string;
4
+ expertise?: string[];
5
+ tags?: string[];
6
+ [key: string]: unknown;
7
+ }
8
+ interface QueryFilter {
9
+ domain?: string;
10
+ expertise?: string[];
11
+ tags?: string[];
12
+ }
13
+ export interface PoolOptions {
14
+ maxActive?: number;
15
+ idleTimeoutMs?: number;
16
+ }
17
+ export interface SendToOptions {
18
+ timeoutMs?: number;
19
+ }
20
+ export interface ControlOptions {
21
+ wsUrl?: string;
22
+ operatorKey?: string;
23
+ }
24
+ export declare class ConnectionPool {
25
+ private readonly manager;
26
+ private readonly configs;
27
+ private readonly responseListeners;
28
+ constructor({ maxActive, idleTimeoutMs }?: PoolOptions);
29
+ /**
30
+ * Register N agent configs. Each will be lazily woken (a fresh
31
+ * `AgentHost` constructed + connected) the first time it's referenced.
32
+ */
33
+ register(configs: AgentHostOptions[], metaArr?: ConnectionManagerMeta[]): void;
34
+ wake(agentId: string): Promise<AgentHost>;
35
+ sleep(agentId: string): Promise<void>;
36
+ disconnectAll(): Promise<void>;
37
+ /**
38
+ * Wake the named agent, deliver a message, and wait for its first outbound
39
+ * `send-message` effect — i.e. the agent's reply. Returns the reply text.
40
+ *
41
+ * The effectTap injected in `register` drains a per-agent listener queue;
42
+ * this method enqueues one resolver and awaits it. Times out if the agent
43
+ * does not reply within `timeoutMs` (default 60 s for LLM agents).
44
+ */
45
+ requestFrom(agentId: string, text: string, metadata?: Record<string, unknown>, { timeoutMs }?: SendToOptions): Promise<string>;
46
+ /**
47
+ * Wake the named agent and deliver one wire frame to it. Returns once the
48
+ * host has finished processing.
49
+ *
50
+ * Note: does not return the agent's response. For request/response
51
+ * semantics use `requestFrom`.
52
+ */
53
+ sendTo(agentId: string, text: string, metadata?: Record<string, unknown>, { timeoutMs }?: SendToOptions): Promise<null>;
54
+ /**
55
+ * Open a launcher (control) socket that the backend uses to push wake
56
+ * hints for any of the registered agents. Pool spins up the right host
57
+ * lazily when a hint arrives.
58
+ */
59
+ startControl({ wsUrl, operatorKey }?: ControlOptions): void;
60
+ stopControl(): void;
61
+ query(filter: QueryFilter): string[];
62
+ list(): string[];
63
+ listActive(): string[];
64
+ get size(): number;
65
+ get maxActive(): number;
66
+ getMeta(id: string): ConnectionManagerMeta | undefined;
67
+ }
68
+ export {};
@@ -0,0 +1,166 @@
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 } from '../AgentHost.js';
16
+ import { runtimeLog } from '../shared/runtimeLog.js';
17
+ export class ConnectionPool {
18
+ manager;
19
+ configs = new Map();
20
+ // Listeners enqueued by requestFrom — drained on outbound send-message effects.
21
+ responseListeners = new Map();
22
+ constructor({ maxActive = 50, idleTimeoutMs = 60_000 } = {}) {
23
+ this.manager = new ConnectionManager({ maxActive, idleTimeoutMs });
24
+ }
25
+ /**
26
+ * Register N agent configs. Each will be lazily woken (a fresh
27
+ * `AgentHost` constructed + connected) the first time it's referenced.
28
+ */
29
+ register(configs, metaArr = []) {
30
+ const metaByAgentId = new Map();
31
+ for (const m of metaArr) {
32
+ const id = m['agentId'];
33
+ if (id)
34
+ metaByAgentId.set(id, m);
35
+ }
36
+ for (const config of configs) {
37
+ const agentId = config.agentId ?? config.workflow?.id;
38
+ if (!agentId) {
39
+ runtimeLog.warn('ConnectionPool', 'skipping config with no resolvable agentId');
40
+ continue;
41
+ }
42
+ this.configs.set(agentId, config);
43
+ this.manager.register(agentId, async () => {
44
+ const cfg = this.configs.get(agentId);
45
+ const originalTap = cfg.effectTap;
46
+ const pool = this;
47
+ const host = new AgentHost({
48
+ ...cfg,
49
+ effectTap(effect, response) {
50
+ if (effect.kind === 'send-message') {
51
+ const listeners = pool.responseListeners.get(agentId);
52
+ if (listeners?.length)
53
+ listeners.shift()(effect.text ?? '');
54
+ }
55
+ originalTap?.(effect, response);
56
+ },
57
+ });
58
+ await host.connectAsync();
59
+ runtimeLog.debug('ConnectionPool', `woke "${agentId}" (active: ${this.manager.listActive().length}/${this.maxActive})`);
60
+ return host;
61
+ }, async (host) => {
62
+ host.disconnect();
63
+ runtimeLog.debug('ConnectionPool', `slept "${agentId}"`);
64
+ }, metaByAgentId.get(agentId));
65
+ }
66
+ }
67
+ async wake(agentId) {
68
+ return await this.manager.wake(agentId);
69
+ }
70
+ async sleep(agentId) {
71
+ return this.manager.sleep(agentId);
72
+ }
73
+ async disconnectAll() {
74
+ return this.manager.sleepAll();
75
+ }
76
+ /**
77
+ * Wake the named agent, deliver a message, and wait for its first outbound
78
+ * `send-message` effect — i.e. the agent's reply. Returns the reply text.
79
+ *
80
+ * The effectTap injected in `register` drains a per-agent listener queue;
81
+ * this method enqueues one resolver and awaits it. Times out if the agent
82
+ * does not reply within `timeoutMs` (default 60 s for LLM agents).
83
+ */
84
+ async requestFrom(agentId, text, metadata = {}, { timeoutMs = 60_000 } = {}) {
85
+ const host = await this.wake(agentId);
86
+ return new Promise((resolve, reject) => {
87
+ let settled = false;
88
+ const timer = setTimeout(() => {
89
+ if (settled)
90
+ return;
91
+ settled = true;
92
+ const ls = this.responseListeners.get(agentId);
93
+ if (ls) {
94
+ const i = ls.indexOf(listener);
95
+ if (i !== -1)
96
+ ls.splice(i, 1);
97
+ }
98
+ reject(new Error(`requestFrom timeout for "${agentId}" after ${timeoutMs}ms`));
99
+ }, timeoutMs);
100
+ const listener = (responseText) => {
101
+ if (settled)
102
+ return;
103
+ settled = true;
104
+ clearTimeout(timer);
105
+ resolve(responseText);
106
+ };
107
+ const ls = this.responseListeners.get(agentId) ?? [];
108
+ ls.push(listener);
109
+ this.responseListeners.set(agentId, ls);
110
+ host.handleMessage(text, metadata).catch((err) => {
111
+ if (settled)
112
+ return;
113
+ settled = true;
114
+ clearTimeout(timer);
115
+ const ls2 = this.responseListeners.get(agentId);
116
+ if (ls2) {
117
+ const i = ls2.indexOf(listener);
118
+ if (i !== -1)
119
+ ls2.splice(i, 1);
120
+ }
121
+ reject(err);
122
+ });
123
+ });
124
+ }
125
+ /**
126
+ * Wake the named agent and deliver one wire frame to it. Returns once the
127
+ * host has finished processing.
128
+ *
129
+ * Note: does not return the agent's response. For request/response
130
+ * semantics use `requestFrom`.
131
+ */
132
+ async sendTo(agentId, text, metadata = {}, { timeoutMs = 30_000 } = {}) {
133
+ const host = await this.wake(agentId);
134
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`sendTo timeout for "${agentId}" after ${timeoutMs}ms`)), timeoutMs));
135
+ await Promise.race([host.handleMessage(text, metadata), timeoutPromise]);
136
+ this.manager.touch(agentId);
137
+ return null;
138
+ }
139
+ /**
140
+ * Open a launcher (control) socket that the backend uses to push wake
141
+ * hints for any of the registered agents. Pool spins up the right host
142
+ * lazily when a hint arrives.
143
+ */
144
+ startControl({ wsUrl, operatorKey } = {}) {
145
+ if (!wsUrl || !operatorKey) {
146
+ runtimeLog.warn('ConnectionPool', 'startControl: wsUrl and operatorKey are required — skipping');
147
+ return;
148
+ }
149
+ // ConnectionManager only accepts `control` at construction; mutating its
150
+ // private field is the smallest way to add it later. (Cleaner fix lives
151
+ // in api-client: expose a `setControl` setter.)
152
+ this.manager['_controlOpts'] = { wsUrl, operatorKey };
153
+ this.manager.start();
154
+ }
155
+ stopControl() {
156
+ const m = this.manager;
157
+ m['_controlHandle']?.close?.();
158
+ m['_controlHandle'] = null;
159
+ }
160
+ query(filter) { return this.manager.query(filter); }
161
+ list() { return this.manager.list(); }
162
+ listActive() { return this.manager.listActive(); }
163
+ get size() { return this.manager.size; }
164
+ get maxActive() { return this.manager.maxActive; }
165
+ getMeta(id) { return this.manager.getMeta(id); }
166
+ }
@@ -0,0 +1,33 @@
1
+ export type ProcessEventFn = (event: unknown, laneKey: string) => Promise<void>;
2
+ export interface EventQueueOptions {
3
+ maxQueueLengthPerLane?: number;
4
+ }
5
+ /**
6
+ * Serializes events per lane and coalesces multiple queued events into one
7
+ * batch dispatch. The runtime sees at most one in-flight processEvent call
8
+ * per laneKey at a time; multiple events arriving while busy are merged
9
+ * into a single { type: 'batch', events: [...] } frame on the next pass.
10
+ */
11
+ export declare class EventQueue {
12
+ private _processEvent;
13
+ private _maxQueueLengthPerLane;
14
+ private _state;
15
+ private _metrics;
16
+ constructor(processEventFn: ProcessEventFn, options?: EventQueueOptions);
17
+ enqueue(event: unknown, laneKey: string): Promise<void>;
18
+ private _processIfNeeded;
19
+ private _processLoop;
20
+ private _trimQueueIfNeeded;
21
+ get isIdle(): boolean;
22
+ /** Resolves when all lanes finish processing, or after timeoutMs. Best-effort — never throws. */
23
+ waitForIdle(timeoutMs?: number): Promise<void>;
24
+ getMetrics(): {
25
+ lanes: number;
26
+ queuedEvents: number;
27
+ enqueued: number;
28
+ dropped: number;
29
+ processedBatches: number;
30
+ processedEvents: number;
31
+ errors: number;
32
+ };
33
+ }
@@ -0,0 +1,98 @@
1
+ import { runtimeLog } from '../shared/runtimeLog.js';
2
+ /**
3
+ * Serializes events per lane and coalesces multiple queued events into one
4
+ * batch dispatch. The runtime sees at most one in-flight processEvent call
5
+ * per laneKey at a time; multiple events arriving while busy are merged
6
+ * into a single { type: 'batch', events: [...] } frame on the next pass.
7
+ */
8
+ export class EventQueue {
9
+ _processEvent;
10
+ _maxQueueLengthPerLane;
11
+ _state = new Map();
12
+ _metrics = {
13
+ enqueued: 0, dropped: 0, processedBatches: 0, processedEvents: 0, errors: 0,
14
+ };
15
+ constructor(processEventFn, options = {}) {
16
+ this._processEvent = processEventFn;
17
+ this._maxQueueLengthPerLane = Number.isFinite(options.maxQueueLengthPerLane)
18
+ ? Math.max(1, options.maxQueueLengthPerLane)
19
+ : 100;
20
+ }
21
+ enqueue(event, laneKey) {
22
+ if (!laneKey) {
23
+ runtimeLog.warn('EventQueue', 'enqueue called without laneKey, skipping');
24
+ return Promise.resolve();
25
+ }
26
+ let state = this._state.get(laneKey);
27
+ if (!state) {
28
+ state = { events: [], processing: false };
29
+ this._state.set(laneKey, state);
30
+ }
31
+ return new Promise((resolve) => {
32
+ this._metrics.enqueued += 1;
33
+ state.events.push({ event, resolve });
34
+ this._trimQueueIfNeeded(state, laneKey);
35
+ this._processIfNeeded(laneKey);
36
+ });
37
+ }
38
+ _processIfNeeded(laneKey) {
39
+ const state = this._state.get(laneKey);
40
+ if (!state || state.processing || state.events.length === 0)
41
+ return;
42
+ state.processing = true;
43
+ this._processLoop(laneKey);
44
+ }
45
+ async _processLoop(laneKey) {
46
+ const state = this._state.get(laneKey);
47
+ if (!state)
48
+ return;
49
+ while (state.events.length > 0) {
50
+ const batch = state.events.splice(0, state.events.length);
51
+ const events = batch.map(e => e.event);
52
+ const resolvers = batch.map(e => e.resolve);
53
+ const coalesced = events.length === 1
54
+ ? events[0]
55
+ : { type: 'batch', events };
56
+ try {
57
+ await this._processEvent(coalesced, laneKey);
58
+ }
59
+ catch (error) {
60
+ this._metrics.errors += 1;
61
+ runtimeLog.error('EventQueue', `processEvent error for laneKey=${laneKey}: ${error?.message || error}`);
62
+ }
63
+ this._metrics.processedBatches += 1;
64
+ this._metrics.processedEvents += events.length;
65
+ for (const r of resolvers)
66
+ r();
67
+ }
68
+ state.processing = false;
69
+ }
70
+ _trimQueueIfNeeded(state, laneKey) {
71
+ while (state.events.length > this._maxQueueLengthPerLane) {
72
+ const dropped = state.events.shift();
73
+ this._metrics.dropped += 1;
74
+ dropped?.resolve();
75
+ runtimeLog.warn('EventQueue', `dropped oldest event laneKey=${laneKey} max=${this._maxQueueLengthPerLane}`);
76
+ }
77
+ }
78
+ get isIdle() {
79
+ return [...this._state.values()].every(s => !s.processing && s.events.length === 0);
80
+ }
81
+ /** Resolves when all lanes finish processing, or after timeoutMs. Best-effort — never throws. */
82
+ async waitForIdle(timeoutMs = 30_000) {
83
+ const deadline = Date.now() + timeoutMs;
84
+ while (Date.now() < deadline) {
85
+ if (this.isIdle)
86
+ return;
87
+ await new Promise(r => setTimeout(r, 50));
88
+ }
89
+ runtimeLog.warn('EventQueue', `waitForIdle timed out after ${timeoutMs}ms`);
90
+ }
91
+ getMetrics() {
92
+ return {
93
+ ...this._metrics,
94
+ lanes: this._state.size,
95
+ queuedEvents: [...this._state.values()].reduce((sum, lane) => sum + lane.events.length, 0),
96
+ };
97
+ }
98
+ }
@@ -0,0 +1,24 @@
1
+ import { type Creds } from '@ziggs-ai/api-client';
2
+ import { SeenMessages } from './SeenMessages.js';
3
+ export interface InboxCatchUpDeps {
4
+ creds: Creds;
5
+ ownAgentId: string;
6
+ baseUrl?: string;
7
+ seen: SeenMessages;
8
+ onMessage: (text: string, metadata: Record<string, unknown>) => Promise<void>;
9
+ onResourceEvent: (event: Record<string, unknown>) => Promise<void>;
10
+ label?: string;
11
+ }
12
+ export interface InboxCatchUpResult {
13
+ scopes: number;
14
+ messagesDelivered: number;
15
+ resourceEvents: number;
16
+ acked: number;
17
+ truncated: boolean;
18
+ }
19
+ /**
20
+ * Pull-based recovery after reconnect (ZIG-434 / ZIG-446 / ZIG-454).
21
+ * Inbox → read message deltas → deliver → ack. Push remains a hint;
22
+ * this path survives backend restarts that drop in-memory pending queues.
23
+ */
24
+ export declare function runInboxCatchUp(deps: InboxCatchUpDeps): Promise<InboxCatchUpResult>;