@ziggs-ai/agent-sdk 0.36.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -12,6 +12,14 @@
12
12
 
13
13
  ## Unreleased
14
14
 
15
+ ### Changed (breaking)
16
+
17
+ - The SDK log level follows `configureApiClient({ logLevel })`. `DEBUG_AGENTPLUS` / `LOG_LEVEL` still apply when the process runs `env-init`, which is what copies them into that config. Setting only the environment, without `configureApiClient`, no longer changes SDK logs.
18
+ - `decodeOperatorKeyClaims` is api-client's decoder. A non-string `boundAgentId` is ignored, and `issuedVia` is kept.
19
+ - The fleet model is `resolveFleetModel`: a per-agent pin, then `LLM_MODEL`, then Sonnet 5.5 (`DEFAULT_MODEL`).
20
+ - Hosted Claude agents load Ziggs tools on demand. Everyday and own tools load up front; every other platform tool is offered by its own name and loaded by the Agent SDK's tool search when the model looks for it. The `tools` / `tool_read` / `tool_write` catalog is gone from the hosted brain.
21
+ - `isHostedCatalogOnly` is now `isHostedDeferred`; `hostedModelTools(all)` takes no executor and returns every tool; `HostedToolExecute` is removed.
22
+
15
23
  ### Fixed
16
24
 
17
25
  - Hosted Claude agents can discover and invoke the access explanation tool named by their recovery instructions.
package/dist/Agent.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { AgentConfig, Ctx, Workflow } from './types.js';
4
4
  import { ZiggsBrain, type TickOutput } from './brain/ZiggsBrain.js';
5
5
  import { ClaudeBrain, type CognitionKind } from './brain/ClaudeBrain.js';
6
6
  import { type BrainSelection } from './brain/index.js';
7
+ export { DEFAULT_MODEL, resolveFleetModel } from './shared/fleetModel.js';
7
8
  export type { CognitionKind };
8
9
  import { type WaitForIdleResult } from './platform/EventQueue.js';
9
10
  import { type NormalizedEvent } from './ingress/normalizeIncoming.js';
@@ -29,14 +30,6 @@ import { ToolManager } from './tools/ToolManager.js';
29
30
  import { type ToolDefinition } from './tools/defineTool.js';
30
31
  import { OpenAIAdapter } from './adapters/OpenAIAdapter.js';
31
32
  import { type AgentNeed } from './tools/needs.js';
32
- /**
33
- * The single fleet-wide default model — the one place the fallback lives.
34
- * Change this (or set `LLM_MODEL`) to move the whole fleet; nothing hardcodes a
35
- * model anywhere else. The id also picks the provider: a `claude-*` value routes
36
- * to AnthropicAdapter (+ ANTHROPIC_API_KEY), anything else to OpenAIAdapter — see
37
- * the `model?.startsWith('claude')` branch in createBackendServices below.
38
- */
39
- export declare const DEFAULT_MODEL = "claude-haiku-4-5";
40
33
  /**
41
34
  * Given a normalized event, return the sessionId for this agent. Return
42
35
  * `null` to skip processing. Lets each Agent define what "session" means —
@@ -251,7 +244,6 @@ export declare class Agent {
251
244
  private taskService;
252
245
  private agreementService;
253
246
  private telemetryClient?;
254
- private messageSender;
255
247
  private seenMessages;
256
248
  private stateTimers;
257
249
  /** The per-agent inbox long-poll loop — this agent's sole wake source. */
package/dist/Agent.js CHANGED
@@ -11,9 +11,10 @@ import { backendWakeGate } from './brain/wakeGate.js';
11
11
  import { applyBrainToAgentOptions, isClaudeCognition, } from './brain/index.js';
