@ziggs-ai/agent-sdk 0.1.4 → 0.1.6

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 (49) hide show
  1. package/README.md +7 -5
  2. package/package.json +2 -2
  3. package/src/AgentHost.ts +165 -12
  4. package/src/adapters/OpenAIAdapter.ts +21 -0
  5. package/src/agent/Agent.ts +5 -2
  6. package/src/cognition/validateContext.ts +1 -1
  7. package/src/context/batch.ts +3 -3
  8. package/src/context/classifyEnvelope.ts +2 -2
  9. package/src/context/routingLabels.ts +1 -1
  10. package/src/formatters/AgreementFormatter.ts +5 -5
  11. package/src/formatters/HistoryFormatter.ts +3 -3
  12. package/src/index.ts +25 -4
  13. package/src/ingress/normalizeIncoming.ts +50 -7
  14. package/src/pricing/fleetDefaults.ts +218 -0
  15. package/src/pricing/fleetEvalFree.ts +24 -0
  16. package/src/pricing/fleetFreeTierA.gen.ts +12 -0
  17. package/src/pricing/fleetTierByAgentId.gen.ts +1022 -0
  18. package/src/runtime/AgentMachine.ts +68 -2
  19. package/src/runtime/PromptBuilder.ts +25 -23
  20. package/src/runtime/buildOutcome.ts +33 -3
  21. package/src/runtime/defaults.ts +3 -0
  22. package/src/runtime/runTurn.ts +115 -61
  23. package/src/runtime/validateWorkflow.ts +16 -0
  24. package/src/server/EventQueue.ts +14 -0
  25. package/src/server/InboxCatchUp.ts +251 -0
  26. package/src/server/SeenMessages.ts +27 -0
  27. package/src/server/ZiggsEffectHandler.ts +82 -8
  28. package/src/server/agreements/AgreementService.ts +7 -1
  29. package/src/server/createHealthServer.ts +79 -2
  30. package/src/server/runLauncher.ts +40 -25
  31. package/src/server/tasks/TaskService.ts +4 -5
  32. package/src/server/tasks/index.ts +0 -3
  33. package/src/server/telemetryIngest.ts +91 -0
  34. package/src/server/tools/index.ts +46 -0
  35. package/src/server/{tasks → tools/tier1}/protocolRunner.ts +52 -20
  36. package/src/server/{tasks → tools/tier1}/protocolTools.ts +6 -3
  37. package/src/server/tools/tier2/connectionTools.ts +75 -0
  38. package/src/server/tools/tier2/contextTools.ts +74 -0
  39. package/src/server/tools/tier2/discoveryTools.ts +34 -0
  40. package/src/server/tools/tier2/marketplaceTools.ts +25 -0
  41. package/src/server/{tasks → tools/tier2}/paymentTools.ts +74 -37
  42. package/src/server/ziggsconnect/ZiggsConnectClient.ts +126 -0
  43. package/src/server/ziggscontext/ZiggsContextClient.ts +137 -0
  44. package/src/server/ziggspay/ZiggsPayClient.ts +12 -12
  45. package/src/shared/types.ts +0 -2
  46. package/src/tools/index.ts +2 -0
  47. package/src/tools/recordReport.ts +82 -0
  48. package/src/types.ts +47 -8
  49. package/src/tasks/taskCore.ts +0 -139
