@ziggs-ai/agent-sdk 0.38.1 → 0.38.3

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/README.md CHANGED
@@ -131,9 +131,9 @@ 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
- The repository’s private `@ziggs-ai/agent-runner` workspace owns `runAgents` and
135
- `createHealthServer`; these are not SDK exports. An operator deploys the runner
136
- and supplies a supervisor such as ECS to restart the process when it exits.
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.
137
137
 
138
138
  ## Migration
139
139
 
@@ -146,7 +146,7 @@ This is a breaking pre-beta rename; update imports before publishing this releas
146
146
  | `ClaudeSdkAgent`, `ClaudeSdkAgentOptions` | `ClaudeBrain`, `ClaudeBrainOptions` |
147
147
  | `host.agent` | `agent.brain` |
148
148
  | `createMcpAgent(...).host` | `createMcpAgent(...).agent` |
149
- | SDK `runLauncher`, `createHealthServer` | Operator package `runAgents`, `createHealthServer` |
149
+ | SDK `runLauncher`, `createHealthServer` | SDK `runAgents`, `createHealthServer` |
150
150
 
151
151
  `createAgent`, `ziggsBrain`, `claudeBrain` and agent IDs on the wire retain their
152
152
  names. No backend identity or stored data migration is required.
package/dist/Agent.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { SERVER_EVENTS } from '@ziggs-ai/contracts';
1
2
  import { withAgreementContext } from './runtime/agreementContext.js';
2
3
  import { agreementLaneId, agreementIdFromLane, isContextAccessRequest } from '@ziggs-ai/api-client';
3
4
  import { createDeclaredPollRunner } from './platform/polls/declaredPollRunner.js';
@@ -556,7 +557,7 @@ export class Agent {
556
557
  : undefined);
557
558
  const actorId = typeof event.actorId === 'string' && event.actorId ? event.actorId : undefined;
