@ziggs-ai/agent-sdk 0.9.0 → 0.10.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/dist/AgentHost.d.ts +3 -0
- package/dist/AgentHost.js +8 -1
- package/dist/agent/ClaudeSdkAgent.d.ts +4 -1
- package/dist/agent/ClaudeSdkAgent.js +7 -4
- package/dist/agent/claudeSdkTools.js +39 -11
- package/dist/defineAgent.js +4 -1
- package/dist/formatters/HistoryFormatter.js +11 -0
- package/dist/ingress/normalizeIncoming.d.ts +5 -0
- package/dist/ingress/normalizeIncoming.js +23 -1
- package/dist/pricing/fleetDefaults.d.ts +0 -3
- package/dist/pricing/fleetDefaults.js +0 -6
- package/dist/runtime/PromptBuilder.js +9 -5
- package/dist/runtime/runTurn.js +1 -1
- package/dist/runtime/taskBookkeeping.d.ts +0 -2
- package/dist/runtime/taskBookkeeping.js +0 -6
- package/dist/runtime/wakeWriteKeys.js +11 -2
- package/dist/server/ZiggsEffectHandler.js +7 -4
- package/dist/server/tasks/TaskService.d.ts +2 -11
- package/dist/server/tools/tier1/protocolRunner.js +17 -14
- package/dist/server/tools/tier1/protocolTools.js +3 -3
- package/dist/tools/capabilityAdapter.js +12 -1
- package/dist/tools/recordReport.js +8 -4
- package/dist/types.d.ts +24 -2
- package/package.json +2 -2
package/dist/AgentHost.d.ts
CHANGED
|
@@ -71,7 +71,10 @@ export interface AgentHostOptions {
|
|
|
71
71
|
agentId?: string;
|
|
72
72
|
name?: string | null;
|
|
73
73
|
wsUrl?: string;
|
|
74
|
+
/** Store-facing one-liner. See DefineAgentInput.description. */
|
|
74
75
|
description?: string;
|
|
76
|
+
/** Claude-brain system prompt (ZIG-1080); falls back to `description`. */
|
|
77
|
+
systemPrompt?: string;
|
|
75
78
|
specialization?: string | null;
|
|
76
79
|
tools?: ToolDefinition[];
|
|
77
80
|
taskTools?: 'all' | 'none' | string[];
|
package/dist/AgentHost.js
CHANGED
|
@@ -90,7 +90,12 @@ function createBackendServices(options) {
|
|
|
90
90
|
const agentId = options.agentId;
|
|
91
91
|
const artifactsClient = new ArtifactsClient(operatorKey, agentId);
|
|
92
92
|
const contextReadClient = new ContextReadClient(operatorKey, agentId);
|
|
93
|
-
|
|
93
|
+
// ZIG-1080: the role text is the prompt when the author split the two; the
|
|
94
|
+
// store-facing `description` is only the fallback for agents that never did.
|
|
95
|
+
const promptBuilder = new PromptBuilder({
|
|
96
|
+
description: options.systemPrompt || options.description,
|
|
97
|
+
specialization: options.specialization,
|
|
98
|
+
});
|
|
94
99
|
const taskService = new TaskService(operatorKey, agentId);
|
|
95
100
|
const agreementService = new AgreementService(operatorKey, agentId);
|
|
96
101
|
const telemetryClient = new TelemetryClient(operatorKey, agentId);
|
|
@@ -145,6 +150,7 @@ export class AgentHost {
|
|
|
145
150
|
name: resolved.name || null,
|
|
146
151
|
wsUrl: resolved.wsUrl,
|
|
147
152
|
description: resolved.description || 'A helpful assistant',
|
|
153
|
+
systemPrompt: resolved.systemPrompt,
|
|
148
154
|
specialization: resolved.specialization ?? null,
|
|
149
155
|
tools: resolved.tools || [],
|
|
150
156
|
taskTools: resolved.taskTools,
|
|
@@ -268,6 +274,7 @@ export class AgentHost {
|
|
|
268
274
|
workflow,
|
|
269
275
|
toolManager: services.toolManager,
|
|
270
276
|
description: this.options.description,
|
|
277
|
+
systemPrompt: this.options.systemPrompt,
|
|
271
278
|
specialization: this.options.specialization,
|
|
272
279
|
model: resolveClaudeModel(this.options.model, this.options.services?.llm?.model),
|
|
273
280
|
anthropicKey: this.options.anthropicKey,
|
|
@@ -21,7 +21,10 @@ export type CognitionKind = 'fsm' | 'claude-sdk';
|
|
|
21
21
|
export interface ClaudeSdkAgentOptions {
|
|
22
22
|
workflow: Workflow;
|
|
23
23
|
toolManager: ToolManager;
|
|
24
|
+
/** Store-facing one-liner. Used as the role block only when `systemPrompt` is absent. */
|
|
24
25
|
description: string;
|
|
26
|
+
/** The agent's system prompt (ZIG-1080). Preferred over `description` for the role block. */
|
|
27
|
+
systemPrompt?: string;
|
|
25
28
|
specialization?: string | null;
|
|
26
29
|
model: string;
|
|
27
30
|
anthropicKey: string;
|
|
@@ -44,7 +47,7 @@ export interface ClaudeSdkAgentOptions {
|
|
|
44
47
|
export declare class ClaudeSdkAgent {
|
|
45
48
|
workflow: Workflow;
|
|
46
49
|
toolManager: ToolManager;
|
|
47
|
-
private
|
|
50
|
+
private rolePrompt;
|
|
48
51
|
private specialization;
|
|
49
52
|
private model;
|
|
50
53
|
private anthropicKey;
|
|
@@ -155,7 +155,7 @@ function resolveOnBehalfAgreementId(rawEvent, contextSnap, agentId, sessionId) {
|
|
|
155
155
|
export class ClaudeSdkAgent {
|
|
156
156
|
workflow;
|
|
157
157
|
toolManager;
|
|
158
|
-
|
|
158
|
+
rolePrompt;
|
|
159
159
|
specialization;
|
|
160
160
|
model;
|
|
161
161
|
anthropicKey;
|
|
@@ -173,7 +173,10 @@ export class ClaudeSdkAgent {
|
|
|
173
173
|
throw new Error('ClaudeSdkAgent: anthropicKey is required');
|
|
174
174
|
this.workflow = opts.workflow;
|
|
175
175
|
this.toolManager = opts.toolManager;
|
|
176
|
-
|
|
176
|
+
// ZIG-1080: the prompt and the store blurb are two different strings. Agents
|
|
177
|
+
// written before the split still carry the prompt in `description`, so fall
|
|
178
|
+
// back to it rather than silently dropping their role.
|
|
179
|
+
this.rolePrompt = opts.systemPrompt || opts.description;
|
|
177
180
|
this.specialization = opts.specialization ?? null;
|
|
178
181
|
this.model = opts.model;
|
|
179
182
|
this.anthropicKey = opts.anthropicKey;
|
|
@@ -259,7 +262,7 @@ export class ClaudeSdkAgent {
|
|
|
259
262
|
}
|
|
260
263
|
const roleBlock = [
|
|
261
264
|
`## Your role`,
|
|
262
|
-
this.
|
|
265
|
+
this.rolePrompt,
|
|
263
266
|
this.specialization ? `Specialization: ${this.specialization}` : '',
|
|
264
267
|
].filter(Boolean).join('\n');
|
|
265
268
|
const systemPrompt = `${CLAUDE_SDK_PROTOCOL}\n\n${roleBlock}`;
|
|
@@ -350,7 +353,7 @@ export class ClaudeSdkAgent {
|
|
|
350
353
|
if (text) {
|
|
351
354
|
// Ordinal drawn synchronously so the fire-and-forget below can't
|
|
352
355
|
// reorder key assignment against other writes of this wake.
|
|
353
|
-
const idempotencyKey = wakeKeys.artifactKey();
|
|
356
|
+
const idempotencyKey = wakeKeys.artifactKey({ kind: 'thought', text });
|
|
354
357
|
void eff({ kind: 'record-event', sessionId: identity.sessionId, entry: { kind: 'thought', text }, idempotencyKey })
|
|
355
358
|
.catch((err) => runtimeLog.warn('ClaudeSdkAgent', `record thought failed session=${identity.sessionId}: ${err.message}`));
|
|
356
359
|
}
|
|
@@ -8,18 +8,37 @@ import { listGrantsTool } from '../server/tools/tier2/grantsTools.js';
|
|
|
8
8
|
// messages cover "what woke me and what was just said"; the model can pass a
|
|
9
9
|
// larger maxMessages explicitly when it genuinely needs deeper history.
|
|
10
10
|
const DEFAULT_SNAPSHOT_MAX_MESSAGES = 12;
|
|
11
|
+
/**
|
|
12
|
+
* First usable string among the candidates, treating blank as absent.
|
|
13
|
+
*
|
|
14
|
+
* ZIG-1081: a model that does not know a value fills the field with `""` rather
|
|
15
|
+
* than omitting it — the schema shows `chatId`, the agent has no way to know it,
|
|
16
|
+
* so it sends an empty string. Plain `??` let that blank win over the lane
|
|
17
|
+
* default, and the very next line rejected it as missing. Every wake opened with
|
|
18
|
+
* a failed `context_snapshot` and a recovery turn.
|
|
19
|
+
*/
|
|
20
|
+
function firstFilled(...candidates) {
|
|
21
|
+
for (const candidate of candidates) {
|
|
22
|
+
if (typeof candidate === 'string' && candidate.trim() !== '')
|
|
23
|
+
return candidate;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
11
27
|
// Named distinctly from ziggs-mcp's per-type `ziggs_context_read`: this is the
|
|
12
28
|
// one-shot aggregate snapshot (grant-fenced server-side), not a cursored read.
|
|
13
29
|
export const contextSnapshotTool = defineTool('context_snapshot', {
|
|
14
|
-
chatId: { type: 'string', description: '
|
|
30
|
+
chatId: { type: 'string', description: 'Omit this to snapshot the chat you were woken in — that is the usual case, and you do not need to know its id. Only pass a chatId to snapshot a DIFFERENT chat.' },
|
|
15
31
|
maxMessages: { type: 'number', description: `Message history cap (default ${DEFAULT_SNAPSHOT_MAX_MESSAGES}). Pass a larger value when you need deeper history.` },
|
|
16
32
|
}, async (args, ctx) => {
|
|
17
33
|
const c = ctx;
|
|
18
34
|
if (!c.eff)
|
|
19
35
|
throw new Error('context_snapshot requires eff in tool context');
|
|
20
|
-
const sessionId = args.chatId
|
|
21
|
-
if (!sessionId)
|
|
22
|
-
throw new Error('context_snapshot:
|
|
36
|
+
const sessionId = firstFilled(args.chatId, c.chatId, c.sessionId);
|
|
37
|
+
if (!sessionId) {
|
|
38
|
+
throw new Error('context_snapshot: this wake carries no chat to snapshot. Call it with no arguments ' +
|
|
39
|
+
'to read the chat you were woken in; if this wake is on an agreement lane with no ' +
|
|
40
|
+
'single chat, read the agreement instead and pass a chatId from one of its chats.');
|
|
41
|
+
}
|
|
23
42
|
return c.eff({
|
|
24
43
|
kind: 'read-context',
|
|
25
44
|
sessionId,
|
|
@@ -29,22 +48,24 @@ export const contextSnapshotTool = defineTool('context_snapshot', {
|
|
|
29
48
|
});
|
|
30
49
|
}, { description: 'Read the chat snapshot: the NEWEST maxMessages of history (chronological order), plus agreements, agents, and users for a session.' });
|
|
31
50
|
export const chatSendTool = defineTool('chat_send', {
|
|
32
|
-
chatId: { type: 'string', description: '
|
|
33
|
-
receiverId: { type: 'string', description: 'Recipient user or agent id.
|
|
51
|
+
chatId: { type: 'string', description: 'Omit this to send in the chat you were woken in — that is the usual case. Only pass a chatId to send in a DIFFERENT chat.' },
|
|
52
|
+
receiverId: { type: 'string', description: 'Recipient user or agent id. Omit when replying to whoever woke you — that is the default.' },
|
|
34
53
|
text: { type: 'string', required: true },
|
|
35
54
|
idempotencyKey: { type: 'string', description: 'Reuse for retry-safe resend of the same logical message.' },
|
|
36
55
|
}, async (args, ctx) => {
|
|
37
56
|
const c = ctx;
|
|
38
57
|
if (!c.eff)
|
|
39
58
|
throw new Error('chat_send requires eff in tool context');
|
|
40
|
-
const sessionId = args.chatId
|
|
41
|
-
if (!sessionId)
|
|
42
|
-
throw new Error('chat_send: chatId
|
|
59
|
+
const sessionId = firstFilled(args.chatId, c.chatId, c.sessionId);
|
|
60
|
+
if (!sessionId) {
|
|
61
|
+
throw new Error('chat_send: this wake carries no chat to send in. Call it with no chatId to reply in ' +
|
|
62
|
+
'the chat you were woken in, or pass the chatId of the chat you mean.');
|
|
63
|
+
}
|
|
43
64
|
// Replying to whoever woke you is the overwhelmingly common case; making
|
|
44
65
|
// the model spell it out cost a failed tool call + a full extra LLM turn
|
|
45
66
|
// per reply. Batch/timer wakes carry no sender — those still require an
|
|
46
67
|
// explicit receiverId.
|
|
47
|
-
const receiverId = args.receiverId
|
|
68
|
+
const receiverId = firstFilled(args.receiverId, c.senderId);
|
|
48
69
|
if (!receiverId)
|
|
49
70
|
throw new Error('chat_send: receiverId is required (this wake has no sender to default to)');
|
|
50
71
|
// Default to the wake's content-derived key (ZIG-1045): a crash-replay that
|
|
@@ -99,12 +120,19 @@ export const listTasksTool = defineTool('task_list', {
|
|
|
99
120
|
if (!c.agentId)
|
|
100
121
|
throw new Error('task_list: agentId missing from tool context');
|
|
101
122
|
const assignedTo = args.assignedToMe ? c.agentId : args.assignedTo;
|
|
123
|
+
// ZIG-1092: the lane fences this to the engagement's tasks. Without it the
|
|
124
|
+
// list is every task the agent's key can reach, across all its customers.
|
|
125
|
+
const laneId = c.chatId || c.sessionId || undefined;
|
|
102
126
|
return listTasks({
|
|
103
127
|
state: args.state,
|
|
104
128
|
cursor: args.cursor,
|
|
105
129
|
limit: typeof args.limit === 'number' ? args.limit : undefined,
|
|
106
130
|
assignedTo,
|
|
107
|
-
}, {
|
|
131
|
+
}, {
|
|
132
|
+
operatorKey: c.operatorKey,
|
|
133
|
+
agentId: c.agentId,
|
|
134
|
+
...(laneId ? { laneId } : {}),
|
|
135
|
+
});
|
|
108
136
|
}, {
|
|
109
137
|
description: 'List tasks reachable by this agent (GET /tasks). Scope follows the operator key — same reach as chats and agreements. Optional state / assignee filters and cursor pagination. Use assignedToMe to list your own open work.',
|
|
110
138
|
});
|
package/dist/defineAgent.js
CHANGED
|
@@ -35,7 +35,10 @@ export function defineAgent(input) {
|
|
|
35
35
|
// states/transitions are validated too.
|
|
36
36
|
const workflow = injectTaskIntakeDefault(injectKickoffDefault({
|
|
37
37
|
id: input.agentId,
|
|
38
|
-
description
|
|
38
|
+
// The workflow description is the agent's role text, which PromptBuilder
|
|
39
|
+
// renders into <self>. ZIG-1080 split that from the store-facing
|
|
40
|
+
// `description`: prefer the prompt when the author supplied one.
|
|
41
|
+
description: input.systemPrompt || input.description,
|
|
39
42
|
initial: input.initial,
|
|
40
43
|
states: input.states,
|
|
41
44
|
}));
|
|
@@ -126,6 +126,17 @@ export class HistoryFormatter {
|
|
|
126
126
|
return 'System';
|
|
127
127
|
if (id === agentId)
|
|
128
128
|
return 'You';
|
|
129
|
+
// ZIG-1137: prefer presentation face when the wire masks the principal.
|
|
130
|
+
const presentation = participant['presentation'];
|
|
131
|
+
const faceName = (typeof presentation?.['name'] === 'string' && presentation['name']) ||
|
|
132
|
+
(typeof presentation?.['persona']?.['name'] === 'string'
|
|
133
|
+
? presentation['persona']['name']
|
|
134
|
+
: null);
|
|
135
|
+
if (faceName)
|
|
136
|
+
return `${faceName} (${id})`;
|
|
137
|
+
if (id.startsWith('rpb_') || id.startsWith('psn_')) {
|
|
138
|
+
return `${id} (presentation)`;
|
|
139
|
+
}
|
|
129
140
|
return `${id} (${participant['type'] || 'unknown'})`;
|
|
130
141
|
}
|
|
131
142
|
formatOwner(task, agentId) {
|
|
@@ -45,6 +45,27 @@ function isAlreadyActivatedProposalCreated(metadata) {
|
|
|
45
45
|
const proposalStatus = agr.proposal?.status;
|
|
46
46
|
return status === 'active' || proposalStatus === 'approved';
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* ZIG-1078 — a `proposal_created` notification addressed to a party whose
|
|
50
|
+
* approval is PENDING is a consent REQUEST, not a status notice. The backend
|
|
51
|
+
* fans `proposal_created` out specifically to the pending approvers
|
|
52
|
+
* (ZIG-244), and parking it meant the hand-off provider (ZIG-1059) — or any
|
|
53
|
+
* agent whose consent a proposal awaits — was alerted and then never looked:
|
|
54
|
+
* the approval sat pending until a human nudged the chat. Same technique as
|
|
55
|
+
* isAlreadyActivatedProposalCreated (ZIG-1052): reclassify by what the
|
|
56
|
+
* embedded agreement SAYS rather than adding a new wake word.
|
|
57
|
+
*/
|
|
58
|
+
function isPendingApprovalRequestNotification(metadata) {
|
|
59
|
+
if (wireOperation(metadata) !== 'proposal_created')
|
|
60
|
+
return false;
|
|
61
|
+
const receiverId = metadata.receiver?.id;
|
|
62
|
+
if (!receiverId)
|
|
63
|
+
return false;
|
|
64
|
+
const approvals = metadata.service?.agreement?.approvals;
|
|
65
|
+
if (!Array.isArray(approvals))
|
|
66
|
+
return false;
|
|
67
|
+
return approvals.some((a) => a && a.partyId === receiverId && a.status === 'pending');
|
|
68
|
+
}
|
|
48
69
|
function isAgreementLifecycleNotification(metadata) {
|
|
49
70
|
const entryType = metadata.entryType || 'message';
|
|
50
71
|
if (entryType !== 'notification')
|
|
@@ -53,7 +74,8 @@ function isAgreementLifecycleNotification(metadata) {
|
|
|
53
74
|
if (typeof operation === 'string' && PROPOSAL_RESOLVED_OPERATIONS.has(operation)) {
|
|
54
75
|
return true;
|
|
55
76
|
}
|
|
56
|
-
return isAlreadyActivatedProposalCreated(metadata)
|
|
77
|
+
return (isAlreadyActivatedProposalCreated(metadata) ||
|
|
78
|
+
isPendingApprovalRequestNotification(metadata));
|
|
57
79
|
}
|
|
58
80
|
function isTerminalTaskState(state) {
|
|
59
81
|
return state === 'completed' || state === 'failed' || state === 'cancelled';
|
|
@@ -19,7 +19,6 @@ export interface ProtocolPricingPayload {
|
|
|
19
19
|
providerId?: string;
|
|
20
20
|
executorId?: string;
|
|
21
21
|
parentAgreementId?: string;
|
|
22
|
-
parentTaskId?: string;
|
|
23
22
|
[key: string]: unknown;
|
|
24
23
|
}
|
|
25
24
|
/**
|
|
@@ -36,8 +35,6 @@ export declare function applyFleetAgreementPricing(operation: 'agreement-propose
|
|
|
36
35
|
* out unwidened by `ProtocolPricingPayload`'s `[key: string]: unknown` index.
|
|
37
36
|
*/
|
|
38
37
|
export declare function applyFleetCounterPricing<T extends ProtocolPricingPayload>(payload: T, actingAgentId: string): T;
|
|
39
|
-
/** User or agent direct-create / marketplace: default from provider specialist. */
|
|
40
|
-
export declare function resolveDefaultPriceForProvider(providerId: string, description?: string | null, explicitPrice?: number | null): number | null;
|
|
41
38
|
/**
|
|
42
39
|
* Infer fleet actor when tool context omits agentId (ZIG-368 safety net).
|
|
43
40
|
*/
|
|
@@ -121,12 +121,6 @@ export function applyFleetCounterPricing(payload, actingAgentId) {
|
|
|
121
121
|
return payload;
|
|
122
122
|
return { ...payload, price: cents };
|
|
123
123
|
}
|
|
124
|
-
/** User or agent direct-create / marketplace: default from provider specialist. */
|
|
125
|
-
export function resolveDefaultPriceForProvider(providerId, description, explicitPrice) {
|
|
126
|
-
if (typeof explicitPrice === 'number')
|
|
127
|
-
return explicitPrice;
|
|
128
|
-
return resolveDefaultPriceCentsForExecutor(providerId, description);
|
|
129
|
-
}
|
|
130
124
|
/**
|
|
131
125
|
* Infer fleet actor when tool context omits agentId (ZIG-368 safety net).
|
|
132
126
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HistoryFormatter, AgreementFormatter } from '../formatters/index.js';
|
|
2
2
|
import { validateContext } from '../cognition/validateContext.js';
|
|
3
|
-
import { isBroadcastTarget } from '@ziggs-ai/api-client';
|
|
3
|
+
import { isBroadcastTarget, isOpaquePresentationRef } from '@ziggs-ai/api-client';
|
|
4
4
|
import { LANE_BOOK_KEY, allCollectedResults, allDelegatedAgreementIds, allPendingSubtasks, } from './taskBookkeeping.js';
|
|
5
5
|
/** Rough chars-per-token ratio for budgeting (English/JSON averages ~4). */
|
|
6
6
|
const CHARS_PER_TOKEN = 4;
|
|
@@ -298,16 +298,20 @@ ${this._renderDecisionSchemaFromActions(actions, senderMeta, ctxObj, omitMessage
|
|
|
298
298
|
const pid = parties[field];
|
|
299
299
|
if (!pid || pid === myAgentId)
|
|
300
300
|
continue;
|
|
301
|
+
// Opaque presentation refs are not principal slots (ZIG-1137).
|
|
302
|
+
if (isOpaquePresentationRef(pid))
|
|
303
|
+
continue;
|
|
301
304
|
if (!agreementParties.has(pid))
|
|
302
305
|
agreementParties.set(pid, []);
|
|
303
306
|
agreementParties.get(pid).push({ agreementId: agId ?? '', role });
|
|
304
307
|
}
|
|
305
308
|
}
|
|
306
|
-
// Chat
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
+
// Chat keeps the full directory — including opaque `rpb_*` / `psn_*` refs
|
|
310
|
+
// the persona layer may surface. Echo those as chat_send receiverId; the
|
|
311
|
+
// backend resolves them in-room. Transfer (and any account-id action) must
|
|
312
|
+
// never list opaque refs — lookup / wake / pay against them 400s (ZIG-1137).
|
|
309
313
|
const chatIds = new Set([...userIds, ...agentIds]);
|
|
310
|
-
const transferIds = new Set([...userIds, ...agentIds]);
|
|
314
|
+
const transferIds = new Set([...userIds, ...agentIds].filter((id) => !isOpaquePresentationRef(id)));
|
|
311
315
|
const lines = ['<recipients>'];
|
|
312
316
|
lines.push('<users>');
|
|
313
317
|
if (userIds.size === 0)
|
package/dist/runtime/runTurn.js
CHANGED
|
@@ -84,7 +84,7 @@ export async function runTurn(input) {
|
|
|
84
84
|
if (s.length === 0)
|
|
85
85
|
return;
|
|
86
86
|
try {
|
|
87
|
-
await eff({ kind: 'record-event', sessionId, entry: { kind: 'thought', text: s }, idempotencyKey: wakeKeys?.artifactKey() });
|
|
87
|
+
await eff({ kind: 'record-event', sessionId, entry: { kind: 'thought', text: s }, idempotencyKey: wakeKeys?.artifactKey({ kind: 'thought', text: s }) });
|
|
88
88
|
}
|
|
89
89
|
catch (err) {
|
|
90
90
|
runtimeLog.warn('runTurn', `record-event:thought failed: ${err.message}`);
|
|
@@ -7,8 +7,6 @@ import type { Ctx, TaskBookkeeping } from '../types.js';
|
|
|
7
7
|
export declare const LANE_BOOK_KEY = "__lane__";
|
|
8
8
|
export declare function emptyTaskBook(agreementId?: string | null): TaskBookkeeping;
|
|
9
9
|
export declare function ensureTaskBook(ctx: Ctx, taskId: string, agreementId?: string | null): TaskBookkeeping;
|
|
10
|
-
/** Focus task's book, or null when nothing is focused / missing. */
|
|
11
|
-
export declare function focusTaskBook(ctx: Ctx): TaskBookkeeping | null;
|
|
12
10
|
/**
|
|
13
11
|
* Book that receives task-delegated / subtask-finished when we know the parent
|
|
14
12
|
* engagement: focus task if assigned, else the coordinator lane bucket.
|
|
@@ -25,12 +25,6 @@ export function ensureTaskBook(ctx, taskId, agreementId) {
|
|
|
25
25
|
}
|
|
26
26
|
return book;
|
|
27
27
|
}
|
|
28
|
-
/** Focus task's book, or null when nothing is focused / missing. */
|
|
29
|
-
export function focusTaskBook(ctx) {
|
|
30
|
-
if (!ctx.activeTaskId)
|
|
31
|
-
return null;
|
|
32
|
-
return ctx.tasks?.[ctx.activeTaskId] ?? null;
|
|
33
|
-
}
|
|
34
28
|
/**
|
|
35
29
|
* Book that receives task-delegated / subtask-finished when we know the parent
|
|
36
30
|
* engagement: focus task if assigned, else the coordinator lane bucket.
|
|
@@ -99,10 +99,19 @@ export function createWakeWriteKeys(laneKey, event) {
|
|
|
99
99
|
let ordinal = 0;
|
|
100
100
|
return {
|
|
101
101
|
wakeKey: wakeHash === null ? null : `wk_${wakeHash}`,
|
|
102
|
-
artifactKey() {
|
|
102
|
+
artifactKey(content) {
|
|
103
103
|
if (wakeHash === null)
|
|
104
104
|
return undefined;
|
|
105
|
-
|
|
105
|
+
// Content-derived for the same reason messageKey is (ZIG-1045): the
|
|
106
|
+
// ordinal restarts at a0 on every wake, so a redelivered event whose
|
|
107
|
+
// re-think writes different artifacts collided with the first wake's
|
|
108
|
+
// and the backend discarded the new write. Harmless for a breadcrumb,
|
|
109
|
+
// not for a result artifact — which is the whole deliverable of an
|
|
110
|
+
// agent like agent-definer. Callers without content keep the ordinal.
|
|
111
|
+
if (content === undefined)
|
|
112
|
+
return `wk_${wakeHash}_a${ordinal++}`;
|
|
113
|
+
const contentHash = sha(typeof content === 'string' ? content : JSON.stringify(content) ?? '').slice(0, 16);
|
|
114
|
+
return `wk_${wakeHash}_a_${contentHash}`;
|
|
106
115
|
},
|
|
107
116
|
taskStateKey(taskId, state) {
|
|
108
117
|
if (wakeHash === null)
|
|
@@ -113,7 +113,7 @@ export async function executeToolWithSideEffects(deps, eff, effect, roster) {
|
|
|
113
113
|
state: 'started',
|
|
114
114
|
tool: effect.name,
|
|
115
115
|
...(argsPreview ? { args: argsPreview } : {}),
|
|
116
|
-
}, effect.wakeKeys?.artifactKey());
|
|
116
|
+
}, effect.wakeKeys?.artifactKey({ s: 'started', t: effect.name, a: argsPreview }));
|
|
117
117
|
try {
|
|
118
118
|
const lastUser = roster.users.find((u) => u.userId || u.id);
|
|
119
119
|
// Accumulate token usage from any nested LLM call the tool makes (llmTool),
|
|
@@ -150,11 +150,14 @@ export async function executeToolWithSideEffects(deps, eff, effect, roster) {
|
|
|
150
150
|
reportUsage,
|
|
151
151
|
turnScratch: {},
|
|
152
152
|
});
|
|
153
|
+
// One clip per tool call: this is a full JSON.stringify of the untruncated
|
|
154
|
+
// result, and the artifact body and the idempotency key want the same string.
|
|
155
|
+
const clipped = clipForOperationArtifact(result);
|
|
153
156
|
await emitToolOperationArtifact(deps.artifactsClient, effect.sessionId, 'completed', {
|
|
154
157
|
state: 'completed',
|
|
155
158
|
tool: effect.name,
|
|
156
|
-
result:
|
|
157
|
-
}, effect.wakeKeys?.artifactKey());
|
|
159
|
+
result: clipped,
|
|
160
|
+
}, effect.wakeKeys?.artifactKey({ s: 'completed', t: effect.name, r: clipped }));
|
|
158
161
|
sendTelemetry(deps.telemetryClient, buildToolIngestPayload({
|
|
159
162
|
sessionId: effect.sessionId,
|
|
160
163
|
tool: effect.name,
|
|
@@ -172,7 +175,7 @@ export async function executeToolWithSideEffects(deps, eff, effect, roster) {
|
|
|
172
175
|
state: 'failed',
|
|
173
176
|
tool: effect.name,
|
|
174
177
|
error: errMsg,
|
|
175
|
-
}, effect.wakeKeys?.artifactKey());
|
|
178
|
+
}, effect.wakeKeys?.artifactKey({ s: 'failed', t: effect.name, e: errMsg }));
|
|
176
179
|
sendTelemetry(deps.telemetryClient, buildToolIngestPayload({
|
|
177
180
|
sessionId: effect.sessionId,
|
|
178
181
|
tool: effect.name,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Task, type TaskState } from '@ziggs-ai/api-client';
|
|
2
|
-
import type { CountTasksFilters } from '@ziggs-ai/api-client';
|
|
2
|
+
import type { CountTasksFilters, CreateTaskData } from '@ziggs-ai/api-client';
|
|
3
3
|
export declare class TaskService {
|
|
4
4
|
private creds;
|
|
5
5
|
constructor(operatorKey: string, agentId: string);
|
|
@@ -7,16 +7,7 @@ export declare class TaskService {
|
|
|
7
7
|
getActiveTasksForAgent(agentId: string): Promise<Task[]>;
|
|
8
8
|
getActiveTasksForChat(chatId: string): Promise<Task[]>;
|
|
9
9
|
getSubtasks(parentTaskId: string): Promise<Task[]>;
|
|
10
|
-
spawnUnderAgreement(agreementId: string, taskData:
|
|
11
|
-
description: string;
|
|
12
|
-
parentTaskId?: string;
|
|
13
|
-
plan?: unknown;
|
|
14
|
-
planReviewTiming?: string;
|
|
15
|
-
requireMidWorkPlanAck?: boolean;
|
|
16
|
-
assigneeId?: string;
|
|
17
|
-
inputArtifactIds?: string[];
|
|
18
|
-
idempotencyKey?: string;
|
|
19
|
-
}): Promise<Task>;
|
|
10
|
+
spawnUnderAgreement(agreementId: string, taskData: Omit<CreateTaskData, 'agreementId'>): Promise<Task>;
|
|
20
11
|
updateState(taskId: string, state: TaskState, result?: unknown, idempotencyKey?: string): Promise<Task>;
|
|
21
12
|
updatePlanStep(taskId: string, stepId: string, status: string, result?: unknown): Promise<Task>;
|
|
22
13
|
cancel(taskId: string): Promise<Task>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { linkSummary, } from '@ziggs-ai/api-client';
|
|
1
|
+
import { isBroadcastTarget, linkSummary, } from '@ziggs-ai/api-client';
|
|
2
2
|
import { applyFleetCounterPricing, applyFleetPricingForProtocol, } from '../../../pricing/fleetDefaults.js';
|
|
3
3
|
/**
|
|
4
4
|
* ZIG-957 — same summary shape as MCP link mutations / link_* tools.
|
|
@@ -22,8 +22,14 @@ function normalizeState(state) {
|
|
|
22
22
|
}
|
|
23
23
|
// ZIG-793: the `plan` tool-schema is underspecified (`plan: {type:'object'}`,
|
|
24
24
|
// `planReviewTiming: 'string'`), so the LLM emits steps missing stepId/order and an
|
|
25
|
-
// out-of-enum planReviewTiming, which the backend DTO rejects. Normalize in-place
|
|
26
|
-
//
|
|
25
|
+
// out-of-enum planReviewTiming, which the backend DTO rejects. Normalize in-place
|
|
26
|
+
// before the write.
|
|
27
|
+
//
|
|
28
|
+
// ZIG-1095: task-spawn is the only op left that carries a plan. Propose, counter
|
|
29
|
+
// and subcontract used to run this too — and their plans were then dropped on the
|
|
30
|
+
// floor, since no agreement route has ever accepted one.
|
|
31
|
+
// Returns the clamped review timing rather than only writing it back, so the
|
|
32
|
+
// spawn call gets the narrowed type from the one place that does the clamping.
|
|
27
33
|
function normalizePlanInPlace(payload) {
|
|
28
34
|
const plan = payload.plan;
|
|
29
35
|
if (plan && Array.isArray(plan.steps)) {
|
|
@@ -41,16 +47,15 @@ function normalizePlanInPlace(payload) {
|
|
|
41
47
|
};
|
|
42
48
|
});
|
|
43
49
|
}
|
|
44
|
-
if (payload.planReviewTiming
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
if (payload.planReviewTiming === undefined)
|
|
51
|
+
return undefined;
|
|
52
|
+
return payload.planReviewTiming === 'before_execution'
|
|
53
|
+
? 'before_execution'
|
|
54
|
+
: 'with_proposal';
|
|
49
55
|
}
|
|
50
56
|
export async function dispatchProtocolOp(services, payload, chatId, agents = [], actingAgentId = null, wakeKeys = null) {
|
|
51
57
|
if (!payload?.operation)
|
|
52
58
|
return null;
|
|
53
|
-
normalizePlanInPlace(payload);
|
|
54
59
|
const { operation } = payload;
|
|
55
60
|
const { agreementService, taskService } = services;
|
|
56
61
|
const actorId = actingAgentId?.trim() || null;
|
|
@@ -87,9 +92,6 @@ export async function dispatchProtocolOp(services, payload, chatId, agents = [],
|
|
|
87
92
|
lifecycle: counterPayload.lifecycle,
|
|
88
93
|
maxExecutions: counterPayload.maxExecutions,
|
|
89
94
|
description: counterPayload.description,
|
|
90
|
-
plan: counterPayload.plan,
|
|
91
|
-
planReviewTiming: counterPayload.planReviewTiming,
|
|
92
|
-
requireMidWorkPlanAck: counterPayload.requireMidWorkPlanAck,
|
|
93
95
|
});
|
|
94
96
|
}
|
|
95
97
|
case 'agreement-check-proposal':
|
|
@@ -159,11 +161,12 @@ export async function dispatchProtocolOp(services, payload, chatId, agents = [],
|
|
|
159
161
|
agreementId: spawnAgreementId,
|
|
160
162
|
};
|
|
161
163
|
}
|
|
164
|
+
const planReviewTiming = normalizePlanInPlace(payload);
|
|
162
165
|
return taskService.spawnUnderAgreement(spawnAgreementId, {
|
|
163
166
|
description: payload.description,
|
|
164
167
|
parentTaskId: payload.parentTaskId,
|
|
165
168
|
plan: payload.plan,
|
|
166
|
-
planReviewTiming
|
|
169
|
+
planReviewTiming,
|
|
167
170
|
requireMidWorkPlanAck: payload.requireMidWorkPlanAck,
|
|
168
171
|
assigneeId: payload.assigneeId,
|
|
169
172
|
inputArtifactIds: Array.isArray(payload.inputArtifactIds)
|
|
@@ -219,7 +222,7 @@ async function proposeAgreement(services, payload, chatId, _agents, actingAgentI
|
|
|
219
222
|
throw new Error('agreement-propose requires `description`');
|
|
220
223
|
if (!payload.proposedTo)
|
|
221
224
|
throw new Error('agreement-propose requires `proposedTo` (userId or agentId). To create a task under an existing agreement, use `task_create` instead.');
|
|
222
|
-
const isBroadcast = payload.proposedTo
|
|
225
|
+
const isBroadcast = isBroadcastTarget(payload.proposedTo);
|
|
223
226
|
// ZIG-1022: a link proposal is an agreement with engagementKind 'link' —
|
|
224
227
|
// no chat, no money; the target owner approves. Folds the old request_link.
|
|
225
228
|
if (payload.engagementKind === 'link') {
|
|
@@ -32,8 +32,8 @@ function handlerFor(operation) {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
-
export const agreementProposeTool = defineTool('agreement_propose', { description: { type: 'string', required: true }, proposedTo: { type: 'string', required: true }, engagementKind: { type: 'string', enum: ['hire', 'service', 'link'], description: "Contract type. Defaults to service. 'link' proposes a bilateral trust link (no work, no money)." }, parentAgreementId: 'string',
|
|
36
|
-
export const agreementSubcontractTool = defineTool('agreement_subcontract', { description: { type: 'string', required: true }, executorId: { type: 'string', required: true }, parentAgreementId: { type: 'string', required: true },
|
|
35
|
+
export const agreementProposeTool = defineTool('agreement_propose', { description: { type: 'string', required: true }, proposedTo: { type: 'string', required: true }, engagementKind: { type: 'string', enum: ['hire', 'service', 'link'], description: "Contract type. Defaults to service. 'link' proposes a bilateral trust link (no work, no money)." }, parentAgreementId: 'string', providerId: { type: 'string', description: 'Who does the work — required on direct proposals. Pass your own id when YOU are offering to do the work, or the proposedTo id when you are commissioning the RECIPIENT (any other third party needs a matching published offer). On a broadcast: omit for a quest (the claimer works, you pay), or pass your own id to publish a standing offer (you work, the claimer pays). The payer is always derived as the non-providing side.' }, price: { type: 'number', description: 'Amount in CENTS — 500 means $5.00, shown as ϟ5.00 in the UI. Convert both ways: a human asking for "ϟ2" or "$2 per task" means 200, not 2. ϟ is the currency symbol, not a cent.' }, lifecycle: lifecycleParam, expiresAt: 'string', maxExecutions: 'integer', 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. 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 QUEST any agent may claim; providerId = your own id publishes a STANDING OFFER (claiming is agreement_claim; browsing is marketplace_view). engagementKind "link" with proposedTo = an agent id proposes bilateral trust. 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 });
|
|
36
|
+
export const agreementSubcontractTool = defineTool('agreement_subcontract', { description: { type: 'string', required: true }, executorId: { type: 'string', required: true }, parentAgreementId: { type: 'string', required: true }, payerId: 'string', price: { type: 'number', description: 'Amount in CENTS — 500 means $5.00, shown as ϟ5.00 in the UI. Convert both ways: a human asking for "ϟ2" or "$2 per task" means 200, not 2. ϟ is the currency symbol, not a cent.' }, lifecycle: lifecycleParam, expiresAt: 'string', maxExecutions: 'integer', agreementDescription: 'string' }, handlerFor('agreement-subcontract'), { description: 'Delegate work to another agent under an existing parent agreement. Requires parentAgreementId.', isAgreementCreation: true });
|
|
37
37
|
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 (quests, 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.' });
|
|
38
38
|
export const agreementClaimTool = defineTool('agreement_claim', { agreementId: { type: 'string', required: true, description: 'The open agreement to claim (quest / offer / link invite id)' } }, handlerFor('agreement-claim'), { description: 'Claim an open broadcast agreement by id — a quest (you do the work, the publisher pays), a standing offer (you buy, the publisher works), a hand-off (providerPinned: the publisher\'s hired agent works FOR you — you become the customer, and the payer when it is priced), or a link invite (bilateral trust forms immediately). You become the open party and the agreement activates. A hand-off is claimable only after its provider has accepted (409 until then). Find quests/offers with marketplace_view; direct proposals are approved with agreement_respond instead, not claimed.' });
|
|
39
39
|
export const taskSetResultTool = defineTool('task_set_result', {
|
|
@@ -46,7 +46,7 @@ export const taskSetResultTool = defineTool('task_set_result', {
|
|
|
46
46
|
description: 'Outcome payload (summary, status, links, …). Finished work is the task result — never a chat message.',
|
|
47
47
|
},
|
|
48
48
|
}, handlerFor('task-update'), { description: 'Transition a task to its terminal state — completed, failed, or cancelled — and record the result. Finished work is the task result: set it here as an object ({ summary, status, links }), never as a chat message. Pass state (not status).' });
|
|
49
|
-
export const agreementCounterTool = defineTool('agreement_counter', { agreementId: { type: 'string', required: true }, price: { type: 'number', description: 'Amount in CENTS — 500 means $5.00.
|
|
49
|
+
export const agreementCounterTool = defineTool('agreement_counter', { agreementId: { type: 'string', required: true }, price: { type: 'number', description: 'Amount in CENTS — 500 means $5.00, shown as ϟ5.00 in the UI. Convert both ways: a human asking for "ϟ2" or "$2 per task" means 200, not 2. ϟ is the currency symbol, not a cent.' }, agreementDescription: 'string', expiresAt: 'string', lifecycle: lifecycleParam, maxExecutions: 'integer', description: 'string' }, handlerFor('agreement-counter-proposal'), { description: 'Counter a pending proposal with revised terms instead of approving or rejecting. Check status first with agreement_get.' });
|
|
50
50
|
export const agreementGetTool = defineTool('agreement_get', { agreementId: { type: 'string', required: true } }, handlerFor('agreement-check-proposal'), { description: 'Read the current proposal status for an agreement (pending, approved, rejected, countered, expired).' });
|
|
51
51
|
export const agreementRevokeTool = defineTool('agreement_revoke', { agreementId: { type: 'string', required: true } }, handlerFor('agreement-revoke'), { description: 'Revoke a pending proposal you created so it can no longer be approved. Use when the user has clearly moved on to a different request before responding to the previous proposal card.' });
|
|
52
52
|
export const agreementFulfillTool = defineTool('agreement_fulfill', { agreementId: { type: 'string', required: true } }, handlerFor('agreement-fulfill'), { description: 'END an agreement you provide — permanently. Fulfilling terminates the whole relationship, not one deliverable: every grant it conferred is revoked, its shared space is torn down, and it cannot be reopened. Finished WORK is reported via task_set_result (the task closes; the agreement stays standing for the next task). Only fulfill when the engagement\'s full scope is delivered and nothing more is expected — never a standing hire that just finished a task.' });
|
|
@@ -10,12 +10,23 @@ export function toolFromCapability(cap) {
|
|
|
10
10
|
return defineTool(cap.names.sdk, cap.params, async (args, ctx) => {
|
|
11
11
|
const operatorKey = ctx?.['operatorKey'];
|
|
12
12
|
const agentId = ctx?.['agentId'];
|
|
13
|
+
// ZIG-1092 — the lane was already threaded to this exact point by
|
|
14
|
+
// ZIG-775's shared executor (`chatId: effect.sessionId` in
|
|
15
|
+
// executeToolWithSideEffects); it just was not used to narrow anything.
|
|
16
|
+
// Reading it here makes the fence brain-agnostic by construction: the
|
|
17
|
+
// Claude brain and the FSM brain both arrive through this adapter.
|
|
18
|
+
const laneId = ctx?.['chatId'] ||
|
|
19
|
+
ctx?.['sessionId'];
|
|
13
20
|
if (!operatorKey)
|
|
14
21
|
throw new Error('operatorKey missing from tool context');
|
|
15
22
|
if (cap.needsAgentId && !agentId)
|
|
16
23
|
throw new Error('agentId missing from tool context');
|
|
17
24
|
const env = {
|
|
18
|
-
creds: {
|
|
25
|
+
creds: {
|
|
26
|
+
operatorKey,
|
|
27
|
+
...(agentId ? { agentId } : {}),
|
|
28
|
+
...(laneId ? { laneId } : {}),
|
|
29
|
+
},
|
|
19
30
|
// BACKEND_URL / ZIGGS_BACKEND_URL are the SDK runner's overrides; the
|
|
20
31
|
// shared clients default to HTTP_URL (api-client convention) otherwise.
|
|
21
32
|
baseUrl: process.env.BACKEND_URL || process.env.ZIGGS_BACKEND_URL || undefined,
|
|
@@ -12,10 +12,14 @@ export async function recordReport(input) {
|
|
|
12
12
|
const { creds, taskId, report, chatId, agreementId, deliverable } = input;
|
|
13
13
|
// ZIG-1037: no container needed. The deliverable is bound to its task, which
|
|
14
14
|
// is what makes it retrievable as `via=task:<taskId>` and readable by the
|
|
15
|
-
// task's parties — a chat or agreement was never what made that work.
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
15
|
+
// task's parties — a chat or agreement was never what made that work.
|
|
16
|
+
//
|
|
17
|
+
// ZIG-1075: the agreement still wins here, and this is now the ONE place that
|
|
18
|
+
// resolves rather than refuses — deliberately, because the caller is not a
|
|
19
|
+
// model choosing arguments. `recordReport` is handed both ids by the runtime
|
|
20
|
+
// (the task's agreement and the chat it is running in), so there is no
|
|
21
|
+
// mistake to surface and nobody to retry: refusing would fail a finished
|
|
22
|
+
// task's delivery over a pair of ids it did not choose.
|
|
19
23
|
const scope = agreementId ? { agreementId } : chatId ? { chatId } : {};
|
|
20
24
|
// Deliverable FIRST, then close the task.
|
|
21
25
|
//
|
package/dist/types.d.ts
CHANGED
|
@@ -385,8 +385,15 @@ export interface RecordedEntry {
|
|
|
385
385
|
export interface WakeWriteKeys {
|
|
386
386
|
/** Stable id of this wake, or null when its trigger has no durable delivery identity (timer/synthetic frames) — null means writes go keyless, as before. */
|
|
387
387
|
readonly wakeKey: string | null;
|
|
388
|
-
/**
|
|
389
|
-
|
|
388
|
+
/**
|
|
389
|
+
* Key for an artifact write of this wake. Pass the artifact's content so the
|
|
390
|
+
* key is derived from it (ZIG-1045 semantics): a wake writing several
|
|
391
|
+
* artifacts gets several keys, and a redelivered wake that re-writes the same
|
|
392
|
+
* content reuses its key instead of colliding on an ordinal slot and being
|
|
393
|
+
* discarded. Omitting content falls back to a per-wake ordinal — call exactly
|
|
394
|
+
* once per write in that case.
|
|
395
|
+
*/
|
|
396
|
+
artifactKey(content?: unknown): string | undefined;
|
|
390
397
|
/** Key for a task state transition. Derived from (taskId, state), so it is stable regardless of tool-call order within the wake. */
|
|
391
398
|
taskStateKey(taskId: string, state: string): string | undefined;
|
|
392
399
|
/**
|
|
@@ -512,7 +519,19 @@ export interface ServicesConfig {
|
|
|
512
519
|
}
|
|
513
520
|
export interface DefineAgentInput {
|
|
514
521
|
agentId: string;
|
|
522
|
+
/**
|
|
523
|
+
* Human-facing one-liner: what this agent does, for a buyer reading the store
|
|
524
|
+
* row. NOT the system prompt — a claude-sdk agent puts its prompt in
|
|
525
|
+
* `systemPrompt` (ZIG-1080: every claude-sdk agent used to pass its prompt
|
|
526
|
+
* here, and `deriveAgentRecord` published it straight to the store).
|
|
527
|
+
*/
|
|
515
528
|
description: string;
|
|
529
|
+
/**
|
|
530
|
+
* Claude-brain only: the agent's system prompt (its `## Your role` block).
|
|
531
|
+
* Falls back to `description` when absent, which is what every FSM agent
|
|
532
|
+
* relies on. Never surfaced in the store.
|
|
533
|
+
*/
|
|
534
|
+
systemPrompt?: string;
|
|
516
535
|
specialization?: string;
|
|
517
536
|
initial: string;
|
|
518
537
|
states: Record<string, State>;
|
|
@@ -534,7 +553,10 @@ export interface AgentConfig {
|
|
|
534
553
|
anthropicKey: string | undefined;
|
|
535
554
|
operatorKey?: string;
|
|
536
555
|
agentId: string;
|
|
556
|
+
/** Store-facing one-liner. See DefineAgentInput.description. */
|
|
537
557
|
description: string;
|
|
558
|
+
/** Claude-brain system prompt; falls back to `description`. */
|
|
559
|
+
systemPrompt?: string;
|
|
538
560
|
specialization?: string;
|
|
539
561
|
tools: unknown[];
|
|
540
562
|
model: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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.204",
|
|
27
27
|
"@anthropic-ai/sdk": "^0.110.0",
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
|
-
"@ziggs-ai/api-client": "^0.
|
|
29
|
+
"@ziggs-ai/api-client": "^0.9.0",
|
|
30
30
|
"ajv": "^8.17.1",
|
|
31
31
|
"dotenv": "^17.2.3",
|
|
32
32
|
"openai": "^4.0.0",
|