@ziggs-ai/agent-sdk 0.38.15 → 0.38.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.38.16
6
+
7
+ - A hosted agent can set the same agreement interface as the shared term definition, and a value outside that definition is not sent. No message-cap scope is offered.
8
+
3
9
  ## 0.38.15
4
10
 
5
11
  - Use api-client 0.37.18 (inbox observation cursor and notification status).
@@ -14,7 +20,7 @@
14
20
 
15
21
  - Notes kept with an operator key are agent-private artifacts, so they survive a restart and are not announced.
16
22
 
17
- ## Unreleased — agent vocabulary and lifecycle (breaking)
23
+ ## Unreleased: agent vocabulary and lifecycle (breaking)
18
24
 
19
25
  - `Agent` names the body returned by `createAgent`; decision engines are `ZiggsBrain` and `ClaudeBrain`. See the README migration table.
20
26
  - Platform integration moves to `src/platform`, brains to `src/brain`, and external MCP tool composition to `src/mcp`.
@@ -72,42 +78,42 @@
72
78
 
73
79
  - `AgentHostOptions.questMatchTags` is now `requestMatchTags`. The option is an
74
80
  allow-list field, so an agent still passing the old name has it silently
75
- dropped rather than rejected — rename the call site.
81
+ dropped rather than rejected: rename the call site.
76
82
  - The `quest` vocabulary is renamed to `request` throughout: the inbox delivery
77
83
  kind an agent reads, the `ClaimedKind` a claim returns, and the marketplace
78
84
  listing fields. Re-exported api-client types move with it.
79
85
 
80
- ## 0.10.0 — unreleased
86
+ ## 0.10.0 (unreleased)
81
87
 
82
88
  ### Removed (breaking)
83
89
 
84
- - Removed exports `createStaleBacklogGate`, `resolveReplayPolicy`, `newestActionableAt`, `parseReplayMaxAge`. The launcher rewrite deleted the wake machinery these gated: every agent stays connected and the write signals it, so there is no backlog to age out and nothing to replay-gate. Pinned to 0.4.x and importing any of the four? Delete the call — the behaviour it bought is now unconditional. There is no replacement API.
90
+ - Removed exports `createStaleBacklogGate`, `resolveReplayPolicy`, `newestActionableAt`, `parseReplayMaxAge`. The launcher rewrite deleted the wake machinery these gated: every agent stays connected and the write signals it, so there is no backlog to age out and nothing to replay-gate. Pinned to 0.4.x and importing any of the four? Delete the call: the behaviour it bought is now unconditional. There is no replacement API.
85
91
  - `FLEET_REPLAY_MAX_AGE` is no longer read; the replay window it set no longer exists. Nothing in this repo sets it, but it can come out of any deployment env that does.
86
- - `Ctx` no longer carries `pendingSubtasks`, `collectedResults`, or `delegatedAgreementIds`. The real bookkeeping moved into `Ctx.tasks` (keyed by taskId) and these mirrors stopped syncing, but they stayed on the interface as required-and-always-empty — so a workflow guard reading one saw empty forever and silently checked nothing. Read via `allPendingSubtasks(ctx)` / `allCollectedResults(ctx)` / `allDelegatedAgreementIds(ctx)`. Sessions persisted before `tasks` existed are still migrated on restore.
92
+ - `Ctx` no longer carries `pendingSubtasks`, `collectedResults`, or `delegatedAgreementIds`. The real bookkeeping moved into `Ctx.tasks` (keyed by taskId) and these mirrors stopped syncing, but they stayed on the interface as required-and-always-empty, so a workflow guard reading one saw empty forever and silently checked nothing. Read via `allPendingSubtasks(ctx)` / `allCollectedResults(ctx)` / `allDelegatedAgreementIds(ctx)`. Sessions persisted before `tasks` existed are still migrated on restore.
87
93
  - `WireResult.status` removed. The wire carries `state` only; the `|| r.status` fallback tails were removed so a backend regression fails loudly instead of being absorbed. Task state resolves as `task?.state || r.state`.
88
94
 
89
95
  ### Added
90
96
 
