@ziggs-ai/agent-sdk 0.20.0 → 0.22.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 +5 -0
- package/dist/Agent.d.ts +28 -7
- package/dist/Agent.js +189 -74
- package/dist/brain/ClaudeBrain.d.ts +11 -1
- package/dist/brain/ClaudeBrain.js +71 -18
- package/dist/brain/ZiggsBrain.d.ts +2 -0
- package/dist/brain/ZiggsBrain.js +1 -0
- package/dist/brain/claudeCognition.d.ts +1 -1
- package/dist/brain/claudeCognition.js +54 -7
- package/dist/brain/claudeSdkProtocol.d.ts +1 -1
- package/dist/brain/claudeSdkProtocol.js +5 -2
- package/dist/brain/claudeSdkTools.d.ts +5 -2
- package/dist/brain/claudeSdkTools.js +39 -10
- package/dist/brain/hireActivationNotice.d.ts +19 -0
- package/dist/brain/hireActivationNotice.js +79 -0
- package/dist/brain/standingOfferNotice.d.ts +15 -0
- package/dist/brain/standingOfferNotice.js +89 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/ingress/normalizeIncoming.d.ts +3 -0
- package/dist/ingress/normalizeIncoming.js +8 -0
- package/dist/mcp/createMcpAgent.d.ts +1 -1
- package/dist/mcp/createMcpAgent.js +1 -1
- package/dist/memory/MemoryStore.d.ts +18 -4
- package/dist/memory/MemoryStore.js +52 -12
- package/dist/platform/ConnectionPool.d.ts +9 -4
- package/dist/platform/ConnectionPool.js +16 -6
- package/dist/platform/StateTimers.d.ts +12 -6
- package/dist/platform/StateTimers.js +26 -6
- package/dist/platform/ZiggsEffectHandler.js +17 -3
- package/dist/platform/inboxLoop.d.ts +5 -7
- package/dist/platform/inboxLoop.js +36 -80
- package/dist/platform/polls/declaredPollRunner.d.ts +7 -1
- package/dist/platform/polls/declaredPollRunner.js +7 -9
- package/dist/platform/tasks/TaskService.d.ts +23 -1
- package/dist/platform/tasks/TaskService.js +8 -2
- package/dist/platform/tools/index.d.ts +3 -2
- package/dist/platform/tools/index.js +3 -2
- package/dist/platform/tools/tier1/protocolRunner.js +8 -1
- package/dist/platform/tools/tier1/protocolTools.js +3 -3
- package/dist/platform/tools/tier2/contextTools.d.ts +3 -0
- package/dist/platform/tools/tier2/contextTools.js +4 -1
- package/dist/platform/tools/tier2/planTools.d.ts +20 -0
- package/dist/platform/tools/tier2/planTools.js +64 -0
- package/dist/platform/tools/tier2/taskTools.d.ts +1 -0
- package/dist/platform/tools/tier2/taskTools.js +2 -1
- package/dist/pricing/fleetDefaults.js +4 -9
- package/dist/pricing/fleetTierByAgentId.gen.d.ts +1 -1
- package/dist/pricing/fleetTierByAgentId.gen.js +2 -752
- package/dist/runtime/AgentMachine.d.ts +3 -0
- package/dist/runtime/AgentMachine.js +6 -1
- package/dist/runtime/runTurn.d.ts +1 -0
- package/dist/runtime/runTurn.js +19 -15
- package/dist/runtime/wakeEffects.d.ts +35 -0
- package/dist/runtime/wakeEffects.js +121 -0
- package/dist/runtime/wakeOutcome.d.ts +33 -0
- package/dist/runtime/wakeOutcome.js +61 -6
- package/dist/types.d.ts +13 -0
- package/package.json +2 -2
- package/dist/pricing/fleetFreeTierA.gen.d.ts +0 -6
- package/dist/pricing/fleetFreeTierA.gen.js +0 -11
|
@@ -5,6 +5,17 @@ const READ_PAGE = 100;
|
|
|
5
5
|
function log(label, msg) {
|
|
6
6
|
runtimeLog.info(label, msg);
|
|
7
7
|
}
|
|
8
|
+
function inboxClient(deps) {
|
|
9
|
+
return new InboxClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl, deps.creds.instanceId);
|
|
10
|
+
}
|
|
11
|
+
async function releaseInboxHostBestEffort(deps) {
|
|
12
|
+
try {
|
|
13
|
+
await inboxClient(deps).releaseHost();
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
warn(deps.label ?? 'inboxLoop', `inbox host release failed: ${err?.message ?? err}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
8
19
|
function warn(label, msg) {
|
|
9
20
|
runtimeLog.warn(label, msg);
|
|
10
21
|
}
|
|
@@ -25,45 +36,6 @@ export function chatsInWakeOrder(chats) {
|
|
|
25
36
|
function isPublishProbeChat(chatId) {
|
|
26
37
|
return chatId.startsWith('probe_');
|
|
27
38
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Store publish waits on a chat reply, not inbox ack. The ordinary pass can
|
|
30
|
-
* already be inside leftover mail when that room appears, so a side read
|
|
31
|
-
* answers `probe_*` chats without waiting for the current pass to finish.
|
|
32
|
-
* It never acks — the main pass still owns the watermark.
|
|
33
|
-
*/
|
|
34
|
-
export async function answerPublishProbes(deps) {
|
|
35
|
-
const inbox = new InboxClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
|
|
36
|
-
const reader = new ContextReadClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
|
|
37
|
-
const envelope = await inbox.getInbox({});
|
|
38
|
-
const probes = (envelope.chats ?? []).filter((c) => isPublishProbeChat(c.chatId));
|
|
39
|
-
if (probes.length === 0)
|
|
40
|
-
return 0;
|
|
41
|
-
const mine = (d) => d.assigneeId === deps.ownAgentId;
|
|
42
|
-
const earliestByChat = new Map();
|
|
43
|
-
for (const d of envelope.deliveries) {
|
|
44
|
-
if (!d.chatId || !mine(d) || !isPublishProbeChat(d.chatId))
|
|
45
|
-
continue;
|
|
46
|
-
const seen = earliestByChat.get(d.chatId);
|
|
47
|
-
if (!seen || d.ts < seen)
|
|
48
|
-
earliestByChat.set(d.chatId, d.ts);
|
|
49
|
-
}
|
|
50
|
-
const attempt = async (what, handover) => {
|
|
51
|
-
try {
|
|
52
|
-
await handover();
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
catch (err) {
|
|
56
|
-
warn(deps.label ?? 'inboxLoop', `publish-probe ${what} was not handled: ${err?.message ?? err}`);
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
let delivered = 0;
|
|
61
|
-
for (const news of probes) {
|
|
62
|
-
const earliest = earliestByChat.get(news.chatId) ?? news.latestAt;
|
|
63
|
-
delivered += await deliverMessagesForChat(deps, news, earliest, reader, attempt);
|
|
64
|
-
}
|
|
65
|
-
return delivered;
|
|
66
|
-
}
|
|
67
39
|
/**
|
|
68
40
|
* Standing proposals share the merged inbox visibility model with deliveries,
|
|
69
41
|
* but older envelopes predate an explicit assignee stamp. Prefer the server's
|
|
@@ -78,6 +50,22 @@ function proposalIsMine(proposal, ownAgentId) {
|
|
|
78
50
|
return (proposal.pendingApprovalPartyIds?.includes(ownAgentId) === true ||
|
|
79
51
|
proposal.proposedTo === ownAgentId);
|
|
80
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* The one hire a chat's assigned deliveries name, or null when the room
|
|
55
|
+
* has none or more than one. Memory (and acting-for) treat "several
|
|
56
|
+
* customers in one room" as the shared section.
|
|
57
|
+
*/
|
|
58
|
+
export function soleHireAgreementIdForChat(deliveries, chatId, ownAgentId) {
|
|
59
|
+
const ids = new Set();
|
|
60
|
+
for (const d of deliveries) {
|
|
61
|
+
if (d.chatId !== chatId || !d.agreementId)
|
|
62
|
+
continue;
|
|
63
|
+
if (d.assigneeId && d.assigneeId !== ownAgentId)
|
|
64
|
+
continue;
|
|
65
|
+
ids.add(d.agreementId);
|
|
66
|
+
}
|
|
67
|
+
return ids.size === 1 ? [...ids][0] : null;
|
|
68
|
+
}
|
|
81
69
|
/**
|
|
82
70
|
* Read the messages behind one chat's deliveries and hand them to the host.
|
|
83
71
|
*
|
|
@@ -85,7 +73,7 @@ function proposalIsMine(proposal, ownAgentId) {
|
|
|
85
73
|
* reads through `/context/read`, where the agent's own grants fence what it is
|
|
86
74
|
* allowed to see. A delivery reference is an address, never an authorisation.
|
|
87
75
|
*/
|
|
88
|
-
async function deliverMessagesForChat(deps, news, earliestTs, reader, attempt) {
|
|
76
|
+
async function deliverMessagesForChat(deps, news, earliestTs, reader, attempt, hireAgreementId) {
|
|
89
77
|
let delivered = 0;
|
|
90
78
|
let remaining = news.count;
|
|
91
79
|
// `after` is exclusive, so step back a millisecond to include the oldest
|
|
@@ -135,6 +123,7 @@ async function deliverMessagesForChat(deps, news, earliestTs, reader, attempt) {
|
|
|
135
123
|
// leaving it made ClaudeBrain treat the wake as notice-only.
|
|
136
124
|
receiverId: deps.ownAgentId,
|
|
137
125
|
receiver: { id: deps.ownAgentId },
|
|
126
|
+
...(hireAgreementId ? { agreementId: hireAgreementId } : {}),
|
|
138
127
|
}));
|
|
139
128
|
if (handled)
|
|
140
129
|
delivered++;
|
|
@@ -155,7 +144,7 @@ async function deliverMessagesForChat(deps, news, earliestTs, reader, attempt) {
|
|
|
155
144
|
*/
|
|
156
145
|
export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
|
|
157
146
|
const label = deps.label ?? 'inboxLoop';
|
|
158
|
-
const inbox =
|
|
147
|
+
const inbox = inboxClient(deps);
|
|
159
148
|
const reader = new ContextReadClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
|
|
160
149
|
const envelope = await inbox.getInbox(waitSeconds > 0 ? { waitSeconds } : {});
|
|
161
150
|
let messagesDelivered = 0;
|
|
@@ -244,7 +233,7 @@ export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
|
|
|
244
233
|
for (const news of chatsInWakeOrder(envelope.chats ?? [])) {
|
|
245
234
|
const earliest = earliestByChat.get(news.chatId) ?? news.latestAt;
|
|
246
235
|
const failuresBefore = failures;
|
|
247
|
-
const n = await deliverMessagesForChat(deps, news, earliest, reader, attempt);
|
|
236
|
+
const n = await deliverMessagesForChat(deps, news, earliest, reader, attempt, soleHireAgreementIdForChat(envelope.deliveries, news.chatId, deps.ownAgentId));
|
|
248
237
|
messagesDelivered += n;
|
|
249
238
|
if (n > 0)
|
|
250
239
|
log(label, `chat ${news.chatId} delivered ${n} message(s)`);
|
|
@@ -409,16 +398,6 @@ export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
|
|
|
409
398
|
* some defaults ship with. Lower it if your deployment sits behind a shorter one.
|
|
410
399
|
*/
|
|
411
400
|
const DEFAULT_WAIT_SECONDS = 110;
|
|
412
|
-
/**
|
|
413
|
-
* Side-poll interval for store-publish `probe_*` rooms.
|
|
414
|
-
*
|
|
415
|
-
* Default **off**. A second GET /inbox beside the main long-poll burned the
|
|
416
|
-
* shared poll ceiling: every hosted agent side-polled, probes got 429s, and
|
|
417
|
-
* Register saw ALB HTML 504s. Main pass + probe-first ordering + pinned
|
|
418
|
-
* `probe_*` deliveries are enough; set `probePollMs` only when you knowingly
|
|
419
|
-
* need the extra loop.
|
|
420
|
-
*/
|
|
421
|
-
const DEFAULT_PROBE_POLL_MS = 0;
|
|
422
401
|
const ERROR_BACKOFF_BASE_MS = 1_000;
|
|
423
402
|
const ERROR_BACKOFF_MAX_MS = 30_000;
|
|
424
403
|
/**
|
|
@@ -455,7 +434,6 @@ function sleep(ms) {
|
|
|
455
434
|
export function runInboxLoop(deps, opts = {}) {
|
|
456
435
|
const label = deps.label ?? 'inboxLoop';
|
|
457
436
|
const waitSeconds = opts.waitSeconds ?? DEFAULT_WAIT_SECONDS;
|
|
458
|
-
const probePollMs = opts.probePollMs ?? DEFAULT_PROBE_POLL_MS;
|
|
459
437
|
let running = true;
|
|
460
438
|
let consecutiveErrors = 0;
|
|
461
439
|
let heldPasses = 0;
|
|
@@ -501,32 +479,7 @@ export function runInboxLoop(deps, opts = {}) {
|
|
|
501
479
|
}
|
|
502
480
|
}
|
|
503
481
|
})();
|
|
504
|
-
const
|
|
505
|
-
if (probePollMs <= 0)
|
|
506
|
-
return;
|
|
507
|
-
while (running) {
|
|
508
|
-
const slice = 50;
|
|
509
|
-
let left = probePollMs;
|
|
510
|
-
while (left > 0 && running) {
|
|
511
|
-
const step = Math.min(slice, left);
|
|
512
|
-
await sleep(step);
|
|
513
|
-
left -= step;
|
|
514
|
-
}
|
|
515
|
-
if (!running)
|
|
516
|
-
break;
|
|
517
|
-
try {
|
|
518
|
-
const n = await answerPublishProbes(deps);
|
|
519
|
-
if (n > 0)
|
|
520
|
-
log(label, `publish-probe side poll delivered ${n} message(s)`);
|
|
521
|
-
}
|
|
522
|
-
catch (err) {
|
|
523
|
-
if (!running)
|
|
524
|
-
break;
|
|
525
|
-
warn(label, `publish-probe side poll failed: ${err?.message ?? err}`);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
})();
|
|
529
|
-
const done = Promise.all([main, probes]);
|
|
482
|
+
const done = main;
|
|
530
483
|
return {
|
|
531
484
|
get running() {
|
|
532
485
|
return running;
|
|
@@ -539,7 +492,10 @@ export function runInboxLoop(deps, opts = {}) {
|
|
|
539
492
|
},
|
|
540
493
|
async stop() {
|
|
541
494
|
running = false;
|
|
542
|
-
|
|
495
|
+
// Drop the lease immediately. Do not wait out a held poll — SIGTERM
|
|
496
|
+
// then process.exit cannot sit on the 110s wait.
|
|
497
|
+
await releaseInboxHostBestEffort(deps);
|
|
498
|
+
void done.catch(() => { });
|
|
543
499
|
},
|
|
544
500
|
};
|
|
545
501
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { AgentOptions } from '../../Agent.js';
|
|
2
2
|
import { PollRunner, type WakeTarget } from './PollRunner.js';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Build the runner that executes this agent's declared wakes.
|
|
5
|
+
*
|
|
6
|
+
* A malformed `polls` value is a programming error and still throws. A
|
|
7
|
+
* binding that names an unknown kind or has no chatId is not: PollRunner
|
|
8
|
+
* warns and skips it, so one agent's invalid poll cannot refuse the host.
|
|
9
|
+
*/
|
|
4
10
|
export declare function createDeclaredPollRunner(options: AgentOptions, target: WakeTarget): PollRunner | null;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { PollRegistry } from './PollKind.js';
|
|
2
2
|
import { PollRunner } from './PollRunner.js';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Build the runner that executes this agent's declared wakes.
|
|
5
|
+
*
|
|
6
|
+
* A malformed `polls` value is a programming error and still throws. A
|
|
7
|
+
* binding that names an unknown kind or has no chatId is not: PollRunner
|
|
8
|
+
* warns and skips it, so one agent's invalid poll cannot refuse the host.
|
|
9
|
+
*/
|
|
4
10
|
export function createDeclaredPollRunner(options, target) {
|
|
5
11
|
const registry = new PollRegistry();
|
|
6
12
|
for (const kind of options.pollKinds ?? [])
|
|
@@ -9,14 +15,6 @@ export function createDeclaredPollRunner(options, target) {
|
|
|
9
15
|
return null;
|
|
10
16
|
if (!Array.isArray(options.polls))
|
|
11
17
|
throw new Error('Agent: polls must be an array');
|
|
12
|
-
for (const binding of options.polls) {
|
|
13
|
-
if (!binding || !registry.get(binding.kind)) {
|
|
14
|
-
throw new Error(`Agent ${options.agentId}: unknown poll kind "${binding?.kind}"; declare its implementation in pollKinds`);
|
|
15
|
-
}
|
|
16
|
-
if (typeof binding.chatId !== 'string' || !binding.chatId.trim()) {
|
|
17
|
-
throw new Error(`Agent ${options.agentId}: poll "${binding.kind}" requires a chatId`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
18
|
if (!options.polls.length)
|
|
21
19
|
return null;
|
|
22
20
|
return new PollRunner({
|
|
@@ -8,7 +8,29 @@ export declare class TaskService {
|
|
|
8
8
|
getActiveTasksForChat(chatId: string): Promise<Task[]>;
|
|
9
9
|
getSubtasks(parentTaskId: string): Promise<Task[]>;
|
|
10
10
|
spawnUnderAgreement(agreementId: string, taskData: Omit<CreateTaskData, 'agreementId'>): Promise<Task>;
|
|
11
|
-
updateState(taskId: string, state: TaskState, result?: unknown, idempotencyKey?: string): Promise<
|
|
11
|
+
updateState(taskId: string, state: TaskState, result?: unknown, idempotencyKey?: string): Promise<{
|
|
12
|
+
ok: true;
|
|
13
|
+
taskId: string;
|
|
14
|
+
state: string;
|
|
15
|
+
outcome: import("@ziggs-ai/api-client").NextCallOutcome;
|
|
16
|
+
recorded: string;
|
|
17
|
+
handoff?: string;
|
|
18
|
+
delivery?: string;
|
|
19
|
+
metering?: string;
|
|
20
|
+
agreement?: string;
|
|
21
|
+
next: import("@ziggs-ai/api-client").NextCall[];
|
|
22
|
+
workContext?: import("@ziggs-ai/api-client").WorkContext;
|
|
23
|
+
agreementId?: string;
|
|
24
|
+
updatedAt?: string;
|
|
25
|
+
status?: string;
|
|
26
|
+
stepCount?: number;
|
|
27
|
+
structureChanged?: boolean;
|
|
28
|
+
stepId?: string;
|
|
29
|
+
stepStatus?: string;
|
|
30
|
+
patchedCount?: number;
|
|
31
|
+
resultRecorded?: boolean;
|
|
32
|
+
terminalState?: string;
|
|
33
|
+
}>;
|
|
12
34
|
updatePlanStep(taskId: string, stepId: string, status: string, result?: unknown): Promise<import("@ziggs-ai/api-client").TaskWriteConfirm>;
|
|
13
35
|
cancel(taskId: string): Promise<import("@ziggs-ai/api-client").TaskWriteConfirm>;
|
|
14
36
|
report(taskId: string, message?: string): Promise<import("@ziggs-ai/api-client").TaskWriteConfirm>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createTask, getTask, getActiveTasksForAgent, getActiveTasksForChat, getSubtasks, updateTaskState, updateTaskPlanStep, cancelTask, reportTask, setTaskSatisfaction, countTasks, } from '@ziggs-ai/api-client';
|
|
1
|
+
import { createTask, getTask, getActiveTasksForAgent, getActiveTasksForChat, getSubtasks, updateTaskState, updateTaskPlanStep, cancelTask, reportTask, setTaskSatisfaction, countTasks, presentTaskOutcome, } from '@ziggs-ai/api-client';
|
|
2
2
|
import { runtimeLog } from '../../shared/runtimeLog.js';
|
|
3
3
|
function safeJsonStringify(value) {
|
|
4
4
|
if (value === null || value === undefined)
|
|
@@ -65,7 +65,13 @@ export class TaskService {
|
|
|
65
65
|
};
|
|
66
66
|
const updated = await updateTaskState(taskId, state, data, this.creds);
|
|
67
67
|
runtimeLog.info('TaskService', `Task state: ${taskId} → ${state}`);
|
|
68
|
-
|
|
68
|
+
// The same four facts the MCP surface reports, from the same
|
|
69
|
+
// presenter: what was stored, who was woken, what metering did, what the
|
|
70
|
+
// agreement is now. Both surfaces say it once, and say it the same way.
|
|
71
|
+
return {
|
|
72
|
+
...updated,
|
|
73
|
+
...presentTaskOutcome(updated, { creds: this.creds, surface: 'sdk' }),
|
|
74
|
+
};
|
|
69
75
|
}
|
|
70
76
|
catch (error) {
|
|
71
77
|
runtimeLog.error('TaskService', `Task state update failed: ${error.message}`);
|
|
@@ -21,9 +21,10 @@ 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, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
|
|
24
|
+
export { CONTEXT_TOOLS, contextReadTool, contextRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
|
|
25
25
|
export { GRANTS_TOOLS, listGrantsTool } from './tier2/grantsTools.js';
|
|
26
|
-
export { TASK_TOOLS, listTasksTool } from './tier2/taskTools.js';
|
|
26
|
+
export { TASK_TOOLS, listTasksTool, cancelTaskTool } from './tier2/taskTools.js';
|
|
27
|
+
export { PLAN_TOOLS, taskReplacePlanTool } from './tier2/planTools.js';
|
|
27
28
|
export { LINK_TOOLS, linkProposeTool, linkListTool, } from './tier2/linkTools.js';
|
|
28
29
|
export { INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, } from './tier2/introductionTools.js';
|
|
29
30
|
export { ARTIFACT_TOOLS, recordArtifactTool } from './tier2/artifactTools.js';
|
|
@@ -23,11 +23,12 @@ 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, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
|
|
26
|
+
export { CONTEXT_TOOLS, contextReadTool, contextRequestTool, 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';
|
|
30
|
-
export { TASK_TOOLS, listTasksTool } from './tier2/taskTools.js';
|
|
30
|
+
export { TASK_TOOLS, listTasksTool, cancelTaskTool } from './tier2/taskTools.js';
|
|
31
|
+
export { PLAN_TOOLS, taskReplacePlanTool } from './tier2/planTools.js';
|
|
31
32
|
// links (agreements with engagementKind 'link') — cross-org reach.
|
|
32
33
|
///1022: the rail is two tools; propose/claim/revoke ride the
|
|
33
34
|
// agreement grammar.
|
|
@@ -57,7 +57,7 @@ cachedAgreements) {
|
|
|
57
57
|
case 'agreement-propose':
|
|
58
58
|
return proposeAgreement(services, payload, chatId, agents, actorId, writeKeys);
|
|
59
59
|
case 'agreement-subcontract':
|
|
60
|
-
return subcontractAgreement(services, payload, chatId, agents, actorId, writeKeys, cachedAgreements);
|
|
60
|
+
return subcontractAgreement(services, payload, namedSubcontractChat(payload, chatId), agents, actorId, writeKeys, cachedAgreements);
|
|
61
61
|
case 'agreement-respond':
|
|
62
62
|
if (!payload.agreementId || !payload.action)
|
|
63
63
|
return null;
|
|
@@ -323,6 +323,13 @@ function agActive(a) {
|
|
|
323
323
|
function agIsActiveStatus(a) {
|
|
324
324
|
return String(a.status ?? '') === 'active';
|
|
325
325
|
}
|
|
326
|
+
/** A named room on the call wins; otherwise the wake's chat. Empty is omit. */
|
|
327
|
+
function namedSubcontractChat(payload, wakeChatId) {
|
|
328
|
+
const named = payload.chatId;
|
|
329
|
+
if (typeof named === 'string' && named.trim())
|
|
330
|
+
return named.trim();
|
|
331
|
+
return wakeChatId;
|
|
332
|
+
}
|
|
326
333
|
async function subcontractAgreement(services, payload, chatId, _agents, actingAgentId, writeKeys = null, cachedAgreements) {
|
|
327
334
|
if (!payload.description || !payload.executorId)
|
|
328
335
|
return null;
|
|
@@ -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, } from '@ziggs-ai/api-client';
|
|
4
|
+
import { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION, agreementClaimCapability, agreementSubcontractCapability, } 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'];
|
|
@@ -61,7 +61,7 @@ function handlerFor(toolName) {
|
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
export const agreementProposeTool = defineTool('agreement_propose', { description: { type: 'string', required: true }, proposedTo: { type: 'string', required: true }, engagementKind: { type: 'string', enum: ['hire', 'service'], description: 'Contract type. Defaults to service. For a bilateral trust link, use link_propose.' }, parentAgreementId: 'string', providerId: { type: 'string', description: AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION }, price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, lifecycle: lifecycleParam, expiresAt: 'string', maxExecutions: 'integer', billing: { type: 'string', enum: ['total', 'per_task'], description: "How price reads: 'total' (default, escrowed and paid at the end) or 'per_task' (a rate settled as each task completes — standing agreements only, and the default for a hire)." }, agreementDescription: 'string' }, handlerFor('agreement_propose'), { description: 'Propose an agreement — direct, broadcast, or link; there are no separate publish tools. Engagement precedence (posted-first): 1) REUSE an active agreement on matching terms, 2) CLAIM the counterparty\'s listing (marketplace_view → agreement_claim; never counter a listing), 3) POST a request broadcast here when nothing listed fits, 4) direct-propose ONLY for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Direction is stated, never guessed: providerId = your own id ("I work, you pay") or providerId = proposedTo ("you work, I pay") on direct proposals. Broadcast with proposedTo="everyone" (fully public) or "org" (your org only): no providerId publishes a REQUEST any agent may claim; providerId = your own id publishes a STANDING OFFER (claiming is agreement_claim; browsing is marketplace_view). For a bilateral trust link, use link_propose: it takes an email or an agent id, or neither for a share link. engagementKind defaults to service. Agreements are STANDING by default (no expiry, unlimited tasks under one agreement); set expiresAt or maxExecutions only when the engagement should end on its own.', isAgreementCreation: true });
|
|
64
|
-
export const agreementSubcontractTool = defineTool('agreement_subcontract',
|
|
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', {
|
|
@@ -73,7 +73,7 @@ export const taskSetResultTool = defineTool('task_set_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
|
-
}, handlerFor('task_set_result'), { description: 'Transition a task to its terminal state — completed, failed, or cancelled — and record the result. Set the result here as an object ({ summary, status, links }): a task the parties opted into is closed by this call, whatever else you also sent them. Pass state (not status).' });
|
|
76
|
+
}, handlerFor('task_set_result'), { description: 'Transition a task to its terminal state — completed, failed, or cancelled — and record the result. Set the result here as an object ({ summary, status, links }): a task the parties opted into is closed by this call, whatever else you also sent them. Pass state (not status). This closes the task and not the engagement: a standing hire stays active for the next task, ending one is agreement_fulfill. The reply reports what actually happened — what was stored, who the completion was queued for (queued, not read), what per-task metering did — so report those and no more; a completed task is not by itself a paid one.' });
|
|
77
77
|
export const agreementCounterTool = defineTool('agreement_counter', { agreementId: { type: 'string', required: true }, price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, agreementDescription: 'string', expiresAt: 'string', lifecycle: lifecycleParam, maxExecutions: 'integer', description: 'string' }, handlerFor('agreement_counter'), { description: 'Counter a pending proposal with revised terms instead of approving or rejecting. Check status first with agreement_get.' });
|
|
78
78
|
export const agreementGetTool = defineTool('agreement_get', { agreementId: { type: 'string', required: true } }, handlerFor('agreement_get'), { description: 'Read the current proposal status for an agreement (pending, approved, rejected, countered, expired).' });
|
|
79
79
|
export const agreementRevokeTool = defineTool('agreement_revoke', { agreementId: { type: 'string', required: true } }, handlerFor('agreement_revoke'), { description: 'Revoke a pending proposal you created so it can no longer be approved. Use when the user has clearly moved on to a different request before responding to the previous proposal card.' });
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { ToolDefinition } from '../../../tools/defineTool.js';
|
|
2
2
|
export declare const contextReadTool: ToolDefinition;
|
|
3
|
+
export declare const openTool: ToolDefinition;
|
|
4
|
+
export declare const accessExplainTool: ToolDefinition;
|
|
5
|
+
export declare const contextRequestTool: ToolDefinition;
|
|
3
6
|
export declare const contextDelegateTool: ToolDefinition;
|
|
4
7
|
export declare const contextExpandReachTool: ToolDefinition;
|
|
5
8
|
export declare const contextDiscoverGrantableTool: ToolDefinition;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { CONTEXT_CAPABILITIES, contextReadCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, } from '@ziggs-ai/api-client';
|
|
1
|
+
import { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextRequestCapability, } 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);
|
|
7
|
+
export const openTool = toolFromCapability(openCapability);
|
|
8
|
+
export const accessExplainTool = toolFromCapability(accessExplainCapability);
|
|
9
|
+
export const contextRequestTool = toolFromCapability(contextRequestCapability);
|
|
7
10
|
export const contextDelegateTool = toolFromCapability(contextDelegateCapability);
|
|
8
11
|
export const contextExpandReachTool = toolFromCapability(contextExpandReachCapability);
|
|
9
12
|
export const contextDiscoverGrantableTool = toolFromCapability(contextDiscoverGrantableCapability);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ToolDefinition } from '../../../tools/defineTool.js';
|
|
2
|
+
/**
|
|
3
|
+
* `task_replace_plan` — write the checklist of a task you hold.
|
|
4
|
+
*
|
|
5
|
+
* The hosted tool set could only UPDATE a step that already existed
|
|
6
|
+
* (`task_update_plan_step` takes a stepId), and a task created without a plan
|
|
7
|
+
* has none, so an agent had no way to record anything about its own work on
|
|
8
|
+
* the task row. That mattered the moment one agent served two customers: from
|
|
9
|
+
* the second customer's room it could not find the approval request it had
|
|
10
|
+
* sent under the first customer's hire, and told them it had never been sent
|
|
11
|
+
* (store-opening lab, 2026-09-15). The MCP surface has had
|
|
12
|
+
* `ziggs_task_replace_plan` all along; this is the same client call for the
|
|
13
|
+
* hosted brain.
|
|
14
|
+
*
|
|
15
|
+
* The plan is the agent's own notebook on the task: step results can carry
|
|
16
|
+
* ids (an approval agreement, an artifact), and `task_list` reads the plan back
|
|
17
|
+
* from any room the agent is woken in.
|
|
18
|
+
*/
|
|
19
|
+
export declare const taskReplacePlanTool: ToolDefinition;
|
|
20
|
+
export declare const PLAN_TOOLS: ToolDefinition[];
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { replaceTaskPlan } from '@ziggs-ai/api-client';
|
|
2
|
+
import { defineTool } from '../../../tools/defineTool.js';
|
|
3
|
+
import { credsFromToolContext } from '../../../tools/toolCreds.js';
|
|
4
|
+
/**
|
|
5
|
+
* `task_replace_plan` — write the checklist of a task you hold.
|
|
6
|
+
*
|
|
7
|
+
* The hosted tool set could only UPDATE a step that already existed
|
|
8
|
+
* (`task_update_plan_step` takes a stepId), and a task created without a plan
|
|
9
|
+
* has none, so an agent had no way to record anything about its own work on
|
|
10
|
+
* the task row. That mattered the moment one agent served two customers: from
|
|
11
|
+
* the second customer's room it could not find the approval request it had
|
|
12
|
+
* sent under the first customer's hire, and told them it had never been sent
|
|
13
|
+
* (store-opening lab, 2026-09-15). The MCP surface has had
|
|
14
|
+
* `ziggs_task_replace_plan` all along; this is the same client call for the
|
|
15
|
+
* hosted brain.
|
|
16
|
+
*
|
|
17
|
+
* The plan is the agent's own notebook on the task: step results can carry
|
|
18
|
+
* ids (an approval agreement, an artifact), and `task_list` reads the plan back
|
|
19
|
+
* from any room the agent is woken in.
|
|
20
|
+
*/
|
|
21
|
+
export const taskReplacePlanTool = defineTool('task_replace_plan', {
|
|
22
|
+
taskId: { type: 'string', required: true, minLength: 1, pattern: '\\S' },
|
|
23
|
+
steps: {
|
|
24
|
+
type: 'array',
|
|
25
|
+
required: true,
|
|
26
|
+
items: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
stepId: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'Stable id for this step. Omit to let the server assign step-<n>.',
|
|
32
|
+
},
|
|
33
|
+
description: { type: 'string', minLength: 1, pattern: '\\S' },
|
|
34
|
+
order: {
|
|
35
|
+
type: 'integer',
|
|
36
|
+
description: 'Position in the plan. Omit to use the array index.',
|
|
37
|
+
},
|
|
38
|
+
status: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
enum: ['pending', 'in_progress', 'completed', 'skipped'],
|
|
41
|
+
description: 'Step progress. Omit for pending.',
|
|
42
|
+
},
|
|
43
|
+
result: {
|
|
44
|
+
type: 'object',
|
|
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
|
+
},
|
|
47
|
+
},
|
|
48
|
+
required: ['description'],
|
|
49
|
+
},
|
|
50
|
+
description: 'The FULL ordered step list. Existing steps are replaced wholesale, not appended to — read the current plan with task_list first and send it back with your changes.',
|
|
51
|
+
},
|
|
52
|
+
}, async (args, ctx) => {
|
|
53
|
+
const creds = credsFromToolContext(ctx, 'task_replace_plan');
|
|
54
|
+
const taskId = typeof args.taskId === 'string' ? args.taskId.trim() : '';
|
|
55
|
+
if (!taskId)
|
|
56
|
+
throw new Error('task_replace_plan: taskId is required');
|
|
57
|
+
const steps = args.steps;
|
|
58
|
+
if (!Array.isArray(steps))
|
|
59
|
+
throw new Error('task_replace_plan: steps must be an array');
|
|
60
|
+
return replaceTaskPlan(taskId, steps, creds);
|
|
61
|
+
}, {
|
|
62
|
+
description: 'Replace the plan of a task you hold with the full ordered step list. Use it to write your own checklist on a task that arrived without one, and to record what a step produced (an approval agreement id, an artifact id) so a later wake — in any room — can find it with task_list. Steps you do not send are removed; mark finished steps completed so closing the task does not rewrite them as skipped.',
|
|
63
|
+
});
|
|
64
|
+
export const PLAN_TOOLS = [taskReplacePlanTool];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { listTasksCapability, TASK_CAPABILITIES } from '@ziggs-ai/api-client';
|
|
1
|
+
import { cancelTaskCapability, listTasksCapability, TASK_CAPABILITIES } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
|
|
3
3
|
// GET /tasks, defined once in api-client. MCP ziggs_task_list and this
|
|
4
4
|
// hosted-Claude tool used to re-declare the same filters.
|
|
5
5
|
export const listTasksTool = toolFromCapability(listTasksCapability);
|
|
6
|
+
export const cancelTaskTool = toolFromCapability(cancelTaskCapability);
|
|
6
7
|
export const TASK_TOOLS = toolsFromCapabilities(TASK_CAPABILITIES);
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Prices are in wallet cents (1 ZIG = 100 cents).
|
|
4
4
|
*/
|
|
5
5
|
import { FLEET_TIER_BY_AGENT_ID } from './fleetTierByAgentId.gen.js';
|
|
6
|
-
import { FREE_TIER_A_AGENT_IDS } from './fleetFreeTierA.gen.js';
|
|
7
6
|
import { isEvalFreeExecutor } from './fleetEvalFree.js';
|
|
8
7
|
export const ZIGGS_OPERATOR_CUT_BPS = 1000; // 10%
|
|
9
8
|
export const TIER_PRICE_CENTS = {
|
|
@@ -13,7 +12,6 @@ export const TIER_PRICE_CENTS = {
|
|
|
13
12
|
D: 100, // 1 ZIG / call (hand-written demo agents)
|
|
14
13
|
};
|
|
15
14
|
const PING_DESCRIPTION_PATTERN = /\b(ping|health\s*check|healthcheck|liveness|readiness|heartbeat)\b/i;
|
|
16
|
-
const TRIVIAL_LOOKUP_DESCRIPTION_PATTERN = /\b(what\s+time|current\s+time|local\s+time|world\s+clock|timezone|time\s+zone|weather|forecast|temperature|uv\s+index)\b/i;
|
|
17
15
|
export function resolveFleetTier(agentId) {
|
|
18
16
|
if (!agentId || typeof agentId !== 'string')
|
|
19
17
|
return null;
|
|
@@ -30,15 +28,12 @@ export function isFreeAllowlistedExecutor(executorId, description) {
|
|
|
30
28
|
return false;
|
|
31
29
|
if (isEvalFreeExecutor(executorId))
|
|
32
30
|
return true;
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
// A health check does not bill, whatever tier the executor is in. This is
|
|
32
|
+
// the only description-based exemption left: an agent whose work should cost
|
|
33
|
+
// nothing says so with a posted price of 0 on its own listing, where the
|
|
34
|
+
// buyer can see it, rather than through a rule inferred here.
|
|
35
35
|
if (description && PING_DESCRIPTION_PATTERN.test(description))
|
|
36
36
|
return true;
|
|
37
|
-
if (description &&
|
|
38
|
-
TRIVIAL_LOOKUP_DESCRIPTION_PATTERN.test(description) &&
|
|
39
|
-
FREE_TIER_A_AGENT_IDS.has(executorId)) {
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
37
|
return false;
|
|
43
38
|
}
|
|
44
39
|
export function resolveDefaultPriceCentsForExecutor(executorId, description) {
|