558
559
  return this.eventQueue.enqueue({
559
- type: 'resource_changed',
560
+ type: SERVER_EVENTS.resourceChanged,
560
561
  resource: event,
561
562
  chatId: event.chatId,
562
563
  taskId: event.taskId,
@@ -5,7 +5,7 @@ import type { ToolManager } from '../tools/ToolManager.js';
5
5
  import { type RawEvent } from '../runtime/buildOutcome.js';
6
6
  import type { TickInput, TickOutput } from './ZiggsBrain.js';
7
7
  import { type AskWakeGate } from './wakeGate.js';
8
- export { isPublishProbeChat, isCapabilityProbeSession } from '../probeChat.js';
8
+ export { isPublishProbeChat } from '../probeChat.js';
9
9
  /**
10
10
  * status notices must not spend a Claude turn answering
11
11
  * nobody. Real work wakes arrive as chat `message`, synthetic `task_result`
@@ -17,7 +17,7 @@ import { filterStandingOfferNotices, isReadableAgreementNoticeLane } from './sta
17
17
  import { formedByFromWake, shouldSkipAgentFormedHireActivation, } from './hireActivationNotice.js';
18
18
  import { isPublishProbeChat } from '../probeChat.js';
19
19
  import { gateableMessages, wakeAgreementId } from './wakeGate.js';
20
- export { isPublishProbeChat, isCapabilityProbeSession } from '../probeChat.js';
20
+ export { isPublishProbeChat } from '../probeChat.js';
21
21
  /**
22
22
  * status notices must not spend a Claude turn answering
23
23
  * nobody. Real work wakes arrive as chat `message`, synthetic `task_result`
@@ -1,16 +1,3 @@
1
- /**
2
- * Shared Claude-Agent-SDK cognition pieces.
3
- *
4
- * Everything a Claude-brain agent needs to turn a Ziggs wake into a `query()`
5
- * call, EXCEPT the loop itself. `ClaudeBrain` owns the loop (drive `query()`,
6
- * collect metrics); this module owns the periphery so it lives in one place and
7
- * is unit-testable without spinning up the SDK.
8
- *
9
- * The lockdown option set (`tools: []`, `strictMcpConfig`, `settingSources: []`,
10
- * bypass permissions, no local settings) is a security boundary, not a default —
11
- * it MUST be applied on every Claude-brain wake. Centralizing it here means a
12
- * caller cannot silently drift from it.
13
- */
14
1
  import { type Options, type SDKAssistantMessage } from '@anthropic-ai/claude-agent-sdk';
15
2
  import type { RawEvent } from '../runtime/buildOutcome.js';
16
3
  export interface ClaudeQueryOptionsParams {
@@ -11,6 +11,7 @@
11
11
  * it MUST be applied on every Claude-brain wake. Centralizing it here means a
12
12
  * caller cannot silently drift from it.
13
13
  */
14
+ import { SERVER_EVENTS } from '@ziggs-ai/contracts';
14
15
  import { SYSTEM_PROMPT_DYNAMIC_BOUNDARY, } from '@anthropic-ai/claude-agent-sdk';
15
16
  import { decisionWords } from '@ziggs-ai/api-client';
16
17
  import { getBatchEvents } from '../context/batch.js';
@@ -215,7 +216,7 @@ function renderEventLines(event, lines, now) {
215
216
  // store claim wakes as resource_changed (no chat, reason
216
217
  // proposal_approved). Distinct from the lifecycle hire path above: open the
217
218
  // buyer chat first, then greet and serve terms.
218
- if (event?.type === 'resource_changed') {
219
+ if (event?.type === SERVER_EVENTS.resourceChanged) {
219
220
  const resource = (event.resource ?? event);
220
221
  const reason = (typeof resource.reason === 'string' && resource.reason) ||
221
222
  (typeof event.reason === 'string' && event.reason) ||
@@ -7,6 +7,7 @@
7
7
  * manifest opts in. A hire a person formed (store claim) still wakes
8
8
  * so the provider can open the room and greet.
9
9
  */
10
+ import { SERVER_EVENTS } from '@ziggs-ai/contracts';
10
11
  const ACTIVATION_LIFECYCLE_OPS = new Set([
11
12
  'proposal_approved',
12
13
  'proposal_approved_execute',
@@ -62,7 +63,7 @@ export function isHireActivationNotice(event) {
62
63
  if (type === 'agreement_lifecycle' && operation && ACTIVATION_LIFECYCLE_OPS.has(operation)) {
63
64
  return true;
64
65
  }
65
- if (type !== 'resource_changed')
66
+ if (type !== SERVER_EVENTS.resourceChanged)
66
67
  return false;
67
68
  const resource = asRecord(ev.resource) ?? ev;
68
69
  const reason = typeof resource.reason === 'string' ? resource.reason : undefined;
@@ -1,3 +1,4 @@
1
+ import { SERVER_EVENTS } from '@ziggs-ai/contracts';
1
2
  import { isAgreementId, isBroadcastTarget } from '@ziggs-ai/api-client';
2
3
  import { getBatchEvents } from '../context/batch.js';
3
4
  function record(value) {
@@ -46,7 +47,7 @@ export async function filterStandingOfferNotices(event, agentId, readAgreement)
46
47
  const resource = record(item?.resource);
47
48
  const reason = resource?.reason ?? item?.reason;
48
49
  const id = agreementEventId(item);
49
- if (item?.type === 'resource_changed' && resource?.kind === 'agreement' &&
50
+ if (item?.type === SERVER_EVENTS.resourceChanged && resource?.kind === 'agreement' &&
50
51
  (reason === 'approval:approved' || reason === 'proposal_approved') && id) {
51
52
  // Inconclusive reads preserve the wake. An unavailable read must not eat
52
53
  // a legitimate customer hire or the answer to an agent's own request.
package/dist/index.d.ts CHANGED
@@ -41,7 +41,7 @@ export { runMechanical } from './runtime/runMechanical.js';
41
41
  export { PromptBuilder } from './runtime/PromptBuilder.js';
42
42
  export { thinkingDefaults, defineThinkingState, defineMechanicalState, DEFAULT_WAIT_PROMPT } from './runtime/defaults.js';
43
43
  export { KICKOFF_STATE, CLAIM_KICKOFF_STATE, injectKickoffDefault, buildKickoffState, buildClaimKickoffState, handlesClaimWake, } from './runtime/kickoff.js';
44
- export { validateWorkflow, WorkflowValidationError } from './runtime/validateWorkflow.js';
44
+ export { validateWorkflow, WorkflowValidationError, PROBE_CTX } from './runtime/validateWorkflow.js';
45
45
  export { outcomeFromEvent, outcomeFromActionResult, applyOutcomeToCtx } from './runtime/buildOutcome.js';
46
46
  export { LANE_BOOK_KEY, allCollectedResults, allDelegatedAgreementIds, allPendingSubtasks, } from './runtime/taskBookkeeping.js';
47
47
  export { createDeliveryWriteKeys } from './runtime/deliveryWriteKeys.js';
@@ -67,7 +67,7 @@ export { HistoryFormatter, historyFormatter, AgreementFormatter, agreementFormat
67
67
  export { ProactiveTrigger } from './platform/proactive/ProactiveTrigger.js';
68
68
  export { PollRegistry, declaredPolls, type PollBinding, type PollContext, type PollKind, type PollResult, } from './platform/polls/PollKind.js';
69
69
  export { PollRunner, type PollRunnerOptions, type WakeTarget } from './platform/polls/PollRunner.js';
70
- export { PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp, PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, contextRequestTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, LISTING_TOOLS, } from './platform/tools/index.js';
70
+ export { PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp, PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, LISTING_TOOLS, } from './platform/tools/index.js';
71
71
  export { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
72
72
  export { hostedModelTools, isHostedDeferred, hostedToolLane } from './brain/hostedToolSurface.js';
73
73
  export { PROTOCOL_TOOL_NAMES, PROTOCOL_TOOL_TO_OPERATION, mapProtocolToolToOperation, isProtocolToolName } from './tasks/protocolRegistry.js';
@@ -83,3 +83,7 @@ export type { ApiClientConfig } from '@ziggs-ai/api-client';
83
83
  export { SeenMessages } from './platform/SeenMessages.js';
84
84
  export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
85
85
  export type { RuntimeLogLevel } from './shared/runtimeLog.js';
86
+ export { runAgents } from './launcher/runAgents.js';
87
+ export type { RunnerOptions, SingleAgentRun, FleetRun } from './launcher/runAgents.js';
88
+ export { createHealthServer, inboxHealth } from './launcher/createHealthServer.js';
89
+ export type { AgentLiveness, HealthRequest, HealthRequestHandler } from './launcher/createHealthServer.js';
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ export { runMechanical } from './runtime/runMechanical.js';
42
42
  export { PromptBuilder } from './runtime/PromptBuilder.js';
43
43
  export { thinkingDefaults, defineThinkingState, defineMechanicalState, DEFAULT_WAIT_PROMPT } from './runtime/defaults.js';
44
44
  export { KICKOFF_STATE, CLAIM_KICKOFF_STATE, injectKickoffDefault, buildKickoffState, buildClaimKickoffState, handlesClaimWake, } from './runtime/kickoff.js';
45
- export { validateWorkflow, WorkflowValidationError } from './runtime/validateWorkflow.js';
45
+ export { validateWorkflow, WorkflowValidationError, PROBE_CTX } from './runtime/validateWorkflow.js';
46
46
  export { outcomeFromEvent, outcomeFromActionResult, applyOutcomeToCtx } from './runtime/buildOutcome.js';
47
47
  export { LANE_BOOK_KEY, allCollectedResults, allDelegatedAgreementIds, allPendingSubtasks, } from './runtime/taskBookkeeping.js';
48
48
  export { createDeliveryWriteKeys } from './runtime/deliveryWriteKeys.js';
@@ -79,7 +79,7 @@ export {
79
79
  // Tier 1 — protocol grammar (framework-managed via taskTools).
80
80
  PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp,
81
81
  // Tier 2 — opt-in capability bundles (spread into `tools:`).
82
- PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, contextRequestTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, LISTING_TOOLS, } from './platform/tools/index.js';
82
+ PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, LISTING_TOOLS, } from './platform/tools/index.js';
83
83
  // The always-on toolset for the hosted Claude cognition (parity guard
84
84
  // reads this as part of the real SDK registry).
85
85
  export { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
@@ -96,3 +96,5 @@ export { MessagesClient, ArtifactsClient, AgentSearchClient, getBackendUrl,
96
96
  configureApiClient, } from '@ziggs-ai/api-client';
97
97
  export { SeenMessages } from './platform/SeenMessages.js';
98
98
  export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
99
+ export { runAgents } from './launcher/runAgents.js';
100
+ export { createHealthServer, inboxHealth } from './launcher/createHealthServer.js';
@@ -0,0 +1,50 @@
1
+ import { type IncomingMessage, type Server, type ServerResponse } from 'node:http';
2
+ import type { Agent } from '../Agent.js';
3
+ import type { ConnectionPool } from '../platform/ConnectionPool.js';
4
+ /** What one agent reports about its own inbox reading. */
5
+ export type AgentLiveness = ReturnType<Agent['inboxLiveness']>;
6
+ /**
7
+ * The health verdict. A process is alive when its agents are still reading,
8
+ * so the answer is derived from the inbox loop rather than from the fact that
9
+ * this HTTP server replied — a wedged process replies happily while none of
10
+ * its agents read anything.
11
+ *
12
+ * The restart decision reads `lastAttemptAt`, not `lastPassAt`. When the
13
+ * backend is down every pass fails while the loops are perfectly healthy, and
14
+ * restarting the fleet would fix nothing and lose in-flight turns. Read
15
+ * staleness is still reported per agent, for the store and for monitoring.
16
+ */
17
+ export declare function inboxHealth(rows: AgentLiveness[], now?: number, staleMs?: number): {
18
+ ok: boolean;
19
+ reason?: string;
20
+ staleMs: number;
21
+ watching: number;
22
+ oldestAttemptAgeMs: number | null;
23
+ oldestPassAgeMs: number | null;
24
+ agents: (AgentLiveness & {
25
+ passAgeMs: number | null;
26
+ attemptAgeMs: number | null;
27
+ })[];
28
+ };
29
+ export type HealthRequest = {
30
+ req: IncomingMessage;
31
+ res: ServerResponse;
32
+ pool?: ConnectionPool;
33
+ };
34
+ /**
35
+ * A route the embedding process adds. Return true once the response is sent.
36
+ * The health page itself only answers `/` and `/health`.
37
+ */
38
+ export type HealthRequestHandler = (call: HealthRequest) => Promise<boolean>;
39
+ export declare function createHealthServer({ port, label, pool, liveness, onRequest, }?: {
40
+ port?: number;
41
+ label?: string;
42
+ pool?: ConnectionPool;
43
+ /**
44
+ * Every agent this process is responsible for. Omit only when there is
45
+ * nothing to watch: without it `/health` can say no more than "this server
46
+ * replied", which is the answer this route exists to stop giving.
47
+ */
48
+ liveness?: () => AgentLiveness[];
49
+ onRequest?: HealthRequestHandler;
50
+ }): Server;
@@ -0,0 +1,68 @@
1
+ import { createServer } from 'node:http';
2
+ import { runtimeLog } from '../shared/runtimeLog.js';
3
+ /**
4
+ * How long an agent's inbox loop may go without turning before this process is
5
+ * called unhealthy. An idle loop completes a pass every `DEFAULT_WAIT_SECONDS`
6
+ * (110s), so this is three missed cycles plus slack: long enough that a slow
7
+ * server or a long turn never flaps, short enough that a wedged process is
8
+ * replaced within minutes.
9
+ */
10
+ const STALE_MS = Number(process.env.FLEET_INBOX_STALE_MS) || 360_000;
11
+ function age(now, at) {
12
+ return at === null ? null : now - at;
13
+ }
14
+ /**
15
+ * The health verdict. A process is alive when its agents are still reading,
16
+ * so the answer is derived from the inbox loop rather than from the fact that
17
+ * this HTTP server replied — a wedged process replies happily while none of
18
+ * its agents read anything.
19
+ *
20
+ * The restart decision reads `lastAttemptAt`, not `lastPassAt`. When the
21
+ * backend is down every pass fails while the loops are perfectly healthy, and
22
+ * restarting the fleet would fix nothing and lose in-flight turns. Read
23
+ * staleness is still reported per agent, for the store and for monitoring.
24
+ */
25
+ export function inboxHealth(rows, now = Date.now(), staleMs = STALE_MS) {
26
+ const agents = rows.map((r) => ({
27
+ ...r,
28
+ passAgeMs: age(now, r.lastPassAt),
29
+ attemptAgeMs: age(now, r.lastAttemptAt),
30
+ }));
31
+ // Only agents that are supposed to be polling can be stale. A connected
32
+ // agent with the inbox loop off (lab and eval paths) has nothing to report,
33
+ // and an agent that was deliberately stopped is not this process failing.
34
+ const watched = agents.filter((r) => r.pollingInbox && r.connected);
35
+ const turning = watched.filter((r) => r.attemptAgeMs !== null && r.attemptAgeMs <= staleMs);
36
+ const ages = watched.map((r) => r.attemptAgeMs).filter((n) => n !== null);
37
+ const passAges = watched.map((r) => r.passAgeMs).filter((n) => n !== null);
38
+ const ok = watched.length === 0 || turning.length > 0;
39
+ return {
40
+ ok,
41
+ ...(ok ? {} : { reason: `no inbox pass attempted by any of ${watched.length} agent(s) within ${staleMs}ms` }),
42
+ staleMs,
43
+ watching: watched.length,
44
+ oldestAttemptAgeMs: ages.length ? Math.max(...ages) : null,
45
+ oldestPassAgeMs: passAges.length ? Math.max(...passAges) : null,
46
+ agents,
47
+ };
48
+ }
49
+ export function createHealthServer({ port = Number(process.env.PORT) || 8080, label = 'agents', pool, liveness, onRequest, } = {}) {
50
+ const server = createServer(async (req, res) => {
51
+ if (await onRequest?.({ req, res, pool }))
52
+ return;
53
+ const url = req.url ?? '/';
54
+ if (url === '/health' || url === '/') {
55
+ const report = inboxHealth(liveness?.() ?? []);
56
+ if (!report.ok) {
57
+ runtimeLog.warn(label, `health: ${report.reason}`);
58
+ }
59
+ res.writeHead(report.ok ? 200 : 503, { 'Content-Type': 'application/json' });
60
+ res.end(JSON.stringify(report));
61
+ return;
62
+ }
63
+ res.writeHead(404);
64
+ res.end();
65
+ });
66
+ server.listen(port, () => runtimeLog.info(label, `health server on port ${port}`));
67
+ return server;
68
+ }
@@ -0,0 +1,32 @@
1
+ import { type Agent, type AgentOptions } from '../Agent.js';
2
+ import { ConnectionPool } from '../platform/ConnectionPool.js';
3
+ import { type HealthRequestHandler } from './createHealthServer.js';
4
+ import type { Server } from 'node:http';
5
+ /** Process concerns only. Every Agent owns its inbox and external polls. */
6
+ export interface RunnerOptions {
7
+ operatorKey?: string;
8
+ label?: string;
9
+ port?: number | string;
10
+ healthServer?: boolean;
11
+ /** Disable when the embedding process owns its signal handlers. */
12
+ handleSignals?: boolean;
13
+ drainTimeoutMs?: number;
14
+ onShutdown?: () => Promise<void>;
15
+ agentMeta?: Record<string, unknown>[];
16
+ primary?: AgentOptions | ((pool: ConnectionPool) => AgentOptions);
17
+ /** A route this process adds. The health page itself only answers `/` and `/health`. */
18
+ onRequest?: HealthRequestHandler;
19
+ }
20
+ export interface SingleAgentRun {
21
+ agent: Agent;
22
+ healthServer?: Server;
23
+ shutdown(): Promise<void>;
24
+ }
25
+ export interface FleetRun {
26
+ pool: ConnectionPool;
27
+ primary: Agent | null;
28
+ healthServer?: Server;
29
+ shutdown(): Promise<void>;
30
+ }
31
+ export declare function runAgents(config: AgentOptions, options?: RunnerOptions): Promise<SingleAgentRun>;
32
+ export declare function runAgents(configs: AgentOptions[], options?: RunnerOptions): Promise<FleetRun>;
@@ -0,0 +1,119 @@
1
+ import { createAgent } from '../Agent.js';
2
+ import { ConnectionPool } from '../platform/ConnectionPool.js';
3
+ import { boundKeyFleetWarning } from '../shared/operatorKeyClaims.js';
4
+ import { runtimeLog } from '../shared/runtimeLog.js';
5
+ import { createHealthServer } from './createHealthServer.js';
6
+ const DRAIN_TIMEOUT_MS = Number(process.env.FLEET_DRAIN_TIMEOUT_MS) || 90_000;
7
+ function shutdownHandler(stop, server, options) {
8
+ let pending;
9
+ const shutdown = () => {
10
+ if (!pending) {
11
+ process.off('SIGTERM', signal);
12
+ process.off('SIGINT', signal);
13
+ pending = (async () => {
14
+ try {
15
+ await stop();
16
+ await options.onShutdown?.();
17
+ }
18
+ finally {
19
+ if (server)
20
+ await new Promise((resolve) => server.close(() => resolve()));
21
+ }
22
+ })();
23
+ }
24
+ return pending;
25
+ };
26
+ const signal = () => {
27
+ runtimeLog.info(options.label || 'runner', 'Shutting down');
28
+ void shutdown().then(() => process.exit(0), (err) => {
29
+ runtimeLog.error(options.label || 'runner', `Shutdown failed: ${err.message}`);
30
+ process.exit(1);
31
+ });
32
+ };
33
+ if (options.handleSignals !== false) {
34
+ process.on('SIGTERM', signal);
35
+ process.on('SIGINT', signal);
36
+ }
37
+ return shutdown;
38
+ }
39
+ async function healthServer(options, liveness, pool) {
40
+ if (options.healthServer === false)
41
+ return undefined;
42
+ const server = createHealthServer({
43
+ port: Number(options.port ?? process.env.PORT ?? 8080),
44
+ label: options.label || 'runner',
45
+ pool,
46
+ liveness,
47
+ onRequest: options.onRequest,
48
+ });
49
+ await new Promise((resolve, reject) => {
50
+ server.once('error', reject);
51
+ server.once('listening', () => { server.off('error', reject); resolve(); });
52
+ });
53
+ return server;
54
+ }
55
+ export async function runAgents(configOrConfigs, options = {}) {
56
+ const effectiveKey = options.operatorKey ?? process.env.ZIGGS_OPERATOR_KEY;
57
+ const decorate = (config) => effectiveKey && !config.operatorKey ? { ...config, operatorKey: effectiveKey } : config;
58
+ const drainTimeout = options.drainTimeoutMs ?? DRAIN_TIMEOUT_MS;
59
+ if (!Array.isArray(configOrConfigs)) {
60
+ const agent = createAgent(decorate(configOrConfigs));
61
+ let server;
62
+ // One budget for stopping intake and finishing in-flight work: the inbox
63
+ // pass gets up to the whole of it to acknowledge, the queue what is left.
64
+ const stop = async () => {
65
+ const deadline = Date.now() + drainTimeout;
66
+ await agent.disconnect(drainTimeout);
67
+ await agent.drain(Math.max(0, deadline - Date.now()));
68
+ };
69
+ try {
70
+ await agent.connectAsync();
71
+ server = await healthServer(options, () => [agent.inboxLiveness()]);
72
+ }
73
+ catch (err) {
74
+ await stop();
75
+ throw err;
76
+ }
77
+ const shutdown = shutdownHandler(stop, server, options);
78
+ return { agent, healthServer: server, shutdown };
79
+ }
80
+ const pool = new ConnectionPool();
81
+ const configs = configOrConfigs.map(decorate);
82
+ pool.register(configs, options.agentMeta);
83
+ const primaryConfig = typeof options.primary === 'function' ? options.primary(pool) : options.primary;
84
+ let primary = null;
85
+ let server;
86
+ // Same single budget as the one-agent path, shared by the pool and the primary.
87
+ const stop = async () => {
88
+ const deadline = Date.now() + drainTimeout;
89
+ await Promise.all([primary?.disconnect(drainTimeout), pool.shutdown(drainTimeout)]);
90
+ await primary?.drain(Math.max(0, deadline - Date.now()));
91
+ };
92
+ try {
93
+ if (primaryConfig)
94
+ primary = createAgent(decorate(primaryConfig));
95
+ const controlKey = effectiveKey ?? configs.find(c => c.operatorKey)?.operatorKey ?? primary?.options.operatorKey;
96
+ if (controlKey) {
97
+ const warning = boundKeyFleetWarning(controlKey, pool.size + (primary ? 1 : 0));
98
+ if (warning)
99
+ runtimeLog.warn(options.label || 'runner', warning);
100
+ }
101
+ await primary?.connectAsync();
102
+ await pool.connectAll();
103
+ const connected = pool.listActive().length;
104
+ if (configs.length > 0 && connected === 0) {
105
+ throw new Error(`0/${configs.length} agents started; process must restart`);
106
+ }
107
+ if (connected !== pool.size || pool.size !== configs.length) {
108
+ runtimeLog.warn(options.label || 'runner', `Only ${connected}/${configs.length} agents started — hosting the ones that came up`);
109
+ }
110
+ server = await healthServer(options, () => [...(primary ? [primary.inboxLiveness()] : []), ...pool.liveness()], pool);
111
+ runtimeLog.info(options.label || 'runner', `Ready — ${connected}/${pool.size} agents started`);
112
+ }
113
+ catch (err) {
114
+ await stop();
115
+ throw err;
116
+ }
117
+ const shutdown = shutdownHandler(stop, server, options);
118
+ return { pool, primary, healthServer: server, shutdown };
119
+ }
@@ -314,9 +314,6 @@ export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
314
314
  handledSoFar.add(request.agreementId);
315
315
  }
316
316
  }
317
- if (envelope.truncatedRequests) {
318
- warn(label, `inbox truncatedRequests=${envelope.truncatedRequests} (more requests than one pass surfaces — the tail follows on the next pass)`);
319
- }
320
317
  // Pending decisions are standing state, not deliveries: they keep appearing
321
318
  // until answered, so dedup them the same way tasks are deduped.
322
319
  for (const proposal of envelope.proposalsAwaitingMe) {
@@ -21,7 +21,7 @@ export { DISCOVERY_TOOLS, agentSearchTool, agentGetTool } from './tier2/discover
21
21
  export { MARKETPLACE_TOOLS, marketplaceViewTool } from './tier2/marketplaceTools.js';
22
22
  export { PAYMENT_TOOLS, paymentBalanceTool } from './tier2/paymentTools.js';
23
23
  export { CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, } from './tier2/connectionTools.js';
24
- export { CONTEXT_TOOLS, contextReadTool, contextRequestTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
24
+ export { CONTEXT_TOOLS, contextReadTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
25
25
  export { GRANTS_TOOLS, listGrantsTool } from './tier2/grantsTools.js';
26
26
  export { TASK_TOOLS, listTasksTool, cancelTaskTool } from './tier2/taskTools.js';
27
27
  export { PLAN_TOOLS, taskReplacePlanTool } from './tier2/planTools.js';
@@ -23,7 +23,7 @@ export { DISCOVERY_TOOLS, agentSearchTool, agentGetTool } from './tier2/discover
23
23
  export { MARKETPLACE_TOOLS, marketplaceViewTool } from './tier2/marketplaceTools.js';
24
24
  export { PAYMENT_TOOLS, paymentBalanceTool } from './tier2/paymentTools.js';
25
25
  export { CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, } from './tier2/connectionTools.js';
26
- export { CONTEXT_TOOLS, contextReadTool, contextRequestTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
26
+ export { CONTEXT_TOOLS, contextReadTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
27
27
  // one grant lister across every rail (retires context_discover +
28
28
  // payment_list_grants). Always-available on the hosted agent — see CLAUDE_SDK_TOOLS.
29
29
  export { GRANTS_TOOLS, listGrantsTool } from './tier2/grantsTools.js';
@@ -1,3 +1,4 @@
1
+ import { AGREEMENT_TERMINAL_STATES } from '@ziggs-ai/contracts';
1
2
  import { isBroadcastTarget, partyActorIds, presentClaimResult, } from '@ziggs-ai/api-client';
2
3
  // the link-summary rule lives in AgreementClient, at the point the
3
4
  // document is parsed. Every verb here inherits it through the client instead of
@@ -300,7 +301,7 @@ async function proposeAgreement(services, payload, chatId, writeKeys = null) {
300
301
  // party — instead of under that worker's sub-agreement, which the backend
301
302
  // rejects with 400 "assignee is not a party". Both are made deterministic here
302
303
  // using the chat's existing agreements.
303
- const TERMINAL_STATUS = new Set(['fulfilled', 'cancelled', 'refunded', 'rejected']);
304
+ const TERMINAL_STATUS = new Set(AGREEMENT_TERMINAL_STATES);
304
305
  async function agreementsInChat(services, chatId, cachedAgreements) {
305
306
  if (!chatId)
306
307
  return [];
@@ -67,13 +67,13 @@ export const agreementClaimTool = defineTool('agreement_claim', agreementClaimCa
67
67
  export const taskSetResultTool = defineTool('task_set_result', {
68
68
  taskId: { type: 'string', required: true },
69
69
  // same param name as MCP ziggs_task_set_result / PATCH /tasks/:id/state.
70
- state: { type: 'string', required: true, enum: ['completed', 'failed', 'cancelled'] },
70
+ state: { type: 'string', required: true, enum: ['completed', 'failed'] },
71
71
  // Same object contract as MCP ({ summary, status, links, … }).
72
72
  result: {
73
73
  type: 'object',
74
74
  description: 'Outcome payload (summary, status, links, …). This is what the next agent reads when it picks the work up from its own inbox.',
75
75
  },
76
- }, handlerFor('task_set_result'), { description: 'Transition a task to its terminal state — completed, failed, or cancelled — and record the result. 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.' });
76
+ }, 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.' });
77
77
  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' }, handlerFor('agreement_counter'), { description: 'Counter a pending proposal with revised terms instead of approving or rejecting. Check status first with agreement_get.' });
78
78
  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).' });
79
79
  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.' });
@@ -3,7 +3,6 @@ export declare const contextReadTool: ToolDefinition;
3
3
  export declare const openTool: ToolDefinition;
4
4
  export declare const accessExplainTool: ToolDefinition;
5
5
  export declare const accessRequestTool: ToolDefinition;
6
- export declare const contextRequestTool: ToolDefinition;
7
6
  export declare const contextDelegateTool: ToolDefinition;
8
7
  export declare const contextExpandReachTool: ToolDefinition;
9
8
  export declare const contextDiscoverGrantableTool: ToolDefinition;
@@ -1,4 +1,4 @@
1
- import { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextRequestCapability, accessRequestCapability, } from '@ziggs-ai/api-client';
1
+ import { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, accessRequestCapability, } from '@ziggs-ai/api-client';
2
2
  import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
3
3
  // context grant tools ride the same api-client
4
4
  // clients as ziggs-mcp; the whole definition (schema + handler) is now shared
@@ -7,7 +7,6 @@ export const contextReadTool = toolFromCapability(contextReadCapability);
7
7
  export const openTool = toolFromCapability(openCapability);
8
8
  export const accessExplainTool = toolFromCapability(accessExplainCapability);
9
9
  export const accessRequestTool = toolFromCapability(accessRequestCapability);
10
- export const contextRequestTool = toolFromCapability(contextRequestCapability);
11
10
  export const contextDelegateTool = toolFromCapability(contextDelegateCapability);
12
11
  export const contextExpandReachTool = toolFromCapability(contextExpandReachCapability);
13
12
  export const contextDiscoverGrantableTool = toolFromCapability(contextDiscoverGrantableCapability);
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * One classifier for store-publish probe rooms.
3
3
  *
4
- * The runner mints `probe_<agentId>_<ts>`. An id prefix must not buy
5
- * capability evidence or a canned brain reply; it only names the room so
6
- * inbox pin and wake order can find it. Capability vs liveness is decided
7
- * by the store sync, not by this prefix.
4
+ * The runner mints `probe_<agentId>_<ts>`. That prefix skips the wake gate
5
+ * and the wake-rate breaker, so a store publish still hears a model reply.
6
+ * It does not write a canned answer, and it is not capability evidence.
7
+ * Capability versus liveness is decided by the store sync.
8
8
  */
9
9
  export declare const PROBE_CHAT_ID_PREFIX = "probe_";
10
10
  export declare function isPublishProbeChat(chatId: string | undefined | null): boolean;
11
- /** @deprecated use {@link isPublishProbeChat} — same classifier, old name. */
12
- export declare const isCapabilityProbeSession: typeof isPublishProbeChat;
package/dist/probeChat.js CHANGED
@@ -1,14 +1,12 @@
1
1
  /**
2
2
  * One classifier for store-publish probe rooms.
3
3
  *
4
- * The runner mints `probe_<agentId>_<ts>`. An id prefix must not buy
5
- * capability evidence or a canned brain reply; it only names the room so
6
- * inbox pin and wake order can find it. Capability vs liveness is decided
7
- * by the store sync, not by this prefix.
4
+ * The runner mints `probe_<agentId>_<ts>`. That prefix skips the wake gate
5
+ * and the wake-rate breaker, so a store publish still hears a model reply.
6
+ * It does not write a canned answer, and it is not capability evidence.
7
+ * Capability versus liveness is decided by the store sync.
8
8
  */
9
9
  export const PROBE_CHAT_ID_PREFIX = 'probe_';
10
10
  export function isPublishProbeChat(chatId) {
11
11
  return typeof chatId === 'string' && chatId.startsWith(PROBE_CHAT_ID_PREFIX);
12
12
  }
13
- /** @deprecated use {@link isPublishProbeChat} — same classifier, old name. */
14
- export const isCapabilityProbeSession = isPublishProbeChat;
@@ -1,3 +1,4 @@
1
+ import { SERVER_EVENTS } from '@ziggs-ai/contracts';
1
2
  import { isProtocolToolName, mapProtocolToolToOperation } from '../tasks/protocolRegistry.js';
2
3
  import { getBatchEvents, isTaskResultRelevantToAgent } from '../context/batch.js';
3
4
  import { bookForDelegation, ensureTaskBook, findBookHoldingPending, removeTaskBook, } from './taskBookkeeping.js';
@@ -108,7 +109,7 @@ function singleEventToOutcome(ev, ownAgentId) {
108
109
  }
109
110
  return { kind: 'subtask-finished', status: 'completed', task, result };
110
111
  }
111
- if (ev.type === 'resource_changed') {
112
+ if (ev.type === SERVER_EVENTS.resourceChanged) {
112
113
  const resource = (ev.resource ?? ev);
113
114
  const reason = resource.reason;
114
115
  if (resource.kind === 'agreement' &&
@@ -1,3 +1,6 @@
1
+ import { ContentTypes, type ContentType } from '@ziggs-ai/api-client';
2
+ export { ContentTypes, type ContentType };
3
+ /** Entry kinds. `task_history` is a legacy alias the wire still accepts. */
1
4
  export declare const EntryTypes: {
2
5
  readonly MESSAGE: "message";
3
6
  readonly NOTIFICATION: "notification";
@@ -5,15 +8,6 @@ export declare const EntryTypes: {
5
8
  readonly TASK_HISTORY: "task_history";
6
9
  };
7
10
  export type EntryType = (typeof EntryTypes)[keyof typeof EntryTypes];
8
- export declare const ContentTypes: {
9
- readonly TEXT: "text";
10
- readonly OPERATION: "operation";
11
- readonly CONTEXT: "context";
12
- readonly RESULT: "result";
13
- readonly TASK_UPDATE: "task_update";
14
- readonly THOUGHT: "thought";
15
- };
16
- export type ContentType = (typeof ContentTypes)[keyof typeof ContentTypes];
17
11
  export declare const ContextProperties: {
18
12
  readonly HISTORY: "history";
19
13
  readonly AGREEMENTS: "agreements";
@@ -1,20 +1,12 @@
1
- // Inlined from `@ziggs-ai/api-client` so the pure SDK doesn't depend on the
2
- // Ziggs HTTP client. Both client and server agree on these string constants
3
- // because the protocol is the same — they're just two views of one schema.
1
+ import { ContentTypes } from '@ziggs-ai/api-client';
2
+ export { ContentTypes };
3
+ /** Entry kinds. `task_history` is a legacy alias the wire still accepts. */
4
4
  export const EntryTypes = {
5
5
  MESSAGE: 'message',
6
6
  NOTIFICATION: 'notification',
7
7
  ARTIFACT: 'artifact',
8
8
  TASK_HISTORY: 'task_history',
9
9
  };
10
- export const ContentTypes = {
11
- TEXT: 'text',
12
- OPERATION: 'operation',
13
- CONTEXT: 'context',
14
- RESULT: 'result',
15
- TASK_UPDATE: 'task_update',
16
- THOUGHT: 'thought',
17
- };
18
10
  export const ContextProperties = {
19
11
  HISTORY: 'history',
20
12
  AGREEMENTS: 'agreements',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/agent-sdk",
3
- "version": "0.38.1",
3
+ "version": "0.38.3",
4
4
  "description": "Agent framework SDK for building autonomous agents on the Ziggs platform",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,7 +26,8 @@
26
26
  "@anthropic-ai/claude-agent-sdk": "^0.3.284",
27
27
  "@anthropic-ai/sdk": "^0.110.0",
28
28
  "@modelcontextprotocol/sdk": "^1.29.0",
29
- "@ziggs-ai/api-client": "0.37.1",
29
+ "@ziggs-ai/api-client": "0.37.5",
30
+ "@ziggs-ai/contracts": "^0.23.3",
30
31
  "ajv": "^8.17.1",
31
32
  "dotenv": "^17.2.3",
32
33
  "openai": "^4.0.0",