@ziggs-ai/agent-sdk 0.1.6 → 0.1.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/agent-sdk",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Agent framework SDK for building autonomous agents on the Ziggs platform",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -16,14 +16,24 @@
16
16
  "test:watch": "node --experimental-test-module-mocks --import tsx/esm --test --watch test/*.test.ts"
17
17
  },
18
18
  "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.29.0",
19
20
  "@ziggs-ai/api-client": "^0.1.9",
20
21
  "ajv": "^8.17.1",
21
22
  "dotenv": "^17.2.3",
22
23
  "openai": "^4.0.0"
23
24
  },
24
- "keywords": ["ziggs", "agent", "sdk", "ai", "autonomous"],
25
+ "keywords": [
26
+ "ziggs",
27
+ "agent",
28
+ "sdk",
29
+ "ai",
30
+ "autonomous"
31
+ ],
25
32
  "license": "MIT",
26
- "files": ["src", "README.md"],
33
+ "files": [
34
+ "src",
35
+ "README.md"
36
+ ],
27
37
  "publishConfig": {
28
38
  "access": "public",
29
39
  "registry": "https://registry.npmjs.org/"
@@ -0,0 +1,173 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
4
+ import { AgentHost, type AgentHostOptions } from '../AgentHost.js';
5
+ import type { ToolDefinition } from '../tools/defineTool.js';
6
+ import { thinkingDefaults } from '../runtime/defaults.js';
7
+ import type { Workflow } from '../types.js';
8
+
9
+ export type McpTransportConfig =
10
+ | { type: 'stdio'; command: string; args?: string[]; env?: Record<string, string> }
11
+ | { type: 'http'; url: string };
12
+
13
+ export interface CreateMcpAgentOptions extends Omit<AgentHostOptions, 'tools' | 'workflow' | 'states' | 'initial'> {
14
+ /** MCP server connection — stdio (local process) or HTTP (remote). */
15
+ transport: McpTransportConfig;
16
+ /** Additional Ziggs tools to register alongside the MCP-derived ones. */
17
+ extraTools?: ToolDefinition[];
18
+ /** Override the default single-state thinking workflow. */
19
+ workflow?: Workflow;
20
+ }
21
+
22
+ export interface McpAgentHandle {
23
+ host: AgentHost;
24
+ /** Disconnect the MCP client (call on shutdown alongside host.disconnect()). */
25
+ closeMcp: () => Promise<void>;
26
+ /** Names of MCP tools that were mapped to Ziggs tools on startup. */
27
+ toolNames: string[];
28
+ }
29
+
30
+ function buildTransport(config: McpTransportConfig) {
31
+ if (config.type === 'stdio') {
32
+ return new StdioClientTransport({
33
+ command: config.command,
34
+ args: config.args ?? [],
35
+ env: config.env,
36
+ });
37
+ }
38
+ return new StreamableHTTPClientTransport(new URL(config.url));
39
+ }
40
+
41
+ type McpTool = {
42
+ name: string;
43
+ description?: string;
44
+ inputSchema?: Record<string, unknown>;
45
+ };
46
+
47
+ type McpContent = { type: string; text?: string };
48
+
49
+ function mcpContentToResult(content: McpContent[]): unknown {
50
+ const text = content
51
+ .map(c => (c.type === 'text' ? c.text ?? '' : JSON.stringify(c)))
52
+ .join('\n');
53
+ try {
54
+ return JSON.parse(text);
55
+ } catch {
56
+ return text;
57
+ }
58
+ }
59
+
60
+ function mapMcpTools(client: Client, tools: McpTool[]): ToolDefinition[] {
61
+ return tools.map(tool => ({
62
+ schema: {
63
+ type: 'function' as const,
64
+ function: {
65
+ name: tool.name,
66
+ description: tool.description ?? '',
67
+ parameters: tool.inputSchema ?? { type: 'object', properties: {} },
68
+ },
69
+ },
70
+ handler: async (args: Record<string, unknown>) => {
71
+ const result = await client.callTool({ name: tool.name, arguments: args }) as {
72
+ isError?: boolean;
73
+ content: McpContent[];
74
+ };
75
+ if (result.isError) {
76
+ throw new Error(mcpContentToResult(result.content) as string);
77
+ }
78
+ return mcpContentToResult(result.content);
79
+ },
80
+ isGenericFallback: true,
81
+ }));
82
+ }
83
+
84
+ /**
85
+ * Wraps any MCP server as a Ziggs AgentHost.
86
+ *
87
+ * 1. Opens a connection to the MCP server (stdio or HTTP).
88
+ * 2. Calls tools/list and maps each tool to a Ziggs ToolDefinition.
89
+ * 3. Returns an AgentHost pre-loaded with those tools, ready to connectAsync().
90
+ *
91
+ * Usage:
92
+ * const { host, closeMcp } = await createMcpAgent({
93
+ * transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'] },
94
+ * agentId: 'my-github-agent',
95
+ * name: 'GitHub Agent',
96
+ * description: 'Wraps the GitHub MCP server',
97
+ * });
98
+ * await host.connectAsync();
99
+ *
100
+ * process.on('SIGTERM', async () => {
101
+ * host.disconnect();
102
+ * await host.drain();
103
+ * await closeMcp();
104
+ * });
105
+ */
106
+ function buildDefaultWorkflow(description: string): Workflow {
107
+ const dflt = thinkingDefaults({ initial: 'idle' });
108
+ return {
109
+ id: 'mcp-agent',
110
+ description: description || 'MCP-backed agent',
111
+ initial: 'idle',
112
+ states: {
113
+ idle: {
114
+ kind: 'thinking',
115
+ prompt: {
116
+ role: 'You are a helpful agent powered by an MCP server.',
117
+ goal: description || 'Help the user by calling the available tools.',
118
+ constraints: 'Use your tools to answer requests. When no action is needed, wait.',
119
+ },
120
+ actions: { ...dflt.actions },
121
+ transitions: [...dflt.transitions],
122
+ },
123
+ },
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Wraps any MCP server as a Ziggs AgentHost.
129
+ *
130
+ * 1. Opens a connection to the MCP server (stdio or HTTP).
131
+ * 2. Calls tools/list and maps each tool to a Ziggs ToolDefinition.
132
+ * 3. Returns an AgentHost pre-loaded with those tools, ready to connectAsync().
133
+ *
134
+ * Usage:
135
+ * const { host, closeMcp } = await createMcpAgent({
136
+ * transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'] },
137
+ * agentId: 'my-github-agent',
138
+ * name: 'GitHub Agent',
139
+ * description: 'Wraps the GitHub MCP server',
140
+ * });
141
+ * await host.connectAsync();
142
+ *
143
+ * process.on('SIGTERM', async () => {
144
+ * host.disconnect();
145
+ * await host.drain();
146
+ * await closeMcp();
147
+ * });
148
+ */
149
+ export async function createMcpAgent(options: CreateMcpAgentOptions): Promise<McpAgentHandle> {
150
+ const { transport: transportConfig, extraTools = [], workflow, ...hostOptions } = options;
151
+
152
+ const transport = buildTransport(transportConfig);
153
+ const client = new Client({ name: 'ziggs-mcp-agent', version: '1.0.0' });
154
+
155
+ await client.connect(transport);
156
+
157
+ const { tools: rawTools } = await client.listTools();
158
+ const mcpTools = mapMcpTools(client, rawTools as McpTool[]);
159
+
160
+ const resolvedWorkflow = workflow ?? buildDefaultWorkflow(hostOptions.description ?? '');
161
+
162
+ const host = new AgentHost({
163
+ ...hostOptions,
164
+ workflow: resolvedWorkflow,
165
+ tools: [...mcpTools, ...extraTools],
166
+ });
167
+
168
+ return {
169
+ host,
170
+ closeMcp: () => client.close(),
171
+ toolNames: rawTools.map((t: McpTool) => t.name),
172
+ };
173
+ }
@@ -62,49 +62,85 @@ export class OpenAIAdapter {
62
62
  : undefined;
63
63
 
64
64
  try {
65
- const { usage: _u, ...restOptions } = options as Record<string, unknown>;
66
- // Verbose LLM request/response logging — commented out to reduce log noise.
67
- // Re-enable by uncommenting to see full prompt/output blocks per LLM call.
68
- // const divider = '─'.repeat(80);
69
- // console.log(`\n${divider}`);
70
- // console.log(`📤 LLM REQUEST → ${this.model} (${messages.length} msg${messages.length === 1 ? '' : 's'})`);
71
- // console.log(divider);
72
- // for (const m of messages) {
73
- // const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content);
74
- // const len = content?.length ?? 0;
75
- // console.log(`\n[${m.role.toUpperCase()}] ${len} chars`);
76
- // console.log(content ?? '');
77
- // }
78
- // console.log(`${divider}\n`);
65
+ const { usage: _u, response_format: _explicitResponseFormat, ...restOptions } = options as Record<string, unknown>;
66
+ const responseFormat = openAITools
67
+ ? undefined
68
+ : ((_explicitResponseFormat as OpenAI.ResponseFormatJSONObject) ?? { type: 'json_object' });
69
+
70
+ // OpenAI 400s response_format:{type:'json_object'} unless the word "json"
71
+ // appears somewhere in the messages. Callers are expected to mention it
72
+ // in their system prompt, but that's an implicit cross-file contract —
73
+ // guarantee it here instead of trusting every call site to remember.
74
+ let finalMessages = messages;
75
+ if (
76
+ responseFormat?.type === 'json_object' &&
77
+ !messages.some(m => {
78
+ const c = typeof m.content === 'string' ? m.content : JSON.stringify(m.content ?? '');
79
+ return /json/i.test(c);
80
+ })
81
+ ) {
82
+ finalMessages = [...messages, { role: 'system', content: 'Respond with valid JSON.' }];
83
+ }
84
+
85
+ // Verbose LLM request/response logging — enabled for debugging.
86
+ // Comment out again to reduce log noise.
87
+ const divider = '─'.repeat(80);
88
+ console.log(`\n${divider}`);
89
+ console.log(`📤 LLM REQUEST → ${this.model} (${finalMessages.length} msg${finalMessages.length === 1 ? '' : 's'})`);
90
+ console.log(divider);
91
+ for (const m of finalMessages) {
92
+ const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content);
93
+ const len = content?.length ?? 0;
94
+ console.log(`\n[${m.role.toUpperCase()}] ${len} chars`);
95
+ console.log(content ?? '');
96
+ }
97
+ console.log(`${divider}\n`);
79
98
  const res = await this.client.chat.completions.create({
80
99
  model: this.model,
81
- messages,
100
+ messages: finalMessages,
82
101
  stream: false,
83
- max_completion_tokens: 3000,
84
- response_format: openAITools ? undefined : ((restOptions['response_format'] as OpenAI.ResponseFormatJSONObject) ?? { type: 'json_object' }),
102
+ // 25000 reserves room for hidden reasoning tokens on reasoning models
103
+ // (gpt-5.x/o-series) — they're billed against this cap before any
104
+ // visible content, so a low cap can exhaust the budget and return
105
+ // empty content with finish_reason "length".
106
+ max_completion_tokens: 25000,
107
+ response_format: responseFormat,
85
108
  ...(openAITools && { tools: openAITools }),
86
109
  ...restOptions,
87
110
  } as OpenAI.Chat.ChatCompletionCreateParamsNonStreaming);
88
111
 
89
112
  const msg = res.choices[0].message;
113
+ const finishReason = res.choices[0].finish_reason;
90
114
  const content = msg.content?.trim() ?? null;
91
115
  const toolCalls = msg.tool_calls?.length ? msg.tool_calls : null;
92
- // Verbose LLM response logging — commented out to reduce log noise.
93
- // console.log(`\n${divider}`);
94
- // console.log(`📥 LLM RESPONSE ← ${this.model}`);
95
- // console.log(divider);
96
- // if (content) {
97
- // console.log(`\n[CONTENT] ${content.length} chars`);
98
- // console.log(content);
99
- // }
100
- // if (toolCalls) {
101
- // console.log(`\n[TOOL CALLS] ${toolCalls.length}`);
102
- // for (const tc of toolCalls) {
103
- // console.log(` → ${tc.function.name}(${tc.function.arguments})`);
104
- // }
105
- // }
106
- // if (!content && !toolCalls) console.log('\n(no content, no tool calls)');
107
- // console.log(`${divider}\n`);
116
+ // Verbose LLM response logging — enabled for debugging.
117
+ // Comment out again to reduce log noise.
118
+ console.log(`\n${divider}`);
119
+ console.log(`📥 LLM RESPONSE ← ${this.model} finish=${finishReason}`);
120
+ console.log(divider);
121
+ if (content) {
122
+ console.log(`\n[CONTENT] ${content.length} chars`);
123
+ console.log(content);
124
+ }
125
+ if (toolCalls) {
126
+ console.log(`\n[TOOL CALLS] ${toolCalls.length}`);
127
+ for (const tc of toolCalls) {
128
+ console.log(` → ${tc.function.name}(${tc.function.arguments})`);
129
+ }
130
+ }
131
+ if (!content && !toolCalls) console.log('\n(no content, no tool calls)');
132
+ if (res.usage) {
133
+ const u = res.usage as OpenAI.CompletionUsage & { completion_tokens_details?: { reasoning_tokens?: number } };
134
+ console.log(`\n[USAGE] prompt=${u.prompt_tokens} completion=${u.completion_tokens}${u.completion_tokens_details?.reasoning_tokens != null ? ` (reasoning=${u.completion_tokens_details.reasoning_tokens})` : ''} total=${u.total_tokens}`);
135
+ }
136
+ console.log(`${divider}\n`);
137
+ if (!content && !toolCalls && finishReason === 'length') {
138
+ const u = res.usage as (OpenAI.CompletionUsage & { completion_tokens_details?: { reasoning_tokens?: number } }) | undefined;
139
+ throw new Error(
140
+ `LLM returned no usable output — completion token budget exhausted before any content ` +
141
+ `(finish_reason=length, reasoning_tokens=${u?.completion_tokens_details?.reasoning_tokens ?? 'unknown'})`,
142
+ );
143
+ }
108
144
  return {
109
145
  content,
110
146
  tool_calls: toolCalls,
@@ -131,7 +167,7 @@ export class OpenAIAdapter {
131
167
  model: this.model,
132
168
  messages,
133
169
  stream: true,
134
- max_completion_tokens: 3000,
170
+ max_completion_tokens: 25000,
135
171
  });
136
172
  let text = '';
137
173
  for await (const chunk of stream) {
@@ -1,3 +1,5 @@
1
+ import { isBroadcastTarget } from '@ziggs-ai/api-client';
2
+
1
3
  export function getBatchEvents(rawEvent: unknown): unknown[] {
2
4
  if (!rawEvent) return [];
3
5
  const e = rawEvent as Record<string, unknown>;
@@ -12,6 +14,6 @@ export function isTaskResultRelevantToAgent(result: Record<string, unknown>, own
12
14
  result['creatorIsYou'] || result['providerIsYou'] || result['payerIsYou'] || result['proposedToIsYou'] ||
13
15
  parties['creator'] === ownAgentId || parties['provider'] === ownAgentId ||
14
16
  parties['payer'] === ownAgentId || parties['proposedTo'] === ownAgentId ||
15
- parties['proposedTo'] === 'everyone'
17
+ isBroadcastTarget(parties['proposedTo'] as string | undefined)
16
18
  );
17
19
  }
@@ -116,7 +116,13 @@ export class HistoryFormatter {
116
116
 
117
117
  formatParticipant(participant: AnyObj | null | undefined, agentId: string): string {
118
118
  if (!participant) return 'unknown';
119
- const id = (participant['id'] as string) || 'unknown';
119
+ // Message.sender is stored as {agentId, underAgreementId} (no `id`, no
120
+ // `type`) while Message.receiver is {id, type} — two different shapes on
121
+ // the same document (see backend Message schema). Without this fallback,
122
+ // every sender renders as "unknown (unknown)" and self-sent history rows
123
+ // never match `id === agentId`, so the agent can't recognize its own
124
+ // past messages in <history>.
125
+ const id = (participant['id'] as string) || (participant['agentId'] as string) || 'unknown';
120
126
  if (participant['type'] === 'system') return 'System';
121
127
  if (id === agentId) return 'You';
122
128
  return `${id} (${participant['type'] || 'unknown'})`;
package/src/index.ts CHANGED
@@ -9,6 +9,8 @@
9
9
  */
10
10
 
11
11
  export { defineAgent } from './defineAgent.js';
12
+ export { createMcpAgent } from './adapters/McpAdapter.js';
13
+ export type { CreateMcpAgentOptions, McpAgentHandle, McpTransportConfig } from './adapters/McpAdapter.js';
12
14
  export { AgentHost, createAgent, createAgentPool } from './AgentHost.js';
13
15
  export { Agent } from './agent/Agent.js';
14
16
  export type { TickInput, TickOutput, AgentOptions } from './agent/Agent.js';
@@ -12,7 +12,7 @@ export const TIER_PRICE_CENTS: Record<FleetPricingTier, number> = {
12
12
  A: 200, // 2 ZIG / call
13
13
  B: 500, // 5 ZIG / call
14
14
  C: 1500, // 15 ZIG / job (orchestrator, charged once at top)
15
- D: 100, // 1 ZIG / call (simulated)
15
+ D: 100, // 1 ZIG / call (hand-written demo agents)
16
16
  };
17
17
 
18
18
  const ZIGGS_ORCHESTRATOR_IDS = new Set(['ziggs-agent']);
@@ -144,11 +144,18 @@ export function applyFleetAgreementPricing(
144
144
  return next;
145
145
  }
146
146
 
147
- /** Default tier price when a fleet agent counters without an explicit `price`. */
148
- export function applyFleetCounterPricing(
149
- payload: ProtocolPricingPayload,
147
+ /**
148
+ * Default tier price when a fleet agent counters without an explicit `price`.
149
+ *
150
+ * Generic over the payload shape so the precise field types of the caller's
151
+ * payload survive the round-trip — this helper only ever sets `price`, so a
152
+ * richer input type (e.g. protocolRunner's `ProtocolPayload`) must come back
153
+ * out unwidened by `ProtocolPricingPayload`'s `[key: string]: unknown` index.
154
+ */
155
+ export function applyFleetCounterPricing<T extends ProtocolPricingPayload>(
156
+ payload: T,
150
157
  actingAgentId: string,
151
- ): ProtocolPricingPayload {
158
+ ): T {
152
159
  if (payload.price != null && payload.price !== undefined) {
153
160
  return payload;
154
161
  }
@@ -157,7 +164,7 @@ export function applyFleetCounterPricing(
157
164
  payload.description ?? payload.agreementDescription,
158
165
  );
159
166
  if (cents == null) return payload;
160
- return { ...payload, price: cents };
167
+ return { ...payload, price: cents } as T;
161
168
  }
162
169
 
163
170
  /** User or agent direct-create / marketplace: default from provider specialist. */
@@ -270,305 +270,6 @@ export const FLEET_TIER_BY_AGENT_ID: Record<string, FleetPricingTier> = {
270
270
  "feature-prioritizer": "C",
271
271
  "threat-model-committee": "C",
272
272
  "negotiation-simulator": "C",
273
- "pizza-ordering-agent": "D",
274
- "sushi-ordering-agent": "D",
275
- "burger-ordering-agent": "D",
276
- "salad-ordering-agent": "D",
277
- "coffee-ordering-agent": "D",
278
- "restaurant-finder": "D",
279
- "reservation-maker": "D",
280
- "menu-browser": "D",
281
- "dietary-filter": "D",
282
- "review-aggregator": "D",
283
- "meal-planner": "D",
284
- "recipe-finder": "D",
285
- "grocery-list-builder": "D",
286
- "calorie-counter": "D",
287
- "wine-pairer": "D",
288
- "food-delivery-tracker": "D",
289
- "tip-calculator": "D",
290
- "split-bill-calculator": "D",
291
- "allergen-checker": "D",
292
- "catering-coordinator": "D",
293
- "party-food-planner": "D",
294
- "food-truck-finder": "D",
295
- "brunch-recommender": "D",
296
- "dessert-advisor": "D",
297
- "happy-hour-finder": "D",
298
- "breakfast-planner": "D",
299
- "late-night-eats-finder": "D",
300
- "food-allergy-restaurant-finder": "D",
301
- "kids-menu-finder": "D",
302
- "flight-searcher": "D",
303
- "hotel-booker": "D",
304
- "car-rental": "D",
305
- "airport-transfer": "D",
306
- "visa-checker": "D",
307
- "trip-planner": "D",
308
- "itinerary-builder": "D",
309
- "packing-list-maker": "D",
310
- "currency-advisor": "D",
311
- "travel-insurance": "D",
312
- "train-booker": "D",
313
- "bus-route-finder": "D",
314
- "ferry-scheduler": "D",
315
- "road-trip-planner": "D",
316
- "toll-calculator": "D",
317
- "rideshare-booker": "D",
318
- "bike-share": "D",
319
- "scooter-rental": "D",
320
- "parking-finder": "D",
321
- "traffic-checker": "D",
322
- "travel-photo-finder": "D",
323
- "local-guide": "D",
324
- "phrase-book": "D",
325
- "embassy-locator": "D",
326
- "timezone-advisor": "D",
327
- "cruise-finder": "D",
328
- "camping-booker": "D",
329
- "hostel-searcher": "D",
330
- "vacation-rental": "D",
331
- "loyalty-tracker": "D",
332
- "electronics-shopper": "D",
333
- "fashion-advisor": "D",
334
- "book-recommender": "D",
335
- "gift-finder": "D",
336
- "price-comparator": "D",
337
- "grocery-shopper": "D",
338
- "furniture-browser": "D",
339
- "auto-parts-finder": "D",
340
- "pet-supplies": "D",
341
- "sports-gear": "D",
342
- "deal-hunter": "D",
343
- "coupon-finder": "D",
344
- "wishlist-manager": "D",
345
- "return-tracker": "D",
346
- "warranty-checker": "D",
347
- "subscription-manager": "D",
348
- "auction-bidder": "D",
349
- "local-store-finder": "D",
350
- "size-converter": "D",
351
- "product-reviewer": "D",
352
- "comparison-builder": "D",
353
- "inventory-checker": "D",
354
- "price-drop-monitor": "D",
355
- "flash-sale-alerter": "D",
356
- "barcode-scanner": "D",
357
- "doctor-appointment-booker": "D",
358
- "dentist-scheduler": "D",
359
- "optometrist-finder": "D",
360
- "symptom-checker": "D",
361
- "medication-reminder": "D",
362
- "prescription-tracker": "D",
363
- "pharmacy-finder": "D",
364
- "fitness-trainer": "D",
365
- "workout-planner": "D",
366
- "yoga-instructor": "D",
367
- "meditation-guide": "D",
368
- "step-counter": "D",
369
- "nutrition-advisor": "D",
370
- "diet-planner": "D",
371
- "water-tracker": "D",
372
- "sleep-tracker": "D",
373
- "mood-logger": "D",
374
- "mental-health-checkin": "D",
375
- "stress-advisor": "D",
376
- "breathing-guide": "D",
377
- "first-aid-advisor": "D",
378
- "vaccine-tracker": "D",
379
- "insurance-claim-helper": "D",
380
- "lab-result-explainer": "D",
381
- "pet-vet-booker": "D",
382
- "smart-light-controller": "D",
383
- "thermostat-manager": "D",
384
- "smart-lock-controller": "D",
385
- "camera-monitor": "D",
386
- "smart-speaker": "D",
387
- "plumber-finder": "D",
388
- "electrician-booker": "D",
389
- "cleaner-scheduler": "D",
390
- "gardener-finder": "D",
391
- "pest-control": "D",
392
- "interior-designer": "D",
393
- "paint-matcher": "D",
394
- "furniture-arranger": "D",
395
- "lighting-planner": "D",
396
- "utility-tracker": "D",
397
- "energy-monitor": "D",
398
- "water-advisor": "D",
399
- "moving-coordinator": "D",
400
- "storage-finder": "D",
401
- "furniture-assembly-helper": "D",
402
- "home-warranty-manager": "D",
403
- "renovation-planner": "D",
404
- "contractor-reviewer": "D",
405
- "package-tracker": "D",
406
- "handyman-finder": "D",
407
- "bank-account-viewer": "D",
408
- "transaction-categorizer": "D",
409
- "budget-planner": "D",
410
- "savings-tracker": "D",
411
- "credit-card-advisor": "D",
412
- "loan-calculator": "D",
413
- "mortgage-estimator": "D",
414
- "refinance-checker": "D",
415
- "portfolio-tracker": "D",
416
- "stock-trader-sim": "D",
417
- "crypto-trader-sim": "D",
418
- "robo-advisor": "D",
419
- "tax-estimator": "D",
420
- "deduction-finder": "D",
421
- "tax-filing-assistant": "D",
422
- "receipt-organizer": "D",
423
- "auto-insurance-quoter": "D",
424
- "home-insurance-quoter": "D",
425
- "life-insurance-quoter": "D",
426
- "health-insurance-quoter": "D",
427
- "claim-filer": "D",
428
- "bill-pay-scheduler": "D",
429
- "subscription-auditor": "D",
430
- "retirement-calculator": "D",
431
- "college-savings-planner": "D",
432
- "uber-sim": "D",
433
- "lyft-sim": "D",
434
- "taxi-booker": "D",
435
- "shuttle-service": "D",
436
- "limo-booker": "D",
437
- "subway-navigator": "D",
438
- "bus-tracker": "D",
439
- "commuter-rail": "D",
440
- "light-rail": "D",
441
- "ferry-booker": "D",
442
- "bike-rental": "D",
443
- "scooter-rental-sim": "D",
444
- "e-bike-finder": "D",
445
- "skateboard-rental": "D",
446
- "segway-tour": "D",
447
- "parking-meter": "D",
448
- "garage-finder": "D",
449
- "valet-service": "D",
450
- "airport-parking": "D",
451
- "street-parking": "D",
452
- "car-wash-finder": "D",
453
- "oil-change-scheduler": "D",
454
- "tire-shop": "D",
455
- "mechanic-finder": "D",
456
- "roadside-assistance": "D",
457
- "course-finder": "D",
458
- "university-searcher": "D",
459
- "scholarship-finder": "D",
460
- "admission-advisor": "D",
461
- "resume-reviewer": "D",
462
- "job-searcher": "D",
463
- "interview-prepper": "D",
464
- "salary-negotiator": "D",
465
- "career-coach": "D",
466
- "online-class-enrollor": "D",
467
- "certificate-tracker": "D",
468
- "skill-assessor": "D",
469
- "mentor-matcher": "D",
470
- "language-lesson-booker": "D",
471
- "music-lesson-scheduler": "D",
472
- "tutoring-booker": "D",
473
- "internship-finder": "D",
474
- "networking-advisor": "D",
475
- "linkedin-optimizer": "D",
476
- "conference-finder": "D",
477
- "workshop-registrar": "D",
478
- "webinar-scheduler": "D",
479
- "library-manager": "D",
480
- "study-group-coordinator": "D",
481
- "campus-navigator": "D",
482
- "concert-ticket-buyer": "D",
483
- "movie-ticket-booker": "D",
484
- "theater-booker": "D",
485
- "party-organizer": "D",
486
- "wedding-planner": "D",
487
- "birthday-coordinator": "D",
488
- "sports-ticket-buyer": "D",
489
- "gym-membership-manager": "D",
490
- "spa-booker": "D",
491
- "streaming-manager": "D",
492
- "playlist-curator": "D",
493
- "podcast-recommender": "D",
494
- "board-game-organizer": "D",
495
- "trivia-host": "D",
496
- "karaoke-picker": "D",
497
- "museum-ticket-buyer": "D",
498
- "zoo-pass-manager": "D",
499
- "amusement-park-planner": "D",
500
- "festival-finder": "D",
501
- "book-club-organizer": "D",
502
- "art-class-booker": "D",
503
- "cooking-class-finder": "D",
504
- "photography-booker": "D",
505
- "escape-room-booker": "D",
506
- "dmv-appointment": "D",
507
- "passport-renewal": "D",
508
- "visa-tracker": "D",
509
- "voter-registration": "D",
510
- "polling-finder": "D",
511
- "official-lookup": "D",
512
- "building-permit-tracker": "D",
513
- "zoning-info": "D",
514
- "noise-complaint-filer": "D",
515
- "jury-duty-manager": "D",
516
- "court-date-tracker": "D",
517
- "public-record-searcher": "D",
518
- "tax-authority-contact": "D",
519
- "benefits-checker": "D",
520
- "park-permit": "D",
521
- "recycling-guide": "D",
522
- "carbon-calculator": "D",
523
- "eco-product-finder": "D",
524
- "volunteer-finder": "D",
525
- "donation-tracker": "D",
526
- "charity-evaluator": "D",
527
- "community-board": "D",
528
- "neighborhood-alert": "D",
529
- "lost-and-found": "D",
530
- "carpool-matcher": "D",
531
- "bike-route-planner": "D",
532
- "transit-optimizer": "D",
533
- "solar-estimator": "D",
534
- "ev-charger-finder": "D",
535
- "green-energy-advisor": "D",
536
- "pet-adoption": "D",
537
- "breeder-finder": "D",
538
- "pet-sitter-booker": "D",
539
- "dog-walker": "D",
540
- "pet-groomer": "D",
541
- "pet-boarding": "D",
542
- "pet-food-delivery": "D",
543
- "pet-toy-recommender": "D",
544
- "pet-training-class": "D",
545
- "pet-insurance-comparer": "D",
546
- "dry-cleaner": "D",
547
- "tailor-finder": "D",
548
- "shoe-repair": "D",
549
- "watch-repair": "D",
550
- "phone-repair": "D",
551
- "car-detailer": "D",
552
- "car-wash-booker": "D",
553
- "auto-detailing": "D",
554
- "mechanic-scheduler": "D",
555
- "tire-rotator": "D",
556
- "photo-printer": "D",
557
- "custom-framer": "D",
558
- "engraver": "D",
559
- "embroiderer": "D",
560
- "storage-unit-finder": "D",
561
- "junk-removal": "D",
562
- "donation-pickup": "D",
563
- "hauler": "D",
564
- "painter-finder": "D",
565
- "roofer-finder": "D",
566
- "window-washer": "D",
567
- "chimney-sweep": "D",
568
- "snow-remover": "D",
569
- "lawn-care": "D",
570
- "tree-trimmer": "D",
571
- "power-washer": "D",
572
273
  "aristotle": "B",
573
274
  "plato": "B",
574
275
  "confucius": "B",
@@ -1019,4 +720,4 @@ export const FLEET_TIER_BY_AGENT_ID: Record<string, FleetPricingTier> = {
1019
720
  "death-studies-scholar": "B"
1020
721
  } as Record<string, FleetPricingTier>;
1021
722
 
1022
- export const FLEET_TIER_MAP_SIZE = 1012;
723
+ export const FLEET_TIER_MAP_SIZE = 713;
@@ -2,6 +2,7 @@ import type { Action, Ctx, PromptDef } from '../types.js';
2
2
  import { HistoryFormatter, AgreementFormatter } from '../formatters/index.js';
3
3
  import { validateContext } from '../cognition/validateContext.js';
4
4
  import type { RawEvent } from './buildOutcome.js';
5
+ import { isBroadcastTarget } from '@ziggs-ai/api-client';
5
6
 
6
7
  type AnyObj = Record<string, unknown>;
7
8
 
@@ -207,7 +208,9 @@ ${this._renderDecisionSchemaFromActions(actions, senderMeta, ctxObj, omitMessage
207
208
  const content = event.text || event.result || event.content;
208
209
  if (!content) return `<event type="${eventType}" from="${from}">(Empty)</event>`;
209
210
  const str = typeof content === 'object' ? JSON.stringify(content) : String(content);
210
- const display = str.length > 1000 ? str.slice(0, 1000) + '...' : str;
211
+ // Matches MAX_MSG_CHARS in runTurn.ts — the incoming instruction shouldn't
212
+ // be truncated tighter than what the agent itself is allowed to send back.
213
+ const display = str.length > 3500 ? str.slice(0, 3500) + '...' : str;
211
214
  return `<event type="${eventType}" from="${from}">\n${display}\n</event>`;
212
215
  }
213
216
 
@@ -370,7 +373,7 @@ ${this._renderDecisionSchemaFromActions(actions, senderMeta, ctxObj, omitMessage
370
373
 
371
374
  const replyTo = (senderMeta.defaultReplyReceiverId as string) || '<receiverId>';
372
375
  const pendingProposals = (ctxObj.agreements || []).filter(
373
- (a) => (a.proposal as Record<string, unknown> | undefined)?.status === 'pending' && (a.proposedToIsYou || (a.parties as Record<string, unknown> | undefined)?.proposedTo === 'everyone'),
376
+ (a) => (a.proposal as Record<string, unknown> | undefined)?.status === 'pending' && (a.proposedToIsYou || isBroadcastTarget((a.parties as Record<string, unknown> | undefined)?.proposedTo as string | undefined)),
374
377
  );
375
378
 
376
379
  if (pendingProposals.length > 0) {
@@ -424,9 +424,13 @@ function eventsAreTerminal(events: EmittedEvent[]): boolean {
424
424
  // (api-client now returns Agreement directly, not wrapped in { agreement }).
425
425
  const ag = (r.agreement ?? (r.agreementId ? r : null)) as Record<string, unknown> | null | undefined;
426
426
  const task = (r.task || (r.taskId ? r : null)) as Record<string, unknown> | null | undefined;
427
+ // Agreement's own status lives flat as `proposalStatus` (see api-client
428
+ // Agreement type) — not nested under `.proposal.status`. Check both so
429
+ // a successful agreement_propose is recognized as terminal instead of
430
+ // being re-issued by the LLM on the next loop iteration.
427
431
  const agProposal = ag?.proposal as Record<string, unknown> | undefined;
428
432
  const taskProposal = task?.proposal as Record<string, unknown> | undefined;
429
- const proposalStatus = agProposal?.status || taskProposal?.status;
433
+ const proposalStatus = ag?.proposalStatus ?? agProposal?.status ?? taskProposal?.status;
430
434
  if (proposalStatus === 'pending' || proposalStatus === 'approved') return true;
431
435
  const state = task?.state || r.state;
432
436
  if (state === 'active' || state === 'in-progress' ||
@@ -445,10 +449,11 @@ function eventsContainAgreementCreation(events: EmittedEvent[]): boolean {
445
449
  if (r.success === false || r.error) return false;
446
450
  // Support both nested { agreement: {...} } and flat Agreement objects.
447
451
  const ag = (r.agreement ?? (r.agreementId ? r : null)) as Record<string, unknown> | undefined;
452
+ // See eventsAreTerminal above: proposalStatus is flat on Agreement, not nested.
448
453
  const agProposal = ag?.proposal as Record<string, unknown> | undefined;
449
454
  const task = r.task as Record<string, unknown> | undefined;
450
455
  const taskProposal = task?.proposal as Record<string, unknown> | undefined;
451
- const proposalStatus = agProposal?.status || taskProposal?.status;
456
+ const proposalStatus = ag?.proposalStatus ?? agProposal?.status ?? taskProposal?.status;
452
457
  return proposalStatus === 'pending' || proposalStatus === 'approved';
453
458
  });
454
459
  }
@@ -171,7 +171,14 @@ export async function runInboxCatchUp(deps: InboxCatchUpDeps): Promise<InboxCatc
171
171
  const toAck: InboxAck[] = [];
172
172
 
173
173
  if (envelope.truncatedScopes > 0) {
174
- warn(label, `inbox truncatedScopes=${envelope.truncatedScopes} — skipping ack until narrowed`);
174
+ // Informational only — does NOT block acking the scopes actually
175
+ // returned below. truncatedScopes just means the caller's total reach
176
+ // exceeded the server's per-call scope cap (backend/src/inbox/inbox.service.ts,
177
+ // INBOX_MAX_SCOPES); each entry in envelope.scopes is still an
178
+ // independently accurate, safe-to-ack unit of news for that scope. Never
179
+ // acking here meant a busy long-lived agent (reach > cap) would never ack
180
+ // anything and re-deliver its entire backlog on every reconnect forever.
181
+ warn(label, `inbox truncatedScopes=${envelope.truncatedScopes} (reach exceeds per-call cap — examined scopes are still acked normally)`);
175
182
  }
176
183
 
177
184
  for (const scope of envelope.scopes) {
@@ -202,11 +209,7 @@ export async function runInboxCatchUp(deps: InboxCatchUpDeps): Promise<InboxCatc
202
209
  }
203
210
  }
204
211
 
205
- if (
206
- envelope.truncatedScopes === 0 &&
207
- scope.latestAt &&
208
- (scope.newMessages > 0 || scope.newArtifacts > 0)
209
- ) {
212
+ if (scope.latestAt && (scope.newMessages > 0 || scope.newArtifacts > 0)) {
210
213
  toAck.push({
211
214
  kind: scope.scope.kind,
212
215
  id: scope.scope.id,
@@ -1,16 +1,15 @@
1
1
  import {
2
- proposeAgreement, delegateAgreement, respondToAgreement, counterAgreement,
2
+ proposeAgreement, proposeBroadcast as proposeBroadcastApi, delegateAgreement, respondToAgreement, counterAgreement,
3
3
  getAgreementStatus, listAgreements, getMyAgreements, getAgreement,
4
4
  publishOffer as publishOfferApi, createAgreement, revokeAgreement, getAgreementsByChat, linkAgreementToChat,
5
5
  getChatsForAgreement, joinAgreement, linkArtifactToAgreement, getArtifactsForAgreement,
6
- linkUserToAgreement, getUsersForAgreement, createContract,
7
- OPEN_AGREEMENT_TARGET,
6
+ linkUserToAgreement, getUsersForAgreement,
8
7
  type Creds, type Agreement,
9
8
  type ProposeAgreementData, type ProposeDirectInput, type ProposeBroadcastInput,
10
9
  type PublishOfferPayload,
11
10
  type DelegateAgreementData, type CounterAgreementData,
12
11
  type CreateAgreementBody, type ListAgreementsFilters, type GetMyAgreementsFilters,
13
- type ChatLinkType, type ArtifactLinkType, type UserRole, type CreateContractInput,
12
+ type ChatLinkType, type ArtifactLinkType, type UserRole,
14
13
  } from '@ziggs-ai/api-client';
15
14
 
16
15
  export class AgreementService {
@@ -22,19 +21,18 @@ export class AgreementService {
22
21
  this.creds = { operatorKey, agentId };
23
22
  }
24
23
 
25
- /** @deprecated Prefer {@link proposeDirect} or {@link proposeBroadcast}. */
26
- contract(input: CreateContractInput): Promise<Agreement> {
27
- return createContract(input, this.creds);
28
- }
29
-
30
24
  /** 1:1 proposal. Server defaults engagementKind to `service` when omitted. */
31
25
  proposeDirect(payload: ProposeDirectInput): Promise<Agreement> {
32
26
  return proposeAgreement(payload, this.creds);
33
27
  }
34
28
 
35
- /** Open buyer-broadcast (`proposedTo: "everyone"`). Server defaults engagementKind to `service`. */
29
+ /**
30
+ * Open buyer-broadcast. `payload.audience` selects who may see/claim it:
31
+ * 'everyone' (default, fully public) or 'org' (members of your active org).
32
+ * Server defaults engagementKind to `service`.
33
+ */
36
34
  proposeBroadcast(payload: ProposeBroadcastInput): Promise<Agreement> {
37
- return proposeAgreement({ ...payload, proposedTo: OPEN_AGREEMENT_TARGET }, this.creds);
35
+ return proposeBroadcastApi(payload, this.creds);
38
36
  }
39
37
 
40
38
  /** @deprecated Alias for {@link proposeDirect}. */
@@ -42,7 +40,10 @@ export class AgreementService {
42
40
  return proposeAgreement(payload, this.creds);
43
41
  }
44
42
 
45
- /** Seller-broadcast marketplace offer (`parties.payer = everyone`). */
43
+ /**
44
+ * Seller-broadcast marketplace offer. `payload.audience` sets the `payer`
45
+ * sentinel: 'everyone' (default, public) or 'org' (your active org only).
46
+ */
46
47
  publishOffer(payload: PublishOfferPayload): Promise<Agreement> {
47
48
  return publishOfferApi(payload, this.creds);
48
49
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createTask, getTask, getActiveTasksForAgent, getActiveTasksForChat, getSubtasks,
3
3
  updateTaskState, cancelTask,
4
- claimLedgerTask, reportTask, setTaskSatisfaction, countTasks,
4
+ reportTask, setTaskSatisfaction, countTasks,
5
5
  type Creds, type Task, type TaskState,
6
6
  } from '@ziggs-ai/api-client';
7
7
  import type { CountTasksFilters } from '@ziggs-ai/api-client';
@@ -50,7 +50,7 @@ export class TaskService {
50
50
  return getSubtasks(parentTaskId, this.creds);
51
51
  }
52
52
 
53
- async spawnUnderAgreement(agreementId: string, taskData: { description: string; parentTaskId?: string; plan?: unknown; planReviewTiming?: string; requireMidWorkPlanAck?: boolean }): Promise<Task> {
53
+ async spawnUnderAgreement(agreementId: string, taskData: { description: string; parentTaskId?: string; plan?: unknown; planReviewTiming?: string; requireMidWorkPlanAck?: boolean; assigneeId?: string }): Promise<Task> {
54
54
  if (!agreementId) throw new Error('TaskService.spawnUnderAgreement: agreementId is required');
55
55
  try {
56
56
  const result = await createTask({ ...taskData, agreementId } as Parameters<typeof createTask>[0], this.creds);
@@ -85,17 +85,6 @@ export class TaskService {
85
85
  }
86
86
  }
87
87
 
88
- async claimLedgerTask(taskId: string): Promise<Task> {
89
- try {
90
- const task = await claimLedgerTask(taskId, this.creds);
91
- runtimeLog.info('TaskService', `[ledger] Claimed task ${taskId}`);
92
- return task as unknown as Task;
93
- } catch (error) {
94
- runtimeLog.warn('TaskService', `[ledger] Claim failed for ${taskId}: ${(error as Error).message}`);
95
- throw error;
96
- }
97
- }
98
-
99
88
  async report(taskId: string, message = ''): Promise<Task> {
100
89
  return reportTask(taskId, message, this.creds);
101
90
  }
@@ -7,7 +7,8 @@
7
7
  * config (default 'all'), dispatched to in-process services (taskService /
8
8
  * agreementService) injected into tool context — NOT passed in the user `tools:`
9
9
  * array. Publishing open work is part of this grammar:
10
- * agreement_propose({ proposedTo: "everyone" }) — there is no separate publish tool.
10
+ * agreement_propose({ proposedTo: "everyone" }) for a public broadcast, or
11
+ * proposedTo: "org" to scope it to your org — there is no separate publish tool.
11
12
  *
12
13
  * Tier 2 — capability bundles (tier2/). Opt-in, HTTP-backed tool bundles an agent
13
14
  * chooses to have. Off by default; an agent gains a bundle by spreading it into
@@ -33,7 +33,7 @@ export const agreementProposeTool: ToolDefinition = defineTool(
33
33
  'agreement_propose',
34
34
  { description: { type: 'string', required: true }, proposedTo: { type: 'string', required: true }, engagementKind: { type: 'string', enum: ['hire', 'service'], description: 'Contract type. Defaults to service when omitted.' }, parentAgreementId: 'string', parentTaskId: 'string', payerId: 'string', providerId: 'string', price: 'number', lifecycle: 'string', expiresAt: 'string', maxExecutions: 'number', agreementDescription: 'string', plan: { type: 'object' }, planReviewTiming: 'string', requireMidWorkPlanAck: 'boolean' },
35
35
  handlerFor('agreement-propose'),
36
- { description: 'Propose a contract to a user, agent, or everyone (proposedTo="everyone" for open broadcast). engagementKind defaults to service.', isAgreementCreation: true },
36
+ { description: 'Propose a contract to a user, agent, or as an open broadcast. For a broadcast set proposedTo="everyone" (fully public — any agent may claim) or proposedTo="org" (scoped to your org — only members of your org see and claim it). engagementKind defaults to service.', isAgreementCreation: true },
37
37
  );
38
38
 
39
39
  export const agreementSubcontractTool: ToolDefinition = defineTool(
@@ -1,10 +1,12 @@
1
1
  import { defineTool, type ToolDefinition } from '../../../tools/defineTool.js';
2
- import { pullFromLedger } from '@ziggs-ai/api-client';
2
+ import { pullQuests } from '@ziggs-ai/api-client';
3
3
 
4
4
  type Ctx = Record<string, unknown>;
5
5
 
6
6
  // Read-only marketplace access. Publishing an open quest is NOT here: it is the
7
- // tier-1 protocol verb agreement_propose({ proposedTo: "everyone" }).
7
+ // tier-1 protocol verb agreement_propose({ proposedTo: "everyone" | "org" }).
8
+ // This feed already returns only quests the caller may see — org-scoped quests
9
+ // appear here only for members of the quest's org (filtered server-side).
8
10
  export const marketplaceViewTool: ToolDefinition = defineTool('marketplace_view',
9
11
  { limit: 'number', since: 'string' },
10
12
  async (args, ctx) => {
@@ -16,10 +18,10 @@ export const marketplaceViewTool: ToolDefinition = defineTool('marketplace_view'
16
18
  limit: typeof args['limit'] === 'number' ? args['limit'] as number : 20,
17
19
  };
18
20
  if (args['since']) options.since = args['since'] as string;
19
- const quests = await pullFromLedger(options, { operatorKey, agentId });
21
+ const quests = await pullQuests(options, { operatorKey, agentId });
20
22
  return { quests, count: quests.length };
21
23
  },
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" }).' },
24
+ { description: 'Browse open quests on the marketplace — work buyers have broadcast to willing agents. Returns public quests plus any org-scoped quests in your org. To claim a quest, use agreement_respond on the returned agreementId. To publish your own, use agreement_propose({ proposedTo: "everyone" }) for a public quest or proposedTo: "org" to keep it inside your org.' },
23
25
  );
24
26
 
25
27
  export const MARKETPLACE_TOOLS: ToolDefinition[] = [marketplaceViewTool];