@ziggs-ai/agent-sdk 0.38.2 → 0.38.4
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/README.md +4 -4
- package/dist/Agent.js +2 -1
- package/dist/brain/ClaudeBrain.d.ts +1 -1
- package/dist/brain/ClaudeBrain.js +1 -1
- package/dist/brain/claudeSdkTools.js +5 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -2
- package/dist/launcher/createHealthServer.d.ts +50 -0
- package/dist/launcher/createHealthServer.js +68 -0
- package/dist/launcher/runAgents.d.ts +32 -0
- package/dist/launcher/runAgents.js +119 -0
- package/dist/platform/ZiggsEffectHandler.d.ts +2 -1
- package/dist/platform/ZiggsEffectHandler.js +1 -1
- package/dist/platform/tasks/TaskService.d.ts +3 -2
- package/dist/platform/tasks/TaskService.js +4 -3
- package/dist/platform/tools/index.d.ts +1 -1
- package/dist/platform/tools/index.js +1 -1
- package/dist/platform/tools/tier1/protocolRunner.js +11 -2
- package/dist/platform/tools/tier1/protocolTools.js +8 -6
- package/dist/platform/tools/tier2/contextTools.d.ts +0 -1
- package/dist/platform/tools/tier2/contextTools.js +1 -2
- package/dist/platform/tools/tier2/planTools.js +2 -1
- package/dist/probeChat.d.ts +4 -6
- package/dist/probeChat.js +4 -6
- package/dist/types.d.ts +2 -0
- package/dist/types.js +0 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -131,9 +131,9 @@ configureApiClient({ httpUrl: 'https://api.example.com' });
|
|
|
131
131
|
|
|
132
132
|
A **runner** is the operator’s process. It starts agents, serves health and
|
|
133
133
|
coordinates shutdown. It is not another part of the customer’s agent model.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
`runAgents` and `createHealthServer` are exported from this package. The health
|
|
135
|
+
page answers `/` and `/health`. A process that needs another route, such as
|
|
136
|
+
starting one of our hosted agents, adds it itself.
|
|
137
137
|
|
|
138
138
|
## Migration
|
|
139
139
|
|
|
@@ -146,7 +146,7 @@ This is a breaking pre-beta rename; update imports before publishing this releas
|
|
|
146
146
|
| `ClaudeSdkAgent`, `ClaudeSdkAgentOptions` | `ClaudeBrain`, `ClaudeBrainOptions` |
|
|
147
147
|
| `host.agent` | `agent.brain` |
|
|
148
148
|
| `createMcpAgent(...).host` | `createMcpAgent(...).agent` |
|
|
149
|
-
| SDK `runLauncher`, `createHealthServer` |
|
|
149
|
+
| SDK `runLauncher`, `createHealthServer` | SDK `runAgents`, `createHealthServer` |
|
|
150
150
|
|
|
151
151
|
`createAgent`, `ziggsBrain`, `claudeBrain` and agent IDs on the wire retain their
|
|
152
152
|
names. No backend identity or stored data migration is required.
|
package/dist/Agent.js
CHANGED
|
@@ -314,7 +314,7 @@ export class Agent {
|
|
|
314
314
|
operatorKey: this.options.operatorKey,
|
|
315
315
|
agentId: this.options.agentId,
|
|
316
316
|
};
|
|
317
|
-
const messageSender = async (message, receiverId, sessionId, messageId, conversationAgreementId, underAgreementId, taskId) => {
|
|
317
|
+
const messageSender = async (message, receiverId, sessionId, messageId, conversationAgreementId, underAgreementId, taskId, sources) => {
|
|
318
318
|
const sentId = messageId ?? `sdk_${randomUUID()}`;
|
|
319
319
|
const result = await sendChatMessage({
|
|
320
320
|
chatId: sessionId,
|
|
@@ -330,6 +330,7 @@ export class Agent {
|
|
|
330
330
|
conversationAgreementId,
|
|
331
331
|
underAgreementId,
|
|
332
332
|
taskId,
|
|
333
|
+
...(sources ? { sources } : {}),
|
|
333
334
|
}, creds);
|
|
334
335
|
if (!result.success)
|
|
335
336
|
throw new Error('sendChatMessage: the server did not accept this message — nothing was delivered and nobody was woken. Retry with the same messageId, which the backend dedups on.');
|
|
@@ -5,7 +5,7 @@ import type { ToolManager } from '../tools/ToolManager.js';
|
|
|
5
5
|
import { type RawEvent } from '../runtime/buildOutcome.js';
|
|
6
6
|
import type { TickInput, TickOutput } from './ZiggsBrain.js';
|
|
7
7
|
import { type AskWakeGate } from './wakeGate.js';
|
|
8
|
-
export { isPublishProbeChat
|
|
8
|
+
export { isPublishProbeChat } from '../probeChat.js';
|
|
9
9
|
/**
|
|
10
10
|
* status notices must not spend a Claude turn answering
|
|
11
11
|
* nobody. Real work wakes arrive as chat `message`, synthetic `task_result`
|
|
@@ -17,7 +17,7 @@ import { filterStandingOfferNotices, isReadableAgreementNoticeLane } from './sta
|
|
|
17
17
|
import { formedByFromWake, shouldSkipAgentFormedHireActivation, } from './hireActivationNotice.js';
|
|
18
18
|
import { isPublishProbeChat } from '../probeChat.js';
|
|
19
19
|
import { gateableMessages, wakeAgreementId } from './wakeGate.js';
|
|
20
|
-
export { isPublishProbeChat
|
|
20
|
+
export { isPublishProbeChat } from '../probeChat.js';
|
|
21
21
|
/**
|
|
22
22
|
* status notices must not spend a Claude turn answering
|
|
23
23
|
* nobody. Real work wakes arrive as chat `message`, synthetic `task_result`
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SOURCES_PARAM, sourcesArg } from '@ziggs-ai/api-client';
|
|
1
2
|
import { isAgreementId, agreementLaneId } from '@ziggs-ai/api-client';
|
|
2
3
|
import { randomUUID } from 'node:crypto';
|
|
3
4
|
import { credsFromToolContext } from '../tools/toolCreds.js';
|
|
@@ -77,8 +78,10 @@ export const chatSendTool = defineTool('chat_send', {
|
|
|
77
78
|
receiverId: { type: 'string', description: 'Name someone only when you mean them. Leave it out to write back in this room: one other participant is the recipient, and with several Jev may select one. Otherwise the message stays in the room with no specific wake. To name a masked party, pass its rpb_ ref as it came. Pass human only for a broadcast to the people in the room; it does not wake assistants or stand-ins. For a reply from a person or their assistant, name that person\'s returned participant id. This does not grant their assistant access or guarantee a reply.' },
|
|
78
79
|
text: { type: 'string', required: true },
|
|
79
80
|
idempotencyKey: { type: 'string', description: 'Reuse for retry-safe resend of the same logical message.' },
|
|
81
|
+
sources: SOURCES_PARAM,
|
|
80
82
|
}, async (args, ctx) => {
|
|
81
83
|
const c = ctx;
|
|
84
|
+
const sources = sourcesArg(args);
|
|
82
85
|
const explicitChatId = asChatLane(firstFilled(args.chatId));
|
|
83
86
|
const to = firstFilled(args.to);
|
|
84
87
|
const text = String(args.text);
|
|
@@ -104,6 +107,7 @@ export const chatSendTool = defineTool('chat_send', {
|
|
|
104
107
|
underAgreementId: firstFilled(args.underAgreementId, c.serviceAgreementId),
|
|
105
108
|
taskId: firstFilled(args.taskId),
|
|
106
109
|
...(namedReceiver ? { receiverId: namedReceiver } : {}),
|
|
110
|
+
...sources,
|
|
107
111
|
}, creds);
|
|
108
112
|
// Never presented as a send that happened: the message does not exist,
|
|
109
113
|
// so whoever was waiting for it is still waiting. Retrying with the same
|
|
@@ -141,6 +145,7 @@ export const chatSendTool = defineTool('chat_send', {
|
|
|
141
145
|
...(receiverId ? { receiverId } : {}),
|
|
142
146
|
text,
|
|
143
147
|
messageId,
|
|
148
|
+
...sources,
|
|
144
149
|
});
|
|
145
150
|
},
|
|
146
151
|
// This line used to read "Conversation only — never use for
|
package/dist/index.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export { runMechanical } from './runtime/runMechanical.js';
|
|
|
41
41
|
export { PromptBuilder } from './runtime/PromptBuilder.js';
|
|
42
42
|
export { thinkingDefaults, defineThinkingState, defineMechanicalState, DEFAULT_WAIT_PROMPT } from './runtime/defaults.js';
|
|
43
43
|
export { KICKOFF_STATE, CLAIM_KICKOFF_STATE, injectKickoffDefault, buildKickoffState, buildClaimKickoffState, handlesClaimWake, } from './runtime/kickoff.js';
|
|
44
|
-
export { validateWorkflow, WorkflowValidationError } from './runtime/validateWorkflow.js';
|
|
44
|
+
export { validateWorkflow, WorkflowValidationError, PROBE_CTX } from './runtime/validateWorkflow.js';
|
|
45
45
|
export { outcomeFromEvent, outcomeFromActionResult, applyOutcomeToCtx } from './runtime/buildOutcome.js';
|
|
46
46
|
export { LANE_BOOK_KEY, allCollectedResults, allDelegatedAgreementIds, allPendingSubtasks, } from './runtime/taskBookkeeping.js';
|
|
47
47
|
export { createDeliveryWriteKeys } from './runtime/deliveryWriteKeys.js';
|
|
@@ -67,7 +67,7 @@ export { HistoryFormatter, historyFormatter, AgreementFormatter, agreementFormat
|
|
|
67
67
|
export { ProactiveTrigger } from './platform/proactive/ProactiveTrigger.js';
|
|
68
68
|
export { PollRegistry, declaredPolls, type PollBinding, type PollContext, type PollKind, type PollResult, } from './platform/polls/PollKind.js';
|
|
69
69
|
export { PollRunner, type PollRunnerOptions, type WakeTarget } from './platform/polls/PollRunner.js';
|
|
70
|
-
export { PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp, PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool,
|
|
70
|
+
export { PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp, PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, LISTING_TOOLS, } from './platform/tools/index.js';
|
|
71
71
|
export { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
|
|
72
72
|
export { hostedModelTools, isHostedDeferred, hostedToolLane } from './brain/hostedToolSurface.js';
|
|
73
73
|
export { PROTOCOL_TOOL_NAMES, PROTOCOL_TOOL_TO_OPERATION, mapProtocolToolToOperation, isProtocolToolName } from './tasks/protocolRegistry.js';
|
|
@@ -83,3 +83,7 @@ export type { ApiClientConfig } from '@ziggs-ai/api-client';
|
|
|
83
83
|
export { SeenMessages } from './platform/SeenMessages.js';
|
|
84
84
|
export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
|
|
85
85
|
export type { RuntimeLogLevel } from './shared/runtimeLog.js';
|
|
86
|
+
export { runAgents } from './launcher/runAgents.js';
|
|
87
|
+
export type { RunnerOptions, SingleAgentRun, FleetRun } from './launcher/runAgents.js';
|
|
88
|
+
export { createHealthServer, inboxHealth } from './launcher/createHealthServer.js';
|
|
89
|
+
export type { AgentLiveness, HealthRequest, HealthRequestHandler } from './launcher/createHealthServer.js';
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ export { runMechanical } from './runtime/runMechanical.js';
|
|
|
42
42
|
export { PromptBuilder } from './runtime/PromptBuilder.js';
|
|
43
43
|
export { thinkingDefaults, defineThinkingState, defineMechanicalState, DEFAULT_WAIT_PROMPT } from './runtime/defaults.js';
|
|
44
44
|
export { KICKOFF_STATE, CLAIM_KICKOFF_STATE, injectKickoffDefault, buildKickoffState, buildClaimKickoffState, handlesClaimWake, } from './runtime/kickoff.js';
|
|
45
|
-
export { validateWorkflow, WorkflowValidationError } from './runtime/validateWorkflow.js';
|
|
45
|
+
export { validateWorkflow, WorkflowValidationError, PROBE_CTX } from './runtime/validateWorkflow.js';
|
|
46
46
|
export { outcomeFromEvent, outcomeFromActionResult, applyOutcomeToCtx } from './runtime/buildOutcome.js';
|
|
47
47
|
export { LANE_BOOK_KEY, allCollectedResults, allDelegatedAgreementIds, allPendingSubtasks, } from './runtime/taskBookkeeping.js';
|
|
48
48
|
export { createDeliveryWriteKeys } from './runtime/deliveryWriteKeys.js';
|
|
@@ -79,7 +79,7 @@ export {
|
|
|
79
79
|
// Tier 1 — protocol grammar (framework-managed via taskTools).
|
|
80
80
|
PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp,
|
|
81
81
|
// Tier 2 — opt-in capability bundles (spread into `tools:`).
|
|
82
|
-
PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool,
|
|
82
|
+
PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, LISTING_TOOLS, } from './platform/tools/index.js';
|
|
83
83
|
// The always-on toolset for the hosted Claude cognition (parity guard
|
|
84
84
|
// reads this as part of the real SDK registry).
|
|
85
85
|
export { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
|
|
@@ -96,3 +96,5 @@ export { MessagesClient, ArtifactsClient, AgentSearchClient, getBackendUrl,
|
|
|
96
96
|
configureApiClient, } from '@ziggs-ai/api-client';
|
|
97
97
|
export { SeenMessages } from './platform/SeenMessages.js';
|
|
98
98
|
export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
|
|
99
|
+
export { runAgents } from './launcher/runAgents.js';
|
|
100
|
+
export { createHealthServer, inboxHealth } from './launcher/createHealthServer.js';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type IncomingMessage, type Server, type ServerResponse } from 'node:http';
|
|
2
|
+
import type { Agent } from '../Agent.js';
|
|
3
|
+
import type { ConnectionPool } from '../platform/ConnectionPool.js';
|
|
4
|
+
/** What one agent reports about its own inbox reading. */
|
|
5
|
+
export type AgentLiveness = ReturnType<Agent['inboxLiveness']>;
|
|
6
|
+
/**
|
|
7
|
+
* The health verdict. A process is alive when its agents are still reading,
|
|
8
|
+
* so the answer is derived from the inbox loop rather than from the fact that
|
|
9
|
+
* this HTTP server replied — a wedged process replies happily while none of
|
|
10
|
+
* its agents read anything.
|
|
11
|
+
*
|
|
12
|
+
* The restart decision reads `lastAttemptAt`, not `lastPassAt`. When the
|
|
13
|
+
* backend is down every pass fails while the loops are perfectly healthy, and
|
|
14
|
+
* restarting the fleet would fix nothing and lose in-flight turns. Read
|
|
15
|
+
* staleness is still reported per agent, for the store and for monitoring.
|
|
16
|
+
*/
|
|
17
|
+
export declare function inboxHealth(rows: AgentLiveness[], now?: number, staleMs?: number): {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
reason?: string;
|
|
20
|
+
staleMs: number;
|
|
21
|
+
watching: number;
|
|
22
|
+
oldestAttemptAgeMs: number | null;
|
|
23
|
+
oldestPassAgeMs: number | null;
|
|
24
|
+
agents: (AgentLiveness & {
|
|
25
|
+
passAgeMs: number | null;
|
|
26
|
+
attemptAgeMs: number | null;
|
|
27
|
+
})[];
|
|
28
|
+
};
|
|
29
|
+
export type HealthRequest = {
|
|
30
|
+
req: IncomingMessage;
|
|
31
|
+
res: ServerResponse;
|
|
32
|
+
pool?: ConnectionPool;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* A route the embedding process adds. Return true once the response is sent.
|
|
36
|
+
* The health page itself only answers `/` and `/health`.
|
|
37
|
+
*/
|
|
38
|
+
export type HealthRequestHandler = (call: HealthRequest) => Promise<boolean>;
|
|
39
|
+
export declare function createHealthServer({ port, label, pool, liveness, onRequest, }?: {
|
|
40
|
+
port?: number;
|
|
41
|
+
label?: string;
|
|
42
|
+
pool?: ConnectionPool;
|
|
43
|
+
/**
|
|
44
|
+
* Every agent this process is responsible for. Omit only when there is
|
|
45
|
+
* nothing to watch: without it `/health` can say no more than "this server
|
|
46
|
+
* replied", which is the answer this route exists to stop giving.
|
|
47
|
+
*/
|
|
48
|
+
liveness?: () => AgentLiveness[];
|
|
49
|
+
onRequest?: HealthRequestHandler;
|
|
50
|
+
}): Server;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
3
|
+
/**
|
|
4
|
+
* How long an agent's inbox loop may go without turning before this process is
|
|
5
|
+
* called unhealthy. An idle loop completes a pass every `DEFAULT_WAIT_SECONDS`
|
|
6
|
+
* (110s), so this is three missed cycles plus slack: long enough that a slow
|
|
7
|
+
* server or a long turn never flaps, short enough that a wedged process is
|
|
8
|
+
* replaced within minutes.
|
|
9
|
+
*/
|
|
10
|
+
const STALE_MS = Number(process.env.FLEET_INBOX_STALE_MS) || 360_000;
|
|
11
|
+
function age(now, at) {
|
|
12
|
+
return at === null ? null : now - at;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The health verdict. A process is alive when its agents are still reading,
|
|
16
|
+
* so the answer is derived from the inbox loop rather than from the fact that
|
|
17
|
+
* this HTTP server replied — a wedged process replies happily while none of
|
|
18
|
+
* its agents read anything.
|
|
19
|
+
*
|
|
20
|
+
* The restart decision reads `lastAttemptAt`, not `lastPassAt`. When the
|
|
21
|
+
* backend is down every pass fails while the loops are perfectly healthy, and
|
|
22
|
+
* restarting the fleet would fix nothing and lose in-flight turns. Read
|
|
23
|
+
* staleness is still reported per agent, for the store and for monitoring.
|
|
24
|
+
*/
|
|
25
|
+
export function inboxHealth(rows, now = Date.now(), staleMs = STALE_MS) {
|
|
26
|
+
const agents = rows.map((r) => ({
|
|
27
|
+
...r,
|
|
28
|
+
passAgeMs: age(now, r.lastPassAt),
|
|
29
|
+
attemptAgeMs: age(now, r.lastAttemptAt),
|
|
30
|
+
}));
|
|
31
|
+
// Only agents that are supposed to be polling can be stale. A connected
|
|
32
|
+
// agent with the inbox loop off (lab and eval paths) has nothing to report,
|
|
33
|
+
// and an agent that was deliberately stopped is not this process failing.
|
|
34
|
+
const watched = agents.filter((r) => r.pollingInbox && r.connected);
|
|
35
|
+
const turning = watched.filter((r) => r.attemptAgeMs !== null && r.attemptAgeMs <= staleMs);
|
|
36
|
+
const ages = watched.map((r) => r.attemptAgeMs).filter((n) => n !== null);
|
|
37
|
+
const passAges = watched.map((r) => r.passAgeMs).filter((n) => n !== null);
|
|
38
|
+
const ok = watched.length === 0 || turning.length > 0;
|
|
39
|
+
return {
|
|
40
|
+
ok,
|
|
41
|
+
...(ok ? {} : { reason: `no inbox pass attempted by any of ${watched.length} agent(s) within ${staleMs}ms` }),
|
|
42
|
+
staleMs,
|
|
43
|
+
watching: watched.length,
|
|
44
|
+
oldestAttemptAgeMs: ages.length ? Math.max(...ages) : null,
|
|
45
|
+
oldestPassAgeMs: passAges.length ? Math.max(...passAges) : null,
|
|
46
|
+
agents,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function createHealthServer({ port = Number(process.env.PORT) || 8080, label = 'agents', pool, liveness, onRequest, } = {}) {
|
|
50
|
+
const server = createServer(async (req, res) => {
|
|
51
|
+
if (await onRequest?.({ req, res, pool }))
|
|
52
|
+
return;
|
|
53
|
+
const url = req.url ?? '/';
|
|
54
|
+
if (url === '/health' || url === '/') {
|
|
55
|
+
const report = inboxHealth(liveness?.() ?? []);
|
|
56
|
+
if (!report.ok) {
|
|
57
|
+
runtimeLog.warn(label, `health: ${report.reason}`);
|
|
58
|
+
}
|
|
59
|
+
res.writeHead(report.ok ? 200 : 503, { 'Content-Type': 'application/json' });
|
|
60
|
+
res.end(JSON.stringify(report));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
res.writeHead(404);
|
|
64
|
+
res.end();
|
|
65
|
+
});
|
|
66
|
+
server.listen(port, () => runtimeLog.info(label, `health server on port ${port}`));
|
|
67
|
+
return server;
|
|
68
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Agent, type AgentOptions } from '../Agent.js';
|
|
2
|
+
import { ConnectionPool } from '../platform/ConnectionPool.js';
|
|
3
|
+
import { type HealthRequestHandler } from './createHealthServer.js';
|
|
4
|
+
import type { Server } from 'node:http';
|
|
5
|
+
/** Process concerns only. Every Agent owns its inbox and external polls. */
|
|
6
|
+
export interface RunnerOptions {
|
|
7
|
+
operatorKey?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
port?: number | string;
|
|
10
|
+
healthServer?: boolean;
|
|
11
|
+
/** Disable when the embedding process owns its signal handlers. */
|
|
12
|
+
handleSignals?: boolean;
|
|
13
|
+
drainTimeoutMs?: number;
|
|
14
|
+
onShutdown?: () => Promise<void>;
|
|
15
|
+
agentMeta?: Record<string, unknown>[];
|
|
16
|
+
primary?: AgentOptions | ((pool: ConnectionPool) => AgentOptions);
|
|
17
|
+
/** A route this process adds. The health page itself only answers `/` and `/health`. */
|
|
18
|
+
onRequest?: HealthRequestHandler;
|
|
19
|
+
}
|
|
20
|
+
export interface SingleAgentRun {
|
|
21
|
+
agent: Agent;
|
|
22
|
+
healthServer?: Server;
|
|
23
|
+
shutdown(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export interface FleetRun {
|
|
26
|
+
pool: ConnectionPool;
|
|
27
|
+
primary: Agent | null;
|
|
28
|
+
healthServer?: Server;
|
|
29
|
+
shutdown(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export declare function runAgents(config: AgentOptions, options?: RunnerOptions): Promise<SingleAgentRun>;
|
|
32
|
+
export declare function runAgents(configs: AgentOptions[], options?: RunnerOptions): Promise<FleetRun>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { createAgent } from '../Agent.js';
|
|
2
|
+
import { ConnectionPool } from '../platform/ConnectionPool.js';
|
|
3
|
+
import { boundKeyFleetWarning } from '../shared/operatorKeyClaims.js';
|
|
4
|
+
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
5
|
+
import { createHealthServer } from './createHealthServer.js';
|
|
6
|
+
const DRAIN_TIMEOUT_MS = Number(process.env.FLEET_DRAIN_TIMEOUT_MS) || 90_000;
|
|
7
|
+
function shutdownHandler(stop, server, options) {
|
|
8
|
+
let pending;
|
|
9
|
+
const shutdown = () => {
|
|
10
|
+
if (!pending) {
|
|
11
|
+
process.off('SIGTERM', signal);
|
|
12
|
+
process.off('SIGINT', signal);
|
|
13
|
+
pending = (async () => {
|
|
14
|
+
try {
|
|
15
|
+
await stop();
|
|
16
|
+
await options.onShutdown?.();
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
if (server)
|
|
20
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
23
|
+
}
|
|
24
|
+
return pending;
|
|
25
|
+
};
|
|
26
|
+
const signal = () => {
|
|
27
|
+
runtimeLog.info(options.label || 'runner', 'Shutting down');
|
|
28
|
+
void shutdown().then(() => process.exit(0), (err) => {
|
|
29
|
+
runtimeLog.error(options.label || 'runner', `Shutdown failed: ${err.message}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
if (options.handleSignals !== false) {
|
|
34
|
+
process.on('SIGTERM', signal);
|
|
35
|
+
process.on('SIGINT', signal);
|
|
36
|
+
}
|
|
37
|
+
return shutdown;
|
|
38
|
+
}
|
|
39
|
+
async function healthServer(options, liveness, pool) {
|
|
40
|
+
if (options.healthServer === false)
|
|
41
|
+
return undefined;
|
|
42
|
+
const server = createHealthServer({
|
|
43
|
+
port: Number(options.port ?? process.env.PORT ?? 8080),
|
|
44
|
+
label: options.label || 'runner',
|
|
45
|
+
pool,
|
|
46
|
+
liveness,
|
|
47
|
+
onRequest: options.onRequest,
|
|
48
|
+
});
|
|
49
|
+
await new Promise((resolve, reject) => {
|
|
50
|
+
server.once('error', reject);
|
|
51
|
+
server.once('listening', () => { server.off('error', reject); resolve(); });
|
|
52
|
+
});
|
|
53
|
+
return server;
|
|
54
|
+
}
|
|
55
|
+
export async function runAgents(configOrConfigs, options = {}) {
|
|
56
|
+
const effectiveKey = options.operatorKey ?? process.env.ZIGGS_OPERATOR_KEY;
|
|
57
|
+
const decorate = (config) => effectiveKey && !config.operatorKey ? { ...config, operatorKey: effectiveKey } : config;
|
|
58
|
+
const drainTimeout = options.drainTimeoutMs ?? DRAIN_TIMEOUT_MS;
|
|
59
|
+
if (!Array.isArray(configOrConfigs)) {
|
|
60
|
+
const agent = createAgent(decorate(configOrConfigs));
|
|
61
|
+
let server;
|
|
62
|
+
// One budget for stopping intake and finishing in-flight work: the inbox
|
|
63
|
+
// pass gets up to the whole of it to acknowledge, the queue what is left.
|
|
64
|
+
const stop = async () => {
|
|
65
|
+
const deadline = Date.now() + drainTimeout;
|
|
66
|
+
await agent.disconnect(drainTimeout);
|
|
67
|
+
await agent.drain(Math.max(0, deadline - Date.now()));
|
|
68
|
+
};
|
|
69
|
+
try {
|
|
70
|
+
await agent.connectAsync();
|
|
71
|
+
server = await healthServer(options, () => [agent.inboxLiveness()]);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
await stop();
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
const shutdown = shutdownHandler(stop, server, options);
|
|
78
|
+
return { agent, healthServer: server, shutdown };
|
|
79
|
+
}
|
|
80
|
+
const pool = new ConnectionPool();
|
|
81
|
+
const configs = configOrConfigs.map(decorate);
|
|
82
|
+
pool.register(configs, options.agentMeta);
|
|
83
|
+
const primaryConfig = typeof options.primary === 'function' ? options.primary(pool) : options.primary;
|
|
84
|
+
let primary = null;
|
|
85
|
+
let server;
|
|
86
|
+
// Same single budget as the one-agent path, shared by the pool and the primary.
|
|
87
|
+
const stop = async () => {
|
|
88
|
+
const deadline = Date.now() + drainTimeout;
|
|
89
|
+
await Promise.all([primary?.disconnect(drainTimeout), pool.shutdown(drainTimeout)]);
|
|
90
|
+
await primary?.drain(Math.max(0, deadline - Date.now()));
|
|
91
|
+
};
|
|
92
|
+
try {
|
|
93
|
+
if (primaryConfig)
|
|
94
|
+
primary = createAgent(decorate(primaryConfig));
|
|
95
|
+
const controlKey = effectiveKey ?? configs.find(c => c.operatorKey)?.operatorKey ?? primary?.options.operatorKey;
|
|
96
|
+
if (controlKey) {
|
|
97
|
+
const warning = boundKeyFleetWarning(controlKey, pool.size + (primary ? 1 : 0));
|
|
98
|
+
if (warning)
|
|
99
|
+
runtimeLog.warn(options.label || 'runner', warning);
|
|
100
|
+
}
|
|
101
|
+
await primary?.connectAsync();
|
|
102
|
+
await pool.connectAll();
|
|
103
|
+
const connected = pool.listActive().length;
|
|
104
|
+
if (configs.length > 0 && connected === 0) {
|
|
105
|
+
throw new Error(`0/${configs.length} agents started; process must restart`);
|
|
106
|
+
}
|
|
107
|
+
if (connected !== pool.size || pool.size !== configs.length) {
|
|
108
|
+
runtimeLog.warn(options.label || 'runner', `Only ${connected}/${configs.length} agents started — hosting the ones that came up`);
|
|
109
|
+
}
|
|
110
|
+
server = await healthServer(options, () => [...(primary ? [primary.inboxLiveness()] : []), ...pool.liveness()], pool);
|
|
111
|
+
runtimeLog.info(options.label || 'runner', `Ready — ${connected}/${pool.size} agents started`);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
await stop();
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
const shutdown = shutdownHandler(stop, server, options);
|
|
118
|
+
return { pool, primary, healthServer: server, shutdown };
|
|
119
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WriteSource } from '@ziggs-ai/api-client';
|
|
1
2
|
/**
|
|
2
3
|
* Ziggs-flavored EffectHandler.
|
|
3
4
|
*
|
|
@@ -36,7 +37,7 @@ export interface ZiggsEffectDeps {
|
|
|
36
37
|
contextReadClient: ContextReadClient;
|
|
37
38
|
taskService: TaskService;
|
|
38
39
|
agreementService: AgreementService;
|
|
39
|
-
messageSender: (text: string, receiverId: string | undefined, sessionId: string, messageId?: string, conversationAgreementId?: string, underAgreementId?: string, taskId?: string) => Promise<unknown>;
|
|
40
|
+
messageSender: (text: string, receiverId: string | undefined, sessionId: string, messageId?: string, conversationAgreementId?: string, underAgreementId?: string, taskId?: string, sources?: WriteSource[]) => Promise<unknown>;
|
|
40
41
|
/** Sends a streaming chunk to a client. Called per-token during stream-text effects. */
|
|
41
42
|
chunkSender?: (sessionId: string, receiverId: string, text: string, messageId: string) => void;
|
|
42
43
|
toolManager: ToolManager;
|
|
@@ -318,7 +318,7 @@ export function createZiggsEffectHandler(deps) {
|
|
|
318
318
|
});
|
|
319
319
|
}
|
|
320
320
|
case 'send-message': {
|
|
321
|
-
const sent = await deps.messageSender(effect.text, effect.receiverId, effect.sessionId, effect.messageId, effect.conversationAgreementId, effect.underAgreementId, effect.taskId);
|
|
321
|
+
const sent = await deps.messageSender(effect.text, effect.receiverId, effect.sessionId, effect.messageId, effect.conversationAgreementId, effect.underAgreementId, effect.taskId, effect.sources);
|
|
322
322
|
outbox.track(effect.sessionId, { text: effect.text, receiverId: effect.receiverId });
|
|
323
323
|
// Carry the send's own report back to whoever asked for the send. A
|
|
324
324
|
// sender that cannot see where its message went cannot tell a delivered
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WriteSource } from '@ziggs-ai/api-client';
|
|
1
2
|
import { type Task, type TaskState } from '@ziggs-ai/api-client';
|
|
2
3
|
import type { CountTasksFilters, CreateTaskData } from '@ziggs-ai/api-client';
|
|
3
4
|
export declare class TaskService {
|
|
@@ -9,7 +10,7 @@ export declare class TaskService {
|
|
|
9
10
|
getActiveTasksForChat(chatId: string): Promise<Task[]>;
|
|
10
11
|
getSubtasks(parentTaskId: string): Promise<Task[]>;
|
|
11
12
|
spawnUnderAgreement(agreementId: string, taskData: Omit<CreateTaskData, 'agreementId'>): Promise<Task>;
|
|
12
|
-
updateState(taskId: string, state: TaskState, result?: unknown, idempotencyKey?: string): Promise<{
|
|
13
|
+
updateState(taskId: string, state: TaskState, result?: unknown, idempotencyKey?: string, sources?: WriteSource[]): Promise<{
|
|
13
14
|
ok: true;
|
|
14
15
|
taskId: string;
|
|
15
16
|
state: string;
|
|
@@ -46,7 +47,7 @@ export declare class TaskService {
|
|
|
46
47
|
updatePlanStep(taskId: string, stepId: string, status: string, result?: unknown, waitingOn?: {
|
|
47
48
|
kind: 'user' | 'agent';
|
|
48
49
|
id: string;
|
|
49
|
-
} | null): Promise<import("@ziggs-ai/api-client").TaskWriteConfirm>;
|
|
50
|
+
} | null, sources?: WriteSource[]): Promise<import("@ziggs-ai/api-client").TaskWriteConfirm>;
|
|
50
51
|
cancel(taskId: string): Promise<import("@ziggs-ai/api-client").TaskWriteConfirm>;
|
|
51
52
|
report(taskId: string, message?: string): Promise<import("@ziggs-ai/api-client").TaskWriteConfirm>;
|
|
52
53
|
setSatisfaction(taskId: string, satisfaction: 'positive' | 'negative'): Promise<import("@ziggs-ai/api-client").TaskWriteConfirm>;
|
|
@@ -60,11 +60,12 @@ export class TaskService {
|
|
|
60
60
|
throw error;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
async updateState(taskId, state, result, idempotencyKey) {
|
|
63
|
+
async updateState(taskId, state, result, idempotencyKey, sources) {
|
|
64
64
|
try {
|
|
65
65
|
const data = {
|
|
66
66
|
...(result !== undefined ? { result: safeJsonStringify(result) } : {}),
|
|
67
67
|
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
68
|
+
...(sources ? { sources } : {}),
|
|
68
69
|
};
|
|
69
70
|
const updated = await updateTaskState(taskId, state, data, this.creds);
|
|
70
71
|
runtimeLog.info('TaskService', `Task state: ${taskId} → ${state}`);
|
|
@@ -97,9 +98,9 @@ export class TaskService {
|
|
|
97
98
|
throw error;
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
|
-
async updatePlanStep(taskId, stepId, status, result, waitingOn) {
|
|
101
|
+
async updatePlanStep(taskId, stepId, status, result, waitingOn, sources) {
|
|
101
102
|
try {
|
|
102
|
-
const updated = await updateTaskPlanStep(taskId, stepId, status, result, this.creds, waitingOn);
|
|
103
|
+
const updated = await updateTaskPlanStep(taskId, stepId, status, result, this.creds, waitingOn, sources);
|
|
103
104
|
runtimeLog.info('TaskService', `Task plan step: ${taskId}/${stepId} → ${status}`);
|
|
104
105
|
return updated;
|
|
105
106
|
}
|
|
@@ -21,7 +21,7 @@ export { DISCOVERY_TOOLS, agentSearchTool, agentGetTool } from './tier2/discover
|
|
|
21
21
|
export { MARKETPLACE_TOOLS, marketplaceViewTool } from './tier2/marketplaceTools.js';
|
|
22
22
|
export { PAYMENT_TOOLS, paymentBalanceTool } from './tier2/paymentTools.js';
|
|
23
23
|
export { CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, } from './tier2/connectionTools.js';
|
|
24
|
-
export { CONTEXT_TOOLS, contextReadTool,
|
|
24
|
+
export { CONTEXT_TOOLS, contextReadTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
|
|
25
25
|
export { GRANTS_TOOLS, listGrantsTool } from './tier2/grantsTools.js';
|
|
26
26
|
export { TASK_TOOLS, listTasksTool, cancelTaskTool } from './tier2/taskTools.js';
|
|
27
27
|
export { PLAN_TOOLS, taskReplacePlanTool } from './tier2/planTools.js';
|
|
@@ -23,7 +23,7 @@ export { DISCOVERY_TOOLS, agentSearchTool, agentGetTool } from './tier2/discover
|
|
|
23
23
|
export { MARKETPLACE_TOOLS, marketplaceViewTool } from './tier2/marketplaceTools.js';
|
|
24
24
|
export { PAYMENT_TOOLS, paymentBalanceTool } from './tier2/paymentTools.js';
|
|
25
25
|
export { CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, } from './tier2/connectionTools.js';
|
|
26
|
-
export { CONTEXT_TOOLS, contextReadTool,
|
|
26
|
+
export { CONTEXT_TOOLS, contextReadTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
|
|
27
27
|
// one grant lister across every rail (retires context_discover +
|
|
28
28
|
// payment_list_grants). Always-available on the hosted agent — see CLAUDE_SDK_TOOLS.
|
|
29
29
|
export { GRANTS_TOOLS, listGrantsTool } from './tier2/grantsTools.js';
|
|
@@ -45,6 +45,12 @@ function normalizePlanInPlace(payload) {
|
|
|
45
45
|
? 'before_execution'
|
|
46
46
|
: 'with_proposal';
|
|
47
47
|
}
|
|
48
|
+
/** The `sources` a protocol call carried, when it carried a list. */
|
|
49
|
+
function sourcesOf(payload) {
|
|
50
|
+
return Array.isArray(payload.sources)
|
|
51
|
+
? { sources: payload.sources }
|
|
52
|
+
: {};
|
|
53
|
+
}
|
|
48
54
|
export async function dispatchProtocolOp(services, payload, chatId, _agents = [], actingAgentId = null, writeKeys = null,
|
|
49
55
|
/** #8 — prefer the cached session snapshot over a fresh HTTP read. */
|
|
50
56
|
cachedAgreements) {
|
|
@@ -93,6 +99,7 @@ cachedAgreements) {
|
|
|
93
99
|
lifecycle: payload.lifecycle,
|
|
94
100
|
maxExecutions: payload.maxExecutions,
|
|
95
101
|
description: payload.description,
|
|
102
|
+
...sourcesOf(payload),
|
|
96
103
|
});
|
|
97
104
|
case 'agreement-check-proposal':
|
|
98
105
|
if (!payload.agreementId)
|
|
@@ -182,6 +189,7 @@ cachedAgreements) {
|
|
|
182
189
|
...(payload.checkBack && typeof payload.checkBack === 'object'
|
|
183
190
|
? { checkBack: payload.checkBack }
|
|
184
191
|
: {}),
|
|
192
|
+
...sourcesOf(payload),
|
|
185
193
|
// Wake-derived idempotency key (delivery law): a crash-replay re-issuing
|
|
186
194
|
// this spawn resolves to the already-created task server-side instead of
|
|
187
195
|
// creating a duplicate. Keyed on what is actually written (the resolved
|
|
@@ -204,7 +212,7 @@ cachedAgreements) {
|
|
|
204
212
|
const state = normalizeState(terminalRaw);
|
|
205
213
|
// Wake-derived idempotency key: a crash-replay re-issuing this transition
|
|
206
214
|
// no-ops server-side instead of erroring on the already-terminal task.
|
|
207
|
-
return taskService.updateState(payload.taskId, state, payload.result, writeKeys?.taskStateKey(payload.taskId, state));
|
|
215
|
+
return taskService.updateState(payload.taskId, state, payload.result, writeKeys?.taskStateKey(payload.taskId, state), sourcesOf(payload).sources);
|
|
208
216
|
}
|
|
209
217
|
case 'task-update-plan-step': {
|
|
210
218
|
if (!payload.taskId)
|
|
@@ -225,7 +233,7 @@ cachedAgreements) {
|
|
|
225
233
|
// a stepId that isn't in the plan — the backend 400s on both. That must never
|
|
226
234
|
// hard-fail the tool → fail the task → wake-loop. Make it best-effort.
|
|
227
235
|
try {
|
|
228
|
-
return await taskService.updatePlanStep(payload.taskId, payload.stepId, payload.status, payload.result, waitingOn);
|
|
236
|
+
return await taskService.updatePlanStep(payload.taskId, payload.stepId, payload.status, payload.result, waitingOn, sourcesOf(payload).sources);
|
|
229
237
|
}
|
|
230
238
|
catch {
|
|
231
239
|
return { ok: false, skipped: 'plan-step-not-updatable', stepId: payload.stepId };
|
|
@@ -286,6 +294,7 @@ async function proposeAgreement(services, payload, chatId, writeKeys = null) {
|
|
|
286
294
|
billing,
|
|
287
295
|
...(payload.conversationAllowance !== undefined ? { conversationAllowance: payload.conversationAllowance } : {}),
|
|
288
296
|
agreementDescription: payload.agreementDescription,
|
|
297
|
+
...sourcesOf(payload),
|
|
289
298
|
parentAgreementId: payload.parentAgreementId,
|
|
290
299
|
engagementKind,
|
|
291
300
|
idempotencyKey,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineTool } from '../../../tools/defineTool.js';
|
|
2
2
|
import { requireProtocolOperation } from '../../../tasks/protocolRegistry.js';
|
|
3
3
|
import { dispatchProtocolOp } from './protocolRunner.js';
|
|
4
|
-
import { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION, agreementClaimCapability, agreementSubcontractCapability, } from '@ziggs-ai/api-client';
|
|
4
|
+
import { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION, agreementClaimCapability, agreementSubcontractCapability, SOURCES_PARAM, } 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'];
|
|
@@ -60,21 +60,22 @@ function handlerFor(toolName) {
|
|
|
60
60
|
return result;
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
|
-
export const agreementProposeTool = defineTool('agreement_propose', { description: { type: 'string', required: true }, proposedTo: { type: 'string', required: true }, engagementKind: { type: 'string', enum: ['hire', 'service'], description: 'Contract type. Defaults to service. For a bilateral trust link, use link_propose.' }, parentAgreementId: 'string', providerId: { type: 'string', description: AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION }, price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, lifecycle: lifecycleParam, expiresAt: 'string', maxExecutions: 'integer', conversationAllowance: { type: 'number', description: 'Incoming-message limit shared across covered rooms; omitted on new agreements means unlimited.' }, billing: { type: 'string', enum: ['total', 'per_task'], description: "How price reads: 'total' (default, escrowed and paid at the end) or 'per_task' (a rate settled as each task completes — standing agreements only, and the default for a hire)." }, agreementDescription: 'string' }, handlerFor('agreement_propose'), { description: 'Propose an agreement — direct, broadcast, or link; there are no separate publish tools. Engagement precedence (posted-first): 1) REUSE an active agreement on matching terms, 2) CLAIM the counterparty\'s listing (marketplace_view → agreement_claim; never counter a listing), 3) POST a request broadcast here when nothing listed fits, 4) direct-propose ONLY for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Direction is stated, never guessed: providerId = your own id ("I work, you pay") or providerId = proposedTo ("you work, I pay") on direct proposals. Broadcast with proposedTo="everyone" (fully public) or "org" (your org only) and no providerId posts a REQUEST any agent may claim. A person or the deploy publishes a standing listing; this agent does not. For a bilateral trust link, use link_propose: it takes an email or an agent id, or neither for a share link. engagementKind defaults to service. Agreements are STANDING by default (no expiry, unlimited tasks under one agreement); set expiresAt or maxExecutions only when the engagement should end on its own.', isAgreementCreation: true });
|
|
63
|
+
export const agreementProposeTool = defineTool('agreement_propose', { description: { type: 'string', required: true }, proposedTo: { type: 'string', required: true }, engagementKind: { type: 'string', enum: ['hire', 'service'], description: 'Contract type. Defaults to service. For a bilateral trust link, use link_propose.' }, parentAgreementId: 'string', providerId: { type: 'string', description: AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION }, price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, lifecycle: lifecycleParam, expiresAt: 'string', maxExecutions: 'integer', conversationAllowance: { type: 'number', description: 'Incoming-message limit shared across covered rooms; omitted on new agreements means unlimited.' }, billing: { type: 'string', enum: ['total', 'per_task'], description: "How price reads: 'total' (default, escrowed and paid at the end) or 'per_task' (a rate settled as each task completes — standing agreements only, and the default for a hire)." }, agreementDescription: 'string', sources: SOURCES_PARAM }, 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) and no providerId posts a REQUEST any agent may claim. A person or the deploy publishes a standing listing; this agent does not. For a bilateral trust link, use link_propose: it takes an email or an agent id, or neither for a share link. engagementKind defaults to service. Agreements are STANDING by default (no expiry, unlimited tasks under one agreement); set expiresAt or maxExecutions only when the engagement should end on its own.', isAgreementCreation: true });
|
|
64
64
|
export const agreementSubcontractTool = defineTool('agreement_subcontract', agreementSubcontractCapability.params, handlerFor('agreement_subcontract'), { description: agreementSubcontractCapability.descriptions.sdk, isAgreementCreation: true });
|
|
65
65
|
export const agreementRespondTool = defineTool('agreement_respond', { agreementId: { type: 'string', required: true }, action: { type: 'string', required: true, enum: ['approve', 'reject'] } }, handlerFor('agreement_respond'), { description: 'Approve or reject a pending DIRECT agreement proposal addressed to you. Open broadcasts (requests, standing offers, link invites) have no personal approval slot — claim those with agreement_claim instead, or ignore them to pass. ONE exception: a hand-off that pins YOU as provider carries your pending approval even as an open broadcast — approving consents to serving whoever claims it (the row stays open for claims); rejecting cancels the hand-off. Ignoring it leaves the hand-off unclaimable, so always respond.' });
|
|
66
66
|
export const agreementClaimTool = defineTool('agreement_claim', agreementClaimCapability.params, handlerFor('agreement_claim'), { description: agreementClaimCapability.descriptions.sdk });
|
|
67
67
|
export const taskSetResultTool = defineTool('task_set_result', {
|
|
68
68
|
taskId: { type: 'string', required: true },
|
|
69
69
|
// same param name as MCP ziggs_task_set_result / PATCH /tasks/:id/state.
|
|
70
|
-
state: { type: 'string', required: true, enum: ['completed', 'failed'
|
|
70
|
+
state: { type: 'string', required: true, enum: ['completed', 'failed'] },
|
|
71
71
|
// Same object contract as MCP ({ summary, status, links, … }).
|
|
72
72
|
result: {
|
|
73
73
|
type: 'object',
|
|
74
74
|
description: 'Outcome payload (summary, status, links, …). This is what the next agent reads when it picks the work up from its own inbox.',
|
|
75
75
|
},
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
sources: SOURCES_PARAM,
|
|
77
|
+
}, handlerFor('task_set_result'), { description: 'Transition a task to its terminal state, completed or failed, and record the result. To cancel a task use task_cancel. Set the result here as an object ({ summary, status, links }): a task the parties opted into is closed by this call, whatever else you also sent them. Pass state (not status). This closes the task and not the engagement: a standing hire stays active for the next task, ending one is agreement_fulfill. The reply reports what actually happened — what was stored, who the completion was queued for (queued, not read), what per-task metering did — so report those and no more; a completed task is not by itself a paid one.' });
|
|
78
|
+
export const agreementCounterTool = defineTool('agreement_counter', { agreementId: { type: 'string', required: true }, price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, agreementDescription: 'string', expiresAt: 'string', lifecycle: lifecycleParam, maxExecutions: 'integer', conversationAllowance: { type: 'number', description: 'Incoming-message limit shared across covered rooms; omitted on new agreements means unlimited.' }, description: 'string', sources: SOURCES_PARAM }, handlerFor('agreement_counter'), { description: 'Counter a pending proposal with revised terms instead of approving or rejecting. Check status first with agreement_get.' });
|
|
78
79
|
export const agreementGetTool = defineTool('agreement_get', { agreementId: { type: 'string', required: true } }, handlerFor('agreement_get'), { description: 'Read the current proposal status for an agreement (pending, approved, rejected, countered, expired).' });
|
|
79
80
|
export const agreementRevokeTool = defineTool('agreement_revoke', { agreementId: { type: 'string', required: true } }, handlerFor('agreement_revoke'), { description: 'Revoke a pending proposal you created so it can no longer be approved. Use when the user has clearly moved on to a different request before responding to the previous proposal card.' });
|
|
80
81
|
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.' });
|
|
@@ -92,6 +93,7 @@ export const taskUpdatePlanStepTool = defineTool('task_update_plan_step', {
|
|
|
92
93
|
description: 'Step progress. Required with stepId.',
|
|
93
94
|
},
|
|
94
95
|
result: 'string',
|
|
96
|
+
sources: SOURCES_PARAM,
|
|
95
97
|
// Both fields are required, but the required list cannot be declared here:
|
|
96
98
|
// defineTool reads a sibling `required` as the flag that makes waitingOn
|
|
97
99
|
// itself mandatory. The backend enforces the pair; the description says so.
|
|
@@ -108,7 +110,7 @@ export const taskUpdatePlanStepTool = defineTool('task_update_plan_step', {
|
|
|
108
110
|
description: "Who this task now waits on, because you just asked them: { kind: 'user' | 'agent', id } — both fields required. Send it when you ask — with the step you are ticking, or on its own when the ask is all that happened. Your person then sees the task as waiting on them rather than as running, which is the difference between a job that looks stalled and one that is waiting for an answer. It clears itself: any later progress that does not restate it, and any terminal state, drops it.",
|
|
109
111
|
},
|
|
110
112
|
}, handlerFor('task_update_plan_step'), { description: "Update the status of a single step within a task's plan, and say who you are waiting on when you have just asked somebody. A task with no plan can still carry the waiting-on on its own." });
|
|
111
|
-
export const taskCreateTool = defineTool('task_create', { agreementId: { type: 'string', required: true }, description: { type: 'string', required: true }, parentTaskId: 'string', assigneeId: { type: 'string', description: 'Explicitly hand this task to one specific agent (e.g. a worker your orchestrator selected via agent search) instead of leaving it unassigned. Must be a party on the agreement (payer, provider, creator, or creator agent).' }, inputArtifactIds: { type: 'array', items: { type: 'string' }, description: 'Prior-step output artifact handles, for structured input links.' }, plan: { type: 'object' }, planReviewTiming: planReviewTimingParam, requireMidWorkPlanAck: 'boolean', dueAt: dueAtParam, checkBack: checkBackParam }, handlerFor('task_create'), { description: 'Create a new task under an existing approved agreement. Use when the contract is already in place. Pass assigneeId to delegate the task to a specific agent rather than leaving it open. For work that means chasing an answer out of somebody, set dueAt and checkBack so the holder knows when to stop and how often it may remind.' });
|
|
113
|
+
export const taskCreateTool = defineTool('task_create', { agreementId: { type: 'string', required: true }, description: { type: 'string', required: true }, parentTaskId: 'string', assigneeId: { type: 'string', description: 'Explicitly hand this task to one specific agent (e.g. a worker your orchestrator selected via agent search) instead of leaving it unassigned. Must be a party on the agreement (payer, provider, creator, or creator agent).' }, inputArtifactIds: { type: 'array', items: { type: 'string' }, description: 'Prior-step output artifact handles, for structured input links.' }, plan: { type: 'object' }, planReviewTiming: planReviewTimingParam, requireMidWorkPlanAck: 'boolean', dueAt: dueAtParam, checkBack: checkBackParam, sources: SOURCES_PARAM }, handlerFor('task_create'), { description: 'Create a new task under an existing approved agreement. Use when the contract is already in place. Pass assigneeId to delegate the task to a specific agent rather than leaving it open. For work that means chasing an answer out of somebody, set dueAt and checkBack so the holder knows when to stop and how often it may remind.' });
|
|
112
114
|
export const PROTOCOL_TOOLS = [
|
|
113
115
|
agreementProposeTool, agreementSubcontractTool,
|
|
114
116
|
agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool,
|
|
@@ -3,7 +3,6 @@ export declare const contextReadTool: ToolDefinition;
|
|
|
3
3
|
export declare const openTool: ToolDefinition;
|
|
4
4
|
export declare const accessExplainTool: ToolDefinition;
|
|
5
5
|
export declare const accessRequestTool: ToolDefinition;
|
|
6
|
-
export declare const contextRequestTool: ToolDefinition;
|
|
7
6
|
export declare const contextDelegateTool: ToolDefinition;
|
|
8
7
|
export declare const contextExpandReachTool: ToolDefinition;
|
|
9
8
|
export declare const contextDiscoverGrantableTool: ToolDefinition;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability,
|
|
1
|
+
import { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, accessRequestCapability, } 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
|
|
@@ -7,7 +7,6 @@ export const contextReadTool = toolFromCapability(contextReadCapability);
|
|
|
7
7
|
export const openTool = toolFromCapability(openCapability);
|
|
8
8
|
export const accessExplainTool = toolFromCapability(accessExplainCapability);
|
|
9
9
|
export const accessRequestTool = toolFromCapability(accessRequestCapability);
|
|
10
|
-
export const contextRequestTool = toolFromCapability(contextRequestCapability);
|
|
11
10
|
export const contextDelegateTool = toolFromCapability(contextDelegateCapability);
|
|
12
11
|
export const contextExpandReachTool = toolFromCapability(contextExpandReachCapability);
|
|
13
12
|
export const contextDiscoverGrantableTool = toolFromCapability(contextDiscoverGrantableCapability);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { replaceTaskPlan } from '@ziggs-ai/api-client';
|
|
1
|
+
import { replaceTaskPlan, SOURCES_PARAM, } from '@ziggs-ai/api-client';
|
|
2
2
|
import { defineTool } from '../../../tools/defineTool.js';
|
|
3
3
|
import { credsFromToolContext } from '../../../tools/toolCreds.js';
|
|
4
4
|
/**
|
|
@@ -44,6 +44,7 @@ export const taskReplacePlanTool = defineTool('task_replace_plan', {
|
|
|
44
44
|
type: 'object',
|
|
45
45
|
description: 'What this step produced, as an object — e.g. { "agreementId": "agr_…" } for an approval you requested, or { "artifactId": "art_…" } for a pack you recorded. Stored with the step and read back by task_list.',
|
|
46
46
|
},
|
|
47
|
+
sources: SOURCES_PARAM,
|
|
47
48
|
},
|
|
48
49
|
required: ['description'],
|
|
49
50
|
},
|
package/dist/probeChat.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One classifier for store-publish probe rooms.
|
|
3
3
|
*
|
|
4
|
-
* The runner mints `probe_<agentId>_<ts>`.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The runner mints `probe_<agentId>_<ts>`. That prefix skips the wake gate
|
|
5
|
+
* and the wake-rate breaker, so a store publish still hears a model reply.
|
|
6
|
+
* It does not write a canned answer, and it is not capability evidence.
|
|
7
|
+
* Capability versus liveness is decided by the store sync.
|
|
8
8
|
*/
|
|
9
9
|
export declare const PROBE_CHAT_ID_PREFIX = "probe_";
|
|
10
10
|
export declare function isPublishProbeChat(chatId: string | undefined | null): boolean;
|
|
11
|
-
/** @deprecated use {@link isPublishProbeChat} — same classifier, old name. */
|
|
12
|
-
export declare const isCapabilityProbeSession: typeof isPublishProbeChat;
|
package/dist/probeChat.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One classifier for store-publish probe rooms.
|
|
3
3
|
*
|
|
4
|
-
* The runner mints `probe_<agentId>_<ts>`.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The runner mints `probe_<agentId>_<ts>`. That prefix skips the wake gate
|
|
5
|
+
* and the wake-rate breaker, so a store publish still hears a model reply.
|
|
6
|
+
* It does not write a canned answer, and it is not capability evidence.
|
|
7
|
+
* Capability versus liveness is decided by the store sync.
|
|
8
8
|
*/
|
|
9
9
|
export const PROBE_CHAT_ID_PREFIX = 'probe_';
|
|
10
10
|
export function isPublishProbeChat(chatId) {
|
|
11
11
|
return typeof chatId === 'string' && chatId.startsWith(PROBE_CHAT_ID_PREFIX);
|
|
12
12
|
}
|
|
13
|
-
/** @deprecated use {@link isPublishProbeChat} — same classifier, old name. */
|
|
14
|
-
export const isCapabilityProbeSession = isPublishProbeChat;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WriteSource } from '@ziggs-ai/api-client';
|
|
1
2
|
/**
|
|
2
3
|
* Foundation types for the agent SDK workflow runtime.
|
|
3
4
|
*
|
|
@@ -519,6 +520,7 @@ export type Effect = {
|
|
|
519
520
|
conversationAgreementId?: string;
|
|
520
521
|
underAgreementId?: string;
|
|
521
522
|
taskId?: string;
|
|
523
|
+
sources?: WriteSource[];
|
|
522
524
|
} | {
|
|
523
525
|
kind: 'record-event';
|
|
524
526
|
sessionId: string;
|
package/dist/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/agent-sdk",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.4",
|
|
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.284",
|
|
27
27
|
"@anthropic-ai/sdk": "^0.110.0",
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
|
-
"@ziggs-ai/api-client": "0.37.
|
|
29
|
+
"@ziggs-ai/api-client": "0.37.6",
|
|
30
30
|
"@ziggs-ai/contracts": "^0.23.3",
|
|
31
31
|
"ajv": "^8.17.1",
|
|
32
32
|
"dotenv": "^17.2.3",
|