@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,143 @@
1
+ import type { Ctx, Outcome, Workflow, Identity, EffectHandler } from '../types.js';
2
+ import { type RawEvent } from './buildOutcome.js';
3
+ import type { runTurn as RunTurnFn } from './runTurn.js';
4
+ import { PromptBuilder } from './PromptBuilder.js';
5
+ import type { MemoryStore } from '../memory/MemoryStore.js';
6
+ import type { ToolManager } from '../tools/ToolManager.js';
7
+ export interface MachineSnapshot {
8
+ value: string;
9
+ ctx: Ctx;
10
+ status: 'active' | 'stopped';
11
+ }
12
+ export type TransitionEvent = {
13
+ kind: 'transition';
14
+ fromState: string;
15
+ toState: string;
16
+ outcomeKind: string;
17
+ depth: number;
18
+ } | {
19
+ kind: 'no-match';
20
+ state: string;
21
+ outcomeKind: string;
22
+ depth: number;
23
+ } | {
24
+ kind: 'depth-guard';
25
+ state: string;
26
+ depth: number;
27
+ } | {
28
+ kind: 'turn-budget-guard';
29
+ state: string;
30
+ turns: number;
31
+ } | {
32
+ kind: 'error-escape';
33
+ fromState: string;
34
+ toState: string;
35
+ } | {
36
+ kind: 'timeout-unhandled';
37
+ state: string;
38
+ waitedMs: number;
39
+ };
40
+ interface MachineInput {
41
+ identity: Identity;
42
+ /** Server-provided ctx for an existing session. If omitted, a fresh ctx is built. */
43
+ ctx?: Ctx;
44
+ /** Server-provided state name for an existing session. Defaults to workflow.initial. */
45
+ state?: string;
46
+ }
47
+ interface MachineOptions {
48
+ workflow: Workflow;
49
+ executionCore: typeof RunTurnFn;
50
+ /** Side channel for all I/O. Provided by the Server. */
51
+ eff: EffectHandler;
52
+ /** Pure tool metadata (schemas, flags). Owned by the Agent. */
53
+ toolManager: ToolManager;
54
+ promptBuilder: PromptBuilder;
55
+ /** Agent-private long-term memory. Operator-provided. */
56
+ memory?: MemoryStore;
57
+ input: MachineInput;
58
+ /** Optional tap for FSM transition events — called synchronously on each transition. */
59
+ transitionTap?: (event: TransitionEvent) => void;
60
+ /** Runaway-loop backstop: max thinking turns without human input before parking. Default 100. */
61
+ maxTurnsWithoutUserInput?: number;
62
+ }
63
+ interface IncomingFrame {
64
+ event?: RawEvent;
65
+ identity?: Identity;
66
+ }
67
+ type Subscriber = (snapshot: MachineSnapshot) => void;
68
+ /**
69
+ * Per-lane interpreter. Holds (current state name, Ctx). Re-entry is the
70
+ * single primitive: every external event and every turn produces exactly
71
+ * one Outcome that re-enters the current state.
72
+ *
73
+ * Parked states stop on entry. Thinking states run an LLM turn → produce an
74
+ * outcome → re-enter. Transitions are evaluated against the new outcome
75
+ * (first matching `when` wins; no `when` is fallthrough).
76
+ *
77
+ * `_running` guards against concurrent turns on the same machine — outer
78
+ * runtime is expected to serialize via EventQueue, but we belt-and-suspenders.
79
+ */
80
+ export declare class AgentMachine {
81
+ workflow: Workflow;
82
+ executionCore: typeof RunTurnFn;
83
+ eff: EffectHandler;
84
+ toolManager: ToolManager;
85
+ promptBuilder: PromptBuilder;
86
+ memory?: MemoryStore;
87
+ state: string;
88
+ status: 'active' | 'stopped';
89
+ ctx: Ctx;
90
+ _running: boolean;
91
+ _subscribers: Set<Subscriber>;
92
+ _prevSnapshot: MachineSnapshot | null;
93
+ _pendingFrames: IncomingFrame[];
94
+ _draining: boolean;
95
+ _llmCalls: number;
96
+ _tokens: {
97
+ total: number;
98
+ input: number;
99
+ output: number;
100
+ };
101
+ _lastUserSenderId: string | null;
102
+ _stateTimeoutUnhandled: boolean;
103
+ _transitionTap: ((event: TransitionEvent) => void) | undefined;
104
+ /** Thinking turns run since the last human (USER) frame. Reset on USER input. */
105
+ _turnsSinceUserInput: number;
106
+ _maxTurnsWithoutUserInput: number;
107
+ get llmCalls(): number;
108
+ get tokens(): {
109
+ total: number;
110
+ input: number;
111
+ output: number;
112
+ };
113
+ get stateTimeoutUnhandled(): boolean;
114
+ constructor({ workflow, executionCore, eff, toolManager, promptBuilder, memory, input, transitionTap, maxTurnsWithoutUserInput }: MachineOptions);
115
+ getSnapshot(): MachineSnapshot;
116
+ /** Parked = currently in a parked state and not executing a turn. */
117
+ get isParked(): boolean;
118
+ subscribe(fn: Subscriber): {
119
+ unsubscribe: () => void;
120
+ };
121
+ /**
122
+ * Process an incoming wire event: convert to outcome → apply to ctx →
123
+ * evaluate current state's transitions → re-enter or stop.
124
+ *
125
+ * Serialized via a single-slot queue: if a turn is already in flight, the
126
+ * frame is held until the running turn settles, then drained. Without this,
127
+ * an inbound event mid-turn re-entered _step with a stale state and a hot
128
+ * `_running=true`, which spawned overlapping turns and re-created the
129
+ * clarifyingDuringApproval feedback loop even after fixing the workflow
130
+ * fall-through (ZIG-181).
131
+ */
132
+ send(frame: IncomingFrame): Promise<void>;
133
+ _handleFrame(frame: IncomingFrame): Promise<void>;
134
+ _step(outcome: Outcome, depth?: number): Promise<void>;
135
+ /** Error with no author-defined escape: park at workflow.initial if it is parked. */
136
+ _escapeToInitialParked(outcome: Outcome, fromState: string): Promise<boolean>;
137
+ /** Sends a short fallback to the last known human user when the machine parks unexpectedly. Best-effort — never throws. */
138
+ _sendFallbackIfUserWaiting(text: string): Promise<void>;
139
+ _evaluateTransitions(outcome: Outcome): string | null;
140
+ _notify(): void;
141
+ _lastIncomingEvent: RawEvent | null;
142
+ }
143
+ export {};
@@ -0,0 +1,400 @@
1
+ import { runtimeLog } from '../shared/runtimeLog.js';
2
+ import { applyOutcomeToCtx, outcomeFromEvent } from './buildOutcome.js';
3
+ import { runMechanical } from './runMechanical.js';
4
+ import { bootstrapCtx } from './bootstrapCtx.js';
5
+ /**
6
+ * Max thinking turns a session may run without any human (USER) input before it
7
+ * is treated as a runaway loop and parked (ZIG-768). The depth-guard only bounds
8
+ * re-entries within a single event chain; this bounds turns ACROSS events, which
9
+ * is how a supervising session ran 436 turns in a day. The counter resets on
10
+ * every USER frame, so human-driven conversations are never affected — only
11
+ * autonomous agent↔agent loops accumulate toward it.
12
+ */
13
+ const DEFAULT_MAX_TURNS_WITHOUT_USER_INPUT = 100;
14
+ /**
15
+ * Per-lane interpreter. Holds (current state name, Ctx). Re-entry is the
16
+ * single primitive: every external event and every turn produces exactly
17
+ * one Outcome that re-enters the current state.
18
+ *
19
+ * Parked states stop on entry. Thinking states run an LLM turn → produce an
20
+ * outcome → re-enter. Transitions are evaluated against the new outcome
21
+ * (first matching `when` wins; no `when` is fallthrough).
22
+ *
23
+ * `_running` guards against concurrent turns on the same machine — outer
24
+ * runtime is expected to serialize via EventQueue, but we belt-and-suspenders.
25
+ */
26
+ export class AgentMachine {
27
+ workflow;
28
+ executionCore;
29
+ eff;
30
+ toolManager;
31
+ promptBuilder;
32
+ memory;
33
+ state;
34
+ status = 'active';
35
+ ctx;
36
+ _running = false;
37
+ _subscribers = new Set();
38
+ _prevSnapshot = null;
39
+ _pendingFrames = [];
40
+ _draining = false;
41
+ _llmCalls = 0;
42
+ _tokens = { total: 0, input: 0, output: 0 };
43
+ _lastUserSenderId = null;
44
+ _stateTimeoutUnhandled = false;
45
+ _transitionTap;
46
+ /** Thinking turns run since the last human (USER) frame. Reset on USER input. */
47
+ _turnsSinceUserInput = 0;
48
+ _maxTurnsWithoutUserInput;
49
+ get llmCalls() { return this._llmCalls; }
50
+ get tokens() { return this._tokens; }
51
+ get stateTimeoutUnhandled() { return this._stateTimeoutUnhandled; }
52
+ constructor({ workflow, executionCore, eff, toolManager, promptBuilder, memory, input, transitionTap, maxTurnsWithoutUserInput }) {
53
+ this.workflow = workflow;
54
+ this.executionCore = executionCore;
55
+ this.eff = eff;
56
+ this.toolManager = toolManager;
57
+ this.promptBuilder = promptBuilder;
58
+ this.memory = memory;
59
+ this._transitionTap = transitionTap;
60
+ this._maxTurnsWithoutUserInput = maxTurnsWithoutUserInput ?? DEFAULT_MAX_TURNS_WITHOUT_USER_INPUT;
61
+ this.state = input.state || workflow.initial;
62
+ this.ctx = bootstrapCtx(input.identity, input.ctx);
63
+ }
64
+ getSnapshot() {
65
+ return { value: this.state, ctx: this.ctx, status: this.status };
66
+ }
67
+ /** Parked = currently in a parked state and not executing a turn. */
68
+ get isParked() {
69
+ if (this._running)
70
+ return false;
71
+ const sd = this.workflow.states[this.state];
72
+ return sd?.kind === 'parked';
73
+ }
74
+ subscribe(fn) {
75
+ this._subscribers.add(fn);
76
+ return { unsubscribe: () => this._subscribers.delete(fn) };
77
+ }
78
+ /**
79
+ * Process an incoming wire event: convert to outcome → apply to ctx →
80
+ * evaluate current state's transitions → re-enter or stop.
81
+ *
82
+ * Serialized via a single-slot queue: if a turn is already in flight, the
83
+ * frame is held until the running turn settles, then drained. Without this,
84
+ * an inbound event mid-turn re-entered _step with a stale state and a hot
85
+ * `_running=true`, which spawned overlapping turns and re-created the
86
+ * clarifyingDuringApproval feedback loop even after fixing the workflow
87
+ * fall-through (ZIG-181).
88
+ */
89
+ async send(frame) {
90
+ if (this.status !== 'active')
91
+ return;
92
+ this._pendingFrames.push(frame);
93
+ if (this._draining)
94
+ return;
95
+ this._draining = true;
96
+ try {
97
+ while (this._pendingFrames.length) {
98
+ const next = this._pendingFrames.shift();
99
+ await this._handleFrame(next);
100
+ }
101
+ }
102
+ finally {
103
+ this._draining = false;
104
+ }
105
+ }
106
+ async _handleFrame(frame) {
107
+ if (this.status !== 'active')
108
+ return;
109
+ if (frame.identity)
110
+ this.ctx.identity = frame.identity;
111
+ const ev = frame.event;
112
+ this._lastIncomingEvent = ev ?? null;
113
+ if (ev?.senderId && String(ev?.senderType ?? '').toUpperCase() === 'USER') {
114
+ this._lastUserSenderId = String(ev.senderId);
115
+ // Human input is real progress — reset the runaway-loop budget (ZIG-768).
116
+ this._turnsSinceUserInput = 0;
117
+ }
118
+ runtimeLog.debug('FSM', `_handleFrame ev.type=${ev?.type} ev.senderId=${ev?.senderId} ev.receiverId=${ev?.receiverId} myId=${this.ctx.identity?.agentId}`);
119
+ const outcome = outcomeFromEvent(ev, this.ctx.identity.agentId);
120
+ // `outcomeFromEvent` never returns null — it falls back to `{ kind: 'enter' }`
121
+ // when all events in a batch are no-ops (resource_changed, self-echo, etc.).
122
+ // If a real raw event produced `enter`, that means the event carried no
123
+ // meaningful signal for the FSM, so skip it entirely. Without this guard
124
+ // `idle`'s unconditional `{ to: 'understanding' }` transition fires on every
125
+ // resource_changed wake-up, kicking off an LLM turn that sends a duplicate
126
+ // message, which triggers another resource_changed, and so on.
127
+ if (ev && outcome.kind === 'enter')
128
+ return;
129
+ // Proposer detection must read pendingProposalAgreementId BEFORE
130
+ // applyOutcomeToCtx clears it on resolution (buildOutcome.ts). Wire frames
131
+ // carry no trustworthy proposer field — agreement_lifecycle drops `parties`
132
+ // in normalizeIncomingEvent, and parties.creator is a principal id, not an
133
+ // agentId (backend ZIG-272) — so the SDK's own "I made this proposal"
134
+ // memory is the signal. Side effect we rely on: the first resolution clears
135
+ // the pending id, so duplicate approval frames compute proposedByMe=false
136
+ // and can never double-kickoff.
137
+ if (outcome.kind === 'proposal-resolved') {
138
+ outcome.proposedByMe =
139
+ this.ctx.pendingProposalAgreementId !== null &&
140
+ outcome.proposal.agreementId === this.ctx.pendingProposalAgreementId;
141
+ }
142
+ applyOutcomeToCtx(this.ctx, outcome);
143
+ await this._step(outcome);
144
+ }
145
+ async _step(outcome, depth = 0) {
146
+ const fromState = this.state;
147
+ const target = this._evaluateTransitions(outcome);
148
+ const o = outcome;
149
+ runtimeLog.debug('FSM', `_step from=${fromState} outcome.kind=${o?.kind} senderId=${o?.senderId ?? '<none>'} myId=${this.ctx.identity?.agentId ?? '<none>'} → target=${target ?? '(stay)'} depth=${depth}`);
150
+ if (target && target !== this.state) {
151
+ this._transitionTap?.({ kind: 'transition', fromState, toState: target, outcomeKind: outcome.kind, depth });
152
+ this.state = target;
153
+ this._notify();
154
+ }
155
+ // A state-timeout only enters at depth 0 (host-armed frame via _handleFrame;
156
+ // turns cannot produce it — validateWorkflow rejects produces:'state-timeout').
157
+ // No matching transition = the author declared timeoutMs but no route: park
158
+ // loudly and tell the host not to re-arm (re-arming would error every
159
+ // timeoutMs forever). Next real event still recovers the lane. A matched
160
+ // transition (target !== null) — including an explicit self-loop — is
161
+ // "handled" and re-arms as a heartbeat.
162
+ if (outcome.kind === 'state-timeout' && depth === 0 && !target) {
163
+ this._stateTimeoutUnhandled = true;
164
+ runtimeLog.error('AgentMachine', `state=${this.state} timed out after ${outcome.waitedMs}ms but no transition matches { kind: 'state-timeout' } — parking without a timer. Add a state-timeout transition to "${this.state}" or remove timeoutMs.`);
165
+ this._transitionTap?.({ kind: 'timeout-unhandled', state: this.state, waitedMs: outcome.waitedMs });
166
+ this._notify();
167
+ return;
168
+ }
169
+ const sd = this.workflow.states[this.state];
170
+ if (!sd)
171
+ return;
172
+ if (sd.kind === 'parked') {
173
+ runtimeLog.debug('FSM', `_step parked state=${this.state} — exit`);
174
+ // Notify so runtime detects park and resolves the wait promise.
175
+ this._notify();
176
+ return;
177
+ }
178
+ if (sd.kind === 'mechanical') {
179
+ if (depth >= 12) {
180
+ runtimeLog.warn('AgentMachine', `state=${this.state} runaway mechanical loop (depth=${depth}); parking`);
181
+ this._transitionTap?.({ kind: 'depth-guard', state: this.state, depth });
182
+ await this._sendFallbackIfUserWaiting('Something went wrong. Please try again.');
183
+ this._notify();
184
+ return;
185
+ }
186
+ if (this._turnsSinceUserInput >= this._maxTurnsWithoutUserInput) {
187
+ runtimeLog.warn('AgentMachine', `state=${this.state} runaway loop: ${this._turnsSinceUserInput} turns without user input (cap ${this._maxTurnsWithoutUserInput}); parking`);
188
+ this._transitionTap?.({ kind: 'turn-budget-guard', state: this.state, turns: this._turnsSinceUserInput });
189
+ await this._sendFallbackIfUserWaiting('I got stuck in a loop and paused. Please send another message to continue.');
190
+ this._notify();
191
+ return;
192
+ }
193
+ runtimeLog.debug('FSM', `_step mechanical state=${this.state} — run tool`);
194
+ this._running = true;
195
+ try {
196
+ const { outcome: turnOutcome, toolResults, llmCalls, tokens } = await runMechanical({
197
+ stateId: this.state,
198
+ state: sd,
199
+ ctx: this.ctx,
200
+ eff: this.eff,
201
+ memory: this.memory,
202
+ incomingEvent: this._lastIncomingEvent,
203
+ });
204
+ this.ctx.toolResults = toolResults;
205
+ this._llmCalls += llmCalls;
206
+ this._turnsSinceUserInput += 1;
207
+ this._tokens.total += tokens.total;
208
+ this._tokens.input += tokens.input;
209
+ this._tokens.output += tokens.output;
210
+ applyOutcomeToCtx(this.ctx, turnOutcome);
211
+ this._running = false;
212
+ this._notify();
213
+ await this._step(turnOutcome, depth + 1);
214
+ }
215
+ catch (error) {
216
+ runtimeLog.error('AgentMachine', `mechanical state=${this.state} error: ${error?.message || error}`);
217
+ const errOutcome = {
218
+ kind: 'error',
219
+ source: 'unknown',
220
+ cause: error?.message || String(error),
221
+ retryable: false,
222
+ };
223
+ applyOutcomeToCtx(this.ctx, errOutcome);
224
+ this._running = false;
225
+ this._notify();
226
+ const fallback = this._evaluateTransitions(errOutcome);
227
+ if (fallback && fallback !== this.state) {
228
+ this.state = fallback;
229
+ this._notify();
230
+ }
231
+ else {
232
+ await this._sendFallbackIfUserWaiting('Something went wrong. Please try again.');
233
+ }
234
+ }
235
+ return;
236
+ }
237
+ // A thinking turn that yields an outcome no transition handles must not
238
+ // silently re-enter the same state and burn another LLM call (ZIG-303).
239
+ // Explicit self-loops still match (target is non-null, even if target === state).
240
+ if (sd.kind === 'thinking' &&
241
+ depth > 0 &&
242
+ fromState === this.state &&
243
+ !target) {
244
+ if (await this._escapeToInitialParked(outcome, fromState))
245
+ return;
246
+ runtimeLog.warn('AgentMachine', `state=${fromState} outcome.kind=${outcome.kind} matched no transition; parking (depth=${depth})`);
247
+ this._transitionTap?.({ kind: 'no-match', state: fromState, outcomeKind: outcome.kind, depth });
248
+ await this._sendFallbackIfUserWaiting('Something went wrong. Please try again.');
249
+ this._notify();
250
+ return;
251
+ }
252
+ // Recursion / runaway-turn guard. Without this, a thinking state whose
253
+ // transitions don't match the turn outcome (e.g. `delegating` not
254
+ // catching `tool-result` from an agent_network search) re-enters the
255
+ // same state forever — the LLM keeps re-rolling search/dummy actions
256
+ // until something accidentally matches, burning tokens and emitting
257
+ // duplicate side effects (ZIG-181: ziggs ran `discoverSpecialist` six
258
+ // times before finally invoking `delegate`). 12 is well above any
259
+ // legitimate same-state turn chain we exercise in tests; if we trip
260
+ // it, treat the state as wedged and park.
261
+ if (depth >= 12) {
262
+ runtimeLog.warn('AgentMachine', `state=${this.state} runaway turn loop (depth=${depth}); parking`);
263
+ this._transitionTap?.({ kind: 'depth-guard', state: this.state, depth });
264
+ await this._sendFallbackIfUserWaiting('Something went wrong. Please try again.');
265
+ this._notify();
266
+ return;
267
+ }
268
+ // Cross-event runaway guard (ZIG-768). The depth-guard above only bounds
269
+ // one event chain; a supervising session that keeps waking on agent/system
270
+ // messages re-enters at depth 0 each time and never trips it. Bound the
271
+ // total thinking turns since the last human input instead — a runaway
272
+ // agent↔agent loop climbs toward the cap while a human conversation resets
273
+ // it on every user message.
274
+ if (this._turnsSinceUserInput >= this._maxTurnsWithoutUserInput) {
275
+ runtimeLog.warn('AgentMachine', `state=${this.state} runaway loop: ${this._turnsSinceUserInput} turns without user input (cap ${this._maxTurnsWithoutUserInput}); parking`);
276
+ this._transitionTap?.({ kind: 'turn-budget-guard', state: this.state, turns: this._turnsSinceUserInput });
277
+ await this._sendFallbackIfUserWaiting('I got stuck in a loop and paused. Please send another message to continue.');
278
+ this._notify();
279
+ return;
280
+ }
281
+ runtimeLog.debug('FSM', `_step thinking state=${this.state} — run turn`);
282
+ // Thinking: run a turn, produce an outcome, re-step.
283
+ this._running = true;
284
+ try {
285
+ const { outcome: turnOutcome, toolResults, llmCalls, tokens } = await this.executionCore({
286
+ stateId: this.state,
287
+ statePrompt: sd.prompt,
288
+ actions: sd.actions,
289
+ incomingEvent: this._lastIncomingEvent,
290
+ ctx: this.ctx,
291
+ eff: this.eff,
292
+ toolManager: this.toolManager,
293
+ promptBuilder: this.promptBuilder,
294
+ memory: this.memory,
295
+ definition: this.workflow,
296
+ });
297
+ this.ctx.toolResults = toolResults;
298
+ this._llmCalls += llmCalls;
299
+ this._turnsSinceUserInput += 1;
300
+ this._tokens.total += tokens.total;
301
+ this._tokens.input += tokens.input;
302
+ this._tokens.output += tokens.output;
303
+ applyOutcomeToCtx(this.ctx, turnOutcome);
304
+ this._running = false;
305
+ this._notify();
306
+ await this._step(turnOutcome, depth + 1);
307
+ }
308
+ catch (error) {
309
+ runtimeLog.error('AgentMachine', `state=${this.state} error: ${error?.message || error}`);
310
+ const errOutcome = {
311
+ kind: 'error',
312
+ source: 'unknown',
313
+ cause: error?.message || String(error),
314
+ retryable: false,
315
+ };
316
+ applyOutcomeToCtx(this.ctx, errOutcome);
317
+ this._running = false;
318
+ this._notify();
319
+ // Try to fall through using current state's transitions; if none match
320
+ // the error outcome the machine just parks (next event will re-enter).
321
+ const fallback = this._evaluateTransitions(errOutcome);
322
+ if (fallback && fallback !== this.state) {
323
+ this.state = fallback;
324
+ this._notify();
325
+ }
326
+ else {
327
+ await this._sendFallbackIfUserWaiting('Something went wrong. Please try again.');
328
+ }
329
+ }
330
+ }
331
+ /** Error with no author-defined escape: park at workflow.initial if it is parked. */
332
+ async _escapeToInitialParked(outcome, fromState) {
333
+ if (outcome.kind !== 'error')
334
+ return false;
335
+ const initial = this.workflow.initial;
336
+ const initialSd = this.workflow.states[initial];
337
+ if (!initialSd || initialSd.kind !== 'parked' || initial === fromState) {
338
+ return false;
339
+ }
340
+ runtimeLog.warn('AgentMachine', `state=${fromState} error outcome had no transition — escaped to parked initial "${initial}"`);
341
+ this._transitionTap?.({ kind: 'error-escape', fromState, toState: initial });
342
+ this.state = initial;
343
+ // The turn failed and no workflow transition handled it — a waiting human
344
+ // got nothing, so apologize before parking (same text as the other parks).
345
+ await this._sendFallbackIfUserWaiting('Something went wrong. Please try again.');
346
+ this._notify();
347
+ return true;
348
+ }
349
+ /** Sends a short fallback to the last known human user when the machine parks unexpectedly. Best-effort — never throws. */
350
+ async _sendFallbackIfUserWaiting(text) {
351
+ const userId = this._lastUserSenderId;
352
+ if (!userId)
353
+ return;
354
+ try {
355
+ await this.eff({ kind: 'send-message', sessionId: this.ctx.identity.sessionId, receiverId: userId, text });
356
+ }
357
+ catch { /* best-effort */ }
358
+ }
359
+ _evaluateTransitions(outcome) {
360
+ const sd = this.workflow.states[this.state];
361
+ if (!sd?.transitions?.length) {
362
+ runtimeLog.debug('FSM', `evaluate state=${this.state}: NO TRANSITIONS DEFINED`);
363
+ return null;
364
+ }
365
+ for (let i = 0; i < sd.transitions.length; i++) {
366
+ const rule = sd.transitions[i];
367
+ let matched;
368
+ let err = null;
369
+ if (!rule.when) {
370
+ matched = true;
371
+ }
372
+ else {
373
+ try {
374
+ matched = !!rule.when(outcome, this.ctx);
375
+ }
376
+ catch (e) {
377
+ matched = false;
378
+ err = e;
379
+ }
380
+ }
381
+ runtimeLog.debug('FSM', `evaluate state=${this.state} rule[${i}] to=${rule.to} when=${rule.when ? 'fn' : 'always'} → ${matched}${err ? ` ERR=${err.message}` : ''}`);
382
+ if (matched)
383
+ return rule.to;
384
+ }
385
+ runtimeLog.debug('FSM', `evaluate state=${this.state}: NONE matched`);
386
+ return null;
387
+ }
388
+ _notify() {
389
+ const snapshot = this.getSnapshot();
390
+ for (const fn of this._subscribers) {
391
+ try {
392
+ fn(snapshot);
393
+ }
394
+ catch { }
395
+ }
396
+ }
397
+ // Held so PromptBuilder/runTurn can render the original wire event in <event>.
398
+ // Set by the runtime layer when it calls send(); we don't classify it twice.
399
+ _lastIncomingEvent = null;
400
+ }
@@ -0,0 +1,96 @@
1
+ import type { Action, CollectedSubtaskResult, Ctx, PromptDef } from '../types.js';
2
+ import type { RawEvent } from './buildOutcome.js';
3
+ type AnyObj = Record<string, unknown>;
4
+ interface ServerContext {
5
+ history?: AnyObj[];
6
+ agreements?: AnyObj[];
7
+ agents?: AnyObj[];
8
+ users?: AnyObj[];
9
+ [key: string]: unknown;
10
+ }
11
+ interface ServerContextBundle {
12
+ context: ServerContext;
13
+ tools: AnyObj[];
14
+ }
15
+ interface SenderMeta {
16
+ senderId?: string;
17
+ senderType?: string;
18
+ [key: string]: unknown;
19
+ }
20
+ interface BuildArgs {
21
+ statePrompt: PromptDef;
22
+ actions: Record<string, Action>;
23
+ serverContext: ServerContextBundle;
24
+ incomingEvent: RawEvent | null;
25
+ definition: {
26
+ description?: string;
27
+ };
28
+ chatId: string | null;
29
+ stateId: string;
30
+ ctx: Ctx;
31
+ /** When true, respond-action schemas omit the "message" field — used for 2-call streaming (ZIG-471). */
32
+ omitMessageFromRespond?: boolean;
33
+ }
34
+ interface HistoryFormatterI {
35
+ format(history: unknown[] | null | undefined, agentId: string, options?: AnyObj): string;
36
+ }
37
+ interface AgreementFormatterI {
38
+ format(agreements: unknown[] | null): string;
39
+ }
40
+ interface LoggerI {
41
+ info?(...args: unknown[]): void;
42
+ warn?(...args: unknown[]): void;
43
+ debug?(...args: unknown[]): void;
44
+ }
45
+ export interface PromptBuilderOptions {
46
+ description?: string;
47
+ specialization?: string | null;
48
+ maxHistoryEntries?: number;
49
+ /**
50
+ * Token budget for the <history> block. History is included newest-first up
51
+ * to this many (estimated) tokens, so a few large turns can't blow the prompt
52
+ * up to tens of thousands of input tokens (ZIG-769). maxHistoryEntries still
53
+ * applies as a secondary hard ceiling.
54
+ */
55
+ maxHistoryTokens?: number;
56
+ historyFormatter?: HistoryFormatterI;
57
+ agreementFormatter?: AgreementFormatterI;
58
+ logger?: LoggerI | null | {
59
+ warn: (msg: string) => void;
60
+ };
61
+ }
62
+ /**
63
+ * Builds the XML prompt for a thinking-state turn:
64
+ * <agent><self/><world/><decision_schema/></agent>
65
+ *
66
+ * Reads Ctx (identity + persistent ids + lastOutcome) for <machine_context>.
67
+ * Action tool binding is direct (action.tool). Decision schema documents the
68
+ * action's `produces` outcome kind.
69
+ */
70
+ export declare class PromptBuilder {
71
+ description: string;
72
+ specialization: string | null;
73
+ maxHistoryEntries: number;
74
+ maxHistoryTokens: number;
75
+ historyFormatter: HistoryFormatterI;
76
+ agreementFormatter: AgreementFormatterI;
77
+ logger: LoggerI | null;
78
+ constructor(options?: PromptBuilderOptions);
79
+ buildFromState(args: BuildArgs): string;
80
+ _renderSelfFromState(description: string, statePrompt: PromptDef, tools?: AnyObj[]): string;
81
+ _renderWorld(event: RawEvent | null, ctxObj: ServerContext, agentId: string | null, senderMeta: SenderMeta, agreements: AnyObj[], otherAgents: AnyObj[], users: AnyObj[], stateId: string, ctx: Ctx): string;
82
+ _renderMachineContext(stateId: string, ctx: Ctx): string;
83
+ _renderSubtaskResults(results: CollectedSubtaskResult[]): string;
84
+ _renderEvent(event: RawEvent | AnyObj | null, senderMeta: SenderMeta): string;
85
+ _renderTaskResultEvent(task: AnyObj, from: string): string;
86
+ _renderHistory(history: AnyObj[] | undefined, agentId: string | null): string;
87
+ /** Rough character size of one history entry, used for the token budget. */
88
+ _estimateEntryChars(entry: AnyObj): number;
89
+ _renderAgreements(agreements: AnyObj[]): string;
90
+ _renderRecipients(users: AnyObj[], otherAgents: AnyObj[], agreements: AnyObj[], myAgentId: string | null | undefined): string;
91
+ _renderDecisionSchemaFromActions(actions: Record<string, Action>, senderMeta: SenderMeta, ctxObj: ServerContext, omitMessage?: boolean): string;
92
+ _buildActionSchema(name: string, replyTo: string, omitMessage?: boolean): string;
93
+ _getSenderMeta(event: RawEvent | AnyObj | null, ctxObj: ServerContext): SenderMeta;
94
+ _getMyAgentId(ctxObj: ServerContext): string | null;
95
+ }
96
+ export {};