@ziggs-ai/agent-sdk 0.22.0 → 0.24.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/Agent.d.ts +8 -2
- package/dist/Agent.js +232 -53
- package/dist/brain/ClaudeBrain.d.ts +1 -8
- package/dist/brain/ClaudeBrain.js +13 -42
- package/dist/brain/ZiggsBrain.d.ts +1 -1
- package/dist/brain/ZiggsBrain.js +9 -2
- package/dist/brain/claudeCognition.js +34 -9
- package/dist/brain/claudeSdkProtocol.d.ts +1 -1
- package/dist/brain/claudeSdkProtocol.js +5 -2
- package/dist/brain/claudeSdkTools.d.ts +3 -0
- package/dist/brain/claudeSdkTools.js +10 -1
- package/dist/brain/index.d.ts +9 -1
- package/dist/brain/index.js +5 -1
- package/dist/brain/toolManagerToSdkMcp.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/ingress/normalizeIncoming.d.ts +1 -0
- package/dist/ingress/normalizeIncoming.js +2 -1
- package/dist/platform/ZiggsEffectHandler.d.ts +1 -1
- package/dist/platform/ZiggsEffectHandler.js +20 -10
- package/dist/platform/inboxLoop.d.ts +4 -3
- package/dist/platform/inboxLoop.js +5 -6
- package/dist/platform/tasks/TaskService.d.ts +16 -1
- package/dist/platform/tasks/TaskService.js +22 -3
- package/dist/platform/tools/tier1/protocolRunner.js +11 -2
- package/dist/platform/tools/tier1/protocolTools.js +20 -5
- package/dist/probeChat.d.ts +12 -0
- package/dist/probeChat.js +14 -0
- package/dist/runtime/PromptBuilder.js +4 -1
- package/dist/runtime/agreementContext.d.ts +5 -0
- package/dist/runtime/agreementContext.js +75 -0
- package/dist/runtime/mandateDeclaration.d.ts +1 -1
- package/dist/runtime/mandateDeclaration.js +4 -4
- package/dist/runtime/runTurn.js +3 -2
- package/dist/runtime/wakeEffects.d.ts +5 -0
- package/dist/runtime/wakeEffects.js +14 -1
- package/dist/tools/capabilityAdapter.js +1 -0
- package/dist/types.d.ts +12 -0
- package/package.json +2 -2
|
@@ -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', 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): 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 });
|
|
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 });
|
|
@@ -74,7 +74,7 @@ export const taskSetResultTool = defineTool('task_set_result', {
|
|
|
74
74
|
description: 'Outcome payload (summary, status, links, …). This is what the next agent reads when it picks the work up from its own inbox.',
|
|
75
75
|
},
|
|
76
76
|
}, handlerFor('task_set_result'), { description: 'Transition a task to its terminal state — completed, failed, or cancelled — and record the result. Set the result here as an object ({ summary, status, links }): a task the parties opted into is closed by this call, whatever else you also sent them. Pass state (not status). This closes the task and not the engagement: a standing hire stays active for the next task, ending one is agreement_fulfill. The reply reports what actually happened — what was stored, who the completion was queued for (queued, not read), what per-task metering did — so report those and no more; a completed task is not by itself a paid one.' });
|
|
77
|
-
export const agreementCounterTool = defineTool('agreement_counter', { agreementId: { type: 'string', required: true }, price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, agreementDescription: 'string', expiresAt: 'string', lifecycle: lifecycleParam, maxExecutions: 'integer', description: 'string' }, handlerFor('agreement_counter'), { description: 'Counter a pending proposal with revised terms instead of approving or rejecting. Check status first with agreement_get.' });
|
|
77
|
+
export const agreementCounterTool = defineTool('agreement_counter', { agreementId: { type: 'string', required: true }, price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, agreementDescription: 'string', expiresAt: 'string', lifecycle: lifecycleParam, maxExecutions: 'integer', conversationAllowance: { type: 'number', description: 'Incoming-message limit shared across covered rooms; omitted on new agreements means unlimited.' }, description: 'string' }, handlerFor('agreement_counter'), { description: 'Counter a pending proposal with revised terms instead of approving or rejecting. Check status first with agreement_get.' });
|
|
78
78
|
export const agreementGetTool = defineTool('agreement_get', { agreementId: { type: 'string', required: true } }, handlerFor('agreement_get'), { description: 'Read the current proposal status for an agreement (pending, approved, rejected, countered, expired).' });
|
|
79
79
|
export const agreementRevokeTool = defineTool('agreement_revoke', { agreementId: { type: 'string', required: true } }, handlerFor('agreement_revoke'), { description: 'Revoke a pending proposal you created so it can no longer be approved. Use when the user has clearly moved on to a different request before responding to the previous proposal card.' });
|
|
80
80
|
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 and it cannot be reopened. Linked chats remain independent rooms governed by their own grants. 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.' });
|
|
@@ -85,14 +85,29 @@ export const agreementFulfillTool = defineTool('agreement_fulfill', { agreementI
|
|
|
85
85
|
// going to render — this makes the model's choice a closed set instead.
|
|
86
86
|
export const taskUpdatePlanStepTool = defineTool('task_update_plan_step', {
|
|
87
87
|
taskId: { type: 'string', required: true },
|
|
88
|
-
stepId: { type: 'string',
|
|
88
|
+
stepId: { type: 'string', description: 'The step that changed. Omit only when this call is nothing but a waitingOn.' },
|
|
89
89
|
status: {
|
|
90
90
|
type: 'string',
|
|
91
|
-
required: true,
|
|
92
91
|
enum: ['pending', 'in_progress', 'completed', 'skipped'],
|
|
92
|
+
description: 'Step progress. Required with stepId.',
|
|
93
93
|
},
|
|
94
94
|
result: 'string',
|
|
95
|
-
|
|
95
|
+
// Both fields are required, but the required list cannot be declared here:
|
|
96
|
+
// defineTool reads a sibling `required` as the flag that makes waitingOn
|
|
97
|
+
// itself mandatory. The backend enforces the pair; the description says so.
|
|
98
|
+
waitingOn: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {
|
|
101
|
+
kind: {
|
|
102
|
+
type: 'string',
|
|
103
|
+
enum: ['user', 'agent'],
|
|
104
|
+
description: 'Whether the answer has to come from a person or an agent.',
|
|
105
|
+
},
|
|
106
|
+
id: { type: 'string', description: 'Who you asked.' },
|
|
107
|
+
},
|
|
108
|
+
description: "Who this task now waits on, because you just asked them: { kind: 'user' | 'agent', id } — both fields required. Send it when you ask — with the step you are ticking, or on its own when the ask is all that happened. Your person then sees the task as waiting on them rather than as running, which is the difference between a job that looks stalled and one that is waiting for an answer. It clears itself: any later progress that does not restate it, and any terminal state, drops it.",
|
|
109
|
+
},
|
|
110
|
+
}, handlerFor('task_update_plan_step'), { description: "Update the status of a single step within a task's plan, and say who you are waiting on when you have just asked somebody. A task with no plan can still carry the waiting-on on its own." });
|
|
96
111
|
export const taskCreateTool = defineTool('task_create', { agreementId: { type: 'string', required: true }, description: { type: 'string', required: true }, parentTaskId: 'string', assigneeId: { type: 'string', description: 'Explicitly hand this task to one specific agent (e.g. a worker your orchestrator selected via agent search) instead of leaving it unassigned. Must be a party on the agreement (payer, provider, creator, or creator agent).' }, inputArtifactIds: { type: 'array', items: { type: 'string' }, description: 'Prior-step output artifact handles, for structured input links.' }, plan: { type: 'object' }, planReviewTiming: planReviewTimingParam, requireMidWorkPlanAck: 'boolean', dueAt: dueAtParam, checkBack: checkBackParam }, handlerFor('task_create'), { description: 'Create a new task under an existing approved agreement. Use when the contract is already in place. Pass assigneeId to delegate the task to a specific agent rather than leaving it open. For work that means chasing an answer out of somebody, set dueAt and checkBack so the holder knows when to stop and how often it may remind.' });
|
|
97
112
|
export const PROTOCOL_TOOLS = [
|
|
98
113
|
agreementProposeTool, agreementSubcontractTool,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One classifier for store-publish probe rooms.
|
|
3
|
+
*
|
|
4
|
+
* The runner mints `probe_<agentId>_<ts>`. An id prefix must not buy
|
|
5
|
+
* capability evidence or a canned brain reply; it only names the room so
|
|
6
|
+
* inbox pin and wake order can find it. Capability vs liveness is decided
|
|
7
|
+
* by the store sync, not by this prefix.
|
|
8
|
+
*/
|
|
9
|
+
export declare const PROBE_CHAT_ID_PREFIX = "probe_";
|
|
10
|
+
export declare function isPublishProbeChat(chatId: string | undefined | null): boolean;
|
|
11
|
+
/** @deprecated use {@link isPublishProbeChat} — same classifier, old name. */
|
|
12
|
+
export declare const isCapabilityProbeSession: typeof isPublishProbeChat;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One classifier for store-publish probe rooms.
|
|
3
|
+
*
|
|
4
|
+
* The runner mints `probe_<agentId>_<ts>`. An id prefix must not buy
|
|
5
|
+
* capability evidence or a canned brain reply; it only names the room so
|
|
6
|
+
* inbox pin and wake order can find it. Capability vs liveness is decided
|
|
7
|
+
* by the store sync, not by this prefix.
|
|
8
|
+
*/
|
|
9
|
+
export const PROBE_CHAT_ID_PREFIX = 'probe_';
|
|
10
|
+
export function isPublishProbeChat(chatId) {
|
|
11
|
+
return typeof chatId === 'string' && chatId.startsWith(PROBE_CHAT_ID_PREFIX);
|
|
12
|
+
}
|
|
13
|
+
/** @deprecated use {@link isPublishProbeChat} — same classifier, old name. */
|
|
14
|
+
export const isCapabilityProbeSession = isPublishProbeChat;
|
|
@@ -96,6 +96,8 @@ ${this._renderDecisionSchemaFromActions(actions, senderMeta, ctxObj, omitMessage
|
|
|
96
96
|
lines.push(this._renderHistory(ctxObj.history, agentId));
|
|
97
97
|
lines.push('');
|
|
98
98
|
lines.push(this._renderAgreements(agreements));
|
|
99
|
+
if (ctxObj.work)
|
|
100
|
+
lines.push(`<work-context>\n${JSON.stringify(ctxObj.work)}\n</work-context>`);
|
|
99
101
|
const collected = ctx ? allCollectedResults(ctx) : [];
|
|
100
102
|
if (collected.length) {
|
|
101
103
|
lines.push('');
|
|
@@ -177,6 +179,7 @@ ${this._renderDecisionSchemaFromActions(actions, senderMeta, ctxObj, omitMessage
|
|
|
177
179
|
if (eventType === 'message_sent') {
|
|
178
180
|
return `<event type="${eventType}" from="${from}">\nYour message was delivered. Do not repeat it.\n</event>`;
|
|
179
181
|
}
|
|
182
|
+
const agreementContext = evAny.agreementContext ? `Service agreement context: ${JSON.stringify(evAny.agreementContext)}\nTool access requires separate grants. Zero remaining messages does not prevent answering the final admitted message.\n` : '';
|
|
180
183
|
const content = event.text || event.result || event.content;
|
|
181
184
|
if (!content)
|
|
182
185
|
return `<event type="${eventType}" from="${from}">(Empty)</event>`;
|
|
@@ -184,7 +187,7 @@ ${this._renderDecisionSchemaFromActions(actions, senderMeta, ctxObj, omitMessage
|
|
|
184
187
|
// Matches MAX_MSG_CHARS in runTurn.ts — the incoming instruction shouldn't
|
|
185
188
|
// be truncated tighter than what the agent itself is allowed to send back.
|
|
186
189
|
const display = str.length > 3500 ? str.slice(0, 3500) + '...' : str;
|
|
187
|
-
return `<event type="${eventType}" from="${from}">\n${display}\n</event>`;
|
|
190
|
+
return `<event type="${eventType}" from="${from}">\n${agreementContext}${display}\n</event>`;
|
|
188
191
|
}
|
|
189
192
|
_renderTaskResultEvent(task, from) {
|
|
190
193
|
const lines = [];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Enrich each wake from the agreement it actually names, never from the room's
|
|
2
|
+
* latest deal. Reads use the agent's credentials; no grants are inferred here. */
|
|
3
|
+
export declare function withAgreementContext(event: unknown, read: (agreementId: string) => Promise<unknown>): Promise<unknown>;
|
|
4
|
+
/** Free chat supplies conversation context, never consent to another engagement. */
|
|
5
|
+
export declare function permitsAutomaticMandate(event: unknown): boolean;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const row = (value) => value && typeof value === 'object' ? value : {};
|
|
2
|
+
/** Enrich each wake from the agreement it actually names, never from the room's
|
|
3
|
+
* latest deal. Reads use the agent's credentials; no grants are inferred here. */
|
|
4
|
+
export async function withAgreementContext(event, read) {
|
|
5
|
+
const reads = new Map();
|
|
6
|
+
const context = (id) => {
|
|
7
|
+
if (!reads.has(id))
|
|
8
|
+
reads.set(id, (async () => {
|
|
9
|
+
try {
|
|
10
|
+
let timer;
|
|
11
|
+
const agreement = row(await Promise.race([
|
|
12
|
+
read(id),
|
|
13
|
+
new Promise((_, reject) => {
|
|
14
|
+
timer = setTimeout(() => reject(new Error('Agreement read timed out')), 5_000);
|
|
15
|
+
}),
|
|
16
|
+
]).finally(() => { if (timer)
|
|
17
|
+
clearTimeout(timer); }));
|
|
18
|
+
if (agreement.agreementId !== id)
|
|
19
|
+
throw new Error('Agreement is unavailable');
|
|
20
|
+
const terms = row(agreement.terms);
|
|
21
|
+
const usage = row(agreement.conversationUsage);
|
|
22
|
+
const money = row(agreement.money);
|
|
23
|
+
const limit = typeof terms.conversationAllowance === 'number' ? terms.conversationAllowance : null;
|
|
24
|
+
const used = typeof usage.messages === 'number' ? usage.messages : 0;
|
|
25
|
+
return {
|
|
26
|
+
agreementId: id, available: true,
|
|
27
|
+
freeConversation: row(agreement.metadata).freeConversation === true,
|
|
28
|
+
status: agreement.status,
|
|
29
|
+
relationship: agreement.engagementKind === 'link' || row(agreement.metadata).freeConversation === true,
|
|
30
|
+
interactionMode: terms.interactionMode ?? 'both',
|
|
31
|
+
parentAgreementId: agreement.parentAgreementId ?? null,
|
|
32
|
+
allowanceScope: terms.conversationAllowanceScope ?? 'direct',
|
|
33
|
+
scope: terms.description,
|
|
34
|
+
lifecycle: terms.lifecycle,
|
|
35
|
+
expiresAt: terms.expiresAt ?? null,
|
|
36
|
+
maxTasks: terms.maxExecutions ?? null,
|
|
37
|
+
wakeCeiling: terms.wakeCeiling ?? null,
|
|
38
|
+
messageLimit: limit,
|
|
39
|
+
messagesUsed: used,
|
|
40
|
+
messagesRemaining: limit === null ? null : Math.max(0, limit - used),
|
|
41
|
+
price: money.price ?? 0,
|
|
42
|
+
billing: terms.billing ?? 'total',
|
|
43
|
+
paymentStatus: money.paymentStatus,
|
|
44
|
+
access: 'Tool and resource access requires separate grants.',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { agreementId: id, available: false,
|
|
49
|
+
instruction: 'Agreement terms could not be read. Verify authorization before starting new service work or making commitments.' };
|
|
50
|
+
}
|
|
51
|
+
})());
|
|
52
|
+
return reads.get(id);
|
|
53
|
+
};
|
|
54
|
+
const enrich = async (value) => {
|
|
55
|
+
const frame = row(value);
|
|
56
|
+
if (frame.type === 'batch' && Array.isArray(frame.events)) {
|
|
57
|
+
return { ...frame, events: await Promise.all(frame.events.map(enrich)) };
|
|
58
|
+
}
|
|
59
|
+
const resource = row(frame.resource);
|
|
60
|
+
const result = row(frame.result);
|
|
61
|
+
const id = frame.agreementId ?? resource.agreementId ?? result.agreementId ?? row(result.agreement).agreementId;
|
|
62
|
+
if (typeof id !== 'string' || !id)
|
|
63
|
+
return value;
|
|
64
|
+
return { ...frame, agreementContext: await context(id) };
|
|
65
|
+
};
|
|
66
|
+
return enrich(event);
|
|
67
|
+
}
|
|
68
|
+
/** Free chat supplies conversation context, never consent to another engagement. */
|
|
69
|
+
export function permitsAutomaticMandate(event) {
|
|
70
|
+
const frame = row(event);
|
|
71
|
+
if (frame.type === 'batch' && Array.isArray(frame.events))
|
|
72
|
+
return frame.events.every(permitsAutomaticMandate);
|
|
73
|
+
const context = row(frame.agreementContext);
|
|
74
|
+
return context.relationship !== true && context.freeConversation !== true && context.available !== false;
|
|
75
|
+
}
|
|
@@ -6,4 +6,4 @@ import type { ToolManager } from '../tools/ToolManager.js';
|
|
|
6
6
|
* declaration, the model already made one, or this wake is not running inside a
|
|
7
7
|
* job at all.
|
|
8
8
|
*/
|
|
9
|
-
export declare function withDeclaredMandate(args: Record<string, unknown>, toolName: string | null | undefined, toolManager: ToolManager | undefined, activeAgreementId: string | null | undefined): Record<string, unknown>;
|
|
9
|
+
export declare function withDeclaredMandate(args: Record<string, unknown>, toolName: string | null | undefined, toolManager: ToolManager | undefined, activeAgreementId: string | null | undefined, allowSpendingMandate?: boolean): Record<string, unknown>;
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
*/
|
|
31
31
|
const MANDATE_PARAM_NAMES = ['mandateAgreementId', 'parentAgreementId'];
|
|
32
32
|
/** The mandate parameter this tool declares, if it declares one. */
|
|
33
|
-
function mandateParamOf(toolName, toolManager) {
|
|
33
|
+
function mandateParamOf(toolName, toolManager, allowSpendingMandate = true) {
|
|
34
34
|
if (!toolName)
|
|
35
35
|
return null;
|
|
36
36
|
const schema = toolManager?.getTool?.(toolName)?.schema;
|
|
37
37
|
const properties = schema?.function?.parameters?.properties;
|
|
38
38
|
if (!properties)
|
|
39
39
|
return null;
|
|
40
|
-
for (const name of MANDATE_PARAM_NAMES) {
|
|
40
|
+
for (const name of (allowSpendingMandate ? MANDATE_PARAM_NAMES : ['parentAgreementId'])) {
|
|
41
41
|
if (Object.prototype.hasOwnProperty.call(properties, name))
|
|
42
42
|
return name;
|
|
43
43
|
}
|
|
@@ -50,10 +50,10 @@ function mandateParamOf(toolName, toolManager) {
|
|
|
50
50
|
* declaration, the model already made one, or this wake is not running inside a
|
|
51
51
|
* job at all.
|
|
52
52
|
*/
|
|
53
|
-
export function withDeclaredMandate(args, toolName, toolManager, activeAgreementId) {
|
|
53
|
+
export function withDeclaredMandate(args, toolName, toolManager, activeAgreementId, allowSpendingMandate = true) {
|
|
54
54
|
if (!activeAgreementId)
|
|
55
55
|
return args;
|
|
56
|
-
const param = mandateParamOf(toolName, toolManager);
|
|
56
|
+
const param = mandateParamOf(toolName, toolManager, allowSpendingMandate);
|
|
57
57
|
if (!param)
|
|
58
58
|
return args;
|
|
59
59
|
if (args[param] !== undefined)
|
package/dist/runtime/runTurn.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { permitsAutomaticMandate } from './agreementContext.js';
|
|
1
2
|
import { isAgreementId } from '@ziggs-ai/api-client';
|
|
2
3
|
import { outcomeFromActionResult } from './buildOutcome.js';
|
|
3
4
|
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
@@ -267,7 +268,7 @@ export async function runTurn(input) {
|
|
|
267
268
|
catch { }
|
|
268
269
|
// Say which job this is part of, when the tool asks and the model did
|
|
269
270
|
// not. The runtime knows; the model would have to be told.
|
|
270
|
-
args = withDeclaredMandate(args, toolName, toolManager, ctx.activeAgreementId);
|
|
271
|
+
args = withDeclaredMandate(args, toolName, toolManager, ctx.activeAgreementId, permitsAutomaticMandate(incomingEvent));
|
|
271
272
|
const decision = { action: matchingAction, tool: toolName, args };
|
|
272
273
|
const result = await executeDecision(decision, actionDef, effWithTrace, sessionId, memory, incomingEvent, addToolUsage, ctx.identity.agentId, Array.isArray(ctxObj.agreements)
|
|
273
274
|
? ctxObj.agreements
|
|
@@ -324,7 +325,7 @@ export async function runTurn(input) {
|
|
|
324
325
|
// Same mandate declaration as the tool-call path above. Both routes reach
|
|
325
326
|
// the same tools, so filling it on only one would make the excuse depend on
|
|
326
327
|
// which shape the model happened to answer in.
|
|
327
|
-
decision.args = withDeclaredMandate((decision.args ?? {}), actionDef?.tool, toolManager, ctx.activeAgreementId);
|
|
328
|
+
decision.args = withDeclaredMandate((decision.args ?? {}), actionDef?.tool, toolManager, ctx.activeAgreementId, permitsAutomaticMandate(incomingEvent));
|
|
328
329
|
if (decision.thought)
|
|
329
330
|
await emitThought(decision.thought);
|
|
330
331
|
// 2-call streaming: if this is a respond action (has receiverId, no bound tool),
|
|
@@ -7,6 +7,11 @@ export interface WorkScope {
|
|
|
7
7
|
export declare function uniqueWorkScope(scopes: WorkScope[]): WorkScope;
|
|
8
8
|
/** Only structured host/backend fields are authority; message text and timer reasons are not. */
|
|
9
9
|
export declare function workScopeFromWake(event: unknown, sessionId?: string): WorkScope;
|
|
10
|
+
/** One packet per distinct work item in a batch; never union their authority. */
|
|
11
|
+
export declare function contextScopesFromWake(event: unknown, sessionId: string): Array<WorkScope & {
|
|
12
|
+
messageId?: string;
|
|
13
|
+
artifactId?: string;
|
|
14
|
+
}>;
|
|
10
15
|
/** Per-wake observation, including effects nested inside tools and cross-room sends. */
|
|
11
16
|
export declare class WakeEffects {
|
|
12
17
|
private readonly initialScope;
|
|
@@ -25,9 +25,22 @@ export function workScopeFromWake(event, sessionId) {
|
|
|
25
25
|
const agreementId = id(rec.agreementId) ?? id(resource.agreementId) ?? id(task.agreementId)
|
|
26
26
|
?? agreementIdFromLane(rec.chatId) ?? agreementIdFromLane(resource.chatId)
|
|
27
27
|
?? agreementIdFromLane(sessionId) ?? undefined;
|
|
28
|
-
const taskId = id(rec.taskId) ?? id(resource.taskId) ?? id(task.taskId);
|
|
28
|
+
const taskId = id(rec.taskId) ?? id(resource.taskId) ?? id(task.taskId) ?? (resource.kind === 'task' ? id(resource.resourceId) : undefined);
|
|
29
29
|
return { ...(agreementId ? { agreementId } : {}), ...(taskId ? { taskId } : {}) };
|
|
30
30
|
}
|
|
31
|
+
/** One packet per distinct work item in a batch; never union their authority. */
|
|
32
|
+
export function contextScopesFromWake(event, sessionId) {
|
|
33
|
+
const frame = record(event);
|
|
34
|
+
if (frame.type === 'batch' && Array.isArray(frame.events)) {
|
|
35
|
+
return [...new Map(frame.events.flatMap(e => contextScopesFromWake(e, sessionId)).map(scope => [JSON.stringify(scope), scope])).values()];
|
|
36
|
+
}
|
|
37
|
+
const scope = workScopeFromWake(event, sessionId);
|
|
38
|
+
const messageId = frame.type === 'message' ? id(frame.messageId) : undefined;
|
|
39
|
+
const resource = record(frame.resource);
|
|
40
|
+
const artifactId = resource.kind === 'artifact' ? id(resource.resourceId) : undefined;
|
|
41
|
+
return scope.agreementId || scope.taskId || messageId || artifactId
|
|
42
|
+
? [{ ...scope, ...(messageId ? { messageId } : {}), ...(artifactId ? { artifactId } : {}) }] : [];
|
|
43
|
+
}
|
|
31
44
|
const TASK_WRITES = new Set(['task_create', 'task_set_result', 'task_update_plan_step', 'task_replace_plan']);
|
|
32
45
|
/** Per-wake observation, including effects nested inside tools and cross-room sends. */
|
|
33
46
|
export class WakeEffects {
|
|
@@ -35,6 +35,7 @@ export function toolFromCapability(cap) {
|
|
|
35
35
|
baseUrl: process.env.BACKEND_URL || process.env.ZIGGS_BACKEND_URL || undefined,
|
|
36
36
|
webUrl: process.env.ZIGGS_WEB_URL,
|
|
37
37
|
surface: 'sdk',
|
|
38
|
+
serviceAgreementId: typeof ctx.serviceAgreementId === 'string' ? ctx.serviceAgreementId : undefined,
|
|
38
39
|
};
|
|
39
40
|
return cap.handler(args, env);
|
|
40
41
|
}, { description: cap.descriptions.sdk, ...(cap.sdkOptions ?? {}) });
|
package/dist/types.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Every state — parked or thinking — re-enters with exactly one Outcome.
|
|
6
6
|
*/
|
|
7
7
|
import type { AgreementParties } from '@ziggs-ai/api-client';
|
|
8
|
+
import type { ContextSnapshotResult } from '@ziggs-ai/api-client/http';
|
|
8
9
|
import type { AgentNeed } from './tools/needs.js';
|
|
9
10
|
export interface AgreementRef {
|
|
10
11
|
agreementId: string;
|
|
@@ -350,6 +351,7 @@ export interface SnapshotParticipant {
|
|
|
350
351
|
export interface ContextSnapshot {
|
|
351
352
|
history: HistoryEntry[];
|
|
352
353
|
agreements: unknown[];
|
|
354
|
+
artifacts?: ContextSnapshotResult['artifacts'];
|
|
353
355
|
agents: SnapshotParticipant[];
|
|
354
356
|
users: SnapshotParticipant[];
|
|
355
357
|
timelineSummary?: string | null;
|
|
@@ -368,6 +370,12 @@ export interface ContextSnapshot {
|
|
|
368
370
|
[key: string]: unknown;
|
|
369
371
|
}
|
|
370
372
|
export interface ReadContextOpts {
|
|
373
|
+
workScopes?: Array<{
|
|
374
|
+
agreementId?: string;
|
|
375
|
+
taskId?: string;
|
|
376
|
+
messageId?: string;
|
|
377
|
+
artifactId?: string;
|
|
378
|
+
}>;
|
|
371
379
|
maxMessages?: number;
|
|
372
380
|
}
|
|
373
381
|
/**
|
|
@@ -473,6 +481,8 @@ export type Effect = {
|
|
|
473
481
|
} | {
|
|
474
482
|
kind: 'tool-call';
|
|
475
483
|
sessionId: string;
|
|
484
|
+
/** Exact work scope from this wake, independent of connection delegation. */
|
|
485
|
+
serviceAgreementId?: string;
|
|
476
486
|
name: string;
|
|
477
487
|
args: unknown;
|
|
478
488
|
memory?: MemoryStore;
|
|
@@ -504,11 +514,13 @@ export type Effect = {
|
|
|
504
514
|
receiverId?: string;
|
|
505
515
|
text: string;
|
|
506
516
|
messageId?: string;
|
|
517
|
+
conversationAgreementId?: string;
|
|
507
518
|
} | {
|
|
508
519
|
kind: 'record-event';
|
|
509
520
|
sessionId: string;
|
|
510
521
|
entry: RecordedEntry;
|
|
511
522
|
idempotencyKey?: string;
|
|
523
|
+
serviceAgreementId?: string;
|
|
512
524
|
};
|
|
513
525
|
/**
|
|
514
526
|
* Typed result map. With this shape, `await eff({ kind: 'read-context', .. })`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.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.23.0",
|
|
30
30
|
"ajv": "^8.17.1",
|
|
31
31
|
"dotenv": "^17.2.3",
|
|
32
32
|
"openai": "^4.0.0",
|