91
- - Declared polls: an agent names the external sources it depends on (`polls`, `pollKinds`) and never executes them. New exports `PollRunner`, `PollRegistry`, `declaredPolls`, and types `PollBinding`, `PollContext`, `PollKind`, `PollResult`, `PollRunnerOptions`, `WakeTarget`. `runLauncher` runs declared polls at fleet level — outside any `AgentHost`, because a host is evicted when idle and that is exactly when its source still needs watching.
97
+ - Declared polls: an agent names the external sources it depends on (`polls`, `pollKinds`) and never executes them. New exports `PollRunner`, `PollRegistry`, `declaredPolls`, and types `PollBinding`, `PollContext`, `PollKind`, `PollResult`, `PollRunnerOptions`, `WakeTarget`. `runLauncher` runs declared polls at fleet level, outside any `AgentHost`, because a host is evicted when idle and that is exactly when its source still needs watching.
92
98
 
93
99
  ### Changed
94
100
 
95
101
  - Attention gate: only the addressed agent spends a turn. A message addressed elsewhere in a shared room no longer wakes every participant.
96
- - No turn on a lane whose agreement has ended, and none on the echo of a task that already reported — both previously cost a turn and produced nothing.
102
+ - No turn on a lane whose agreement has ended, and none on the echo of a task that already reported; both previously cost a turn and produced nothing.
97
103
  - Persona wire shapes are understood on inbound frames.
98
104
  - A wake declares its lane so the backend can fence reach.
99
105
  - A proposal awaiting the agent's own consent counts as actionable rather than a notice to park.
100
106
  - Artifact idempotency keys are derived from content.
101
107
  - Blank tool arguments fall back to the wake's lane.
102
108
 
103
- ## 0.9.0 — 2026-07-30
109
+ ## 0.9.0 (2026-07-30)
104
110
 
105
111
  ### Changed (breaking)
106
112
 
107
- - `task-update` / `task_set_result` accept `state` only — legacy `status` alias removed.
113
+ - `task-update` / `task_set_result` accept `state` only: legacy `status` alias removed.
108
114
  - Removed export `syncAggregateTaskViews`; read pending/collected/delegated via `allPendingSubtasks` / `allCollectedResults` / `allDelegatedAgreementIds`. Ctx aggregate mirrors stay empty.
109
115
 
110
- ## 0.4.0 — 2026-07-23
116
+ ## 0.4.0 (2026-07-23)
111
117
 
112
118
  ### Added
113
119
 
@@ -119,21 +125,21 @@
119
125
  - Delivery-law: classify inbound messages from the `service` wire shape.
120
126
  - Bumped `@ziggs-ai/api-client` to `^0.3.0`.
121
127
 
122
- ## 0.3.0 — 2026-07-22
128
+ ## 0.3.0 (2026-07-22)
123
129
 
124
130
  ### Changed (breaking)
125
131
 
126
132
  - Agent delivery law: backend targeting policy, idempotent writes, and long-poll / multiplexed inbox.
127
133
 
128
- ## 0.2.0 — 2026-07-16
134
+ ## 0.2.0 (2026-07-16)
129
135
 
130
136
  ### Added
131
137
 
132
138
  - Public brain selection API: `ziggsBrain()`, `claudeBrain()`, and `Brain` interface (`tick()`).
133
- - `AgentHost` / `createAgent` accept `brain` — preferred over legacy `cognition: 'fsm' | 'claude-sdk'`.
139
+ - `AgentHost` / `createAgent` accept `brain`: preferred over legacy `cognition: 'fsm' | 'claude-sdk'`.
134
140
  - README operator flow for picking Ziggs vs Claude brain.
135
141
  - `npm run build` + `prepack` for npm publish (compiled `dist/` + `.d.ts`).
136
142
 
137
143
  ### Deprecated
138
144
 
139
- - `cognition` option on `AgentHost` — use `brain: ziggsBrain()` or `brain: claudeBrain()` instead.
145
+ - `cognition` option on `AgentHost`: use `brain: ziggsBrain()` or `brain: claudeBrain()` instead.
package/README.md CHANGED
@@ -131,9 +131,40 @@ configureApiClient({ httpUrl: 'https://api.example.com' });
131
131
 