@@ -20,7 +20,7 @@ interface SingleOptions {
20
20
  interface FleetOptions extends SingleOptions {
21
21
  wsUrl?: string;
22
22
  agentMeta?: Record<string, unknown>[];
23
- orchestrator?: AgentConfig | ((pool: ConnectionPool) => AgentConfig);
23
+ primary?: AgentConfig | ((pool: ConnectionPool) => AgentConfig);
24
24
  preWake?: string[];
25
25
  poolOptions?: { maxActive?: number; idleTimeoutMs?: number };
26
26
  }
@@ -40,17 +40,22 @@ async function runSingle(
40
40
  const effectiveKey = operatorKey ?? process.env.ZIGGS_OPERATOR_KEY;
41
41
  const decorated = effectiveKey && !config.operatorKey ? { ...config, operatorKey: effectiveKey } : config;
42
42
  const agent = new AgentHost(decorated);
43
- await agent.connectAsync();
44
- runtimeLog.info(
45
- label,
46
- `"${(agent as unknown as Record<string, unknown>)['options'] ? ((agent as unknown as Record<string, {agentId?: string; name?: string | null}>)['options']?.agentId ?? (agent as unknown as Record<string, {name?: string | null}>)['options']?.name) : 'agent'}" connected`,
47
- );
43
+ try {
44
+ await agent.connectAsync();
45
+ runtimeLog.info(
46
+ label,
47
+ `"${(agent as unknown as Record<string, unknown>)['options'] ? ((agent as unknown as Record<string, {agentId?: string; name?: string | null}>)['options']?.agentId ?? (agent as unknown as Record<string, {name?: string | null}>)['options']?.name) : 'agent'}" connected`,
48
+ );
49
+ } catch (err) {
50
+ runtimeLog.warn(label, `Agent failed to connect at startup: ${(err as Error).message} — will retry`);
51
+ }
48
52
 
49
53
  const server = healthServer ? createHealthServer({ port: Number(port), label }) : null;
50
54
 
51
55
  process.on('SIGTERM', async () => {
52
56
  runtimeLog.info(label, 'SIGTERM — shutting down');
53
57
  agent.disconnect();
58
+ await agent.drain(30_000);
54
59
  try {
55
60
  await onShutdown?.();
56
61
  } catch (err) {
@@ -68,39 +73,48 @@ async function runFleet(
68
73
  {
69
74
  operatorKey, wsUrl = process.env.WS_URL, port = process.env.PORT || 8080,
70
75
  healthServer = true, label = 'launcher', agentMeta,
71
- orchestrator: orchestratorConfig, preWake = [], poolOptions, onShutdown,
76
+ primary: primaryConfig, preWake = [], poolOptions, onShutdown,
72
77
  }: FleetOptions = {},
73
- ): Promise<{ pool: ConnectionPool; orchestrator: unknown; healthServer?: Server }> {
78
+ ): Promise<{ pool: ConnectionPool; primary: unknown; healthServer?: Server }> {
74
79
  const pool = new ConnectionPool(poolOptions);
75
80
  const effectiveKey = operatorKey ?? process.env.ZIGGS_OPERATOR_KEY;
76
81
  const decoratedConfigs = effectiveKey
77
82
  ? configs.map(c => (c.operatorKey ? c : { ...c, operatorKey: effectiveKey }))
78
83
  : configs;
79
84
  pool.register(decoratedConfigs, agentMeta as Record<string, unknown>[]);
80
- const server = healthServer ? createHealthServer({ port: Number(port), label }) : null;
85
+ const server = healthServer ? createHealthServer({ port: Number(port), label, pool }) : null;
81
86
 
82
- let resolvedOrchConfig: AgentConfig | undefined = typeof orchestratorConfig === 'function'
83
- ? orchestratorConfig(pool)
84
- : orchestratorConfig;
85
- if (resolvedOrchConfig && effectiveKey && !resolvedOrchConfig.operatorKey) {
86
- resolvedOrchConfig = { ...resolvedOrchConfig, operatorKey: effectiveKey };
87
+ let resolvedPrimaryConfig: AgentConfig | undefined = typeof primaryConfig === 'function'
88
+ ? primaryConfig(pool)
89
+ : primaryConfig;
90
+ if (resolvedPrimaryConfig && effectiveKey && !resolvedPrimaryConfig.operatorKey) {
91
+ resolvedPrimaryConfig = { ...resolvedPrimaryConfig, operatorKey: effectiveKey };
87
92
  }
88
93
 
89
- const controlKey = effectiveKey ?? configs.find(c => c.operatorKey)?.operatorKey ?? resolvedOrchConfig?.operatorKey;
94
+ const controlKey = effectiveKey ?? configs.find(c => c.operatorKey)?.operatorKey ?? resolvedPrimaryConfig?.operatorKey;
90
95
  if (controlKey) {
91
96
  pool.startControl({ wsUrl, operatorKey: controlKey });
92
97
  } else {
93
98
  runtimeLog.warn(label, 'No operatorKey provided — skipping control socket');
94
99
  }
95
100
 
96
- let orchestrator: unknown = null;
97
- if (resolvedOrchConfig) {
98
- orchestrator = new AgentHost(resolvedOrchConfig);
99
- await (orchestrator as { connectAsync: () => Promise<void> }).connectAsync();
100
- runtimeLog.info(
101
- label,
102
- `Orchestrator "${resolvedOrchConfig.name || resolvedOrchConfig.ownAgentId || 'orchestrator'}" connected`,
103
- );
101
+ let primary: unknown = null;
102
+ if (resolvedPrimaryConfig) {
103
+ primary = new AgentHost(resolvedPrimaryConfig);
104
+ try {
105
+ await (primary as { connectAsync: () => Promise<void> }).connectAsync();
106
+ runtimeLog.info(
107
+ label,
108
+ `Primary agent "${resolvedPrimaryConfig.name || resolvedPrimaryConfig.ownAgentId || 'primary'}" connected`,
109
+ );
110
+ } catch (err) {
111
+ // Backend may not be ready yet — socket.io reconnects automatically.
112
+ // Don't crash the process; the primary agent will be live once it reconnects.
113
+ runtimeLog.warn(
114
+ label,
115
+ `Primary agent "${resolvedPrimaryConfig.name || resolvedPrimaryConfig.ownAgentId || 'primary'}" failed to connect at startup: ${(err as Error).message} — will retry`,
116
+ );
117
+ }
104
118
  }
105
119
 
106
120
  if (preWake.length > 0) {
@@ -116,7 +130,8 @@ async function runFleet(
116
130
  process.on('SIGTERM', async () => {
117
131
  runtimeLog.info(label, 'SIGTERM — shutting down');
118
132
  pool.stopControl();
119
- (orchestrator as { disconnect?: () => void })?.disconnect?.();
133
+ (primary as { disconnect?: () => void })?.disconnect?.();
134
+ await (primary as { drain?: (ms: number) => Promise<void> })?.drain?.(30_000);
120
135
  await pool.disconnectAll();
121
136
  try {
122
137
  await onShutdown?.();
@@ -127,5 +142,5 @@ async function runFleet(
127
142
  process.exit(0);
128
143
  });
129
144
 
130
- return { pool, orchestrator, healthServer: server ?? undefined };
145
+ return { pool, primary, healthServer: server ?? undefined };
131
146
  }
@@ -2,9 +2,8 @@ import {
2
2
  createTask, getTask, getActiveTasksForAgent, getActiveTasksForChat, getSubtasks,
3
3
  updateTaskState, cancelTask,
4
4
  claimLedgerTask, reportTask, setTaskSatisfaction, countTasks,
5
- type Creds, type Task,
5
+ type Creds, type Task, type TaskState,
6
6
  } from '@ziggs-ai/api-client';
7
- import type { TaskWithFlags, TaskStateValue } from '../../tasks/taskCore.js';
8
7
  import type { CountTasksFilters } from '@ziggs-ai/api-client';
9
8
  import { runtimeLog } from '../../shared/runtimeLog.js';
10
9
 
@@ -63,7 +62,7 @@ export class TaskService {
63
62
  }
64
63
  }
65
64
 
66
- async updateState(taskId: string, state: TaskStateValue, result?: unknown): Promise<Task> {
65
+ async updateState(taskId: string, state: TaskState, result?: unknown): Promise<Task> {
67
66
  try {
68
67
  const data = result !== undefined ? { result: safeJsonStringify(result) } : {};
69
68
  const updated = await updateTaskState(taskId, state, data, this.creds);
@@ -86,11 +85,11 @@ export class TaskService {
86
85
  }
87
86
  }
88
87
 
89
- async claimLedgerTask(taskId: string): Promise<TaskWithFlags> {
88
+ async claimLedgerTask(taskId: string): Promise<Task> {
90
89
  try {
91
90
  const task = await claimLedgerTask(taskId, this.creds);
92
91
  runtimeLog.info('TaskService', `[ledger] Claimed task ${taskId}`);
93
- return task as unknown as TaskWithFlags;
92
+ return task as unknown as Task;
94
93
  } catch (error) {
95
94
  runtimeLog.warn('TaskService', `[ledger] Claim failed for ${taskId}: ${(error as Error).message}`);
96
95
  throw error;
@@ -1,4 +1 @@
1
1
  export { TaskService } from './TaskService.js';
2
- export { PROTOCOL_TOOLS } from './protocolTools.js';
3
- export { PROTOCOL_TOOL_NAMES, PROTOCOL_TOOL_TO_OPERATION, mapProtocolToolToOperation, isProtocolToolName } from '../../tasks/protocolRegistry.js';
4
- export { dispatchProtocolOp } from './protocolRunner.js';
@@ -0,0 +1,91 @@
1
+ import type { LlmResponse } from '../types.js';
2
+ import type { TelemetryClient } from '@ziggs-ai/api-client';
3
+
4
+ export function isTelemetryIngestEnabled(
5
+ explicit?: boolean,
6
+ ): boolean {
7
+ if (explicit === false) return false;
8
+ if (explicit === true) return true;
9
+ const env = process.env.ZIGGS_TELEMETRY_INGEST;
10
+ if (env === '0' || env === 'false') return false;
11
+ return true;
12
+ }
13
+
14
+ export function usageFromLlmResponse(response: LlmResponse): {
15
+ tokensTotal: number;
16
+ tokensInput: number;
17
+ tokensOutput: number;
18
+ } {
19
+ const u = response.usage;
20
+ const tokensInput =
21
+ Number(u?.prompt_tokens ?? u?.promptTokens ?? 0) || 0;
22
+ const tokensOutput =
23
+ Number(u?.completion_tokens ?? u?.completionTokens ?? 0) || 0;
24
+ const tokensTotal =
25
+ Number(u?.total_tokens ?? u?.totalTokens ?? 0) ||
26
+ tokensInput + tokensOutput;
27
+ return { tokensTotal, tokensInput, tokensOutput };
28
+ }
29
+
30
+ export function buildLlmIngestPayload(
31
+ response: LlmResponse,
32
+ ctx: { sessionId?: string; durationMs: number; runId?: string; stateId?: string },
33
+ ): Record<string, unknown> {
34
+ const { tokensTotal, tokensInput, tokensOutput } =
35
+ usageFromLlmResponse(response);
36
+ const chatId = ctx.sessionId ?? null;
37
+ return {
38
+ source: 'agent',
39
+ chatId,
40
+ sessionId: chatId,
41
+ execution: {
42
+ llmCallsTotal: 1,
43
+ tokensTotal,
44
+ tokensInput,
45
+ tokensOutput,
46
+ ...(tokensTotal > 0 ? { tokensPerLlmCall: [tokensTotal] } : {}),
47
+ },
48
+ resource: { durationMs: Math.max(0, Math.round(ctx.durationMs)) },
49
+ meta: {
50
+ effect: 'llm-call',
51
+ ...(ctx.runId ? { runId: ctx.runId } : {}),
52
+ ...(ctx.stateId ? { stateId: ctx.stateId } : {}),
53
+ },
54
+ };
55
+ }
56
+
57
+ export function buildToolIngestPayload(
58
+ ctx: {
59
+ sessionId: string;
60
+ tool: string;
61
+ durationMs: number;
62
+ ok: boolean;
63
+ runId?: string;
64
+ stateId?: string;
65
+ },
66
+ ): Record<string, unknown> {
67
+ return {
68
+ source: 'agent',
69
+ chatId: ctx.sessionId,
70
+ sessionId: ctx.sessionId,
71
+ execution: { executionLoops: 1 },
72
+ resource: { durationMs: Math.max(0, Math.round(ctx.durationMs)) },
73
+ meta: {
74
+ effect: 'tool-call',
75
+ tool: ctx.tool,
76
+ ok: ctx.ok,
77
+ ...(ctx.runId ? { runId: ctx.runId } : {}),
78
+ ...(ctx.stateId ? { stateId: ctx.stateId } : {}),
79
+ },
80
+ };
81
+ }
82
+
83
+ /** Fire-and-forget; never throws to the effect handler. */
84
+ export function sendTelemetry(
85
+ client: TelemetryClient | undefined,
86
+ payload: Record<string, unknown>,
87
+ enabled: boolean,
88
+ ): void {
89
+ if (!enabled || !client) return;
90
+ void client.send(payload).catch(() => {});
91
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Agent tool bundles, in two tiers. Single home for everything an agent can
3
+ * "speak" or "do" over the wire.
4
+ *
5
+ * Tier 1 — protocol grammar (tier1/). The verbs every agent uses to participate
6
+ * in the agreement/task system. Framework-managed: attached via the `taskTools`
7
+ * config (default 'all'), dispatched to in-process services (taskService /
8
+ * agreementService) injected into tool context — NOT passed in the user `tools:`
9
+ * array. Publishing open work is part of this grammar:
10
+ * agreement_propose({ proposedTo: "everyone" }) — there is no separate publish tool.
11
+ *
12
+ * Tier 2 — capability bundles (tier2/). Opt-in, HTTP-backed tool bundles an agent
13
+ * chooses to have. Off by default; an agent gains a bundle by spreading it into
14
+ * its `tools:` array. Each tool reads operatorKey/agentId from tool context and
15
+ * wraps a backend HTTP client — no per-agent plumbing.
16
+ */
17
+
18
+ // ── Tier 1: protocol grammar ────────────────────────────────────────────────
19
+ export {
20
+ PROTOCOL_TOOLS,
21
+ agreementProposeTool, agreementSubcontractTool, agreementRespondTool,
22
+ agreementCounterProposalTool, agreementCheckProposalTool, agreementRevokeTool,
23
+ taskSpawnTool, taskUpdateTool, taskUpdatePlanStepTool,
24
+ } from './tier1/protocolTools.js';
25
+ export { dispatchProtocolOp } from './tier1/protocolRunner.js';
26
+
27
+ // ── Tier 2: capability bundles ──────────────────────────────────────────────
28
+ export { DISCOVERY_TOOLS, agentSearchTool, agentGetTool } from './tier2/discoveryTools.js';
29
+ export { MARKETPLACE_TOOLS, marketplaceViewTool } from './tier2/marketplaceTools.js';
30
+ export {
31
+ PAYMENT_TOOLS,
32
+ paymentBalanceTool, paymentTransferTool, paymentWaitForApprovalTool,
33
+ paymentHoldTool, paymentReleaseTool, paymentResolveWalletTool,
34
+ paymentIssueTokenTool, paymentAttenuateTokenTool, paymentRevokeTokenTool, paymentListTokensTool,
35
+ } from './tier2/paymentTools.js';
36
+ export {
37
+ CONNECTION_TOOLS,
38
+ connectionProxyTool,
39
+ connectionListGrantsTool,
40
+ } from './tier2/connectionTools.js';
41
+ export {
42
+ CONTEXT_TOOLS,
43
+ contextDiscoverTool,
44
+ contextListGrantsTool,
45
+ contextDelegateTool,
46
+ } from './tier2/contextTools.js';
@@ -1,6 +1,16 @@
1
- import { normalizeState } from '../../tasks/taskCore.js';
2
- import type { AgreementService } from '../agreements/AgreementService.js';
3
- import type { TaskService } from './TaskService.js';
1
+ import type { AgreementService } from '../../agreements/AgreementService.js';
2
+ import type { TaskService } from '../../tasks/TaskService.js';
3
+ import {
4
+ applyFleetCounterPricing,
5
+ applyFleetPricingForProtocol,
6
+ } from '../../../pricing/fleetDefaults.js';
7
+
8
+ // Map legacy task-state aliases onto the canonical set.
9
+ function normalizeState(state: string): string {
10
+ if (state === 'done') return 'completed';
11
+ if (state === 'error') return 'failed';
12
+ return state;
13
+ }
4
14
 
5
15
  interface Services {
6
16
  agreementService: AgreementService;
@@ -37,32 +47,40 @@ export async function dispatchProtocolOp(
37
47
  payload: ProtocolPayload,
38
48
  chatId: string | null,
39
49
  agents: unknown[] = [],
50
+ actingAgentId: string | null = null,
40
51
  ): Promise<unknown> {
41
52
  if (!payload?.operation) return null;
42
53
  const { operation } = payload;
43
54
  const { agreementService, taskService } = services;
55
+ const actorId = actingAgentId?.trim() || null;
44
56
 
45
57
  switch (operation) {
46
58
  case 'agreement-propose':
47
- return proposeAgreement(services, payload, chatId, agents);
59
+ return proposeAgreement(services, payload, chatId, agents, actorId);
48
60
  case 'agreement-subcontract':
49
- return subcontractAgreement(services, payload, chatId, agents);
61
+ return subcontractAgreement(services, payload, chatId, agents, actorId);
50
62
  case 'agreement-respond':
51
63
  if (!payload.agreementId || !payload.action) return null;
52
64
  return agreementService.respond(payload.agreementId, payload.action);
53
65
  case 'agreement-counter-proposal':
54
66
  if (!payload.agreementId) return null;
55
- return agreementService.counter(payload.agreementId, {
56
- price: payload.price,
57
- agreementDescription: payload.agreementDescription,
58
- expiresAt: payload.expiresAt,
59
- lifecycle: payload.lifecycle,
60
- maxExecutions: payload.maxExecutions,
61
- description: payload.description,
62
- plan: payload.plan,
63
- planReviewTiming: payload.planReviewTiming as import('@ziggs-ai/api-client').PlanReviewTiming | undefined,
64
- requireMidWorkPlanAck: payload.requireMidWorkPlanAck,
65
- });
67
+ {
68
+ const counterPayload =
69
+ actorId != null
70
+ ? applyFleetCounterPricing(payload, actorId)
71
+ : payload;
72
+ return agreementService.counter(payload.agreementId, {
73
+ price: counterPayload.price,
74
+ agreementDescription: counterPayload.agreementDescription,
75
+ expiresAt: counterPayload.expiresAt,
76
+ lifecycle: counterPayload.lifecycle,
77
+ maxExecutions: counterPayload.maxExecutions,
78
+ description: counterPayload.description,
79
+ plan: counterPayload.plan,
80
+ planReviewTiming: counterPayload.planReviewTiming as import('@ziggs-ai/api-client').PlanReviewTiming | undefined,
81
+ requireMidWorkPlanAck: counterPayload.requireMidWorkPlanAck,
82
+ });
83
+ }
66
84
  case 'agreement-check-proposal':
67
85
  if (!payload.agreementId) return null;
68
86
  return agreementService.getStatus(payload.agreementId);
@@ -86,16 +104,30 @@ export async function dispatchProtocolOp(
86
104
  }
87
105
  }
88
106
 
89
- async function proposeAgreement(services: Services, payload: ProtocolPayload, chatId: string | null, _agents: unknown[]): Promise<unknown> {
107
+ async function proposeAgreement(
108
+ services: Services,
109
+ payload: ProtocolPayload,
110
+ chatId: string | null,
111
+ _agents: unknown[],
112
+ actingAgentId: string | null,
113
+ ): Promise<unknown> {
90
114
  if (!payload.description) throw new Error('agreement-propose requires `description`');
91
115
  if (!payload.proposedTo) throw new Error('agreement-propose requires `proposedTo` (userId or agentId). To spawn a task under an existing agreement, use `task_spawn` instead.');
92
- const { operation: _op, ...fields } = payload;
116
+ const priced = applyFleetPricingForProtocol('agreement-propose', payload, actingAgentId);
117
+ const { operation: _op, ...fields } = priced;
93
118
  return services.agreementService.propose({ ...fields, chatId } as Parameters<typeof services.agreementService.propose>[0]);
94
119
  }
95
120
 
96
- async function subcontractAgreement(services: Services, payload: ProtocolPayload, chatId: string | null, _agents: unknown[]): Promise<unknown> {
121
+ async function subcontractAgreement(
122
+ services: Services,
123
+ payload: ProtocolPayload,
124
+ chatId: string | null,
125
+ _agents: unknown[],
126
+ actingAgentId: string | null,
127
+ ): Promise<unknown> {
97
128
  if (!payload.description || !payload.executorId) return null;
98
- const { operation: _op, ...fields } = payload;
129
+ const priced = applyFleetPricingForProtocol('agreement-subcontract', payload, actingAgentId);
130
+ const { operation: _op, ...fields } = priced;
99
131
  return services.agreementService.delegate({ ...fields, chatId } as Parameters<typeof services.agreementService.delegate>[0]);
100
132
  }
101
133
 
@@ -1,13 +1,15 @@
1
- import { defineTool, type ToolDefinition } from '../../tools/defineTool.js';
1
+ import { defineTool, type ToolDefinition } from '../../../tools/defineTool.js';
2
2
  import { dispatchProtocolOp } from './protocolRunner.js';
3
- import type { AgreementService } from '../agreements/AgreementService.js';
4
- import type { TaskService } from './TaskService.js';
3
+ import type { AgreementService } from '../../agreements/AgreementService.js';
4
+ import type { TaskService } from '../../tasks/TaskService.js';
5
5
 
6
6
  interface ToolContext extends Record<string, unknown> {
7
7
  taskService: TaskService;
8
8
  agreementService: AgreementService;
9
9
  chatId?: string | null;
10
10
  agents?: unknown[];
11
+ /** Acting agent (ZIG-368 default pricing). */
12
+ agentId?: string;
11
13
  }
12
14
 
13
15
  function handlerFor(operation: string) {
@@ -20,6 +22,7 @@ function handlerFor(operation: string) {
20
22
  { operation, ...args },
21
23
  c.chatId ?? null,
22
24
  c.agents ?? [],
25
+ typeof c.agentId === 'string' ? c.agentId : null,
23
26
  );
24
27
  if (result == null) throw new Error(`Operation ${operation} returned no result`);
25
28
  return result;
@@ -0,0 +1,75 @@
1
+ import { defineTool, type ToolDefinition } from '../../../tools/defineTool.js';
2
+ import { ZiggsConnectClient } from '../../ziggsconnect/ZiggsConnectClient.js';
3
+
4
+ type Ctx = Record<string, unknown>;
5
+ type AnyObj = Record<string, unknown>;
6
+
7
+ interface ConnectError extends Error {
8
+ status?: number;
9
+ body?: string;
10
+ }
11
+
12
+ function clientFor(ctx: Ctx): ZiggsConnectClient {
13
+ const operatorKey = ctx?.['operatorKey'] as string | undefined;
14
+ if (!operatorKey) throw new Error('operatorKey missing from tool context');
15
+ return new ZiggsConnectClient({
16
+ operatorKey,
17
+ actAsAgentId: (ctx?.['agentId'] as string) || undefined,
18
+ });
19
+ }
20
+
21
+ function rethrowWithContext(error: unknown, prefix: string): never {
22
+ const e = error as ConnectError;
23
+ const wrapped = new Error(`${prefix}: ${e.message}`) as ConnectError;
24
+ if (e.status !== undefined) wrapped.status = e.status;
25
+ if (e.body !== undefined) wrapped.body = e.body;
26
+ (wrapped as unknown as AnyObj)['cause'] = error;
27
+ throw wrapped;
28
+ }
29
+
30
+ export const connectionProxyTool: ToolDefinition = defineTool(
31
+ 'connection_proxy',
32
+ {
33
+ connectionId: { type: 'string', required: true },
34
+ grantId: { type: 'string', required: true },
35
+ action: { type: 'string', required: true },
36
+ payload: { type: 'object' },
37
+ },
38
+ async (args, ctx) => {
39
+ if (!args['connectionId']) throw new Error('connectionId is required');
40
+ if (!args['grantId']) throw new Error('grantId is required');
41
+ if (!args['action']) throw new Error('action is required');
42
+ try {
43
+ const result = await clientFor(ctx as Ctx).proxy({
44
+ connectionId: args['connectionId'] as string,
45
+ grantId: args['grantId'] as string,
46
+ action: args['action'] as string,
47
+ payload: args['payload'],
48
+ });
49
+ return { status: 'ok', result };
50
+ } catch (e) {
51
+ rethrowWithContext(e, 'Connection proxy failed');
52
+ }
53
+ },
54
+ );
55
+
56
+ export const connectionListGrantsTool: ToolDefinition = defineTool(
57
+ 'connection_list_grants',
58
+ { connectionId: { type: 'string', required: true } },
59
+ async (args, ctx) => {
60
+ if (!args['connectionId']) throw new Error('connectionId is required');
61
+ try {
62
+ const grants = await clientFor(ctx as Ctx).listGrants({
63
+ connectionId: args['connectionId'] as string,
64
+ });
65
+ return { grants: grants || [] };
66
+ } catch (e) {
67
+ rethrowWithContext(e, 'Failed to list connection grants');
68
+ }
69
+ },
70
+ );
71
+
72
+ export const CONNECTION_TOOLS: ToolDefinition[] = [
73
+ connectionProxyTool,
74
+ connectionListGrantsTool,
75
+ ];
@@ -0,0 +1,74 @@
1
+ import { defineTool, type ToolDefinition } from '../../../tools/defineTool.js';
2
+ import { ZiggsContextClient } from '../../ziggscontext/ZiggsContextClient.js';
3
+
4
+ type Ctx = Record<string, unknown>;
5
+
6
+ function clientFor(ctx: Ctx): ZiggsContextClient {
7
+ const operatorKey = ctx?.['operatorKey'] as string | undefined;
8
+ if (!operatorKey) throw new Error('operatorKey missing from tool context');
9
+ return new ZiggsContextClient({
10
+ operatorKey,
11
+ actAsAgentId: (ctx?.['agentId'] as string) || undefined,
12
+ });
13
+ }
14
+
15
+ export const contextDiscoverTool: ToolDefinition = defineTool(
16
+ 'context_discover',
17
+ {},
18
+ async (_args, ctx) => clientFor(ctx as Ctx).discover(),
19
+ {
20
+ description:
21
+ 'List which chats, agreements, and org scopes this agent can reach via context grants (descriptors only — no message or artifact content).',
22
+ },
23
+ );
24
+
25
+ export const contextListGrantsTool: ToolDefinition = defineTool(
26
+ 'context_list_grants',
27
+ {},
28
+ async (_args, ctx) => clientFor(ctx as Ctx).listGrants(),
29
+ {
30
+ description:
31
+ 'List context grants held by this agent (grant ids and bounds). Does not fetch scoped content.',
32
+ },
33
+ );
34
+
35
+ export const contextDelegateTool: ToolDefinition = defineTool(
36
+ 'context_delegate',
37
+ {
38
+ parentGrantId: { type: 'string', required: true },
39
+ holderId: { type: 'string', required: true },
40
+ scopeKind: { type: 'string', required: true },
41
+ scopeId: { type: 'string', required: true },
42
+ temporal: { type: 'string', required: true },
43
+ expiresAt: 'string',
44
+ watermarkAt: 'string',
45
+ },
46
+ async (args, ctx) => {
47
+ const temporal = args['temporal'] as string;
48
+ if (temporal !== 'from-now' && temporal !== 'from-start') {
49
+ throw new Error('temporal must be from-now or from-start');
50
+ }
51
+ const scopeKind = args['scopeKind'] as string;
52
+ if (scopeKind !== 'chat' && scopeKind !== 'agreement' && scopeKind !== 'org') {
53
+ throw new Error('scopeKind must be chat, agreement, or org');
54
+ }
55
+ return clientFor(ctx as Ctx).delegate({
56
+ parentGrantId: args['parentGrantId'] as string,
57
+ holderId: args['holderId'] as string,
58
+ scope: { kind: scopeKind as 'chat' | 'agreement' | 'org', id: args['scopeId'] as string },
59
+ temporal,
60
+ expiresAt: (args['expiresAt'] as string | undefined) ?? undefined,
61
+ watermarkAt: args['watermarkAt'] as string | undefined,
62
+ });
63
+ },
64
+ {
65
+ description:
66
+ 'Delegate a narrower child context grant (tighter scope, shorter expiry, or from-now). Holder-only.',
67
+ },
68
+ );
69
+
70
+ export const CONTEXT_TOOLS = [
71
+ contextDiscoverTool,
72
+ contextListGrantsTool,
73
+ contextDelegateTool,
74
+ ] as const;
@@ -0,0 +1,34 @@
1
+ import { defineTool, type ToolDefinition } from '../../../tools/defineTool.js';
2
+ import { AgentSearchClient } from '@ziggs-ai/api-client';
3
+
4
+ type Ctx = Record<string, unknown>;
5
+
6
+ function clientFor(ctx: Ctx): AgentSearchClient {
7
+ const operatorKey = ctx?.['operatorKey'] as string | undefined;
8
+ const agentId = ctx?.['agentId'] as string | undefined;
9
+ if (!operatorKey) throw new Error('operatorKey missing from tool context');
10
+ if (!agentId) throw new Error('agentId missing from tool context');
11
+ return new AgentSearchClient(operatorKey, agentId);
12
+ }
13
+
14
+ export const agentSearchTool: ToolDefinition = defineTool('agent_search',
15
+ { query: { type: 'string', required: true }, limit: 'number' },
16
+ async (args, ctx) => {
17
+ if (!args['query']) throw new Error('query is required');
18
+ const options: { limit?: number } = {};
19
+ if (typeof args['limit'] === 'number') options.limit = args['limit'] as number;
20
+ return clientFor(ctx as Ctx).searchAgents(args['query'] as string, options);
21
+ },
22
+ { description: 'Search for agents by capability, name, or description. Returns ranked results with relevance scores. Use before agreement_propose or agreement_subcontract to discover the right agent for a job.', isGenericFallback: true },
23
+ );
24
+
25
+ export const agentGetTool: ToolDefinition = defineTool('agent_get',
26
+ { agentId: { type: 'string', required: true } },
27
+ async (args, ctx) => {
28
+ if (!args['agentId']) throw new Error('agentId is required');
29
+ return clientFor(ctx as Ctx).getAgentById(args['agentId'] as string);
30
+ },
31
+ { description: 'Fetch the full profile of a specific agent by ID. Use to confirm capabilities and terms before proposing an agreement.' },
32
+ );
33
+
34
+ export const DISCOVERY_TOOLS: ToolDefinition[] = [agentSearchTool, agentGetTool];
@@ -0,0 +1,25 @@
1
+ import { defineTool, type ToolDefinition } from '../../../tools/defineTool.js';
2
+ import { pullFromLedger } from '@ziggs-ai/api-client';
3
+
4
+ type Ctx = Record<string, unknown>;
5
+
6
+ // Read-only marketplace access. Publishing an open quest is NOT here: it is the
7
+ // tier-1 protocol verb agreement_propose({ proposedTo: "everyone" }).
8
+ export const marketplaceViewTool: ToolDefinition = defineTool('marketplace_view',
9
+ { limit: 'number', since: 'string' },
10
+ async (args, ctx) => {
11
+ const operatorKey = (ctx as Ctx)?.['operatorKey'] as string | undefined;
12
+ const agentId = (ctx as Ctx)?.['agentId'] as string | undefined;
13
+ if (!operatorKey) throw new Error('operatorKey missing from tool context');
14
+ if (!agentId) throw new Error('agentId missing from tool context');
15
+ const options: { limit?: number; since?: string } = {
16
+ limit: typeof args['limit'] === 'number' ? args['limit'] as number : 20,
17
+ };
18
+ if (args['since']) options.since = args['since'] as string;
19
+ const quests = await pullFromLedger(options, { operatorKey, agentId });
20
+ return { quests, count: quests.length };
21
+ },
22
+ { description: 'Browse open quests on the marketplace — work that buyers have broadcast to any willing agent. Use to find available jobs. To claim a quest, use agreement_respond on the returned agreementId. To publish your own quest, use agreement_propose({ proposedTo: "everyone" }).' },
23
+ );
24
+
25
+ export const MARKETPLACE_TOOLS: ToolDefinition[] = [marketplaceViewTool];