12
12
  import { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
13
13
  import { resolveClaudeModel } from './brain/claudeCognition.js';
14
+ import { resolveFleetModel } from './shared/fleetModel.js';
15
+ export { DEFAULT_MODEL, resolveFleetModel } from './shared/fleetModel.js';
14
16
  import { CHAT_TOOLS } from './platform/tools/tier2/chatTools.js';
15
17
  import { contextReadTool, openTool, accessExplainTool } from './platform/tools/tier2/contextTools.js';
16
- import { hostedModelTools } from './brain/hostedToolSurface.js';
17
18
  import { EventQueue } from './platform/EventQueue.js';
18
19
  import { StateTimers } from './platform/StateTimers.js';
19
20
  import { laneKeyFor } from './platform/laneKey.js';
@@ -40,14 +41,6 @@ import { runtimeLog } from './shared/runtimeLog.js';
40
41
  import { isTelemetryIngestEnabled, sendTelemetry, } from './platform/telemetryIngest.js';
41
42
  import { buildWakeOutcomePayload, classifyWakeOutcome, wakeWasAsked, } from './runtime/wakeOutcome.js';
42
43
  import { agreementIdFromWake, formedByFromWake, isHireActivationNotice, shouldSkipAgentFormedHireActivation, } from './brain/hireActivationNotice.js';
43
- /**
44
- * The single fleet-wide default model — the one place the fallback lives.
45
- * Change this (or set `LLM_MODEL`) to move the whole fleet; nothing hardcodes a
46
- * model anywhere else. The id also picks the provider: a `claude-*` value routes
47
- * to AnthropicAdapter (+ ANTHROPIC_API_KEY), anything else to OpenAIAdapter — see
48
- * the `model?.startsWith('claude')` branch in createBackendServices below.
49
- */
50
- export const DEFAULT_MODEL = 'claude-haiku-4-5';
51
44
  // ConnectionPool lives in a separate module; resolved via a computed-path
52
45
  // dynamic import to avoid a circular import with Agent.
53
46
  async function loadConnectionPool() {
@@ -95,8 +88,8 @@ function createToolManager(options) {
95
88
  // chat_open is always-on so claim/provider kickoff (FSM) and the
96
89
  // Claude always-on set can open the buyer chat after a store claim. CHAT_TOOLS
97
90
  // is also folded into CLAUDE_SDK_TOOLS; Map registration makes duplicates a no-op.
98
- // wake_at stays registered for the FSM and for the hosted catalog. The Claude
99
- // model reaches it through tool_write; an agent that can be handed a task
91
+ // wake_at stays registered for the FSM and for the hosted model, where it is
92
+ // deferred behind tool search; an agent that can be handed a task
100
93
  // with a check-back rule but cannot arm a wake would have to busy-wait, and
101
94
  // there is nothing to busy-wait with.
102
95
  // open / context_read are the hosted twins of the MCP everyday set.
@@ -110,18 +103,6 @@ function createToolManager(options) {
110
103
  const platformTools = [...taskTools, ...CHAT_TOOLS, ...browseTools, ...claudeSdkTools, ...everydayContext, ...WAKE_TOOLS];
111
104
  warnOnShadowedPlatformTools(platformTools, ownTools, options.agentId);
112
105
  toolManager.registerAll([...platformTools, ...ownTools]);
113
- // The Claude bridge executes every mounted name through the tool manager,
114
- // including the catalog. Register those three here or a wake that calls
115
- // `tools` dies with "Tool not found" before the dispatcher runs.
116
- if (isClaudeCognition(options.cognition)) {
117
- const catalog = hostedModelTools([...platformTools, ...ownTools], (name, args, ctx) => toolManager.executeTool(name, args, ctx));
118
- for (const tool of catalog) {
119
- const name = toolName(tool);
120
- if ((name === 'tools' || name === 'tool_read' || name === 'tool_write') && !toolManager.getTool(name)) {
121
- toolManager.register(tool);
122
- }
123
- }
124
- }
125
106
  return toolManager;
126
107
  }
127
108
  /**
@@ -214,7 +195,6 @@ export class Agent {
214
195
  taskService;
215
196
  agreementService;
216
197
  telemetryClient;
217
- messageSender;
218
198
  seenMessages = new SeenMessages();
219
199
  stateTimers;
220
200
  /** The per-agent inbox long-poll loop — this agent's sole wake source. */
@@ -240,7 +220,8 @@ export class Agent {
240
220
  this.options = {
241
221
  openaiKey: resolved.openaiKey || process.env.OPENAI_API_KEY,
242
222
  anthropicKey: resolved.anthropicKey || process.env.ANTHROPIC_API_KEY,
243
- model: resolved.model || (!isLlmAdapter(resolved.services?.llm) ? resolved.services?.llm?.model : undefined) || DEFAULT_MODEL,
223
+ model: resolveFleetModel(resolved.model ||
224
+ (!isLlmAdapter(resolved.services?.llm) ? resolved.services?.llm?.model : undefined)),
244
225
  operatorKey: resolved.operatorKey || process.env.ZIGGS_OPERATOR_KEY || undefined,
245
226
  agentId: resolved.agentId || '',
246
227
  name: resolved.name || null,
@@ -360,7 +341,6 @@ export class Agent {
360
341
  this.taskService = services.taskService;
361
342
  this.agreementService = services.agreementService;
362
343
  this.telemetryClient = services.telemetryClient;
363
- this.messageSender = messageSender;
364
344
  let workflow = this.options.workflow;
365
345
  if (!workflow && this.options.states) {
366
346
  workflow = {
@@ -1162,13 +1142,6 @@ export class Agent {
1162
1142
  const args = effect.args;
1163
1143
  if (typeof args.taskId === 'string')
1164
1144
  return args.taskId;
1165
- const nested = args.args;
1166
- if ((effect.name === 'tool_read' || effect.name === 'tool_write')
1167
- && nested && typeof nested === 'object' && !Array.isArray(nested)) {
1168
- const inner = nested.taskId;
1169
- if (typeof inner === 'string')
1170
- return inner;
1171
- }
1172
1145
  return undefined;
1173
1146
  };
1174
1147
  const eff = async (effect) => {
@@ -7,7 +7,7 @@ import { bootstrapCtx } from '../runtime/bootstrapCtx.js';
7
7
  import { createDeliveryWriteKeys } from '../runtime/deliveryWriteKeys.js';
8
8
  import { CLAUDE_SDK_PROTOCOL } from './claudeSdkProtocol.js';
9
9
  import { hireIdsFromEvent, memorySectionForWake } from '../memory/MemoryStore.js';
10
- import { hostedModelTools } from './hostedToolSurface.js';
10
+ import { hostedModelTools, isHostedDeferred } from './hostedToolSurface.js';
11
11
  import { buildZiggsSdkMcpServer } from './toolManagerToSdkMcp.js';
12
12
  import { claudeQueryOptions, buildClaudeEventPrompt, summarizeContext, extractAssistantText, } from './claudeCognition.js';
13
13
  import { assertHostedAgentZiggsTools } from './hostedAgentConnection.js';
@@ -418,7 +418,7 @@ export class ClaudeBrain {
418
418
  let sdkMcpServer = this.sdkMcpByLane.get(lane);
419
419
  if (!sdkMcpServer) {
420
420
  const availableTools = await this.toolManager.getAvailableTools();
421
- const mounted = hostedModelTools(availableTools, (name, args, ctx) => this.toolManager.executeTool(name, args, ctx));
421
+ const mounted = hostedModelTools(availableTools);
422
422
  sdkMcpServer = buildZiggsSdkMcpServer(mounted, () => {
423
423
  const ctx = this.toolContextByLane.get(lane);
424
424
  if (!ctx) {
@@ -427,7 +427,8 @@ export class ClaudeBrain {
427
427
  return ctx;
428
428
  }, {
429
429
  name: 'ziggs',
430
- instructions: 'In-process Ziggs tools. Everyday tools and your own tools are loaded by name. tools lists the rest; tool_read and tool_write call them. agreement_revoke is called by name only.',
430
+ instructions: 'In-process Ziggs tools. Everyday tools and your own tools are loaded; every other Ziggs tool loads when you search for it.',
431
+ alwaysLoad: (name) => !isHostedDeferred(name),
431
432
  });
432
433
  this.sdkMcpByLane.set(lane, sdkMcpServer);
433
434
  }
@@ -44,7 +44,7 @@ export interface ClaudeQueryOptionsParams {
44
44
  * Pick the Claude model for a Claude-brain wake. The brain must run a `claude-*`
45
45
  * model, but the host's default model is provider-agnostic (an OpenAI id today),
46
46
  * so this prefers an explicit `claude-*` id (direct or via `services.llm`) and
47
- * otherwise falls back to a safe Claude default — never an OpenAI id.
47
+ * otherwise falls back to {@link DEFAULT_MODEL} — never an OpenAI id.
48
48
  */
49
49
  export declare function resolveClaudeModel(preferred?: string, servicesModel?: string): string;
50
50
  /**
@@ -14,19 +14,19 @@
14
14
  import { SYSTEM_PROMPT_DYNAMIC_BOUNDARY, } from '@anthropic-ai/claude-agent-sdk';
15
15
  import { decisionWords } from '@ziggs-ai/api-client';
16
16
  import { getBatchEvents } from '../context/batch.js';
17
- const DEFAULT_CLAUDE_MODEL = 'claude-sonnet-5-5';
17
+ import { DEFAULT_MODEL } from '../shared/fleetModel.js';
18
18
  /**
19
19
  * Pick the Claude model for a Claude-brain wake. The brain must run a `claude-*`
20
20
  * model, but the host's default model is provider-agnostic (an OpenAI id today),
21
21
  * so this prefers an explicit `claude-*` id (direct or via `services.llm`) and
22
- * otherwise falls back to a safe Claude default — never an OpenAI id.
22
+ * otherwise falls back to {@link DEFAULT_MODEL} — never an OpenAI id.
23
23
  */
24
24
  export function resolveClaudeModel(preferred, servicesModel) {
25
25
  if (preferred?.startsWith('claude'))
26
26
  return preferred;
27
27
  if (servicesModel?.startsWith('claude'))
28
28
  return servicesModel;
29
- return DEFAULT_CLAUDE_MODEL;
29
+ return DEFAULT_MODEL;
30
30
  }
31
31
  /**
32
32
  * Build the `query()` options for a Claude-brain wake. The lockdown fields are
@@ -58,7 +58,10 @@ export function claudeQueryOptions(params) {
58
58
  mcpServers: params.mcpServers,
59
59
  // The agent's ONLY capabilities are its MCP tools — no built-in file/bash/web
60
60
  // tools, no project/user/plugin MCP config, no local settings / CLAUDE.md.
61
- tools: [],
61
+ // ToolSearch is the one built-in: it loads a deferred Ziggs tool's schema
62
+ // when the model looks for it. Without it the SDK ignores deferral and
63
+ // sends every schema on every wake.
64
+ tools: ['ToolSearch'],
62
65
  strictMcpConfig: true,
63
66
  settingSources: [],
64
67
  permissionMode: 'bypassPermissions',
@@ -16,7 +16,7 @@ export const CLAUDE_SDK_PROTOCOL = `You are an autonomous agent on a Ziggs team,
16
16
 
17
17
  Each wake hands you ONE event (or a small batch). Read workBrief for current work and audience. Resource open/access_explain returns the same scoped actions: use tool, args and required inputs; recovery names a suggested next call. Available is checked at read time, needs_decision names missing authority, unavailable names a known blocker, and not_evaluated requires further inputs or execution checks. It is backend-resolved context, not a permission token. Use normal tools for actions marked not_evaluated; that state alone does not require human approval. Never infer authority from role names or combine customers’ permissions. actingFor.state ambiguous means several customers are present and only your own account authority applies; relationship.side is the agreement side you hold and authorizes nothing by itself. A chat_send refused with WORK_SELECTION_REQUIRED lists the eligible agreements in choices: set conversationAgreementId to one of them and send again; never guess which one. Handle the event, then stop.
18
18
 
19
- Ziggs tools loaded with their full schemas are ${loadedByName}, plus any tools that belong to you specifically. Every other Ziggs tool is one call away: \`tools\` lists each one (name and one line); pass \`describe\` to \`tools\` for the schema before you call a tool you have not used. A read goes through \`tool_read\` { tool, args }; a change goes through \`tool_write\` { tool, args }. \`agreement_revoke\` is irreversible, so call it by its own name — the dispatchers refuse it. When the lines below name \`context_snapshot\`, \`wake_at\`, \`task_list\`, \`task_update_plan_step\`, \`grant_list\`, or an agreement verb, that is how you reach it.
19
+ Ziggs tools loaded with their full schemas are ${loadedByName}, plus any tools that belong to you specifically. Every other Ziggs tool exists too and is called by its own name; its schema loads when you look it up with ToolSearch, by name or by what you need done. When the lines below name \`context_snapshot\`, \`wake_at\`, \`task_list\`, \`task_update_plan_step\`, \`grant_list\`, or an agreement verb, look it up that way and call it.
20
20
 
21
21
  Your prose and final summary are private host notes, not messages to the people or agents you work with. To answer an incoming question, including a simple status question, call \`chat_send\` in the requesting conversation. Reading the answer and writing it in your final summary does not deliver it. Do not finish a question wake until the reply tool confirms delivery, or you have surfaced a concrete blocker through the appropriate channel.
22
22
 
@@ -1,14 +1,15 @@
1
1
  import { type ToolDefinition } from '../tools/defineTool.js';
2
- export type HostedToolExecute = (name: string, args: Record<string, unknown>, context: Record<string, unknown>) => Promise<unknown>;
3
2
  type Lane = 'read-only' | 'write' | 'destructive';
4
3
  export declare function hostedToolLane(name: string): Lane;
5
- /** A Ziggs platform tool the model reaches through the catalog, not by schema. */
6
- export declare function isHostedCatalogOnly(name: string): boolean;
7
4
  /**
8
- * The tool block the hosted model is shown.
9
- *
10
- * `execute` runs a catalog call through the ToolManager. Omit it when only
11
- * the schemas are being priced — the dispatchers then refuse to run.
5
+ * A Ziggs platform tool whose schema loads when the model searches for it,
6
+ * rather than on every wake. Everyday tools and the agent's own tools are
7
+ * never deferred.
12
8
  */
13
- export declare function hostedModelTools(all: ToolDefinition[], execute?: HostedToolExecute): ToolDefinition[];
9
+ export declare function isHostedDeferred(name: string): boolean;
10
+ /**
11
+ * The tools the hosted model is offered: every registered tool, each by its
12
+ * own name and schema. Which of them load up front is `isHostedDeferred`.
13
+ */
14
+ export declare function hostedModelTools(all: ToolDefinition[]): ToolDefinition[];
14
15
  export {};
@@ -1,20 +1,18 @@
1
1
  /**
2
2
  * What the hosted Claude brain mounts into the model.
3
3
  *
4
- * The MCP door already lists only the everyday set plus a catalog. This is
5
- * that same cut for the in-process brain: everyday SDK tools stay loaded by
6
- * name, agent-owned tools stay loaded by name, and every other Ziggs platform
7
- * tool is named by `tools` and called through `tool_read` or `tool_write`.
8
- * The tools stay registered on the ToolManager — the FSM brain and the
9
- * dispatcher both call them — they just stop riding every wake as schemas.
4
+ * Every tool the agent has is mounted with its own schema. Everyday SDK tools
5
+ * and the agent's own tools load on every wake; every other Ziggs platform
6
+ * tool is deferred, and the Agent SDK's tool search loads its schema when the
7
+ * model looks for it. The model sees each tool by its own name and calls it
8
+ * directly — there is no catalog to read and no dispatcher in between, so the
9
+ * host's per-tool permission and the trace name the real tool.
10
10
  */
11
- import { EVERYDAY_SDK_TOOLS, annotationForSdkTool, backendToolPick } from '@ziggs-ai/api-client';
12
- import { credsFromToolContext } from '../tools/toolCreds.js';
13
- import { defineTool, toolName } from '../tools/defineTool.js';
11
+ import { EVERYDAY_SDK_TOOLS, annotationForSdkTool } from '@ziggs-ai/api-client';
12
+ import { toolName } from '../tools/defineTool.js';
14
13
  /**
15
- * Platform tools written by hand, not as a shared capability. Their lane is
16
- * what the dispatcher uses; a name here is also a platform name, so it leaves
17
- * the model block unless it is in the everyday set.
14
+ * Platform tools written by hand, not as a shared capability. A name here is
15
+ * a platform name, so it is deferred unless it is in the everyday set.
18
16
  */
19
17
  const HAND_LANE = {
20
18
  context_snapshot: 'read-only',
@@ -38,8 +36,6 @@ const HAND_LANE = {
38
36
  connection_list_grants: 'read-only',
39
37
  };
40
38
  const EVERYDAY = new Set(EVERYDAY_SDK_TOOLS);
41
- const CATALOG_NAMES = ['tools', 'tool_read', 'tool_write'];
42
- const CATALOG = new Set(CATALOG_NAMES);
43
39
  export function hostedToolLane(name) {
44
40
  const hand = HAND_LANE[name];
45
41
  if (hand)
@@ -52,166 +48,27 @@ export function hostedToolLane(name) {
52
48
  function isPlatform(name) {
53
49
  return name in HAND_LANE || annotationForSdkTool(name) !== undefined;
54
50
  }
55
- /** A Ziggs platform tool the model reaches through the catalog, not by schema. */
56
- export function isHostedCatalogOnly(name) {
57
- return isPlatform(name) && !EVERYDAY.has(name);
58
- }
59
- function oneLine(description, name) {
60
- const text = String(description ?? name).replace(/\s+/g, ' ').trim();
61
- const sentence = text.split(/(?<=\.)\s/)[0] ?? text;
62
- return sentence.length > 180 ? `${sentence.slice(0, 177)}...` : sentence;
63
- }
64
- function callLine(name, lane, native) {
65
- const dispatcher = lane === 'read-only' ? 'tool_read' : 'tool_write';
66
- if (native) {
67
- return `call it by name: ${name}; ${dispatcher} { tool: "${name}", args } also works`;
68
- }
69
- return `call via ${dispatcher} { tool, args }`;
70
- }
71
- function rowFor(tool) {
72
- const name = toolName(tool) ?? '';
73
- const lane = hostedToolLane(name);
74
- const native = !isHostedCatalogOnly(name);
75
- return {
76
- name,
77
- line: oneLine(tool.schema.function['description'], name),
78
- readOnly: lane === 'read-only',
79
- native,
80
- call: callLine(name, lane, native),
81
- };
82
- }
83
- function refuseWrongLane(name, lane, via) {
84
- const readOnly = lane === 'read-only';
85
- const askedRead = via === 'tool_read';
86
- if (readOnly === askedRead)
87
- return;
88
- throw new Error(readOnly
89
- ? `${name} is read-only: call it with tool_read { tool, args }, not tool_write.`
90
- : `${name} changes state: call it with tool_write { tool, args }, not tool_read.`);
91
- }
92
51
  /**
93
- * The Claude bridge does not call a tool's handler. It emits `tool-call`, and
94
- * the effect handler looks the name up on the ToolManager. A catalog call has
95
- * to go back through that same effect so the target still gets the timeline,
96
- * the write keys, and the wake scope. A direct executor is only for a caller
97
- * that has no effect handler (a schema probe, a unit test).
52
+ * A Ziggs platform tool whose schema loads when the model searches for it,
53
+ * rather than on every wake. Everyday tools and the agent's own tools are
54
+ * never deferred.
98
55
  */
99
- async function callCatalogTarget(name, payload, ctx, execute) {
100
- const eff = ctx.eff;
101
- const sessionId = typeof ctx.chatId === 'string' ? ctx.chatId
102
- : typeof ctx.sessionId === 'string' ? ctx.sessionId
103
- : undefined;
104
- if (typeof eff === 'function' && sessionId) {
105
- const result = await eff({
106
- kind: 'tool-call',
107
- sessionId,
108
- name,
109
- args: payload,
110
- senderId: typeof ctx.senderId === 'string' ? ctx.senderId : undefined,
111
- senderType: typeof ctx.senderType === 'string' ? ctx.senderType : undefined,
112
- onBehalfOfAgreementId: typeof ctx.onBehalfOfAgreementId === 'string' ? ctx.onBehalfOfAgreementId : undefined,
113
- serviceAgreementId: typeof ctx.serviceAgreementId === 'string' ? ctx.serviceAgreementId : undefined,
114
- runId: typeof ctx.runId === 'string' ? ctx.runId : undefined,
115
- writeKeys: ctx.writeKeys,
116
- memory: ctx.memory,
117
- });
118
- const settled = result;
119
- if (settled && settled.ok === false)
120
- throw new Error(settled.error ?? `${name} failed`);
121
- if (settled && 'ok' in settled)
122
- return settled.result;
123
- return result;
124
- }
125
- if (!execute)
126
- throw new Error('hosted catalog has no executor');
127
- return execute(name, payload, ctx);
128
- }
129
- function catalogTools(present, execute) {
130
- const list = () => [...present.values()].map(rowFor).sort((a, b) => a.name.localeCompare(b.name));
131
- const toolsTool = defineTool('tools', {
132
- need: { type: 'string', description: 'Describe what you need to do. Suggests a tool and its schema; falls back to the catalog when unsure.' },
133
- search: {
134
- type: 'string',
135
- description: 'Filter by name or the one-line description. Omit to list every tool.',
136
- },
137
- describe: {
138
- type: 'array',
139
- items: { type: 'string' },
140
- description: 'Tool names whose full schema you want before calling them.',
141
- },
142
- }, async (args, ctx) => {
143
- const describe = Array.isArray(args.describe) ? args.describe.map(String) : [];
144
- if (describe.length > 0) {
145
- return describe.map((name) => {
146
- const tool = present.get(name);
147
- if (!tool) {
148
- return { name, error: `No such tool: ${name}. Call tools for the full list of names.` };
149
- }
150
- return { ...rowFor(tool), schema: tool.schema.function };
151
- });
152
- }
153
- const need = typeof args.need === 'string' ? args.need.trim() : '';
154
- if (need) {
155
- const rows = list();
156
- try {
157
- const pick = await backendToolPick(credsFromToolContext(ctx, 'tools'))(need, rows.map(({ name, line }) => ({ name, line })));
158
- const tool = pick?.how === 'pick' && pick.tool ? present.get(pick.tool) : undefined;
159
- if (tool)
160
- return { suggestion: { ...rowFor(tool), schema: tool.schema.function }, confidence: pick?.confidence };
161
- }
162
- catch { /* Missing credentials or picker failure still leaves every tool available. */ }
163
- return { tools: rows, note: 'No confident suggestion. Choose from the catalog; no action was taken.' };
164
- }
165
- const q = typeof args.search === 'string' ? args.search.toLowerCase() : '';
166
- return list().filter((row) => !q || row.name.includes(q) || row.line.toLowerCase().includes(q));
167
- }, {
168
- description: 'Every Ziggs tool that exists: name plus one line. A handful are already loaded by name; the rest are called through tool_read or tool_write. Pass need for a plain-language tool suggestion with its schema, or describe with exact names. Suggestions never execute tools. Nothing is hidden.',
169
- });
170
- const dispatch = (via) => async (args, ctx) => {
171
- const name = typeof args.tool === 'string' ? args.tool : '';
172
- if (!name || !present.has(name)) {
173
- throw new Error(`No such tool: ${name || '(missing)'}. Call tools for the full list of names.`);
174
- }
175
- refuseWrongLane(name, hostedToolLane(name), via);
176
- const payload = args.args && typeof args.args === 'object' && !Array.isArray(args.args)
177
- ? args.args
178
- : {};
179
- return callCatalogTarget(name, payload, ctx, execute);
180
- };
181
- const toolRead = defineTool('tool_read', {
182
- tool: { type: 'string', required: true, description: 'Read-only tool name from tools.' },
183
- args: { type: 'object', description: 'Arguments for that tool, as its schema defines them.' },
184
- }, dispatch('tool_read'), {
185
- description: 'Call a read-only Ziggs tool by name. A tool that writes is refused here and pointed at tool_write. Look it up with tools describe=[name] first when you do not already know its parameters. The result is exactly what the tool itself returns.',
186
- });
187
- const toolWrite = defineTool('tool_write', {
188
- tool: { type: 'string', required: true, description: 'Writing tool name from tools.' },
189
- args: { type: 'object', description: 'Arguments for that tool, as its schema defines them.' },
190
- }, dispatch('tool_write'), {
191
- description: 'Call a Ziggs tool that creates or changes something, by name. Read-only tools are refused here and pointed at tool_read. Irreversible tools (agreement_revoke) are called by their own name only — this dispatcher refuses them. Look the tool up with tools describe=[name] first when you do not already know its parameters.',
192
- });
193
- return [toolsTool, toolRead, toolWrite];
56
+ export function isHostedDeferred(name) {
57
+ return isPlatform(name) && !EVERYDAY.has(name);
194
58
  }
195
59
  /**
196
- * The tool block the hosted model is shown.
197
- *
198
- * `execute` runs a catalog call through the ToolManager. Omit it when only
199
- * the schemas are being priced — the dispatchers then refuse to run.
60
+ * The tools the hosted model is offered: every registered tool, each by its
61
+ * own name and schema. Which of them load up front is `isHostedDeferred`.
200
62
  */
201
- export function hostedModelTools(all, execute) {
202
- const present = new Map();
63
+ export function hostedModelTools(all) {
64
+ const seen = new Set();
65
+ const out = [];
203
66
  for (const tool of all) {
204
67
  const name = toolName(tool);
205
- if (!name || CATALOG.has(name))
206
- continue;
207
- present.set(name, tool);
208
- }
209
- const visible = [];
210
- for (const [name, tool] of present) {
211
- if (isHostedCatalogOnly(name))
68
+ if (!name || seen.has(name))
212
69
  continue;
213
- visible.push(tool);
70
+ seen.add(name);
71
+ out.push(tool);
214
72
  }
215
- visible.push(...catalogTools(present, execute));
216
- return visible;
73
+ return out;
217
74
  }
@@ -5,8 +5,12 @@ import type { ClaudeSdkToolContext } from './claudeSdkTools.js';
5
5
  * Map Ziggs `ToolDefinition`s to Claude Agent SDK MCP tools. Each handler routes
6
6
  * execution through `eff({ kind: 'tool-call' })` — the shared executor.
7
7
  */
8
- export declare function buildSdkToolDefs(toolDefs: ToolDefinition[], getContext: () => ClaudeSdkToolContext): Array<SdkMcpToolDefinition>;
8
+ export declare function buildSdkToolDefs(toolDefs: ToolDefinition[], getContext: () => ClaudeSdkToolContext,
9
+ /** Which tools load on every wake; the rest wait for tool search. Default: all. */
10
+ alwaysLoad?: (name: string) => boolean): Array<SdkMcpToolDefinition>;
9
11
  export declare function buildZiggsSdkMcpServer(toolDefs: ToolDefinition[], getContext: () => ClaudeSdkToolContext, options?: {
10
12
  name?: string;
11
13
  instructions?: string;
14
+ /** Per tool; a tool it returns false for is deferred behind tool search. */
15
+ alwaysLoad?: (name: string) => boolean;
12
16
  }): import("@anthropic-ai/claude-agent-sdk").McpSdkServerConfigWithInstance;
@@ -85,7 +85,9 @@ function toCallToolError(err) {
85
85
  * Map Ziggs `ToolDefinition`s to Claude Agent SDK MCP tools. Each handler routes
86
86
  * execution through `eff({ kind: 'tool-call' })` — the shared executor.
87
87
  */
88
- export function buildSdkToolDefs(toolDefs, getContext) {
88
+ export function buildSdkToolDefs(toolDefs, getContext,
89
+ /** Which tools load on every wake; the rest wait for tool search. Default: all. */
90
+ alwaysLoad = () => true) {
89
91
  return toolDefs.map((def) => {
90
92
  const fn = def.schema.function;
91
93
  const name = String(fn['name'] ?? '');
@@ -99,7 +101,7 @@ export function buildSdkToolDefs(toolDefs, getContext) {
99
101
  name,
100
102
  description,
101
103
  inputSchema,
102
- _meta: { 'anthropic/alwaysLoad': true },
104
+ _meta: { 'anthropic/alwaysLoad': alwaysLoad(name) },
103
105
  handler: async (args) => {
104
106
  try {
105
107
  const ctx = getContext();
@@ -147,7 +149,6 @@ export function buildZiggsSdkMcpServer(toolDefs, getContext, options = {}) {
147
149
  name: options.name ?? 'ziggs',
148
150
  version: '1.0.0',
149
151
  instructions: options.instructions,
150
- tools: buildSdkToolDefs(toolDefs, getContext),
151
- alwaysLoad: true,
152
+ tools: buildSdkToolDefs(toolDefs, getContext, options.alwaysLoad),
152
153
  });
153
154
  }
@@ -2,7 +2,7 @@ import { validateWorkflow } from './runtime/validateWorkflow.js';
2
2
  import { injectKickoffDefault } from './runtime/kickoff.js';
3
3
  import { injectTaskIntakeDefault } from './runtime/taskIntake.js';
4
4
  import { runtimeLog } from './shared/runtimeLog.js';
5
- import { DEFAULT_MODEL } from './Agent.js';
5
+ import { resolveFleetModel } from './shared/fleetModel.js';
6
6
  import { normalizeNeeds, toolsForNeeds } from './tools/needs.js';
7
7
  import { toolName } from './tools/defineTool.js';
8
8
  /**
@@ -74,13 +74,8 @@ export function defineAgent(input) {
74
74
  tools[at] = tool;
75
75
  }
76
76
  }
77
- // Model resolution, most-specific-first: a per-agent pin, then the
78
- // fleet-wide `LLM_MODEL` lever, then the single `DEFAULT_MODEL` fallback (the
79
- // ONE place the default lives — see Agent). The id also picks the provider
80
- // (claude-* → Anthropic). No agent hardcodes its own fallback.
81
- const model = input.services?.llm?.model ||
82
- process.env.LLM_MODEL ||
83
- DEFAULT_MODEL;
77
+ // One resolution for every agent: a pin, then LLM_MODEL, then DEFAULT_MODEL.
78
+ const model = resolveFleetModel(input.services?.llm?.model);
84
79
  // Strip the structural fields we already consumed; pass through everything
85
80
  // else (operatorKey, taskTools, custom keys consumed by Agent).
86
81
  const { agentId, description, specialization, services: _services, initial: _initial, states: _states, tools: _tools, operatorKey, taskTools, ...rest } = input;
@@ -53,7 +53,7 @@ export class AgreementFormatter {
53
53
  lines.push(`${indent} Proposal: ${proposal['status']}${proposal['respondedBy'] ? ` (by ${proposal['respondedBy']})` : ''}`);
54
54
  const money = ag['money'];
55
55
  if (typeof money?.['price'] === 'number' && money['price'] > 0) {
56
- const parts = [`${money['price']} cents`];
56
+ const parts = [`${money['price']} hundredths of a point`];
57
57
  if (money['paymentStatus'] && money['paymentStatus'] !== 'none')
58
58
  parts.push(money['paymentStatus']);
59
59
  lines.push(`${indent} Money: ${parts.join(', ')}`);
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export { parseSkill, skillHash, skillRolePrompt, skillReferenceName, skillRefere
14
14
  export type { ParsedSkill, SkillFile, SkillReference } from './skills/parseSkill.js';
15
15
  export { loadSkill, readSkillFiles, assertSkillHash } from './skills/loadSkill.js';
16
16
  export { skillReadReferenceTool } from './skills/skillReadReference.js';
17
- export { Agent, createAgent, createAgentPool, DEFAULT_MODEL } from './Agent.js';
17
+ export { Agent, createAgent, createAgentPool, DEFAULT_MODEL, resolveFleetModel } from './Agent.js';
18
18
  export { ZiggsBrain } from './brain/ZiggsBrain.js';
19
19
  export { ClaudeBrain } from './brain/ClaudeBrain.js';
20
20
  export type { CognitionKind, ClaudeBrainOptions } from './brain/ClaudeBrain.js';
@@ -69,8 +69,7 @@ export { PollRegistry, declaredPolls, type PollBinding, type PollContext, type P
69
69
  export { PollRunner, type PollRunnerOptions, type WakeTarget } from './platform/polls/PollRunner.js';
70
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';
71
71
  export { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
72
- export { hostedModelTools, isHostedCatalogOnly, hostedToolLane } from './brain/hostedToolSurface.js';
73
- export type { HostedToolExecute } from './brain/hostedToolSurface.js';
72
+ export { hostedModelTools, isHostedDeferred, hostedToolLane } from './brain/hostedToolSurface.js';
74
73
  export { PROTOCOL_TOOL_NAMES, PROTOCOL_TOOL_TO_OPERATION, mapProtocolToolToOperation, isProtocolToolName } from './tasks/protocolRegistry.js';
75
74
  export { InMemoryStore, FileMemoryStore } from './memory/MemoryStore.js';
76
75
  export type { MemoryStore } from './memory/MemoryStore.js';
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ export { createMcpAgent, resolveZiggsGatewayTarget, wrapGatewayFetch, ZiggsGatew
16
16
  export { parseSkill, skillHash, skillRolePrompt, skillReferenceName, skillReferenceOneLine, splitFrontmatter, SkillValidationError, SKILL_MAX_CHARS, } from './skills/parseSkill.js';
17
17
  export { loadSkill, readSkillFiles, assertSkillHash } from './skills/loadSkill.js';
18
18
  export { skillReadReferenceTool } from './skills/skillReadReference.js';
19
- export { Agent, createAgent, createAgentPool, DEFAULT_MODEL } from './Agent.js';
19
+ export { Agent, createAgent, createAgentPool, DEFAULT_MODEL, resolveFleetModel } from './Agent.js';
20
20
  export { ZiggsBrain } from './brain/ZiggsBrain.js';
21
21
  export { ClaudeBrain } from './brain/ClaudeBrain.js';
22
22
  // The exact strings a Claude-brain wake sends: the shared protocol block, the
@@ -83,7 +83,7 @@ PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connec
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';
86
- export { hostedModelTools, isHostedCatalogOnly, hostedToolLane } from './brain/hostedToolSurface.js';
86
+ export { hostedModelTools, isHostedDeferred, hostedToolLane } from './brain/hostedToolSurface.js';
87
87
  export { PROTOCOL_TOOL_NAMES, PROTOCOL_TOOL_TO_OPERATION, mapProtocolToolToOperation, isProtocolToolName } from './tasks/protocolRegistry.js';
88
88
  export { InMemoryStore, FileMemoryStore } from './memory/MemoryStore.js';
89
89
  export { classifyIncomingEvent } from './context/classifyEnvelope.js';
@@ -7,7 +7,12 @@ function log(label, msg) {
7
7
  runtimeLog.info(label, msg);
8
8
  }
9
9
  function inboxClient(deps) {
10
- return new InboxClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl, deps.creds.instanceId);
10
+ return new InboxClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl, deps.creds.instanceId, {
11
+ clientSessionId: deps.creds.clientSessionId,
12
+ workspaceIds: deps.creds.workspaceIds,
13
+ app: deps.creds.app,
14
+ sessionName: deps.creds.sessionName,
15
+ });
11
16
  }
12
17
  async function releaseInboxHostBestEffort(deps) {
13
18
  try {
@@ -255,6 +255,12 @@ async function proposeAgreement(services, payload, chatId, writeKeys = null) {
255
255
  // propose resolves to the already-created agreement server-side instead of
256
256
  // minting a duplicate proposal. Keyed on what the proposal writes, so
257
257
  // multiple proposals in one wake stay distinct.
258
+ // A standing listing is published by a person or the deploy.
259
+ // A broadcast with a provider is that listing. A request omits providerId.
260
+ // A named counterparty is a direct proposal and stays.
261
+ if (isBroadcastTarget(payload.proposedTo) && payload.providerId) {
262
+ throw new Error('A standing listing is published by a person or the deploy. Broadcast without providerId to post a request, or name a counterparty to propose terms inside a deal.');
263
+ }
258
264
  const idempotencyKey = writeKeys?.agreementProposeKey({
259
265
  proposedTo: payload.proposedTo,
260
266
  providerId: payload.providerId,
@@ -60,7 +60,7 @@ function handlerFor(toolName) {
60
60
  return result;
61
61
  };
62
62
  }
63
- export const agreementProposeTool = defineTool('agreement_propose', { description: { type: 'string', required: true }, proposedTo: { type: 'string', required: true }, engagementKind: { type: 'string', enum: ['hire', 'service'], description: 'Contract type. Defaults to service. For a bilateral trust link, use link_propose.' }, parentAgreementId: 'string', providerId: { type: 'string', description: AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION }, price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, lifecycle: lifecycleParam, expiresAt: 'string', maxExecutions: 'integer', conversationAllowance: { type: 'number', description: 'Incoming-message limit shared across covered rooms; omitted on new agreements means unlimited.' }, billing: { type: 'string', enum: ['total', 'per_task'], description: "How price reads: 'total' (default, escrowed and paid at the end) or 'per_task' (a rate settled as each task completes — standing agreements only, and the default for a hire)." }, agreementDescription: 'string' }, handlerFor('agreement_propose'), { description: 'Propose an agreement — direct, broadcast, or link; there are no separate publish tools. Engagement precedence (posted-first): 1) REUSE an active agreement on matching terms, 2) CLAIM the counterparty\'s listing (marketplace_view → agreement_claim; never counter a listing), 3) POST a request broadcast here when nothing listed fits, 4) direct-propose ONLY for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Direction is stated, never guessed: providerId = your own id ("I work, you pay") or providerId = proposedTo ("you work, I pay") on direct proposals. Broadcast with proposedTo="everyone" (fully public) or "org" (your org only): no providerId publishes a REQUEST any agent may claim; providerId = your own id publishes a STANDING OFFER (claiming is agreement_claim; browsing is marketplace_view). For a bilateral trust link, use link_propose: it takes an email or an agent id, or neither for a share link. engagementKind defaults to service. Agreements are STANDING by default (no expiry, unlimited tasks under one agreement); set expiresAt or maxExecutions only when the engagement should end on its own.', isAgreementCreation: true });
63
+ export const agreementProposeTool = defineTool('agreement_propose', { description: { type: 'string', required: true }, proposedTo: { type: 'string', required: true }, engagementKind: { type: 'string', enum: ['hire', 'service'], description: 'Contract type. Defaults to service. For a bilateral trust link, use link_propose.' }, parentAgreementId: 'string', providerId: { type: 'string', description: AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION }, price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, lifecycle: lifecycleParam, expiresAt: 'string', maxExecutions: 'integer', conversationAllowance: { type: 'number', description: 'Incoming-message limit shared across covered rooms; omitted on new agreements means unlimited.' }, billing: { type: 'string', enum: ['total', 'per_task'], description: "How price reads: 'total' (default, escrowed and paid at the end) or 'per_task' (a rate settled as each task completes — standing agreements only, and the default for a hire)." }, agreementDescription: 'string' }, handlerFor('agreement_propose'), { description: 'Propose an agreement — direct, broadcast, or link; there are no separate publish tools. Engagement precedence (posted-first): 1) REUSE an active agreement on matching terms, 2) CLAIM the counterparty\'s listing (marketplace_view → agreement_claim; never counter a listing), 3) POST a request broadcast here when nothing listed fits, 4) direct-propose ONLY for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Direction is stated, never guessed: providerId = your own id ("I work, you pay") or providerId = proposedTo ("you work, I pay") on direct proposals. Broadcast with proposedTo="everyone" (fully public) or "org" (your org only) and no providerId posts a REQUEST any agent may claim. A person or the deploy publishes a standing listing; this agent does not. For a bilateral trust link, use link_propose: it takes an email or an agent id, or neither for a share link. engagementKind defaults to service. Agreements are STANDING by default (no expiry, unlimited tasks under one agreement); set expiresAt or maxExecutions only when the engagement should end on its own.', isAgreementCreation: true });
64
64
  export const agreementSubcontractTool = defineTool('agreement_subcontract', agreementSubcontractCapability.params, handlerFor('agreement_subcontract'), { description: agreementSubcontractCapability.descriptions.sdk, isAgreementCreation: true });
65
65
  export const agreementRespondTool = defineTool('agreement_respond', { agreementId: { type: 'string', required: true }, action: { type: 'string', required: true, enum: ['approve', 'reject'] } }, handlerFor('agreement_respond'), { description: 'Approve or reject a pending DIRECT agreement proposal addressed to you. Open broadcasts (requests, standing offers, link invites) have no personal approval slot — claim those with agreement_claim instead, or ignore them to pass. ONE exception: a hand-off that pins YOU as provider carries your pending approval even as an open broadcast — approving consents to serving whoever claims it (the row stays open for claims); rejecting cancels the hand-off. Ignoring it leaves the hand-off unclaimable, so always respond.' });
66
66
  export const agreementClaimTool = defineTool('agreement_claim', agreementClaimCapability.params, handlerFor('agreement_claim'), { description: agreementClaimCapability.descriptions.sdk });
@@ -206,7 +206,7 @@ ${this._renderDecisionSchemaFromActions(actions, senderMeta, ctxObj, omitMessage
206
206
  }
207
207
  const money = ag.money;
208
208
  if (typeof money?.price === 'number' && money.price > 0) {
209
- lines.push(` Price: ${money.price} cents (${money.paymentStatus})`);
209
+ lines.push(` Price: ${money.price} hundredths of a point (${money.paymentStatus})`);
210
210
  }
211
211
  }
212
212
  lines.push('');
@@ -493,27 +493,6 @@ export async function runTurn(input) {
493
493
  return { outcome, toolResults, llmCalls: llmCallsTotal, tokens: { total: tokensTotal, input: tokensInput, output: tokensOutput } };
494
494
  }
495
495
  // ─────────────────────────────────────────────────────────────────────────────
496
- function extractIncomingMessageText(event) {
497
- if (!event)
498
- return '';
499
- const parts = [];
500
- const push = (v) => {
501
- if (typeof v === 'string' && v.trim())
502
- parts.push(v.trim());
503
- };
504
- push(event.text);
505
- push(event.message);
506
- const agreement = event.agreement;
507
- if (agreement) {
508
- const terms = agreement.terms;
509
- push(terms?.description);
510
- push(JSON.stringify(agreement));
511
- }
512
- const task = event.task;
513
- if (task)
514
- push(JSON.stringify(task));
515
- return parts.join('\n');
516
- }
517
496
  function normalizeContextSnapshot(raw) {
518
497
  if (!raw || typeof raw !== 'object') {
519
498
  return { history: [], agreements: [], agents: [], users: [] };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The one model table. Sonnet 5.5 is the default.
3
+ * A per-agent pin wins, then `LLM_MODEL`, then {@link DEFAULT_MODEL}.
4
+ * The id also picks the provider: `claude-*` routes to Anthropic.
5
+ */
6
+ export declare const DEFAULT_MODEL = "claude-sonnet-5-5";
7
+ /** Pin, then `LLM_MODEL`, then {@link DEFAULT_MODEL}. */
8
+ export declare function resolveFleetModel(pinned?: string): string;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The one model table. Sonnet 5.5 is the default.
3
+ * A per-agent pin wins, then `LLM_MODEL`, then {@link DEFAULT_MODEL}.
4
+ * The id also picks the provider: `claude-*` routes to Anthropic.
5
+ */
6
+ export const DEFAULT_MODEL = 'claude-sonnet-5-5';
7
+ /** Pin, then `LLM_MODEL`, then {@link DEFAULT_MODEL}. */
8
+ export function resolveFleetModel(pinned) {
9
+ const pin = pinned?.trim();
10
+ if (pin)
11
+ return pin;
12
+ const fromEnv = process.env.LLM_MODEL?.trim();
13
+ if (fromEnv)
14
+ return fromEnv;
15
+ return DEFAULT_MODEL;
16
+ }
@@ -1,18 +1,11 @@
1
1
  /**
2
- * Client-side peek at an operator key's JWT payload (signature NOT verified —
3
- * identity hint only, the backend remains the authority). Used by the fleet
4
- * launcher to warn when the key is agent-bound: a bound key
5
- * collapses the operator multiplex to its one agent and 403s every other
6
- * per-agent loop, so a large fleet quietly watches a single agent.
2
+ * Client-side peek at an operator key. The decoder lives in api-client (it
3
+ * keeps `issuedVia` and ignores a non-string `boundAgentId`, and it does not
4
+ * use Node's Buffer). This file re-exports it and keeps the fleet warning.
7
5
  */
8
- export interface OperatorKeyClaims {
9
- type?: string;
10
- keyId?: string;
11
- ownerId?: string;
12
- boundAgentId?: string | null;
13
- exp?: number;
14
- }
15
- export declare function decodeOperatorKeyClaims(token: string): OperatorKeyClaims | null;
6
+ import { decodeOperatorKeyClaims } from '@ziggs-ai/api-client';
7
+ export { decodeOperatorKeyClaims };
8
+ export type { OperatorKeyClaims } from '@ziggs-ai/api-client';
16
9
  /**
17
10
  * The startup check: warn (never refuse — a one-agent launcher on a
18
11
  * bound key is legitimate) when the fleet's control key is bound to a single
@@ -1,22 +1,10 @@
1
- export function decodeOperatorKeyClaims(token) {
2
- const parts = token.trim().split('.');
3
- if (parts.length !== 3)
4
- return null;
5
- try {
6
- const json = Buffer.from(parts[1], 'base64url').toString('utf8');
7
- const payload = JSON.parse(json);
8
- return {
9
- type: payload.type,
10
- keyId: payload.keyId,
11
- ownerId: payload.ownerId,
12
- boundAgentId: payload.boundAgentId ?? null,
13
- exp: payload.exp,
14
- };
15
- }
16
- catch {
17
- return null;
18
- }
19
- }
1
+ /**
2
+ * Client-side peek at an operator key. The decoder lives in api-client (it
3
+ * keeps `issuedVia` and ignores a non-string `boundAgentId`, and it does not
4
+ * use Node's Buffer). This file re-exports it and keeps the fleet warning.
5
+ */
6
+ import { decodeOperatorKeyClaims } from '@ziggs-ai/api-client';
7
+ export { decodeOperatorKeyClaims };
20
8
  /**
21
9
  * The startup check: warn (never refuse — a one-agent launcher on a
22
10
  * bound key is legitimate) when the fleet's control key is bound to a single
@@ -1,18 +1,6 @@
1
1
  /**
2
- * Process-wide log levels for agentplus runtimes. Default is `info` so FSM /
3
- * socket chatter stays quiet unless operators opt in.
4
- *
5
- * Env (first match wins for the debug shortcut):
6
- * - `DEBUG_AGENTPLUS=1` or `AGENTPLUS_DEBUG=1` → threshold `debug`
7
- * - `LOG_LEVEL` or `AGENTPLUS_LOG_LEVEL` → `debug` | `info` | `warn` | `error` | `silent`
8
- * (`silent` keeps only `error` output)
2
+ * The SDK follows `configureApiClient({ logLevel })`. The level table lives in
3
+ * api-client; this file only re-exports it so existing imports keep working.
9
4
  */
10
- export type RuntimeLogLevel = 'debug' | 'info' | 'warn' | 'error';
11
- /** Test hook: re-read env after `process.env` mutations. */
12
- export declare function resetRuntimeLogLevelCache(): void;
13
- export declare const runtimeLog: {
14
- debug(scope: string, msg: string, ...rest: unknown[]): void;
15
- info(scope: string, msg: string, ...rest: unknown[]): void;
16
- warn(scope: string, msg: string, ...rest: unknown[]): void;
17
- error(scope: string, msg: string, ...rest: unknown[]): void;
18
- };
5
+ export { runtimeLog, resetRuntimeLogLevelCache, } from '@ziggs-ai/api-client';
6
+ export type { RuntimeLogLevel } from '@ziggs-ai/api-client';
@@ -1,68 +1,5 @@
1
1
  /**
2
- * Process-wide log levels for agentplus runtimes. Default is `info` so FSM /
3
- * socket chatter stays quiet unless operators opt in.
4
- *
5
- * Env (first match wins for the debug shortcut):
6
- * - `DEBUG_AGENTPLUS=1` or `AGENTPLUS_DEBUG=1` → threshold `debug`
7
- * - `LOG_LEVEL` or `AGENTPLUS_LOG_LEVEL` → `debug` | `info` | `warn` | `error` | `silent`
8
- * (`silent` keeps only `error` output)
2
+ * The SDK follows `configureApiClient({ logLevel })`. The level table lives in
3
+ * api-client; this file only re-exports it so existing imports keep working.
9
4
  */
10
- const SEVERITY = {
11
- debug: 0,
12
- info: 1,
13
- warn: 2,
14
- error: 3,
15
- };
16
- function parseThreshold() {
17
- if (process.env.DEBUG_AGENTPLUS === '1' || process.env.AGENTPLUS_DEBUG === '1') {
18
- return SEVERITY.debug;
19
- }
20
- const raw = (process.env.LOG_LEVEL ||
21
- process.env.AGENTPLUS_LOG_LEVEL ||
22
- 'info').toLowerCase();
23
- if (raw === 'silent' || raw === 'none')
24
- return SEVERITY.error;
25
- if (raw === 'debug' || raw === 'trace')
26
- return SEVERITY.debug;
27
- if (raw === 'warn')
28
- return SEVERITY.warn;
29
- if (raw === 'error')
30
- return SEVERITY.error;
31
- return SEVERITY.info;
32
- }
33
- let cachedThreshold = null;
34
- function threshold() {
35
- if (cachedThreshold === null)
36
- cachedThreshold = parseThreshold();
37
- return cachedThreshold;
38
- }
39
- /** Test hook: re-read env after `process.env` mutations. */
40
- export function resetRuntimeLogLevelCache() {
41
- cachedThreshold = null;
42
- }
43
- function shouldEmit(level) {
44
- return SEVERITY[level] >= threshold();
45
- }
46
- function fmt(scope, msg) {
47
- return `[${scope}] ${msg}`;
48
- }
49
- export const runtimeLog = {
50
- debug(scope, msg, ...rest) {
51
- if (!shouldEmit('debug'))
52
- return;
53
- console.log(fmt(scope, msg), ...rest);
54
- },
55
- info(scope, msg, ...rest) {
56
- if (!shouldEmit('info'))
57
- return;
58
- console.log(fmt(scope, msg), ...rest);
59
- },
60
- warn(scope, msg, ...rest) {
61
- if (!shouldEmit('warn'))
62
- return;
63
- console.warn(fmt(scope, msg), ...rest);
64
- },
65
- error(scope, msg, ...rest) {
66
- console.error(fmt(scope, msg), ...rest);
67
- },
68
- };
5
+ export { runtimeLog, resetRuntimeLogLevelCache, } from '@ziggs-ai/api-client';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/agent-sdk",
3
- "version": "0.36.0",
3
+ "version": "0.38.0",
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,7 @@
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.35.0",
29
+ "@ziggs-ai/api-client": "0.37.0",
30
30
  "ajv": "^8.17.1",
31
31
  "dotenv": "^17.2.3",
32
32
  "openai": "^4.0.0",