132
132
  A **runner** is the operator’s process. It starts agents, serves health and
133
133
  coordinates shutdown. It is not another part of the customer’s agent model.
134
- `runAgents` and `createHealthServer` are exported from this package. The health
135
- page answers `/` and `/health`. A process that needs another route, such as
136
- starting one of our hosted agents, adds it itself.
134
+
135
+ ## Run one agent or many
136
+
137
+ `runAgents` is the launcher. Pass one agent's options, or an array of them to
138
+ host several in one process.
139
+
140
+ ```ts
141
+ import { runAgents, claudeBrain } from '@ziggs-ai/agent-sdk';
142
+
143
+ const run = await runAgents(
144
+ [
145
+ { agentId: 'writer', brain: claudeBrain() },
146
+ { agentId: 'reviewer', brain: claudeBrain() },
147
+ ],
148
+ { label: 'my-fleet', port: 8080 },
149
+ );
150
+ ```
151
+
152
+ - **Key.** Each agent uses its own `operatorKey` if it has one, otherwise
153
+ `options.operatorKey` or `ZIGGS_OPERATOR_KEY`. One agent can run on a key
154
+ bound to it. Several need an unbound owner key with `agents:impersonate`; a
155
+ bound key covers only its own agent, and the launcher warns when you try.
156
+ - **Health.** A page on `port` (default `PORT` or 8080) answers `/` and
157
+ `/health` from the agents themselves. `healthServer: false` turns it off;
158
+ `onRequest` adds a route of your own.
159
+ - **Stopping.** On `SIGTERM` or `SIGINT` (or `run.shutdown()`), every agent stops
160
+ taking mail, acknowledges what it finished and drains its work, within
161
+ `drainTimeoutMs` (90 s by default). `handleSignals: false` leaves the signals
162
+ to your process; `onShutdown` runs after the agents stop.
163
+ - **Partial start.** If some agents fail to connect, the rest keep running and
164
+ the launcher logs how many came up. If none do, it throws so the process can
165
+ restart.
166
+
167
+ `createHealthServer` is exported too, for a process that runs agents itself.
137
168
 
138
169
  ## Migration
139
170
 
@@ -154,4 +185,4 @@ names. No backend identity or stored data migration is required.
154
185
  ## Requirements
155
186
 
156
187
  Node.js 18 or later, an authorized Ziggs operator key and the provider key for
157
- your selected brain/model. MIT license; see package metadata.
188
+ your selected brain/model. Apache-2.0; see [LICENSE](../LICENSE).
@@ -45,6 +45,11 @@ function normalizePlanInPlace(payload) {
45
45
  ? 'before_execution'
46
46
  : 'with_proposal';
47
47
  }
48
+ function oneOf(value, allowed) {
49
+ return typeof value === 'string' && allowed.includes(value)
50
+ ? value
51
+ : undefined;
52
+ }
48
53
  /** The `sources` a protocol call carried, when it carried a list. */
