@ziggs-ai/agent-sdk 0.10.8 → 0.11.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 -11
- package/dist/AgentHost.d.ts +15 -15
- package/dist/AgentHost.js +22 -22
- package/dist/adapters/AnthropicAdapter.js +1 -1
- package/dist/adapters/McpAdapter.d.ts +10 -10
- package/dist/adapters/McpAdapter.js +33 -21
- package/dist/adapters/OpenAIAdapter.js +1 -1
- package/dist/agent/Agent.d.ts +2 -2
- package/dist/agent/ClaudeSdkAgent.d.ts +6 -6
- package/dist/agent/ClaudeSdkAgent.js +25 -25
- package/dist/agent/claudeCognition.d.ts +3 -3
- package/dist/agent/claudeCognition.js +7 -7
- package/dist/agent/claudeSdkTools.d.ts +3 -3
- package/dist/agent/claudeSdkTools.js +9 -9
- package/dist/agent/hostedAgentConnection.d.ts +1 -1
- package/dist/agent/hostedAgentConnection.js +1 -1
- package/dist/agent/toolManagerToSdkMcp.d.ts +1 -1
- package/dist/agent/toolManagerToSdkMcp.js +2 -2
- package/dist/context/applyEffects.js +1 -1
- package/dist/defineAgent.js +2 -2
- package/dist/formatters/HistoryFormatter.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/ingress/normalizeIncoming.d.ts +1 -1
- package/dist/ingress/normalizeIncoming.js +7 -7
- package/dist/pricing/fleetDefaults.d.ts +3 -3
- package/dist/pricing/fleetDefaults.js +3 -3
- package/dist/pricing/fleetEvalFree.d.ts +1 -1
- package/dist/pricing/fleetEvalFree.js +1 -1
- package/dist/pricing/fleetFreeTierA.gen.d.ts +1 -1
- package/dist/pricing/fleetFreeTierA.gen.js +1 -1
- package/dist/pricing/fleetTierByAgentId.gen.d.ts +1 -1
- package/dist/runtime/AgentMachine.d.ts +1 -1
- package/dist/runtime/AgentMachine.js +8 -8
- package/dist/runtime/PromptBuilder.d.ts +2 -2
- package/dist/runtime/PromptBuilder.js +3 -3
- package/dist/runtime/bootstrapCtx.d.ts +1 -1
- package/dist/runtime/bootstrapCtx.js +2 -2
- package/dist/runtime/buildOutcome.d.ts +1 -1
- package/dist/runtime/buildOutcome.js +4 -4
- package/dist/runtime/defaults.d.ts +5 -5
- package/dist/runtime/defaults.js +4 -4
- package/dist/runtime/deliveryWriteKeys.d.ts +1 -1
- package/dist/runtime/deliveryWriteKeys.js +3 -3
- package/dist/runtime/kickoff.d.ts +3 -3
- package/dist/runtime/kickoff.js +3 -3
- package/dist/runtime/runMechanical.d.ts +1 -1
- package/dist/runtime/runMechanical.js +1 -1
- package/dist/runtime/runTurn.d.ts +1 -1
- package/dist/runtime/runTurn.js +10 -10
- package/dist/runtime/taskBookkeeping.d.ts +3 -3
- package/dist/runtime/taskBookkeeping.js +3 -3
- package/dist/runtime/taskIntake.js +2 -2
- package/dist/server/ConnectionPool.js +1 -1
- package/dist/server/EventQueue.d.ts +1 -1
- package/dist/server/EventQueue.js +1 -1
- package/dist/server/SeenMessages.d.ts +2 -2
- package/dist/server/SeenMessages.js +2 -2
- package/dist/server/ZiggsEffectHandler.d.ts +2 -2
- package/dist/server/ZiggsEffectHandler.js +8 -8
- package/dist/server/agreements/AgreementService.d.ts +3 -8
- package/dist/server/agreements/AgreementService.js +4 -7
- package/dist/server/inboxLoop.d.ts +5 -5
- package/dist/server/inboxLoop.js +21 -10
- package/dist/server/questMatch.d.ts +1 -1
- package/dist/server/questMatch.js +1 -1
- package/dist/server/runLauncher.js +3 -3
- package/dist/server/tools/index.d.ts +1 -0
- package/dist/server/tools/index.js +7 -6
- package/dist/server/tools/tier1/protocolRunner.d.ts +1 -1
- package/dist/server/tools/tier1/protocolRunner.js +19 -19
- package/dist/server/tools/tier1/protocolTools.js +4 -4
- package/dist/server/tools/tier2/artifactTools.js +2 -2
- package/dist/server/tools/tier2/chatTools.js +2 -2
- package/dist/server/tools/tier2/connectionTools.js +3 -3
- package/dist/server/tools/tier2/contextTools.js +1 -1
- package/dist/server/tools/tier2/discoveryTools.js +2 -2
- package/dist/server/tools/tier2/grantsTools.js +1 -1
- package/dist/server/tools/tier2/linkTools.js +3 -3
- package/dist/server/tools/tier2/marketplaceTools.js +1 -1
- package/dist/server/tools/tier2/mcpConnectionTools.d.ts +4 -0
- package/dist/server/tools/tier2/mcpConnectionTools.js +136 -0
- package/dist/server/tools/tier2/paymentTools.js +1 -1
- package/dist/shared/operatorKeyClaims.d.ts +2 -2
- package/dist/shared/operatorKeyClaims.js +1 -1
- package/dist/tasks/protocolRegistry.d.ts +1 -1
- package/dist/tasks/protocolRegistry.js +1 -1
- package/dist/tools/ToolManager.js +1 -1
- package/dist/tools/capabilityAdapter.d.ts +1 -1
- package/dist/tools/capabilityAdapter.js +3 -3
- package/dist/tools/defineTool.js +1 -1
- package/dist/tools/recordReport.d.ts +3 -3
- package/dist/tools/recordReport.js +3 -3
- package/dist/types.d.ts +17 -17
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isBroadcastTarget, } 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
|
|
5
5
|
// each one restating `engagementKind === 'link' ? linkSummary(a) : a` — which is
|
|
6
6
|
// how counter, fulfill and subcontract came to return the raw document.
|
|
@@ -12,12 +12,12 @@ function normalizeState(state) {
|
|
|
12
12
|
return 'failed';
|
|
13
13
|
return state;
|
|
14
14
|
}
|
|
15
|
-
//
|
|
15
|
+
// the `plan` tool-schema is underspecified (`plan: {type:'object'}`,
|
|
16
16
|
// `planReviewTiming: 'string'`), so the LLM emits steps missing stepId/order and an
|
|
17
17
|
// out-of-enum planReviewTiming, which the backend DTO rejects. Normalize in-place
|
|
18
18
|
// before the write.
|
|
19
19
|
//
|
|
20
|
-
//
|
|
20
|
+
// task-spawn is the only op left that carries a plan. Propose, counter
|
|
21
21
|
// and subcontract used to run this too — and their plans were then dropped on the
|
|
22
22
|
// floor, since no agreement route has ever accepted one.
|
|
23
23
|
// Returns the clamped review timing rather than only writing it back, so the
|
|
@@ -46,7 +46,7 @@ function normalizePlanInPlace(payload) {
|
|
|
46
46
|
: 'with_proposal';
|
|
47
47
|
}
|
|
48
48
|
export async function dispatchProtocolOp(services, payload, chatId, agents = [], actingAgentId = null, writeKeys = null,
|
|
49
|
-
/**
|
|
49
|
+
/** #8 — prefer the cached session snapshot over a fresh HTTP read. */
|
|
50
50
|
cachedAgreements) {
|
|
51
51
|
if (!payload?.operation)
|
|
52
52
|
return null;
|
|
@@ -63,7 +63,7 @@ cachedAgreements) {
|
|
|
63
63
|
return null;
|
|
64
64
|
return agreementService.respond(payload.agreementId, payload.action);
|
|
65
65
|
case 'agreement-claim': {
|
|
66
|
-
//
|
|
66
|
+
// the one claim verb — quest, standing offer, or link invite.
|
|
67
67
|
if (!payload.agreementId)
|
|
68
68
|
throw new Error('agreement-claim requires `agreementId`');
|
|
69
69
|
return await agreementService.claim(payload.agreementId);
|
|
@@ -101,11 +101,11 @@ cachedAgreements) {
|
|
|
101
101
|
return null;
|
|
102
102
|
let spawnAgreementId = payload.agreementId;
|
|
103
103
|
const ags = await agreementsInChat(services, chatId, cachedAgreements);
|
|
104
|
-
// b2
|
|
104
|
+
// b2: if the assignee is not a party on the cited agreement,
|
|
105
105
|
// redirect the spawn to the ACTIVE sub-agreement (same root) where the
|
|
106
106
|
// assignee IS the provider — the orchestrator often cites the top-level
|
|
107
107
|
// hire, which the backend rejects with "assignee not a party".
|
|
108
|
-
//
|
|
108
|
+
// also redirect when the cited agreement is no longer active —
|
|
109
109
|
// a count-bound sub fulfills after its quota (default 1), and the LLM
|
|
110
110
|
// keeps citing that now-terminal sub for the next spawn even when a
|
|
111
111
|
// fresh active sub for the same executor exists.
|
|
@@ -120,14 +120,14 @@ cachedAgreements) {
|
|
|
120
120
|
spawnAgreementId = sub.agreementId;
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
-
// Safety guard
|
|
123
|
+
// Safety guard: a spawn against an agreement that is not active,
|
|
124
124
|
// or whose assignee is not a party on it, hard-400s at the backend AND makes
|
|
125
125
|
// the worker invent a "connection needed" message. That happens when the
|
|
126
126
|
// orchestrator spawns before its sub-agreement is subcontracted/approved.
|
|
127
127
|
// Skip GRACEFULLY instead — the sub-agreement's activation event will wake
|
|
128
128
|
// the orchestrator to spawn correctly on a later turn, no 400, no cascade.
|
|
129
129
|
const target = ags.find((a) => a.agreementId === spawnAgreementId);
|
|
130
|
-
//
|
|
130
|
+
// a TERMINAL target is not the same as a not-yet-active one — no
|
|
131
131
|
// activation event will ever arrive, so the "wait for a later turn" skip
|
|
132
132
|
// below would stall the run. Tell the caller the actionable next step:
|
|
133
133
|
// subcontract a fresh sub for this executor, then spawn under it.
|
|
@@ -176,7 +176,7 @@ cachedAgreements) {
|
|
|
176
176
|
});
|
|
177
177
|
}
|
|
178
178
|
case 'task-update': {
|
|
179
|
-
//
|
|
179
|
+
// `state` only — legacy `status` alias removed. Fleet prompts
|
|
180
180
|
// must emit state (coffee / fruit / ledger / agent-definer).
|
|
181
181
|
const terminalRaw = typeof payload.state === 'string' && payload.state ? payload.state : '';
|
|
182
182
|
if (!payload.taskId || !terminalRaw)
|
|
@@ -193,7 +193,7 @@ cachedAgreements) {
|
|
|
193
193
|
throw new Error('task-update-plan-step requires `stepId`');
|
|
194
194
|
if (!payload.status)
|
|
195
195
|
throw new Error('task-update-plan-step requires `status`');
|
|
196
|
-
//
|
|
196
|
+
// a plan-step tick is cosmetic progress. Worker subtasks usually
|
|
197
197
|
// have NO plan (only the orchestrator's job task does), and the LLM may tick
|
|
198
198
|
// a stepId that isn't in the plan — the backend 400s on both. That must never
|
|
199
199
|
// hard-fail the tool → fail the task → wake-loop. Make it best-effort.
|
|
@@ -213,7 +213,7 @@ async function proposeAgreement(services, payload, chatId, _agents, actingAgentI
|
|
|
213
213
|
if (!payload.proposedTo)
|
|
214
214
|
throw new Error('agreement-propose requires `proposedTo` (userId or agentId). To create a task under an existing agreement, use `task_create` instead.');
|
|
215
215
|
const isBroadcast = isBroadcastTarget(payload.proposedTo);
|
|
216
|
-
//
|
|
216
|
+
// a link proposal is an agreement with engagementKind 'link' —
|
|
217
217
|
// no chat, no money; the target owner approves. Folds the old request_link.
|
|
218
218
|
if (payload.engagementKind === 'link') {
|
|
219
219
|
if (isBroadcast) {
|
|
@@ -225,7 +225,7 @@ async function proposeAgreement(services, payload, chatId, _agents, actingAgentI
|
|
|
225
225
|
throw new Error('agreement-propose requires `providerId` — name who does the work: your own id (you are offering to work) or the proposedTo id (you are commissioning the recipient).');
|
|
226
226
|
}
|
|
227
227
|
if (isBroadcast && payload.providerId) {
|
|
228
|
-
//
|
|
228
|
+
// providerId = self on a broadcast publishes a STANDING OFFER
|
|
229
229
|
// (seller side open to any claimer). Any other providerId stays an error.
|
|
230
230
|
if (actingAgentId && payload.providerId === actingAgentId) {
|
|
231
231
|
const priced = applyFleetPricingForProtocol('agreement-propose', payload, actingAgentId);
|
|
@@ -256,7 +256,7 @@ async function proposeAgreement(services, payload, chatId, _agents, actingAgentI
|
|
|
256
256
|
});
|
|
257
257
|
return services.agreementService.propose({ ...fields, chatId, idempotencyKey });
|
|
258
258
|
}
|
|
259
|
-
// ──
|
|
259
|
+
// ── deterministic sub-agreement resolution ─────────────────────────
|
|
260
260
|
// The orchestrator delegates one task per worker, but the LLM inconsistently
|
|
261
261
|
// (a) re-subcontracts the same worker (duplicate sub-agreements) and (b)
|
|
262
262
|
// task_spawns a worker under the TOP-LEVEL hire — where the worker is not a
|
|
@@ -267,7 +267,7 @@ const TERMINAL_STATUS = new Set(['fulfilled', 'cancelled', 'refunded', 'rejected
|
|
|
267
267
|
async function agreementsInChat(services, chatId, cachedAgreements) {
|
|
268
268
|
if (!chatId)
|
|
269
269
|
return [];
|
|
270
|
-
//
|
|
270
|
+
// #8 — createZiggsEffectHandler already cached the session snapshot;
|
|
271
271
|
// prefer that over another GET /context/snapshot just for the agreements array.
|
|
272
272
|
// Empty array is a real answer ("none in this chat") — do not re-fetch.
|
|
273
273
|
if (cachedAgreements !== undefined) {
|
|
@@ -297,14 +297,14 @@ function agActive(a) {
|
|
|
297
297
|
// Spawnable = actually ACTIVE. A freshly-subcontracted sub is `draft` until its
|
|
298
298
|
// counterparty auto-approves; spawning under a draft (or citing an agreement the
|
|
299
299
|
// assignee isn't a party on) 400s and cascades into a bogus worker "connection
|
|
300
|
-
// needed" message. We treat only 'active' as spawnable
|
|
300
|
+
// needed" message. We treat only 'active' as spawnable.
|
|
301
301
|
function agIsActiveStatus(a) {
|
|
302
302
|
return String(a.status ?? '') === 'active';
|
|
303
303
|
}
|
|
304
304
|
async function subcontractAgreement(services, payload, chatId, _agents, actingAgentId, writeKeys = null, cachedAgreements) {
|
|
305
305
|
if (!payload.description || !payload.executorId)
|
|
306
306
|
return null;
|
|
307
|
-
// b1
|
|
307
|
+
// b1: idempotent subcontract — if a non-terminal sub-agreement for
|
|
308
308
|
// this executor already exists under the same parent, reuse it instead of
|
|
309
309
|
// creating a duplicate (which bloats the chain and blocks the fulfill cascade).
|
|
310
310
|
if (chatId) {
|
|
@@ -316,7 +316,7 @@ async function subcontractAgreement(services, payload, chatId, _agents, actingAg
|
|
|
316
316
|
}
|
|
317
317
|
const priced = applyFleetPricingForProtocol('agreement-subcontract', payload, actingAgentId);
|
|
318
318
|
const { operation: _op, ...fields } = priced;
|
|
319
|
-
//
|
|
319
|
+
// a subcontract must carry a working close condition, or it hangs
|
|
320
320
|
// 'active' forever — and because the backend only auto-fulfills a parent hire
|
|
321
321
|
// once every child sub-agreement is terminal (the fulfill cascade), one
|
|
322
322
|
// never-closing child leaves the whole engagement open. Deciding lifecycle is
|
|
@@ -328,7 +328,7 @@ async function subcontractAgreement(services, payload, chatId, _agents, actingAg
|
|
|
328
328
|
// sub — which then cascades the parent closed. A genuinely durable subcontract
|
|
329
329
|
// still opts out by setting maxExecutions>1 or a time-bound expiresAt.
|
|
330
330
|
const f = fields;
|
|
331
|
-
//
|
|
331
|
+
// repair incomplete terms instead of clobbering them. The model
|
|
332
332
|
// often emits a real quota or deadline but gets the envelope wrong — a
|
|
333
333
|
// numeric-string maxExecutions, or a quota/deadline with the lifecycle field
|
|
334
334
|
// missing or mismatched. The old check treated all of those as "no close
|
|
@@ -2,7 +2,7 @@ import { defineTool } from '../../../tools/defineTool.js';
|
|
|
2
2
|
import { requireProtocolOperation } from '../../../tasks/protocolRegistry.js';
|
|
3
3
|
import { dispatchProtocolOp } from './protocolRunner.js';
|
|
4
4
|
import { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION, } from '@ziggs-ai/api-client';
|
|
5
|
-
/**
|
|
5
|
+
/** keep these enums in lockstep with MCP ziggs_agreement_* / ziggs_task_create. */
|
|
6
6
|
const LIFECYCLE_ENUM = ['open', 'time-bound', 'count-bound'];
|
|
7
7
|
const PLAN_REVIEW_TIMING_ENUM = ['with_proposal', 'before_execution'];
|
|
8
8
|
const lifecycleParam = {
|
|
@@ -17,7 +17,7 @@ const planReviewTimingParam = {
|
|
|
17
17
|
};
|
|
18
18
|
/**
|
|
19
19
|
* The handler for a protocol verb, looked up by tool name in the registry
|
|
20
|
-
|
|
20
|
+
*. Keying off the tool name rather than restating the operation is
|
|
21
21
|
* what makes half-registration impossible: a verb missing from
|
|
22
22
|
* PROTOCOL_TOOL_TO_OPERATION throws here, while this module loads, instead of
|
|
23
23
|
* dispatching correctly and then being unrecognised as protocol at every
|
|
@@ -43,13 +43,13 @@ 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'
|
|
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: 'Amount in CENTS — 500 means $5.00, shown as ϟ5.00 in the UI. Convert both ways: a human asking for "ϟ2" or "$2 per task" means 200, not 2. ϟ is the currency symbol, not a cent.' }, lifecycle: lifecycleParam, expiresAt: 'string', maxExecutions: 'integer', billing: { type: 'string', enum: ['total', 'per_task'], description: "How price reads: 'total' (default, escrowed and paid at the end) or 'per_task' (a rate settled as each task completes — standing agreements only, and the default for a hire)." }, agreementDescription: 'string' }, handlerFor('agreement_propose'), { description: 'Propose an agreement — direct, broadcast, or link; there are no separate publish tools. 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 quest 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 QUEST 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 (or link_create_invite when you lack the agent id). 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
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: 'Amount in CENTS — 500 means $5.00, shown as ϟ5.00 in the UI. Convert both ways: a human asking for "ϟ2" or "$2 per task" means 200, not 2. ϟ is the currency symbol, not a cent.' }, lifecycle: lifecycleParam, expiresAt: 'string', maxExecutions: 'integer', agreementDescription: 'string' }, handlerFor('agreement_subcontract'), { description: 'Delegate work to another agent under an existing parent agreement. Requires parentAgreementId.', isAgreementCreation: true });
|
|
48
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 (quests, standing offers, link invites) have no personal approval slot — claim those with agreement_claim instead, or ignore them to pass. ONE exception: a hand-off that pins YOU as provider carries your pending approval even as an open broadcast — approving consents to serving whoever claims it (the row stays open for claims); rejecting cancels the hand-off. Ignoring it leaves the hand-off unclaimable, so always respond.' });
|
|
49
49
|
export const agreementClaimTool = defineTool('agreement_claim', { agreementId: { type: 'string', required: true, description: 'The open agreement to claim (quest / offer / link invite id)' } }, handlerFor('agreement_claim'), { description: 'Claim an open broadcast agreement by id — the DEFAULT way to engage: terms are already posted, consent is the claim, activation is instant, no negotiation turns. Claims a quest (you do the work, the publisher pays), a standing offer (you buy, the publisher works), a hand-off (providerPinned: the publisher\'s hired agent works FOR you — you become the customer, and the payer when it is priced), or a link invite (bilateral trust forms immediately). You become the open party and the agreement activates. A hand-off is claimable only after its provider has accepted (409 until then). Find quests/offers with marketplace_view; listings are take-it-or-leave-it — never counter one. Direct proposals are approved with agreement_respond instead, not claimed.' });
|
|
50
50
|
export const taskSetResultTool = defineTool('task_set_result', {
|
|
51
51
|
taskId: { type: 'string', required: true },
|
|
52
|
-
//
|
|
52
|
+
// same param name as MCP ziggs_task_set_result / PATCH /tasks/:id/state.
|
|
53
53
|
state: { type: 'string', required: true, enum: ['completed', 'failed', 'cancelled'] },
|
|
54
54
|
// Same object contract as MCP ({ summary, status, links, … }).
|
|
55
55
|
result: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ARTIFACT_CAPABILITIES, recordArtifactCapability } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
|
|
3
|
-
//
|
|
4
|
-
// api-client's shared capability layer (including the
|
|
3
|
+
// deliberate artifact recording, defined once in
|
|
4
|
+
// api-client's shared capability layer (including the result-slot
|
|
5
5
|
// teaching, worded per surface task grammar).
|
|
6
6
|
export const recordArtifactTool = toolFromCapability(recordArtifactCapability);
|
|
7
7
|
export const ARTIFACT_TOOLS = toolsFromCapabilities(ARTIFACT_CAPABILITIES);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CHAT_CAPABILITIES, openConversationCapability } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
|
|
3
|
-
//
|
|
4
|
-
// context-rail read,
|
|
3
|
+
// chat_open only (chat listing stays a
|
|
4
|
+
// context-rail read, rule); defined once in api-client's shared
|
|
5
5
|
// capability layer.
|
|
6
6
|
export const openConversationTool = toolFromCapability(openConversationCapability);
|
|
7
7
|
export const CHAT_TOOLS = toolsFromCapabilities(CHAT_CAPABILITIES);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { connectionProxyCapability, requestConnectionCapability, ConnectionsClient, } from '@ziggs-ai/api-client';
|
|
2
2
|
import { defineTool } from '../../../tools/defineTool.js';
|
|
3
3
|
import { toolFromCapability } from '../../../tools/capabilityAdapter.js';
|
|
4
|
-
//
|
|
4
|
+
// connection_proxy / connection_request are shared capability
|
|
5
5
|
// definitions (api-client). connection_list_grants stays SDK-only: the MCP
|
|
6
|
-
// surface folds connection listing into ziggs_connection_list (
|
|
6
|
+
// surface folds connection listing into ziggs_connection_list (
|
|
7
7
|
// allowance in the parity table).
|
|
8
8
|
export const connectionProxyTool = toolFromCapability(connectionProxyCapability);
|
|
9
9
|
export const requestConnectionTool = toolFromCapability(requestConnectionCapability);
|
|
@@ -35,7 +35,7 @@ export const connectionListGrantsTool = defineTool('connection_list_grants', {
|
|
|
35
35
|
throw wrapped;
|
|
36
36
|
}
|
|
37
37
|
}, {
|
|
38
|
-
//
|
|
38
|
+
// this shipped with no description at all — the model was handed a
|
|
39
39
|
// name and a parameter and left to guess what the tool was for. Found by the
|
|
40
40
|
// new SDK tool-text guard.
|
|
41
41
|
description: "List the grants issued to this agent on one stored connection. Returns the grantId that connection_proxy needs. Use it when you have a connectionId but not the grantId — grant_list (scopeKind=connection) is the wider read across every connection.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CONTEXT_CAPABILITIES, contextReadCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
|
|
3
|
-
//
|
|
3
|
+
// context grant tools ride the same api-client
|
|
4
4
|
// clients as ziggs-mcp; the whole definition (schema + handler) is now shared
|
|
5
5
|
// via the capability layer, so the two surfaces cannot fork.
|
|
6
6
|
export const contextReadTool = toolFromCapability(contextReadCapability);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { agentSearchCapability, agentGetCapability } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability } from '../../../tools/capabilityAdapter.js';
|
|
3
|
-
//
|
|
4
|
-
// so search shaping (
|
|
3
|
+
// agent discovery is a shared capability definition (api-client),
|
|
4
|
+
// so search shaping (zero-hit hint, reachability rows) is identical
|
|
5
5
|
// on both surfaces.
|
|
6
6
|
export const agentSearchTool = toolFromCapability(agentSearchCapability);
|
|
7
7
|
export const agentGetTool = toolFromCapability(agentGetCapability);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GRANTS_CAPABILITIES, listGrantsCapability } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
|
|
3
|
-
//
|
|
3
|
+
// the single "what authority do I hold?" tool, defined
|
|
4
4
|
// once in api-client's shared capability layer.
|
|
5
5
|
export const listGrantsTool = toolFromCapability(listGrantsCapability);
|
|
6
6
|
export const GRANTS_TOOLS = toolsFromCapabilities(GRANTS_CAPABILITIES);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { LINK_CAPABILITIES, createLinkInviteCapability, listLinksCapability, } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
|
|
3
|
-
//
|
|
3
|
+
// links are agreements with engagementKind 'link':
|
|
4
4
|
// bilateral agent-to-agent trust that gives any unpublished agent cross-org
|
|
5
5
|
// reach. Defined once in api-client's shared capability layer (including the
|
|
6
|
-
//
|
|
6
|
+
// linkSummary shaping); this file only adapts them onto the SDK.
|
|
7
7
|
//
|
|
8
|
-
|
|
8
|
+
///1022 shrank the rail to two tools. The agreement verbs carry the
|
|
9
9
|
// rest: request a direct link with agreement_propose (engagementKind 'link'),
|
|
10
10
|
// claim an invite with agreement_claim, end a link with agreement_revoke.
|
|
11
11
|
export const linkCreateInviteTool = toolFromCapability(createLinkInviteCapability);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
|
|
3
|
-
//
|
|
3
|
+
// the marketplace read (quests 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
6
|
// "everyone" | "org" (providerId = own id for an offer, omitted for a quest);
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
|
+
import { ConnectionsClient } from '@ziggs-ai/api-client';
|
|
4
|
+
import { defineTool } from '../../../tools/defineTool.js';
|
|
5
|
+
import { resolveZiggsGatewayTarget } from '../../../adapters/McpAdapter.js';
|
|
6
|
+
/**
|
|
7
|
+
* ZIG-1104 — calling a tool on a connected MCP server, one shot.
|
|
8
|
+
*
|
|
9
|
+
* `connection_proxy` cannot do this. That path resolves a NAMED connector from
|
|
10
|
+
* `connection.provider` and checks the action against its fixed action list; a
|
|
11
|
+
* remote MCP server is stored with `provider: 'mcp'`, which no connector matches,
|
|
12
|
+
* so every attempt came back "Unknown provider: mcp". A hosted agent therefore had
|
|
13
|
+
* no way to use an MCP connection at all — found when a Linear grant was issued to
|
|
14
|
+
* `shipped-digest` and its task still failed after two proxy attempts.
|
|
15
|
+
*
|
|
16
|
+
* The gateway (`POST /connections/:id/mcp`) has always been the right door and is
|
|
17
|
+
* already in production; what was missing was a tool that walks through it. The
|
|
18
|
+
* transport, headers and MCP handshake are the same ones `McpAdapter`'s `ziggs`
|
|
19
|
+
* transport uses — reused rather than re-derived, so a change to the gateway
|
|
20
|
+
* contract lands in one place.
|
|
21
|
+
*
|
|
22
|
+
* Discovery is deliberate: with no `connectionId`/`grantId` the tool asks
|
|
23
|
+
* `GET /grants` for the agent's own live connection grants. One connection means
|
|
24
|
+
* no argument to guess; several means the caller must name one, because picking
|
|
25
|
+
* for them is how an agent reads the wrong workspace and reports it as fact.
|
|
26
|
+
*/
|
|
27
|
+
async function resolveTarget(ctx, connectionId, grantId) {
|
|
28
|
+
if (connectionId && grantId)
|
|
29
|
+
return { connectionId, grantId };
|
|
30
|
+
const operatorKey = ctx['operatorKey'];
|
|
31
|
+
const agentId = ctx['agentId'];
|
|
32
|
+
if (!operatorKey)
|
|
33
|
+
throw new Error('operatorKey missing from tool context');
|
|
34
|
+
if (!agentId)
|
|
35
|
+
throw new Error('agentId missing from tool context');
|
|
36
|
+
// No explicit baseUrl: ConnectionsClient already resolves it the way the fleet
|
|
37
|
+
// configures it (HTTP_URL). Passing BACKEND_URL here was the same mistake as in
|
|
38
|
+
// the transport — undefined in deployment.
|
|
39
|
+
const groups = await new ConnectionsClient(operatorKey, agentId).listForHolder();
|
|
40
|
+
const live = groups.filter((g) => (g.grants ?? []).length > 0);
|
|
41
|
+
if (live.length === 0) {
|
|
42
|
+
throw new Error('No live connection grant. Ask the connection owner to issue one for this agent, then retry.');
|
|
43
|
+
}
|
|
44
|
+
const chosen = connectionId
|
|
45
|
+
? live.find((g) => g.connectionId === connectionId)
|
|
46
|
+
: live.length === 1
|
|
47
|
+
? live[0]
|
|
48
|
+
: undefined;
|
|
49
|
+
if (!chosen) {
|
|
50
|
+
throw new Error(`Name the connection to use — grants exist on ${live
|
|
51
|
+
.map((g) => g.connectionId)
|
|
52
|
+
.join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
const grant = grantId
|
|
55
|
+
? (chosen.grants ?? []).find((g) => g.grantId === grantId)
|
|
56
|
+
: (chosen.grants ?? [])[0];
|
|
57
|
+
if (!grant) {
|
|
58
|
+
throw new Error(`No live grant on ${chosen.connectionId} for this agent`);
|
|
59
|
+
}
|
|
60
|
+
return { connectionId: chosen.connectionId, grantId: grant.grantId };
|
|
61
|
+
}
|
|
62
|
+
async function withClient(ctx, connectionId, grantId, run) {
|
|
63
|
+
const target = resolveZiggsGatewayTarget({
|
|
64
|
+
type: 'ziggs',
|
|
65
|
+
connectionId,
|
|
66
|
+
grantId,
|
|
67
|
+
operatorKey: ctx['operatorKey'],
|
|
68
|
+
agentId: ctx['agentId'],
|
|
69
|
+
}, { agentId: ctx['agentId'] });
|
|
70
|
+
const transport = new StreamableHTTPClientTransport(target.url, {
|
|
71
|
+
requestInit: { headers: target.headers },
|
|
72
|
+
});
|
|
73
|
+
const client = new Client({ name: 'ziggs-agent-sdk', version: '1.0.0' });
|
|
74
|
+
try {
|
|
75
|
+
await client.connect(transport);
|
|
76
|
+
return await run(client);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
// A leaked session holds an upstream connection open on the gateway.
|
|
80
|
+
await client.close().catch(() => { });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export const mcpToolsListTool = defineTool('mcp_tools_list', {
|
|
84
|
+
connectionId: {
|
|
85
|
+
type: 'string',
|
|
86
|
+
description: 'Connection to inspect. Omit when you hold a grant on exactly one — it is then resolved for you.',
|
|
87
|
+
},
|
|
88
|
+
grantId: {
|
|
89
|
+
type: 'string',
|
|
90
|
+
description: 'Grant to use. Omit to use the live grant found on that connection.',
|
|
91
|
+
},
|
|
92
|
+
}, async (args, ctx) => {
|
|
93
|
+
const t = await resolveTarget(ctx, args['connectionId'], args['grantId']);
|
|
94
|
+
const tools = await withClient(ctx, t.connectionId, t.grantId, async (c) => {
|
|
95
|
+
const res = await c.listTools();
|
|
96
|
+
return (res.tools ?? []).map((tool) => ({
|
|
97
|
+
name: tool.name,
|
|
98
|
+
description: tool.description ?? null,
|
|
99
|
+
}));
|
|
100
|
+
});
|
|
101
|
+
return { connectionId: t.connectionId, grantId: t.grantId, tools };
|
|
102
|
+
}, {
|
|
103
|
+
description: "List the tools a connected MCP server exposes, through the Ziggs gateway — the owner's credential stays server-side. The grant may allow only some of them; a call outside the grant is refused by the gateway, not by you.",
|
|
104
|
+
});
|
|
105
|
+
export const mcpToolCallTool = defineTool('mcp_tool_call', {
|
|
106
|
+
tool: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
required: true,
|
|
109
|
+
description: 'Tool name on the connected MCP server, e.g. list_issues.',
|
|
110
|
+
},
|
|
111
|
+
args: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
description: "Arguments for that tool, as the server's schema defines them.",
|
|
114
|
+
},
|
|
115
|
+
connectionId: {
|
|
116
|
+
type: 'string',
|
|
117
|
+
description: 'Connection to call through. Omit when you hold a grant on exactly one.',
|
|
118
|
+
},
|
|
119
|
+
grantId: {
|
|
120
|
+
type: 'string',
|
|
121
|
+
description: 'Grant to use. Omit to use the live grant found on that connection.',
|
|
122
|
+
},
|
|
123
|
+
}, async (args, ctx) => {
|
|
124
|
+
const name = args['tool'];
|
|
125
|
+
if (!name)
|
|
126
|
+
throw new Error('tool is required');
|
|
127
|
+
const t = await resolveTarget(ctx, args['connectionId'], args['grantId']);
|
|
128
|
+
const result = await withClient(ctx, t.connectionId, t.grantId, (c) => c.callTool({
|
|
129
|
+
name,
|
|
130
|
+
arguments: args['args'] ?? {},
|
|
131
|
+
}));
|
|
132
|
+
return { connectionId: t.connectionId, tool: name, result };
|
|
133
|
+
}, {
|
|
134
|
+
description: "Call one tool on a connected MCP server through the Ziggs gateway. Use this for a connection whose provider is a remote MCP server — connection_proxy is for named connectors (github, jira) and refuses these with \"Unknown provider: mcp\". The gateway injects the owner's token upstream and checks the tool name against your grant, so you never hold the credential. Discover names with mcp_tools_list, or read allowed_actions off the grant in grant_list.",
|
|
135
|
+
});
|
|
136
|
+
export const MCP_CONNECTION_TOOLS = [mcpToolsListTool, mcpToolCallTool];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PAYMENT_CAPABILITIES, paymentBalanceCapability, paymentTransferCapability, paymentWaitForApprovalCapability, paymentHoldCapability, paymentReleaseCapability, paymentResolveWalletCapability, paymentIssueGrantCapability, paymentAttenuateGrantCapability, paymentRevokeGrantCapability, } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
|
|
3
|
-
//
|
|
3
|
+
// the wallet toolset is defined once in api-client's shared
|
|
4
4
|
// capability layer; this file only adapts it onto the SDK surface. Safety is
|
|
5
5
|
// unchanged: money-moving calls stay policy-gated server-side, and there is
|
|
6
6
|
// deliberately NO approve/decide tool on the agent surface.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client-side peek at an operator key's JWT payload (signature NOT verified —
|
|
3
3
|
* identity hint only, the backend remains the authority). Used by the fleet
|
|
4
|
-
* launcher to warn when the key is agent-bound
|
|
4
|
+
* launcher to warn when the key is agent-bound: a bound key
|
|
5
5
|
* collapses the operator multiplex to its one agent and 403s every other
|
|
6
6
|
* per-agent loop, so a large fleet quietly watches a single agent.
|
|
7
7
|
*/
|
|
@@ -14,7 +14,7 @@ export interface OperatorKeyClaims {
|
|
|
14
14
|
}
|
|
15
15
|
export declare function decodeOperatorKeyClaims(token: string): OperatorKeyClaims | null;
|
|
16
16
|
/**
|
|
17
|
-
* The
|
|
17
|
+
* The startup check: warn (never refuse — a one-agent launcher on a
|
|
18
18
|
* bound key is legitimate) when the fleet's control key is bound to a single
|
|
19
19
|
* agent while more than one agent is registered. Returns the warning line, or
|
|
20
20
|
* null when the setup is coherent.
|
|
@@ -18,7 +18,7 @@ export function decodeOperatorKeyClaims(token) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* The
|
|
21
|
+
* The startup check: warn (never refuse — a one-agent launcher on a
|
|
22
22
|
* bound key is legitimate) when the fleet's control key is bound to a single
|
|
23
23
|
* agent while more than one agent is registered. Returns the warning line, or
|
|
24
24
|
* null when the setup is coherent.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The protocol verbs and the state operation each one performs.
|
|
3
3
|
*
|
|
4
|
-
* This is the single place the set is written down
|
|
4
|
+
* This is the single place the set is written down. It used to be
|
|
5
5
|
* restated: the tool definitions passed their operation string to `handlerFor`
|
|
6
6
|
* by hand, and this table listed them again. `agreement_fulfill` was in the
|
|
7
7
|
* first list and not the second, so it dispatched fine but
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The protocol verbs and the state operation each one performs.
|
|
3
3
|
*
|
|
4
|
-
* This is the single place the set is written down
|
|
4
|
+
* This is the single place the set is written down. It used to be
|
|
5
5
|
* restated: the tool definitions passed their operation string to `handlerFor`
|
|
6
6
|
* by hand, and this table listed them again. `agreement_fulfill` was in the
|
|
7
7
|
* first list and not the second, so it dispatched fine but
|
|
@@ -10,7 +10,7 @@ function makeError(message, type, extra) {
|
|
|
10
10
|
return err;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Ajv's own wording for a rejected unknown param is "must NOT have
|
|
14
14
|
* additional properties", which names neither the offending key nor the ones
|
|
15
15
|
* that would have worked. An agent reading that has nothing to correct against,
|
|
16
16
|
* so the typo is named alongside the menu it should have come from.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ToolDefinition } from './defineTool.js';
|
|
2
2
|
import type { CapabilityDefinition } from '@ziggs-ai/api-client';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* the SDK surface adapter for shared capability definitions
|
|
5
5
|
* (api-client `capabilities/`). One capability (schema + handler + guidance)
|
|
6
6
|
* becomes a defineTool ToolDefinition: the neutral param DSL feeds defineTool's
|
|
7
7
|
* JSON-schema converter unchanged, and the handler runs with creds pulled from
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineTool } from './defineTool.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* the SDK surface adapter for shared capability definitions
|
|
4
4
|
* (api-client `capabilities/`). One capability (schema + handler + guidance)
|
|
5
5
|
* becomes a defineTool ToolDefinition: the neutral param DSL feeds defineTool's
|
|
6
6
|
* JSON-schema converter unchanged, and the handler runs with creds pulled from
|
|
@@ -10,8 +10,8 @@ export function toolFromCapability(cap) {
|
|
|
10
10
|
return defineTool(cap.names.sdk, cap.params, async (args, ctx) => {
|
|
11
11
|
const operatorKey = ctx?.['operatorKey'];
|
|
12
12
|
const agentId = ctx?.['agentId'];
|
|
13
|
-
//
|
|
14
|
-
//
|
|
13
|
+
// the lane was already threaded to this exact point by
|
|
14
|
+
//'s shared executor (`chatId: effect.sessionId` in
|
|
15
15
|
// executeToolWithSideEffects); it just was not used to narrow anything.
|
|
16
16
|
// Reading it here makes the fence brain-agnostic by construction: the
|
|
17
17
|
// Claude brain and the FSM brain both arrive through this adapter.
|
package/dist/tools/defineTool.js
CHANGED
|
@@ -48,7 +48,7 @@ export function defineTool(name, params, handler, options = {}) {
|
|
|
48
48
|
throw new Error('defineTool: name is required');
|
|
49
49
|
if (!handler || typeof handler !== 'function')
|
|
50
50
|
throw new Error('defineTool: handler function is required');
|
|
51
|
-
//
|
|
51
|
+
// the top level is closed so a misspelled param is rejected by
|
|
52
52
|
// ToolManager's Ajv pass instead of being dropped on the floor. JSON Schema
|
|
53
53
|
// allows unknown keys by default, so without this the validation ran and
|
|
54
54
|
// passed while the handler quietly ignored what the model asked for.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Creds } from '@ziggs-ai/api-client';
|
|
2
2
|
/**
|
|
3
|
-
* The canonical report shape (B1 convention
|
|
3
|
+
* The canonical report shape (B1 convention).
|
|
4
4
|
* Matches Task.result as set by ziggs_task_set_result.
|
|
5
5
|
*/
|
|
6
6
|
export interface TaskReport {
|
|
@@ -13,7 +13,7 @@ export interface RecordReportInput {
|
|
|
13
13
|
taskId: string;
|
|
14
14
|
report: TaskReport;
|
|
15
15
|
/**
|
|
16
|
-
* Optional container for the deliverable artifact (
|
|
16
|
+
* Optional container for the deliverable artifact (it used to be
|
|
17
17
|
* required). The task binding is what makes the deliverable retrievable and
|
|
18
18
|
* readable by the task's parties; a chat or agreement only widens where it also
|
|
19
19
|
* shows up. Pass at most one; when both arrive, agreementId wins.
|
|
@@ -32,7 +32,7 @@ export interface RecordReportResult {
|
|
|
32
32
|
artifactId?: string;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
* Record a conformant report for a completed task (B2
|
|
35
|
+
* Record a conformant report for a completed task (B2).
|
|
36
36
|
*
|
|
37
37
|
* Always sets Task.result via updateTaskState (the canonical done payload).
|
|
38
38
|
* When `deliverable` is provided, also writes a task-bound result artifact
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ArtifactsClient, updateTaskState, } from '@ziggs-ai/api-client';
|
|
2
2
|
/**
|
|
3
|
-
* Record a conformant report for a completed task (B2
|
|
3
|
+
* Record a conformant report for a completed task (B2).
|
|
4
4
|
*
|
|
5
5
|
* Always sets Task.result via updateTaskState (the canonical done payload).
|
|
6
6
|
* When `deliverable` is provided, also writes a task-bound result artifact
|
|
@@ -10,11 +10,11 @@ import { ArtifactsClient, updateTaskState, } from '@ziggs-ai/api-client';
|
|
|
10
10
|
*/
|
|
11
11
|
export async function recordReport(input) {
|
|
12
12
|
const { creds, taskId, report, chatId, agreementId, deliverable } = input;
|
|
13
|
-
//
|
|
13
|
+
// no container needed. The deliverable is bound to its task, which
|
|
14
14
|
// is what makes it retrievable as `via=task:<taskId>` and readable by the
|
|
15
15
|
// task's parties — a chat or agreement was never what made that work.
|
|
16
16
|
//
|
|
17
|
-
//
|
|
17
|
+
// the agreement still wins here, and this is now the ONE place that
|
|
18
18
|
// resolves rather than refuses — deliberately, because the caller is not a
|
|
19
19
|
// model choosing arguments. `recordReport` is handed both ids by the runtime
|
|
20
20
|
// (the task's agreement and the chat it is running in), so there is no
|