@ziggs-ai/agent-sdk 0.13.0 → 0.14.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/claudeCognition.js +12 -0
- package/dist/agent/claudeSdkTools.js +5 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/pricing/fleetDefaults.d.ts +0 -1
- package/dist/pricing/fleetDefaults.js +7 -12
- package/dist/runtime/kickoff.d.ts +17 -4
- package/dist/runtime/kickoff.js +55 -15
- package/dist/server/agreements/AgreementService.d.ts +1 -3
- package/dist/server/agreements/AgreementService.js +1 -7
- package/dist/server/inboxLoop.js +19 -0
- package/dist/server/tools/tier1/protocolTools.js +1 -1
- package/dist/tools/capabilityAdapter.js +10 -7
- package/dist/tools/toolCreds.d.ts +37 -0
- package/dist/tools/toolCreds.js +49 -0
- package/package.json +2 -2
|
@@ -151,6 +151,18 @@ function renderEventLines(event, lines) {
|
|
|
151
151
|
lines.push(`Agreement: ${agreementId}`);
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
+
// The other side of the same policy: a message this agent sent to a
|
|
155
|
+
// published agent landed and was NOT rung through. Nothing is wrong and
|
|
156
|
+
// nothing is lost, so this must not read as a refusal — but without it the
|
|
157
|
+
// sender waits on a reply that is never coming and its rational next move
|
|
158
|
+
// is to send again.
|
|
159
|
+
if (reason === 'contact_policy_withheld') {
|
|
160
|
+
lines.push('Your message was delivered and is readable, but the agent you wrote ' +
|
|
161
|
+
'to is not woken for it: its listing is claim-first, so it starts ' +
|
|
162
|
+
'work once you claim the listing. Do not resend — claim the listing ' +
|
|
163
|
+
'(ziggs_marketplace_view to find it, then ziggs_agreement_claim), or ' +
|
|
164
|
+
'wait if somebody else is claiming on your behalf.');
|
|
165
|
+
}
|
|
154
166
|
}
|
|
155
167
|
if (typeof event?.agreementId === 'string' && event.agreementId) {
|
|
156
168
|
lines.push(`Agreement: ${event.agreementId}`);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { laneFromToolContext } from '../tools/toolCreds.js';
|
|
2
3
|
import { listTasks } from '@ziggs-ai/api-client';
|
|
3
4
|
import { defineTool } from '../tools/defineTool.js';
|
|
4
5
|
import { listGrantsTool } from '../server/tools/tier2/grantsTools.js';
|
|
@@ -177,7 +178,10 @@ export const listTasksTool = defineTool('task_list', {
|
|
|
177
178
|
const createdBy = args.createdByMe ? c.agentId : undefined;
|
|
178
179
|
// the lane fences this to the engagement's tasks. Without it the
|
|
179
180
|
// list is every task the agent's key can reach, across all its customers.
|
|
180
|
-
|
|
181
|
+
// One reader for the lane, shared with the capability adapter and with
|
|
182
|
+
// hand-written tools — three copies of `chatId || sessionId` is how one of
|
|
183
|
+
// them ends up not claiming the reach the fence exists to grant.
|
|
184
|
+
const laneId = laneFromToolContext(c);
|
|
181
185
|
return listTasks({
|
|
182
186
|
state: args.state,
|
|
183
187
|
cursor: args.cursor,
|
package/dist/index.d.ts
CHANGED
|
@@ -53,6 +53,7 @@ export { ToolManager } from './tools/ToolManager.js';
|
|
|
53
53
|
export { ToolProvider } from './tools/ToolProvider.js';
|
|
54
54
|
export { defineTool } from './tools/defineTool.js';
|
|
55
55
|
export { recordReport } from './tools/recordReport.js';
|
|
56
|
+
export { credsFromToolContext, laneFromToolContext } from './tools/toolCreds.js';
|
|
56
57
|
export type { RecordReportInput, RecordReportResult, TaskReport } from './tools/recordReport.js';
|
|
57
58
|
export { MCP_PROVIDER, describeNeed, normalizeNeeds, toolsForNeed, toolsForNeeds, unmetNeeds, unmetNeedsMessage, } from './tools/needs.js';
|
|
58
59
|
export type { AgentNeed, ConnectionNeed } from './tools/needs.js';
|
package/dist/index.js
CHANGED
|
@@ -55,6 +55,7 @@ export { ToolManager } from './tools/ToolManager.js';
|
|
|
55
55
|
export { ToolProvider } from './tools/ToolProvider.js';
|
|
56
56
|
export { defineTool } from './tools/defineTool.js';
|
|
57
57
|
export { recordReport } from './tools/recordReport.js';
|
|
58
|
+
export { credsFromToolContext, laneFromToolContext } from './tools/toolCreds.js';
|
|
58
59
|
// An agent declares the access it cannot work without, and the declaration
|
|
59
60
|
// produces the tools for it.
|
|
60
61
|
export { MCP_PROVIDER, describeNeed, normalizeNeeds, toolsForNeed, toolsForNeeds, unmetNeeds, unmetNeedsMessage, } from './tools/needs.js';
|
|
@@ -8,7 +8,6 @@ export declare const TIER_PRICE_CENTS: Record<FleetPricingTier, number>;
|
|
|
8
8
|
export declare function resolveFleetTier(agentId: string): FleetPricingTier | null;
|
|
9
9
|
export declare function isFirstPartyFleetAgent(agentId: string): boolean;
|
|
10
10
|
export declare function tierPriceCents(tier: FleetPricingTier): number;
|
|
11
|
-
export declare function isZiggsOrchestrator(agentId: string): boolean;
|
|
12
11
|
export declare function isFreeAllowlistedExecutor(executorId: string, description?: string | null): boolean;
|
|
13
12
|
export declare function resolveDefaultPriceCentsForExecutor(executorId: string, description?: string | null): number | null;
|
|
14
13
|
export interface ProtocolPricingPayload {
|
|
@@ -12,7 +12,6 @@ export const TIER_PRICE_CENTS = {
|
|
|
12
12
|
C: 1500, // 15 ZIG / job (orchestrator, charged once at top)
|
|
13
13
|
D: 100, // 1 ZIG / call (hand-written demo agents)
|
|
14
14
|
};
|
|
15
|
-
const ZIGGS_ORCHESTRATOR_IDS = new Set(['ziggs-agent']);
|
|
16
15
|
const PING_DESCRIPTION_PATTERN = /\b(ping|health\s*check|healthcheck|liveness|readiness|heartbeat)\b/i;
|
|
17
16
|
const TRIVIAL_LOOKUP_DESCRIPTION_PATTERN = /\b(what\s+time|current\s+time|local\s+time|world\s+clock|timezone|time\s+zone|weather|forecast|temperature|uv\s+index)\b/i;
|
|
18
17
|
export function resolveFleetTier(agentId) {
|
|
@@ -26,9 +25,6 @@ export function isFirstPartyFleetAgent(agentId) {
|
|
|
26
25
|
export function tierPriceCents(tier) {
|
|
27
26
|
return TIER_PRICE_CENTS[tier];
|
|
28
27
|
}
|
|
29
|
-
export function isZiggsOrchestrator(agentId) {
|
|
30
|
-
return ZIGGS_ORCHESTRATOR_IDS.has(agentId);
|
|
31
|
-
}
|
|
32
28
|
export function isFreeAllowlistedExecutor(executorId, description) {
|
|
33
29
|
if (!executorId)
|
|
34
30
|
return false;
|
|
@@ -82,14 +78,13 @@ export function applyFleetAgreementPricing(operation, payload, actingAgentId) {
|
|
|
82
78
|
}
|
|
83
79
|
const proposedTo = next.proposedTo != null ? String(next.proposedTo) : '';
|
|
84
80
|
const providerId = next.providerId != null ? String(next.providerId) : '';
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// Tier C orchestrator proposing scope to a user (non-ziggs fleet orchestrators).
|
|
81
|
+
// A tier C orchestrator proposing scope to a user — 15 ZIG once at top.
|
|
82
|
+
//
|
|
83
|
+
// The ziggs delegate used to have its own branch above this one, keyed on a
|
|
84
|
+
// hardcoded id. It asked the same question with the same answer — that agent
|
|
85
|
+
// is tier C, so this line already priced it. A second way of asking the tier
|
|
86
|
+
// question the tier had already answered, which is the same shape that was
|
|
87
|
+
// removed from the backend copy.
|
|
93
88
|
const actingTier = resolveFleetTier(actingAgentId);
|
|
94
89
|
if (actingTier === 'C' && proposedTo && !isFirstPartyFleetAgent(proposedTo)) {
|
|
95
90
|
next.price = TIER_PRICE_CENTS.C;
|
|
@@ -11,15 +11,28 @@ export declare function buildKickoffState(initial: string): ThinkingState;
|
|
|
11
11
|
export declare function buildClaimKickoffState(initial: string): ThinkingState;
|
|
12
12
|
/**
|
|
13
13
|
* Agreement-kickoff defaults. For every state whose transitions do not already
|
|
14
|
-
*
|
|
15
|
-
* state.
|
|
16
|
-
* (first match wins in AgentMachine._evaluateTransitions). Returns a NEW
|
|
17
|
-
* workflow — input states are not mutated.
|
|
14
|
+
* handle the corresponding probe, add a transition to the injected kickoff
|
|
15
|
+
* state. Returns a NEW workflow — input states are not mutated.
|
|
18
16
|
*
|
|
19
17
|
* Two kickoffs:
|
|
20
18
|
* - `kickingOff` — proposer-side approval → task_create
|
|
21
19
|
* - `claimKickingOff` — store-claim provider wake → chat_open + greet
|
|
22
20
|
* Direct-proposal counterparties (lifecycle approve, !proposedByMe, no
|
|
23
21
|
* fromClaimWake) still never auto-kickoff.
|
|
22
|
+
*
|
|
23
|
+
* **They are ordered differently on purpose** (first match wins in
|
|
24
|
+
* AgentMachine._evaluateTransitions):
|
|
25
|
+
*
|
|
26
|
+
* - `kickingOff` is appended LAST. It fires on an approval of a proposal THIS
|
|
27
|
+
* agent made, which the author was already expecting — whatever route they
|
|
28
|
+
* wrote for it should keep winning.
|
|
29
|
+
* - `claimKickingOff` is inserted FIRST. It fires on a wake the author never
|
|
30
|
+
* wrote a route for, and it has to beat the unconditional fallthrough an
|
|
31
|
+
* `idle` state uses to reach its listening loop — otherwise the claim is
|
|
32
|
+
* swallowed into the idle loop and the buyer is never greeted. Going first
|
|
33
|
+
* is safe because its `when` is narrow: only a `fromClaimWake` approve
|
|
34
|
+
* matches, so nothing else is shadowed. An author who wants the claim for
|
|
35
|
+
* themselves keys on `fromClaimWake`, which suppresses the injection
|
|
36
|
+
* outright (stateHandlesClaimKickoff).
|
|
24
37
|
*/
|
|
25
38
|
export declare function injectKickoffDefault(workflow: Workflow): Workflow;
|
package/dist/runtime/kickoff.js
CHANGED
|
@@ -25,6 +25,18 @@ const PROBE_CLAIM_APPROVE = {
|
|
|
25
25
|
proposedByMe: false,
|
|
26
26
|
fromClaimWake: true,
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* A bare counterparty approve — someone approved a proposal we did not make,
|
|
30
|
+
* with no store claim behind it. Used as a NEGATIVE probe: a transition that
|
|
31
|
+
* matches this as well as PROBE_CLAIM_APPROVE is a generic proposal-resolved
|
|
32
|
+
* route, not a deliberate claim handler, so it does not suppress injection.
|
|
33
|
+
*/
|
|
34
|
+
const PROBE_BARE_APPROVE = {
|
|
35
|
+
kind: 'proposal-resolved',
|
|
36
|
+
action: 'approve',
|
|
37
|
+
proposal: { agreementId: '__bare_approve_probe__' },
|
|
38
|
+
proposedByMe: false,
|
|
39
|
+
};
|
|
28
40
|
const KICKOFF_TRANSITION = {
|
|
29
41
|
to: KICKOFF_STATE,
|
|
30
42
|
when: (o, ctx) => o.kind === 'proposal-resolved' &&
|
|
@@ -69,17 +81,32 @@ function stateHandlesProposerApproval(state) {
|
|
|
69
81
|
}
|
|
70
82
|
});
|
|
71
83
|
}
|
|
84
|
+
function matches(t, probe) {
|
|
85
|
+
if (!t.when)
|
|
86
|
+
return false;
|
|
87
|
+
try {
|
|
88
|
+
return !!t.when(probe, PROBE_CTX);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A state handles the claim kickoff itself only when it says so EXPLICITLY —
|
|
96
|
+
* a transition that fires for a store-claim wake and does NOT also fire for a
|
|
97
|
+
* bare counterparty approve. In practice that means keying on `fromClaimWake`.
|
|
98
|
+
*
|
|
99
|
+
* Two weaker rules read as "handled" when nothing was. A when-less fallthrough
|
|
100
|
+
* counted, so a state shaped `idle → listening` never got the transition
|
|
101
|
+
* injected and routed every claim into its idle loop; and a `when: o => o.kind
|
|
102
|
+
* === 'proposal-resolved'` route counted too, because the claim probe is also a
|
|
103
|
+
* proposal-resolved/approve. Neither author had written anything about claims.
|
|
104
|
+
* Requiring the negative probe to MISS is what tells a deliberate claim handler
|
|
105
|
+
* apart from a generic one, and the unconditional case is safe to inject over
|
|
106
|
+
* because the claim transition goes in FIRST — see injectKickoffDefault.
|
|
107
|
+
*/
|
|
72
108
|
function stateHandlesClaimKickoff(state) {
|
|
73
|
-
return state.transitions.some(t =>
|
|
74
|
-
if (!t.when)
|
|
75
|
-
return true;
|
|
76
|
-
try {
|
|
77
|
-
return !!t.when(PROBE_CLAIM_APPROVE, PROBE_CTX);
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
});
|
|
109
|
+
return state.transitions.some(t => matches(t, PROBE_CLAIM_APPROVE) && !matches(t, PROBE_BARE_APPROVE));
|
|
83
110
|
}
|
|
84
111
|
export function buildKickoffState(initial) {
|
|
85
112
|
const dflt = thinkingDefaults({ initial });
|
|
@@ -161,16 +188,29 @@ export function buildClaimKickoffState(initial) {
|
|
|
161
188
|
}
|
|
162
189
|
/**
|
|
163
190
|
* Agreement-kickoff defaults. For every state whose transitions do not already
|
|
164
|
-
*
|
|
165
|
-
* state.
|
|
166
|
-
* (first match wins in AgentMachine._evaluateTransitions). Returns a NEW
|
|
167
|
-
* workflow — input states are not mutated.
|
|
191
|
+
* handle the corresponding probe, add a transition to the injected kickoff
|
|
192
|
+
* state. Returns a NEW workflow — input states are not mutated.
|
|
168
193
|
*
|
|
169
194
|
* Two kickoffs:
|
|
170
195
|
* - `kickingOff` — proposer-side approval → task_create
|
|
171
196
|
* - `claimKickingOff` — store-claim provider wake → chat_open + greet
|
|
172
197
|
* Direct-proposal counterparties (lifecycle approve, !proposedByMe, no
|
|
173
198
|
* fromClaimWake) still never auto-kickoff.
|
|
199
|
+
*
|
|
200
|
+
* **They are ordered differently on purpose** (first match wins in
|
|
201
|
+
* AgentMachine._evaluateTransitions):
|
|
202
|
+
*
|
|
203
|
+
* - `kickingOff` is appended LAST. It fires on an approval of a proposal THIS
|
|
204
|
+
* agent made, which the author was already expecting — whatever route they
|
|
205
|
+
* wrote for it should keep winning.
|
|
206
|
+
* - `claimKickingOff` is inserted FIRST. It fires on a wake the author never
|
|
207
|
+
* wrote a route for, and it has to beat the unconditional fallthrough an
|
|
208
|
+
* `idle` state uses to reach its listening loop — otherwise the claim is
|
|
209
|
+
* swallowed into the idle loop and the buyer is never greeted. Going first
|
|
210
|
+
* is safe because its `when` is narrow: only a `fromClaimWake` approve
|
|
211
|
+
* matches, so nothing else is shadowed. An author who wants the claim for
|
|
212
|
+
* themselves keys on `fromClaimWake`, which suppresses the injection
|
|
213
|
+
* outright (stateHandlesClaimKickoff).
|
|
174
214
|
*/
|
|
175
215
|
export function injectKickoffDefault(workflow) {
|
|
176
216
|
if (workflow.states[KICKOFF_STATE]) {
|
|
@@ -189,7 +229,7 @@ export function injectKickoffDefault(workflow) {
|
|
|
189
229
|
injectedProposer = true;
|
|
190
230
|
}
|
|
191
231
|
if (!stateHandlesClaimKickoff(state)) {
|
|
192
|
-
transitions.
|
|
232
|
+
transitions.unshift(CLAIM_KICKOFF_TRANSITION);
|
|
193
233
|
injectedClaim = true;
|
|
194
234
|
}
|
|
195
235
|
states[name] =
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Agreement, type ClaimedKind, type ClaimOptions, type ProposeAgreementData, type ProposeDirectInput, type ProposeBroadcastInput, type PublishOfferPayload, type DelegateAgreementData, type CounterAgreementData, type CreateLinkBody, type CreateLinkResult, type ListAgreementsFilters, type GetMyAgreementsFilters, type ChatLinkType, type ArtifactLinkType
|
|
1
|
+
import { type Agreement, type ClaimedKind, type ClaimOptions, type ProposeAgreementData, type ProposeDirectInput, type ProposeBroadcastInput, type PublishOfferPayload, type DelegateAgreementData, type CounterAgreementData, type CreateLinkBody, type CreateLinkResult, type ListAgreementsFilters, type GetMyAgreementsFilters, type ChatLinkType, type ArtifactLinkType } from '@ziggs-ai/api-client';
|
|
2
2
|
export declare class AgreementService {
|
|
3
3
|
private creds;
|
|
4
4
|
constructor(operatorKey: string, agentId: string);
|
|
@@ -70,6 +70,4 @@ export declare class AgreementService {
|
|
|
70
70
|
getChats(agreementId: string): Promise<unknown[]>;
|
|
71
71
|
linkArtifact(agreementId: string, artifactId: string, linkType?: ArtifactLinkType): Promise<unknown | null>;
|
|
72
72
|
getArtifacts(agreementId: string): Promise<unknown[]>;
|
|
73
|
-
linkUser(agreementId: string, userId: string, role: UserRole): Promise<unknown | null>;
|
|
74
|
-
getUsers(agreementId: string): Promise<unknown[]>;
|
|
75
73
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { proposeAgreement, proposeBroadcast as proposeBroadcastApi, delegateAgreement, respondToAgreement, counterAgreement, claimOpenAgreement, getAgreementStatus, listAgreements, getMyAgreements, getAgreement, publishOffer as publishOfferApi, createLink, revokeAgreement, fulfillAgreement, linkAgreementToChat, getChatsForAgreement, linkArtifactToAgreement, getArtifactsForAgreement,
|
|
1
|
+
import { proposeAgreement, proposeBroadcast as proposeBroadcastApi, delegateAgreement, respondToAgreement, counterAgreement, claimOpenAgreement, getAgreementStatus, listAgreements, getMyAgreements, getAgreement, publishOffer as publishOfferApi, createLink, revokeAgreement, fulfillAgreement, linkAgreementToChat, getChatsForAgreement, linkArtifactToAgreement, getArtifactsForAgreement, } from '@ziggs-ai/api-client';
|
|
2
2
|
export class AgreementService {
|
|
3
3
|
creds;
|
|
4
4
|
constructor(operatorKey, agentId) {
|
|
@@ -107,10 +107,4 @@ export class AgreementService {
|
|
|
107
107
|
getArtifacts(agreementId) {
|
|
108
108
|
return getArtifactsForAgreement(agreementId, this.creds);
|
|
109
109
|
}
|
|
110
|
-
linkUser(agreementId, userId, role) {
|
|
111
|
-
return linkUserToAgreement(agreementId, userId, role, this.creds);
|
|
112
|
-
}
|
|
113
|
-
getUsers(agreementId) {
|
|
114
|
-
return getUsersForAgreement(agreementId, this.creds);
|
|
115
|
-
}
|
|
116
110
|
}
|
package/dist/server/inboxLoop.js
CHANGED
|
@@ -8,6 +8,20 @@ function log(label, msg) {
|
|
|
8
8
|
function warn(label, msg) {
|
|
9
9
|
runtimeLog.warn(label, msg);
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Standing proposals share the merged inbox visibility model with deliveries,
|
|
13
|
+
* but older envelopes predate an explicit assignee stamp. Prefer the server's
|
|
14
|
+
* role-to-actor assignment. On an older server, wake only for an agent-keyed
|
|
15
|
+
* approval/responder slot — withholding a wake is safer than waking every
|
|
16
|
+
* sibling merely because they share an org principal.
|
|
17
|
+
*/
|
|
18
|
+
function proposalIsMine(proposal, ownAgentId) {
|
|
19
|
+
if (proposal.assignedAgentId !== undefined) {
|
|
20
|
+
return proposal.assignedAgentId === ownAgentId;
|
|
21
|
+
}
|
|
22
|
+
return (proposal.pendingApprovalPartyIds?.includes(ownAgentId) === true ||
|
|
23
|
+
proposal.proposedTo === ownAgentId);
|
|
24
|
+
}
|
|
11
25
|
/**
|
|
12
26
|
* Read the messages behind one chat's deliveries and hand them to the host.
|
|
13
27
|
*
|
|
@@ -203,6 +217,11 @@ export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
|
|
|
203
217
|
// Pending decisions are standing state, not deliveries: they keep appearing
|
|
204
218
|
// until answered, so dedup them the same way tasks are deduped.
|
|
205
219
|
for (const proposal of envelope.proposalsAwaitingMe) {
|
|
220
|
+
// A proposal may be visible because this agent shares the accountable
|
|
221
|
+
// principal's mailbox. Only the role-designated side actor wakes; ambient
|
|
222
|
+
// sibling proposals remain available as context without creating a lane.
|
|
223
|
+
if (!proposalIsMine(proposal, deps.ownAgentId))
|
|
224
|
+
continue;
|
|
206
225
|
const key = `proposal:${proposal.agreementId}`;
|
|
207
226
|
if (deps.seen.has(key))
|
|
208
227
|
continue;
|
|
@@ -65,7 +65,7 @@ export const taskSetResultTool = defineTool('task_set_result', {
|
|
|
65
65
|
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.' });
|
|
66
66
|
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).' });
|
|
67
67
|
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.' });
|
|
68
|
-
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
|
|
68
|
+
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.' });
|
|
69
69
|
// `status` names its four values here because nothing else does: the backend is
|
|
70
70
|
// the only place the vocabulary was written down, and it now rejects anything
|
|
71
71
|
// outside it. Every reader (the chat step badge, the SDK's own plan formatter)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineTool } from './defineTool.js';
|
|
2
|
+
import { laneFromToolContext } from './toolCreds.js';
|
|
2
3
|
/**
|
|
3
4
|
* the SDK surface adapter for shared capability definitions
|
|
4
5
|
* (api-client `capabilities/`). One capability (schema + handler + guidance)
|
|
@@ -10,13 +11,15 @@ export function toolFromCapability(cap) {
|
|
|
10
11
|
return defineTool(cap.names.sdk, cap.params, async (args, ctx) => {
|
|
11
12
|
const operatorKey = ctx?.['operatorKey'];
|
|
12
13
|
const agentId = ctx?.['agentId'];
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// executeToolWithSideEffects)
|
|
16
|
-
//
|
|
17
|
-
// Claude brain
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
// The lane the wake is running in was already threaded to this exact
|
|
15
|
+
// point by the shared executor (`chatId: effect.sessionId` in
|
|
16
|
+
// executeToolWithSideEffects). `laneFromToolContext` is the one reader
|
|
17
|
+
// for it, which makes the fence brain-agnostic by construction — the
|
|
18
|
+
// Claude brain, the FSM brain and hand-written tools all claim the same
|
|
19
|
+
// lane instead of each author remembering to. A capability may run
|
|
20
|
+
// without an agent id (`needsAgentId: false`), which is why this builds
|
|
21
|
+
// the creds itself rather than calling `credsFromToolContext`.
|
|
22
|
+
const laneId = laneFromToolContext(ctx);
|
|
20
23
|
if (!operatorKey)
|
|
21
24
|
throw new Error('operatorKey missing from tool context');
|
|
22
25
|
if (cap.needsAgentId && !agentId)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* toolCreds.ts — the credentials a tool acts under, read off its own context.
|
|
3
|
+
*
|
|
4
|
+
* A custom tool builds `{ operatorKey, agentId }` by hand and stops there, so
|
|
5
|
+
* every call it makes runs on the narrowest reach fence there is: the agent's
|
|
6
|
+
* own org. `AgentHost` already threads the wake's lane into the tool context
|
|
7
|
+
* (as `chatId` / `sessionId`, which is what the capability adapter reads), and
|
|
8
|
+
* the backend's fence is "own org ∪ the orgs of the parties to this lane" — so
|
|
9
|
+
* a tool that does not pass it loses reach it is entitled to, and loses it
|
|
10
|
+
* SILENTLY: enumerations come back short rather than refused.
|
|
11
|
+
*
|
|
12
|
+
* Measured on a five-node graph run: a coordinator asking which tasks it had
|
|
13
|
+
* created saw 0 of 5 with no lane and 5 of 5 with its own lane declared. The
|
|
14
|
+
* `task_list({ createdByMe: true })` convergence check that `lab-hirer`'s prompt
|
|
15
|
+
* recommends would therefore answer "nothing yet" and rebuild the whole graph,
|
|
16
|
+
* billing every worker twice.
|
|
17
|
+
*
|
|
18
|
+
* So: one reader, used by every tool, rather than a field each author has to
|
|
19
|
+
* remember. Omitting the lane can only narrow reach, never widen it, which is
|
|
20
|
+
* why forgetting it never fails loudly.
|
|
21
|
+
*/
|
|
22
|
+
import type { Creds } from '@ziggs-ai/api-client';
|
|
23
|
+
/** The loose bag `defineTool` hands a handler. */
|
|
24
|
+
type ToolContext = Record<string, unknown> | undefined | null;
|
|
25
|
+
/**
|
|
26
|
+
* The wake's lane, as the backend's `X-Ziggs-Lane` expects it: a chat id, or
|
|
27
|
+
* `agrn-<agreementId>` for an agreement lane. Undefined when the tool is
|
|
28
|
+
* running outside any lane (a bootstrap sweep, a test), which the fence reads
|
|
29
|
+
* as "own org only".
|
|
30
|
+
*/
|
|
31
|
+
export declare function laneFromToolContext(ctx: ToolContext): string | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The creds a tool should act under. Throws when the context is missing what a
|
|
34
|
+
* call needs, rather than building something that 401s later somewhere else.
|
|
35
|
+
*/
|
|
36
|
+
export declare function credsFromToolContext(ctx: ToolContext, toolName?: string): Creds;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* toolCreds.ts — the credentials a tool acts under, read off its own context.
|
|
3
|
+
*
|
|
4
|
+
* A custom tool builds `{ operatorKey, agentId }` by hand and stops there, so
|
|
5
|
+
* every call it makes runs on the narrowest reach fence there is: the agent's
|
|
6
|
+
* own org. `AgentHost` already threads the wake's lane into the tool context
|
|
7
|
+
* (as `chatId` / `sessionId`, which is what the capability adapter reads), and
|
|
8
|
+
* the backend's fence is "own org ∪ the orgs of the parties to this lane" — so
|
|
9
|
+
* a tool that does not pass it loses reach it is entitled to, and loses it
|
|
10
|
+
* SILENTLY: enumerations come back short rather than refused.
|
|
11
|
+
*
|
|
12
|
+
* Measured on a five-node graph run: a coordinator asking which tasks it had
|
|
13
|
+
* created saw 0 of 5 with no lane and 5 of 5 with its own lane declared. The
|
|
14
|
+
* `task_list({ createdByMe: true })` convergence check that `lab-hirer`'s prompt
|
|
15
|
+
* recommends would therefore answer "nothing yet" and rebuild the whole graph,
|
|
16
|
+
* billing every worker twice.
|
|
17
|
+
*
|
|
18
|
+
* So: one reader, used by every tool, rather than a field each author has to
|
|
19
|
+
* remember. Omitting the lane can only narrow reach, never widen it, which is
|
|
20
|
+
* why forgetting it never fails loudly.
|
|
21
|
+
*/
|
|
22
|
+
const str = (ctx, key) => {
|
|
23
|
+
const value = ctx?.[key];
|
|
24
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The wake's lane, as the backend's `X-Ziggs-Lane` expects it: a chat id, or
|
|
28
|
+
* `agrn-<agreementId>` for an agreement lane. Undefined when the tool is
|
|
29
|
+
* running outside any lane (a bootstrap sweep, a test), which the fence reads
|
|
30
|
+
* as "own org only".
|
|
31
|
+
*/
|
|
32
|
+
export function laneFromToolContext(ctx) {
|
|
33
|
+
return str(ctx, 'chatId') ?? str(ctx, 'sessionId');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The creds a tool should act under. Throws when the context is missing what a
|
|
37
|
+
* call needs, rather than building something that 401s later somewhere else.
|
|
38
|
+
*/
|
|
39
|
+
export function credsFromToolContext(ctx, toolName) {
|
|
40
|
+
const where = toolName ? `${toolName}: ` : '';
|
|
41
|
+
const operatorKey = str(ctx, 'operatorKey');
|
|
42
|
+
if (!operatorKey)
|
|
43
|
+
throw new Error(`${where}operatorKey missing from tool context`);
|
|
44
|
+
const agentId = str(ctx, 'agentId');
|
|
45
|
+
if (!agentId)
|
|
46
|
+
throw new Error(`${where}agentId missing from tool context`);
|
|
47
|
+
const laneId = laneFromToolContext(ctx);
|
|
48
|
+
return { operatorKey, agentId, ...(laneId ? { laneId } : {}) };
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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.13.0",
|
|
30
30
|
"ajv": "^8.17.1",
|
|
31
31
|
"dotenv": "^17.2.3",
|
|
32
32
|
"openai": "^4.0.0",
|