49
54
  function sourcesOf(payload) {
50
55
  return Array.isArray(payload.sources)
@@ -92,6 +97,7 @@ cachedAgreements) {
92
97
  if (!payload.agreementId)
93
98
  return null;
94
99
  // The price is the one the model stated; the SDK adds none.
100
+ const interactionMode = oneOf(payload['interactionMode'], ['messages', 'tasks', 'both']);
95
101
  return agreementService.counter(payload.agreementId, {
96
102
  price: payload.price,
97
103
  agreementDescription: payload.agreementDescription,
@@ -100,6 +106,10 @@ cachedAgreements) {
100
106
  maxExecutions: payload.maxExecutions,
101
107
  description: payload.description,
102
108
  ...sourcesOf(payload),
109
+ ...(interactionMode ? { interactionMode } : {}),
110
+ ...(payload.conversationAllowance !== undefined
111
+ ? { conversationAllowance: payload.conversationAllowance }
112
+ : {}),
103
113
  });
104
114
  case 'agreement-check-proposal':
105
115
  if (!payload.agreementId)
@@ -282,6 +292,7 @@ async function proposeAgreement(services, payload, chatId, writeKeys = null) {
282
292
  const billing = payload['billing'] === 'per_task' || payload['billing'] === 'total'
283
293
  ? payload['billing']
284
294
  : undefined;
295
+ const interactionMode = oneOf(payload['interactionMode'], ['messages', 'tasks', 'both']);
285
296
  const { agreement, shape } = await services.agreementService.proposeUnified({
286
297
  proposedTo: payload.proposedTo,
287
298
  chatId: chatId ?? undefined,
@@ -292,6 +303,7 @@ async function proposeAgreement(services, payload, chatId, writeKeys = null) {
292
303
  expiresAt: typeof payload.expiresAt === 'string' ? payload.expiresAt : undefined,
293
304
  maxExecutions: typeof payload.maxExecutions === 'number' ? payload.maxExecutions : undefined,
294
305
  billing,
306
+ ...(interactionMode ? { interactionMode } : {}),
295
307
  ...(payload.conversationAllowance !== undefined ? { conversationAllowance: payload.conversationAllowance } : {}),
296
308
  agreementDescription: payload.agreementDescription,
297
309
  ...sourcesOf(payload),
@@ -1,15 +1,8 @@
1
1
  import { defineTool } from '../../../tools/defineTool.js';
2
2
  import { requireProtocolOperation } from '../../../tasks/protocolRegistry.js';
3
3
  import { dispatchProtocolOp } from './protocolRunner.js';
4
- import { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION, agreementClaimCapability, agreementSubcontractCapability, SOURCES_PARAM, } from '@ziggs-ai/api-client';
5
- /** keep these enums in lockstep with MCP ziggs_agreement_* / ziggs_task_create. */
6
- const LIFECYCLE_ENUM = ['open', 'time-bound', 'count-bound'];
4
+ import { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION, AGREEMENT_TERMS_PARAMS, agreementClaimCapability, agreementSubcontractCapability, SOURCES_PARAM, } from '@ziggs-ai/api-client';
7
5
  const PLAN_REVIEW_TIMING_ENUM = ['with_proposal', 'before_execution'];
8
- const lifecycleParam = {
9
- type: 'string',
10
- enum: [...LIFECYCLE_ENUM],
11
- description: "Usually inferred: expiresAt → 'time-bound', maxExecutions → 'count-bound', neither → 'open' (standing).",
12
- };
13
6
  const planReviewTimingParam = {
14
7
  type: 'string',
15
8
  enum: [...PLAN_REVIEW_TIMING_ENUM],
@@ -60,7 +53,18 @@ function handlerFor(toolName) {
60
53
  return result;
61
54
  };
62
55
  }
63
- export const agreementProposeTool = defineTool('agreement_propose', { description: { type: 'string', required: true }, proposedTo: { type: 'string', required: true }, engagementKind: { type: 'string', enum: ['hire', 'service'], description: 'Contract type. Defaults to service. For a bilateral trust link, use link_propose.' }, parentAgreementId: 'string', providerId: { type: 'string', description: AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION }, price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, lifecycle: lifecycleParam, expiresAt: 'string', maxExecutions: 'integer', conversationAllowance: { type: 'number', description: 'Incoming-message limit shared across covered rooms; omitted on new agreements means unlimited.' }, billing: { type: 'string', enum: ['total', 'per_task'], description: "How price reads: 'total' (default, escrowed and paid at the end) or 'per_task' (a rate settled as each task completes — standing agreements only, and the default for a hire)." }, agreementDescription: 'string', sources: SOURCES_PARAM }, handlerFor('agreement_propose'), { description: 'Propose an agreement — direct, broadcast, or link; there are no separate publish tools. Engagement precedence (posted-first): 1) REUSE an active agreement on matching terms, 2) CLAIM the counterparty\'s listing (marketplace_view → agreement_claim; never counter a listing), 3) POST a request broadcast here when nothing listed fits, 4) direct-propose ONLY for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Direction is stated, never guessed: providerId = your own id ("I work, you pay") or providerId = proposedTo ("you work, I pay") on direct proposals. Broadcast with proposedTo="everyone" (fully public) or "org" (your org only) and no providerId posts a REQUEST any agent may claim. A person or the deploy publishes a standing listing; this agent does not. For a bilateral trust link, use link_propose: it takes an email or an agent id, or neither for a share link. engagementKind defaults to service. Agreements are STANDING by default (no expiry, unlimited tasks under one agreement); set expiresAt or maxExecutions only when the engagement should end on its own.', isAgreementCreation: true });
56
+ export const agreementProposeTool = defineTool('agreement_propose', {
57
+ ...AGREEMENT_TERMS_PARAMS,
58
+ proposedTo: { type: 'string', required: true },
59
+ engagementKind: {
60
+ type: 'string',
61
+ enum: ['hire', 'service'],
62
+ description: 'Contract type. Defaults to service. For a bilateral trust link, use link_propose.',
63
+ },
64
+ parentAgreementId: 'string',
65
+ providerId: { type: 'string', description: AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION },
66
+ agreementDescription: 'string',
67
+ }, handlerFor('agreement_propose'), { description: 'Propose an agreement — direct, broadcast, or link; there are no separate publish tools. Engagement precedence (posted-first): 1) REUSE an active agreement on matching terms, 2) CLAIM the counterparty\'s listing (marketplace_view → agreement_claim; never counter a listing), 3) POST a request broadcast here when nothing listed fits, 4) direct-propose ONLY for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Direction is stated, never guessed: providerId = your own id ("I work, you pay") or providerId = proposedTo ("you work, I pay") on direct proposals. Broadcast with proposedTo="everyone" (fully public) or "org" (your org only) and no providerId posts a REQUEST any agent may claim. A person or the deploy publishes a standing listing; this agent does not. For a bilateral trust link, use link_propose: it takes an email or an agent id, or neither for a share link. engagementKind defaults to service. Agreements are STANDING by default (no expiry, unlimited tasks under one agreement); set expiresAt or maxExecutions only when the engagement should end on its own.', isAgreementCreation: true });
64
68
  export const agreementSubcontractTool = defineTool('agreement_subcontract', agreementSubcontractCapability.params, handlerFor('agreement_subcontract'), { description: agreementSubcontractCapability.descriptions.sdk, isAgreementCreation: true });
65
69
  export const agreementRespondTool = defineTool('agreement_respond', { agreementId: { type: 'string', required: true }, action: { type: 'string', required: true, enum: ['approve', 'reject'] } }, handlerFor('agreement_respond'), { description: 'Approve or reject a pending DIRECT agreement proposal addressed to you. Open broadcasts (requests, standing offers, link invites) have no personal approval slot — claim those with agreement_claim instead, or ignore them to pass. ONE exception: a hand-off that pins YOU as provider carries your pending approval even as an open broadcast — approving consents to serving whoever claims it (the row stays open for claims); rejecting cancels the hand-off. Ignoring it leaves the hand-off unclaimable, so always respond.' });
66
70
  export const agreementClaimTool = defineTool('agreement_claim', agreementClaimCapability.params, handlerFor('agreement_claim'), { description: agreementClaimCapability.descriptions.sdk });
@@ -75,7 +79,18 @@ export const taskSetResultTool = defineTool('task_set_result', {
75
79
  },
76
80
  sources: SOURCES_PARAM,
77
81
  }, handlerFor('task_set_result'), { description: 'Transition a task to its terminal state, completed or failed, and record the result. To cancel a task use task_cancel. Set the result here as an object ({ summary, status, links }): a task the parties opted into is closed by this call, whatever else you also sent them. Pass state (not status). This closes the task and not the engagement: a standing hire stays active for the next task, ending one is agreement_fulfill. The reply reports what actually happened — what was stored, who the completion was queued for (queued, not read), what per-task metering did — so report those and no more; a completed task is not by itself a paid one.' });
78
- export const agreementCounterTool = defineTool('agreement_counter', { agreementId: { type: 'string', required: true }, price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, agreementDescription: 'string', expiresAt: 'string', lifecycle: lifecycleParam, maxExecutions: 'integer', conversationAllowance: { type: 'number', description: 'Incoming-message limit shared across covered rooms; omitted on new agreements means unlimited.' }, description: 'string', sources: SOURCES_PARAM }, handlerFor('agreement_counter'), { description: 'Counter a pending proposal with revised terms instead of approving or rejecting. Check status first with agreement_get.' });
82
+ export const agreementCounterTool = defineTool('agreement_counter', {
83
+ agreementId: { type: 'string', required: true },
84
+ price: AGREEMENT_TERMS_PARAMS.price,
85
+ agreementDescription: 'string',
86
+ expiresAt: AGREEMENT_TERMS_PARAMS.expiresAt,
87
+ lifecycle: AGREEMENT_TERMS_PARAMS.lifecycle,
88
+ maxExecutions: AGREEMENT_TERMS_PARAMS.maxExecutions,
89
+ interactionMode: AGREEMENT_TERMS_PARAMS.interactionMode,
90
+ conversationAllowance: AGREEMENT_TERMS_PARAMS.conversationAllowance,
91
+ description: 'string',
92
+ sources: AGREEMENT_TERMS_PARAMS.sources,
93
+ }, handlerFor('agreement_counter'), { description: 'Counter a pending proposal with revised terms instead of approving or rejecting. Check status first with agreement_get.' });
79
94
  export const agreementGetTool = defineTool('agreement_get', { agreementId: { type: 'string', required: true } }, handlerFor('agreement_get'), { description: 'Read the current proposal status for an agreement (pending, approved, rejected, countered, expired).' });
80
95
  export const agreementRevokeTool = defineTool('agreement_revoke', { agreementId: { type: 'string', required: true } }, handlerFor('agreement_revoke'), { description: 'Revoke a pending proposal you created so it can no longer be approved. Use when the user has clearly moved on to a different request before responding to the previous proposal card.' });
81
96
  export const agreementFulfillTool = defineTool('agreement_fulfill', { agreementId: { type: 'string', required: true } }, handlerFor('agreement_fulfill'), { description: 'END an agreement you provide — permanently. Fulfilling terminates the whole relationship, not one deliverable: every grant it conferred is revoked and it cannot be reopened. Linked chats remain independent rooms governed by their own grants. Finished WORK is reported via task_set_result (the task closes; the agreement stays standing for the next task). Only fulfill when the engagement\'s full scope is delivered and nothing more is expected — never a standing hire that just finished a task.' });
@@ -49,7 +49,10 @@ export async function withAgreementContext(event, read) {
49
49
  relationship: agreement.engagementKind === 'link',
50
50
  interactionMode: terms.interactionMode ?? 'both',
51
51
  parentAgreementId: agreement.parentAgreementId ?? null,
52
- allowanceScope: terms.conversationAllowanceScope ?? 'direct',
52
+ allowanceScope: (money.paymentStatus === 'included' || money.paymentStatus === 'allocated') &&
53
+ agreement.parentAgreementId
54
+ ? 'job'
55
+ : 'own',
53
56
  scope: terms.description,
54
57
  lifecycle: terms.lifecycle,
55
58
  expiresAt: terms.expiresAt ?? null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/agent-sdk",
3
- "version": "0.38.15",
3
+ "version": "0.38.16",
4
4
  "description": "Agent framework SDK for building autonomous agents on the Ziggs platform",
5
5
  "homepage": "https://ziggsai.com/docs",
6
6
  "bugs": {
@@ -30,7 +30,7 @@
30
30
  "@anthropic-ai/claude-agent-sdk": "^0.3.284",
31
31
  "@anthropic-ai/sdk": "^0.110.0",
32
32
  "@modelcontextprotocol/sdk": "^1.29.0",
33
- "@ziggs-ai/api-client": "0.37.18",
33
+ "@ziggs-ai/api-client": "0.37.19",
34
34
  "@ziggs-ai/contracts": "^0.23.3",
35
35
  "ajv": "^8.17.1",
36
36
  "dotenv": "^17.2.3",