@ziggs-ai/agent-sdk 0.11.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/AgentHost.d.ts +27 -5
- package/dist/AgentHost.js +110 -15
- package/dist/agent/ClaudeSdkAgent.js +2 -2
- package/dist/agent/claudeSdkProtocol.d.ts +1 -1
- package/dist/agent/claudeSdkProtocol.js +3 -2
- package/dist/agent/claudeSdkTools.js +46 -10
- package/dist/cognition/validateContext.js +4 -1
- package/dist/context/batch.d.ts +3 -0
- package/dist/context/batch.js +8 -5
- package/dist/context/classifyEnvelope.js +4 -5
- package/dist/context/routingLabels.js +2 -3
- package/dist/formatters/AgreementFormatter.d.ts +7 -1
- package/dist/formatters/AgreementFormatter.js +17 -8
- package/dist/formatters/HistoryFormatter.d.ts +5 -0
- package/dist/formatters/HistoryFormatter.js +16 -7
- package/dist/index.d.ts +4 -2
- package/dist/index.js +8 -1
- package/dist/ingress/normalizeIncoming.d.ts +3 -8
- package/dist/runtime/PromptBuilder.js +32 -22
- package/dist/runtime/buildOutcome.js +4 -4
- package/dist/runtime/mandateDeclaration.d.ts +9 -0
- package/dist/runtime/mandateDeclaration.js +62 -0
- package/dist/runtime/runTurn.js +8 -0
- package/dist/server/ConnectionPool.d.ts +19 -0
- package/dist/server/ConnectionPool.js +30 -0
- package/dist/server/OutboxBuffer.d.ts +1 -1
- package/dist/server/ZiggsEffectHandler.d.ts +1 -1
- package/dist/server/ZiggsEffectHandler.js +6 -2
- package/dist/server/agreements/AgreementService.d.ts +19 -10
- package/dist/server/agreements/AgreementService.js +22 -11
- package/dist/server/inboxLoop.d.ts +6 -6
- package/dist/server/inboxLoop.js +31 -18
- package/dist/server/proactive/ProactiveTrigger.d.ts +7 -1
- package/dist/server/proactive/ProactiveTrigger.js +2 -2
- package/dist/server/requestMatch.d.ts +5 -0
- package/dist/server/{questMatch.js → requestMatch.js} +2 -2
- package/dist/server/runLauncher.js +24 -3
- package/dist/server/tools/index.d.ts +2 -2
- package/dist/server/tools/index.js +2 -2
- package/dist/server/tools/tier1/protocolRunner.js +16 -10
- package/dist/server/tools/tier1/protocolTools.js +12 -7
- package/dist/server/tools/tier2/linkTools.d.ts +1 -1
- package/dist/server/tools/tier2/linkTools.js +11 -9
- package/dist/server/tools/tier2/marketplaceTools.js +2 -2
- package/dist/server/tools/tier2/mcpConnectionTools.d.ts +33 -0
- package/dist/server/tools/tier2/mcpConnectionTools.js +138 -6
- package/dist/server/tools/tier2/paymentTools.d.ts +0 -8
- package/dist/server/tools/tier2/paymentTools.js +8 -12
- package/dist/types.d.ts +25 -10
- package/package.json +2 -2
- package/dist/server/questMatch.d.ts +0 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type InboxRequestRef, type InboxTaskRef, type Creds } from '@ziggs-ai/api-client';
|
|
2
2
|
import { SeenMessages } from './SeenMessages.js';
|
|
3
3
|
/**
|
|
4
4
|
* Per-agent inbox long-poll loop (delivery law, phase 4). This is the ONLY way
|
|
@@ -27,11 +27,11 @@ export interface InboxLoopDeps {
|
|
|
27
27
|
*/
|
|
28
28
|
onTask?: (task: InboxTaskRef) => Promise<void>;
|
|
29
29
|
/**
|
|
30
|
-
* A marketplace
|
|
30
|
+
* A marketplace request doorbell. The host MUST triage with a plain
|
|
31
31
|
* string compare and return without an LLM wake on miss — otherwise every
|
|
32
|
-
* public
|
|
32
|
+
* public request burns a full Claude turn per published supplier.
|
|
33
33
|
*/
|
|
34
|
-
|
|
34
|
+
onRequest?: (request: InboxRequestRef) => Promise<void>;
|
|
35
35
|
label?: string;
|
|
36
36
|
}
|
|
37
37
|
export interface InboxPassResult {
|
|
@@ -41,8 +41,8 @@ export interface InboxPassResult {
|
|
|
41
41
|
resourceEvents: number;
|
|
42
42
|
/** Assigned open tasks delivered this pass. */
|
|
43
43
|
tasksDelivered: number;
|
|
44
|
-
/**
|
|
45
|
-
|
|
44
|
+
/** Request refs triaged this pass — includes misses that only ack. */
|
|
45
|
+
requestsTriaged: number;
|
|
46
46
|
/** True when the watermark advanced this pass. */
|
|
47
47
|
acked: boolean;
|
|
48
48
|
/** True when there was more mail than one envelope carries. */
|
package/dist/server/inboxLoop.js
CHANGED
|
@@ -83,7 +83,7 @@ export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
|
|
|
83
83
|
let messagesDelivered = 0;
|
|
84
84
|
let resourceEvents = 0;
|
|
85
85
|
let tasksDelivered = 0;
|
|
86
|
-
let
|
|
86
|
+
let requestsTriaged = 0;
|
|
87
87
|
let failures = 0;
|
|
88
88
|
// a handover that failed must not be acked, and must not be
|
|
89
89
|
// remembered as seen — otherwise the redelivery the held watermark buys is
|
|
@@ -107,10 +107,16 @@ export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
|
|
|
107
107
|
// make a badly backed-up agent replay its whole backlog forever.
|
|
108
108
|
warn(label, 'inbox deliveriesCapped (more mail than one envelope carries — the tail follows on the next pass)');
|
|
109
109
|
}
|
|
110
|
-
//
|
|
110
|
+
// The single-assign law: a row is MINE to act on iff it carries my stamp.
|
|
111
|
+
// Everything else in the merged view (a sibling carrier's mail, the owner's
|
|
112
|
+
// needs-human rows, granted ambient lanes) is context the host may read on
|
|
113
|
+
// demand — never a wake, never mine to ack as handled.
|
|
114
|
+
const mine = (d) => d.assigneeId === deps.ownAgentId;
|
|
115
|
+
// Oldest ASSIGNED delivery per chat: where to start reading that chat's
|
|
116
|
+
// messages. Ambient chat rows must not drag the read window earlier.
|
|
111
117
|
const earliestByChat = new Map();
|
|
112
118
|
for (const d of envelope.deliveries) {
|
|
113
|
-
if (!d.chatId)
|
|
119
|
+
if (!d.chatId || !mine(d))
|
|
114
120
|
continue;
|
|
115
121
|
const seen = earliestByChat.get(d.chatId);
|
|
116
122
|
if (!seen || d.ts < seen)
|
|
@@ -126,10 +132,14 @@ export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
|
|
|
126
132
|
// Everything that is not chat text is handed over as a resource event, from
|
|
127
133
|
// the delivery itself — the reference already carries the ids the host needs,
|
|
128
134
|
// so nothing has to be re-derived to name what changed.
|
|
129
|
-
//
|
|
135
|
+
// Requests must never take this path — even if a pre-cut server
|
|
130
136
|
// left them in `deliveries`, skip so they cannot wake the LLM before triage.
|
|
137
|
+
// Unassigned rows never take it either: waking the brain on a sibling's
|
|
138
|
+
// mail is the N-wakes-per-event cost the party-owned inbox exists to kill.
|
|
131
139
|
for (const d of envelope.deliveries) {
|
|
132
|
-
if (d.kind === 'message' || d.kind === '
|
|
140
|
+
if (d.kind === 'message' || d.kind === 'request')
|
|
141
|
+
continue;
|
|
142
|
+
if (!mine(d))
|
|
133
143
|
continue;
|
|
134
144
|
const key = `delivery:${d.kind}:${d.resourceId}:${d.ts}`;
|
|
135
145
|
if (deps.seen.has(key))
|
|
@@ -169,26 +179,26 @@ export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
|
|
|
169
179
|
if (envelope.truncatedTasks) {
|
|
170
180
|
warn(label, `inbox truncatedTasks=${envelope.truncatedTasks} (more assigned tasks than one pass surfaces — they appear as earlier ones close)`);
|
|
171
181
|
}
|
|
172
|
-
//
|
|
182
|
+
// request channel. Triage is the host's job (exact string compare);
|
|
173
183
|
// a miss must still count as handled so the watermark can advance with 0 tokens.
|
|
174
|
-
for (const
|
|
175
|
-
const key = `
|
|
184
|
+
for (const request of envelope.requestsAwaitingMe ?? []) {
|
|
185
|
+
const key = `request:${request.agreementId}:${request.ts}`;
|
|
176
186
|
if (deps.seen.has(key))
|
|
177
187
|
continue;
|
|
178
188
|
deps.seen.mark(key);
|
|
179
|
-
if (deps.
|
|
180
|
-
const handled = await attempt(`
|
|
189
|
+
if (deps.onRequest) {
|
|
190
|
+
const handled = await attempt(`request ${request.agreementId}`, () => deps.onRequest(request), () => deps.seen.unmark(key));
|
|
181
191
|
if (handled)
|
|
182
|
-
|
|
192
|
+
requestsTriaged++;
|
|
183
193
|
}
|
|
184
194
|
else {
|
|
185
195
|
// No handler → still count as handled (ack) so an old host cannot wedge
|
|
186
|
-
// forever on
|
|
187
|
-
|
|
196
|
+
// forever on request mail it does not understand.
|
|
197
|
+
requestsTriaged++;
|
|
188
198
|
}
|
|
189
199
|
}
|
|
190
|
-
if (envelope.
|
|
191
|
-
warn(label, `inbox
|
|
200
|
+
if (envelope.truncatedRequests) {
|
|
201
|
+
warn(label, `inbox truncatedRequests=${envelope.truncatedRequests} (more requests than one pass surfaces — the tail follows on the next pass)`);
|
|
192
202
|
}
|
|
193
203
|
// Pending decisions are standing state, not deliveries: they keep appearing
|
|
194
204
|
// until answered, so dedup them the same way tasks are deduped.
|
|
@@ -226,10 +236,13 @@ export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
|
|
|
226
236
|
runtimeLog.error(label, `acking a window that failed ${failures} handover(s) — giving up on redelivery to keep the ` +
|
|
227
237
|
`agent alive; this mail is lost (ackTo=${envelope.ackTo})`);
|
|
228
238
|
}
|
|
239
|
+
// Assigned rows only: the server's bury-guard checks what was MINE to
|
|
240
|
+
// handle. Ambient rows slide past the cursor unlisted — they were never
|
|
241
|
+
// this agent's to act on, and the owner's seen mark tracks them.
|
|
229
242
|
const handledResourceIds = [
|
|
230
243
|
...new Set([
|
|
231
|
-
...envelope.deliveries.map((d) => d.resourceId),
|
|
232
|
-
...(envelope.
|
|
244
|
+
...envelope.deliveries.filter(mine).map((d) => d.resourceId),
|
|
245
|
+
...(envelope.requestsAwaitingMe ?? []).map((q) => q.agreementId),
|
|
233
246
|
]),
|
|
234
247
|
].filter((id) => typeof id === 'string' && id.length > 0);
|
|
235
248
|
await inbox.ack(envelope.ackTo, { handledResourceIds });
|
|
@@ -245,7 +258,7 @@ export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
|
|
|
245
258
|
messagesDelivered,
|
|
246
259
|
resourceEvents,
|
|
247
260
|
tasksDelivered,
|
|
248
|
-
|
|
261
|
+
requestsTriaged,
|
|
249
262
|
acked,
|
|
250
263
|
truncated: envelope.deliveriesCapped,
|
|
251
264
|
failures,
|
|
@@ -5,7 +5,13 @@ interface TickContext {
|
|
|
5
5
|
marketplace: {
|
|
6
6
|
publishOffer: (payload: PublishOfferPayload) => Promise<unknown>;
|
|
7
7
|
pullOffers: (options?: PullOffersOptions) => Promise<unknown[]>;
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Claim any open broadcast. One verb: the backend routes on which side of
|
|
10
|
+
* the row is open, and a caller cannot work that out for itself — reading
|
|
11
|
+
* the agreement first is party-scoped, and a claimer is by definition not
|
|
12
|
+
* yet a party.
|
|
13
|
+
*/
|
|
14
|
+
claimAgreement: (agreementId: string) => Promise<unknown>;
|
|
9
15
|
};
|
|
10
16
|
}
|
|
11
17
|
interface ProactiveTriggerOptions {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { publishOffer, pullOffers,
|
|
1
|
+
import { publishOffer, pullOffers, claimOpenAgreement } from '@ziggs-ai/api-client';
|
|
2
2
|
import { runtimeLog } from '../../shared/runtimeLog.js';
|
|
3
3
|
export class ProactiveTrigger {
|
|
4
4
|
interval;
|
|
@@ -64,7 +64,7 @@ export class ProactiveTrigger {
|
|
|
64
64
|
marketplace: {
|
|
65
65
|
publishOffer: (payload) => publishOffer(payload, creds),
|
|
66
66
|
pullOffers: (options) => pullOffers(options, creds),
|
|
67
|
-
|
|
67
|
+
claimAgreement: (agreementId) => claimOpenAgreement(agreementId, creds),
|
|
68
68
|
},
|
|
69
69
|
};
|
|
70
70
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* exact-match triage for marketplace
|
|
2
|
+
* exact-match triage for marketplace requests.
|
|
3
3
|
* Empty match never hits (token-safe default). Comparison is exact after trim.
|
|
4
4
|
*/
|
|
5
|
-
export function
|
|
5
|
+
export function requestExactMatch(match, tags) {
|
|
6
6
|
const needle = typeof match === 'string' ? match.trim() : '';
|
|
7
7
|
if (!needle)
|
|
8
8
|
return false;
|
|
@@ -5,6 +5,14 @@ import { boundKeyFleetWarning } from '../shared/operatorKeyClaims.js';
|
|
|
5
5
|
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
6
6
|
import { PollRegistry } from './polls/PollKind.js';
|
|
7
7
|
import { PollRunner } from './polls/PollRunner.js';
|
|
8
|
+
/**
|
|
9
|
+
* How long a shutdown waits for in-flight turns after it has stopped taking new
|
|
10
|
+
* work. An agent turn is an LLM call, so the old 30s was under the length of the
|
|
11
|
+
* work it was meant to protect; the container's stop timeout is what has to be
|
|
12
|
+
* larger still, or the drain is cut short by SIGKILL regardless (ECS
|
|
13
|
+
* `stopTimeout`, 120s on the fleet task definitions).
|
|
14
|
+
*/
|
|
15
|
+
const DRAIN_TIMEOUT_MS = Number(process.env.FLEET_DRAIN_TIMEOUT_MS) || 90_000;
|
|
8
16
|
/**
|
|
9
17
|
* Collect the poll kinds the agents themselves ship and start a runner for
|
|
10
18
|
* whatever they declared. This lives in the launcher, not in a caller's
|
|
@@ -57,7 +65,7 @@ async function runSingle(config, { operatorKey, label = 'agent', port = process.
|
|
|
57
65
|
runtimeLog.info(label, 'SIGTERM — shutting down');
|
|
58
66
|
pollRunner?.stop();
|
|
59
67
|
agent.disconnect();
|
|
60
|
-
await agent.drain(
|
|
68
|
+
await agent.drain(DRAIN_TIMEOUT_MS);
|
|
61
69
|
try {
|
|
62
70
|
await onShutdown?.();
|
|
63
71
|
}
|
|
@@ -122,10 +130,23 @@ async function runFleet(configs, { operatorKey, port = process.env.PORT || 8080,
|
|
|
122
130
|
});
|
|
123
131
|
process.on('SIGTERM', async () => {
|
|
124
132
|
runtimeLog.info(label, 'SIGTERM — shutting down');
|
|
133
|
+
// Stop taking new work everywhere first, then wait for what is running.
|
|
134
|
+
//
|
|
135
|
+
// This used to disconnect and drain only `primary` — which `run.ts` never
|
|
136
|
+
// sets, so in the deployed fleet the drain was a no-op on null and the
|
|
137
|
+
// process exited straight through `disconnectAll`, killing every in-flight
|
|
138
|
+
// turn. A buyer whose request landed mid-rollout got a host that stopped
|
|
139
|
+
// mid-sentence and never reported.
|
|
140
|
+
//
|
|
141
|
+
// Draining before stopping intake is the other half of the same bug: the
|
|
142
|
+
// wait is exactly the window in which a host on its way out keeps accepting
|
|
143
|
+
// deliveries the incoming host is also being handed.
|
|
125
144
|
pollRunner?.stop();
|
|
126
145
|
primary?.disconnect?.();
|
|
127
|
-
await
|
|
128
|
-
|
|
146
|
+
await Promise.all([
|
|
147
|
+
pool.shutdown(DRAIN_TIMEOUT_MS),
|
|
148
|
+
primary?.drain?.(DRAIN_TIMEOUT_MS) ?? Promise.resolve(),
|
|
149
|
+
]);
|
|
129
150
|
try {
|
|
130
151
|
await onShutdown?.();
|
|
131
152
|
}
|
|
@@ -19,11 +19,11 @@ export { PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreeme
|
|
|
19
19
|
export { dispatchProtocolOp } from './tier1/protocolRunner.js';
|
|
20
20
|
export { DISCOVERY_TOOLS, agentSearchTool, agentGetTool } from './tier2/discoveryTools.js';
|
|
21
21
|
export { MARKETPLACE_TOOLS, marketplaceViewTool } from './tier2/marketplaceTools.js';
|
|
22
|
-
export { PAYMENT_TOOLS, paymentBalanceTool
|
|
22
|
+
export { PAYMENT_TOOLS, paymentBalanceTool } from './tier2/paymentTools.js';
|
|
23
23
|
export { CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, } from './tier2/connectionTools.js';
|
|
24
24
|
export { CONTEXT_TOOLS, contextReadTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
|
|
25
25
|
export { GRANTS_TOOLS, listGrantsTool } from './tier2/grantsTools.js';
|
|
26
|
-
export { LINK_TOOLS,
|
|
26
|
+
export { LINK_TOOLS, linkProposeTool, linkListTool, } from './tier2/linkTools.js';
|
|
27
27
|
export { ARTIFACT_TOOLS, recordArtifactTool } from './tier2/artifactTools.js';
|
|
28
28
|
export { MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, } from './tier2/mcpConnectionTools.js';
|
|
29
29
|
export { CHAT_TOOLS, openConversationTool } from './tier2/chatTools.js';
|
|
@@ -21,7 +21,7 @@ export { dispatchProtocolOp } from './tier1/protocolRunner.js';
|
|
|
21
21
|
// ── Tier 2: capability bundles ──────────────────────────────────────────────
|
|
22
22
|
export { DISCOVERY_TOOLS, agentSearchTool, agentGetTool } from './tier2/discoveryTools.js';
|
|
23
23
|
export { MARKETPLACE_TOOLS, marketplaceViewTool } from './tier2/marketplaceTools.js';
|
|
24
|
-
export { PAYMENT_TOOLS, paymentBalanceTool
|
|
24
|
+
export { PAYMENT_TOOLS, paymentBalanceTool } from './tier2/paymentTools.js';
|
|
25
25
|
export { CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, } from './tier2/connectionTools.js';
|
|
26
26
|
export { CONTEXT_TOOLS, contextReadTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
|
|
27
27
|
// one grant lister across every rail (retires context_discover +
|
|
@@ -30,7 +30,7 @@ export { GRANTS_TOOLS, listGrantsTool } from './tier2/grantsTools.js';
|
|
|
30
30
|
// links (agreements with engagementKind 'link') — cross-org reach.
|
|
31
31
|
///1022: the rail is two tools; propose/claim/revoke ride the
|
|
32
32
|
// agreement grammar.
|
|
33
|
-
export { LINK_TOOLS,
|
|
33
|
+
export { LINK_TOOLS, linkProposeTool, linkListTool, } from './tier2/linkTools.js';
|
|
34
34
|
// deliberate artifact recording (the model decides "record this").
|
|
35
35
|
export { ARTIFACT_TOOLS, recordArtifactTool } from './tier2/artifactTools.js';
|
|
36
36
|
export { MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, } from './tier2/mcpConnectionTools.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isBroadcastTarget, } from '@ziggs-ai/api-client';
|
|
1
|
+
import { isBroadcastTarget, partyActorIds, } from '@ziggs-ai/api-client';
|
|
2
2
|
import { applyFleetCounterPricing, applyFleetPricingForProtocol, } from '../../../pricing/fleetDefaults.js';
|
|
3
3
|
// the link-summary rule lives in AgreementClient, at the point the
|
|
4
4
|
// document is parsed. Every verb here inherits it through the client instead of
|
|
@@ -63,10 +63,19 @@ cachedAgreements) {
|
|
|
63
63
|
return null;
|
|
64
64
|
return agreementService.respond(payload.agreementId, payload.action);
|
|
65
65
|
case 'agreement-claim': {
|
|
66
|
-
// the one claim verb —
|
|
66
|
+
// the one claim verb — request, standing offer, or link invite.
|
|
67
67
|
if (!payload.agreementId)
|
|
68
68
|
throw new Error('agreement-claim requires `agreementId`');
|
|
69
|
-
|
|
69
|
+
// The job this claim is part of, when the caller named one
|
|
70
|
+
// (the runtime fills it from the task being executed). It authorizes the
|
|
71
|
+
// claim; it does not re-parent the row, which stays in the publisher's
|
|
72
|
+
// tree.
|
|
73
|
+
const declaredMandate = payload.mandateAgreementId;
|
|
74
|
+
return await agreementService.claim(payload.agreementId, {
|
|
75
|
+
...(typeof declaredMandate === 'string' && declaredMandate
|
|
76
|
+
? { mandateAgreementId: declaredMandate }
|
|
77
|
+
: {}),
|
|
78
|
+
});
|
|
70
79
|
}
|
|
71
80
|
case 'agreement-counter-proposal':
|
|
72
81
|
if (!payload.agreementId)
|
|
@@ -217,7 +226,7 @@ async function proposeAgreement(services, payload, chatId, _agents, actingAgentI
|
|
|
217
226
|
// no chat, no money; the target owner approves. Folds the old request_link.
|
|
218
227
|
if (payload.engagementKind === 'link') {
|
|
219
228
|
if (isBroadcast) {
|
|
220
|
-
throw new Error('A broadcast link is an invite — use
|
|
229
|
+
throw new Error('A broadcast link is an invite — use link_propose with no target instead; it returns a share link.');
|
|
221
230
|
}
|
|
222
231
|
return services.agreementService.requestLink(payload.proposedTo, payload.description);
|
|
223
232
|
}
|
|
@@ -240,7 +249,7 @@ async function proposeAgreement(services, payload, chatId, _agents, actingAgentI
|
|
|
240
249
|
audience: payload.proposedTo === 'org' ? 'org' : 'everyone',
|
|
241
250
|
});
|
|
242
251
|
}
|
|
243
|
-
throw new Error('On a broadcast, providerId must be your own agent id (a standing offer) or omitted (a
|
|
252
|
+
throw new Error('On a broadcast, providerId must be your own agent id (a standing offer) or omitted (a request — the claiming agent fills the open side).');
|
|
244
253
|
}
|
|
245
254
|
const priced = applyFleetPricingForProtocol('agreement-propose', payload, actingAgentId);
|
|
246
255
|
const { operation: _op, ...fields } = priced;
|
|
@@ -283,13 +292,10 @@ async function agreementsInChat(services, chatId, cachedAgreements) {
|
|
|
283
292
|
}
|
|
284
293
|
}
|
|
285
294
|
function agIsProvider(a, agentId) {
|
|
286
|
-
|
|
287
|
-
return p['provider'] === agentId || p['providerAgent'] === agentId || a['providerId'] === agentId;
|
|
295
|
+
return a.parties?.provider?.actor === agentId || a['providerId'] === agentId;
|
|
288
296
|
}
|
|
289
297
|
function agIsParty(a, agentId) {
|
|
290
|
-
|
|
291
|
-
return ['provider', 'providerAgent', 'payer', 'proposedTo', 'creator', 'creatorAgent']
|
|
292
|
-
.some((k) => p[k] === agentId);
|
|
298
|
+
return partyActorIds(a.parties).includes(agentId);
|
|
293
299
|
}
|
|
294
300
|
function agActive(a) {
|
|
295
301
|
return !a.status || !TERMINAL_STATUS.has(String(a.status));
|
|
@@ -43,10 +43,15 @@ function handlerFor(toolName) {
|
|
|
43
43
|
return result;
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
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: '
|
|
47
|
-
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: '
|
|
48
|
-
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 (
|
|
49
|
-
export const agreementClaimTool = defineTool('agreement_claim', {
|
|
46
|
+
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 });
|
|
47
|
+
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: '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', agreementDescription: 'string' }, handlerFor('agreement_subcontract'), { description: 'Delegate work to another agent under an existing parent agreement. Requires parentAgreementId.', isAgreementCreation: true });
|
|
48
|
+
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.' });
|
|
49
|
+
export const agreementClaimTool = defineTool('agreement_claim', {
|
|
50
|
+
agreementId: { type: 'string', required: true, description: 'The open agreement to claim (request / offer / link invite id)' },
|
|
51
|
+
// The job this claim is part of. Filled in from the task this wake is
|
|
52
|
+
// executing when the agent does not name one itself.
|
|
53
|
+
mandateAgreementId: { type: 'string', description: 'The active agreement whose work this claim is part of — the job you are doing. Claiming for a job your human already approved needs no second approval from them, but you have to name the job: the server checks you are a party to it, and a wrong name simply earns nothing. Leave it out when the claim belongs to no job.' },
|
|
54
|
+
}, handlerFor('agreement_claim'), { description: 'Claim an open broadcast agreement by id — the DEFAULT way to engage: terms are already posted, consent is the claim, activation is instant, no negotiation turns. Claims a request (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 requests/offers with marketplace_view; listings are take-it-or-leave-it — never counter one. Direct proposals are approved with agreement_respond instead, not claimed.' });
|
|
50
55
|
export const taskSetResultTool = defineTool('task_set_result', {
|
|
51
56
|
taskId: { type: 'string', required: true },
|
|
52
57
|
// same param name as MCP ziggs_task_set_result / PATCH /tasks/:id/state.
|
|
@@ -54,10 +59,10 @@ export const taskSetResultTool = defineTool('task_set_result', {
|
|
|
54
59
|
// Same object contract as MCP ({ summary, status, links, … }).
|
|
55
60
|
result: {
|
|
56
61
|
type: 'object',
|
|
57
|
-
description: 'Outcome payload (summary, status, links, …).
|
|
62
|
+
description: 'Outcome payload (summary, status, links, …). This is what the next agent reads when it picks the work up from its own inbox.',
|
|
58
63
|
},
|
|
59
|
-
}, handlerFor('task_set_result'), { description: 'Transition a task to its terminal state — completed, failed, or cancelled — and record the result.
|
|
60
|
-
export const agreementCounterTool = defineTool('agreement_counter', { agreementId: { type: 'string', required: true }, price: { type: 'number', description: '
|
|
64
|
+
}, 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).' });
|
|
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.' });
|
|
61
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).' });
|
|
62
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.' });
|
|
63
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, 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.' });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ToolDefinition } from '../../../tools/defineTool.js';
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const linkProposeTool: ToolDefinition;
|
|
3
3
|
export declare const linkListTool: ToolDefinition;
|
|
4
4
|
export declare const LINK_TOOLS: ToolDefinition[];
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { LINK_CAPABILITIES,
|
|
1
|
+
import { LINK_CAPABILITIES, listLinksCapability, proposeLinkCapability, } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
|
|
3
|
-
// links are agreements with engagementKind 'link':
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// links are agreements with engagementKind 'link': bilateral trust between two
|
|
4
|
+
// PEOPLE that gives either side reach to the other. Defined once in api-client's
|
|
5
|
+
// shared capability layer (including the linkSummary shaping); this file only
|
|
6
|
+
// adapts them onto the SDK.
|
|
7
7
|
//
|
|
8
|
-
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
|
|
8
|
+
// Two tools. There were three: `link_create_invite` was `link_propose` with a
|
|
9
|
+
// different way of naming the other side, so a caller had to choose between them
|
|
10
|
+
// before knowing which applied. One verb takes an email, an agent id, or
|
|
11
|
+
// nothing. The agreement verbs carry the rest: claim an invite with
|
|
12
|
+
// agreement_claim, end a link with agreement_revoke.
|
|
13
|
+
export const linkProposeTool = toolFromCapability(proposeLinkCapability);
|
|
12
14
|
export const linkListTool = toolFromCapability(listLinksCapability);
|
|
13
15
|
export const LINK_TOOLS = toolsFromCapabilities(LINK_CAPABILITIES);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
|
|
3
|
-
// the marketplace read (
|
|
3
|
+
// the marketplace read (requests AND standing offers),
|
|
4
4
|
// defined once in api-client's shared capability layer. Publishing is NOT
|
|
5
5
|
// here: it is the tier-1 verb agreement_propose with proposedTo
|
|
6
|
-
// "everyone" | "org" (providerId = own id for an offer, omitted for a
|
|
6
|
+
// "everyone" | "org" (providerId = own id for an offer, omitted for a request);
|
|
7
7
|
// claiming is agreement_claim.
|
|
8
8
|
export const marketplaceViewTool = toolFromCapability(marketplaceViewCapability);
|
|
9
9
|
export const MARKETPLACE_TOOLS = toolsFromCapabilities(MARKETPLACE_CAPABILITIES);
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
import { type ToolDefinition } from '../../../tools/defineTool.js';
|
|
2
2
|
export declare const mcpToolsListTool: ToolDefinition;
|
|
3
|
+
/**
|
|
4
|
+
* How much of one MCP tool result a wake will carry.
|
|
5
|
+
*
|
|
6
|
+
* A remote MCP server hands back the upstream API's payload verbatim, and those
|
|
7
|
+
* are sized for programs, not for a context window: GitHub's `list_pull_requests`
|
|
8
|
+
* with no paging argument answered ~150,000 characters. Past the runtime's limit
|
|
9
|
+
* the result stops being a result — the Claude SDK spills an oversized tool
|
|
10
|
+
* result to a file, and a hosted agent has no filesystem tool to read it with, so
|
|
11
|
+
* the wake ends in "the answer was too big and I cannot see it". Same class of
|
|
12
|
+
* problem as an MCP server that ships a 30K-character tool description, one layer
|
|
13
|
+
* later.
|
|
14
|
+
*
|
|
15
|
+
* A bounded answer with a marker beats an unreadable one: the model can act on
|
|
16
|
+
* "truncated, narrow the call", and it cannot act on a file path.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MAX_MCP_RESULT_CHARS = 24000;
|
|
19
|
+
/**
|
|
20
|
+
* Tool schemas are bigger than tool results and are read once per wake, so they
|
|
21
|
+
* get their own, larger ceiling — a schema cut in half is worse than useless,
|
|
22
|
+
* because the half that survives looks complete.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MAX_MCP_TOOLS_LIST_CHARS = 60000;
|
|
25
|
+
/**
|
|
26
|
+
* Bound one MCP result, telling the model plainly what was cut.
|
|
27
|
+
*
|
|
28
|
+
* Text blocks are cut individually rather than the JSON being sliced, because a
|
|
29
|
+
* sliced JSON string is invalid and a model handed invalid JSON reports the
|
|
30
|
+
* server as broken.
|
|
31
|
+
*/
|
|
32
|
+
export declare function boundMcpResult(result: unknown, max?: number): {
|
|
33
|
+
result: unknown;
|
|
34
|
+
truncated?: Record<string, unknown>;
|
|
35
|
+
};
|
|
3
36
|
export declare const mcpToolCallTool: ToolDefinition;
|
|
4
37
|
export declare const MCP_CONNECTION_TOOLS: ToolDefinition[];
|