@ziggs-ai/agent-sdk 0.1.3 → 0.1.5
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 +3 -1
- package/package.json +9 -4
- package/src/AgentHost.ts +495 -0
- package/src/adapters/OpenAIAdapter.ts +146 -0
- package/src/agent/Agent.ts +101 -0
- package/src/cognition/validateContext.ts +95 -0
- package/src/context/applyEffects.ts +80 -0
- package/src/context/batch.ts +17 -0
- package/src/context/classifyEnvelope.ts +38 -0
- package/src/context/routingLabels.ts +46 -0
- package/src/defineAgent.ts +62 -0
- package/src/formatters/AgreementFormatter.ts +111 -0
- package/src/formatters/HistoryFormatter.ts +166 -0
- package/src/formatters/index.ts +2 -0
- package/src/index.ts +105 -0
- package/src/ingress/normalizeIncoming.ts +162 -0
- package/src/memory/MemoryStore.ts +104 -0
- package/src/pricing/fleetDefaults.ts +218 -0
- package/src/pricing/fleetEvalFree.ts +24 -0
- package/src/pricing/fleetFreeTierA.gen.ts +12 -0
- package/src/pricing/fleetTierByAgentId.gen.ts +1022 -0
- package/src/runtime/AgentMachine.ts +364 -0
- package/src/runtime/PromptBuilder.ts +463 -0
- package/src/runtime/buildOutcome.ts +518 -0
- package/src/runtime/defaults.ts +75 -0
- package/src/runtime/runTurn.ts +691 -0
- package/src/runtime/validateWorkflow.ts +181 -0
- package/src/server/ConnectionPool.ts +155 -0
- package/src/server/EventQueue.ts +133 -0
- package/src/server/InboxCatchUp.ts +251 -0
- package/src/server/OutboxBuffer.ts +90 -0
- package/src/server/SeenMessages.ts +27 -0
- package/src/server/ZiggsEffectHandler.ts +409 -0
- package/src/server/agreements/AgreementService.ts +117 -0
- package/src/server/createHealthServer.ts +85 -0
- package/src/server/proactive/ProactiveTrigger.ts +83 -0
- package/src/server/runLauncher.ts +146 -0
- package/src/server/tasks/TaskService.ts +110 -0
- package/src/server/tasks/index.ts +1 -0
- package/src/server/telemetryIngest.ts +91 -0
- package/src/server/tools/index.ts +46 -0
- package/src/server/tools/tier1/protocolRunner.ts +133 -0
- package/src/server/tools/tier1/protocolTools.ts +99 -0
- package/src/server/tools/tier2/connectionTools.ts +75 -0
- package/src/server/tools/tier2/contextTools.ts +74 -0
- package/src/server/tools/tier2/discoveryTools.ts +34 -0
- package/src/server/tools/tier2/marketplaceTools.ts +25 -0
- package/src/server/tools/tier2/paymentTools.ts +193 -0
- package/src/server/ziggsconnect/ZiggsConnectClient.ts +126 -0
- package/src/server/ziggscontext/ZiggsContextClient.ts +137 -0
- package/src/server/ziggspay/ZiggsPayClient.ts +193 -0
- package/src/shared/ids.ts +3 -0
- package/src/shared/runtimeLog.ts +72 -0
- package/src/shared/types.ts +29 -0
- package/src/tasks/protocolRegistry.ts +25 -0
- package/src/tools/ToolManager.ts +95 -0
- package/src/tools/{ToolProvider.js → ToolProvider.ts} +5 -15
- package/src/tools/defineTool.ts +90 -0
- package/src/tools/index.ts +5 -0
- package/src/types.ts +407 -0
- package/src/utils/jsonExtractor.ts +100 -0
- package/src/ConnectionPool.js +0 -133
- package/src/adapters/OpenAIAdapter.js +0 -73
- package/src/agent/Agent.js +0 -121
- package/src/agent/EventQueue.js +0 -68
- package/src/agent/OutboxBuffer.js +0 -62
- package/src/cognition/PromptBuilder.js +0 -312
- package/src/cognition/resolveActionTool.js +0 -12
- package/src/cognition/runTurn.js +0 -578
- package/src/context/applyEffects.js +0 -133
- package/src/context/batch.js +0 -25
- package/src/context/classifyEnvelope.js +0 -82
- package/src/context/routingLabels.js +0 -54
- package/src/createHealthServer.js +0 -28
- package/src/formatters/HistoryFormatter.js +0 -257
- package/src/formatters/TaskFormatter.js +0 -180
- package/src/formatters/index.js +0 -9
- package/src/index.js +0 -76
- package/src/ingress/normalizeIncoming.js +0 -70
- package/src/runLauncher.js +0 -159
- package/src/shared/ids.js +0 -7
- package/src/shared/types.js +0 -86
- package/src/tasks/TaskService.js +0 -247
- package/src/tasks/index.js +0 -9
- package/src/tasks/taskCore.js +0 -229
- package/src/tasks/taskProtocolRegistry.js +0 -22
- package/src/tasks/taskProtocolRunner.js +0 -107
- package/src/tasks/taskProtocolTools.js +0 -87
- package/src/tools/ToolManager.js +0 -79
- package/src/tools/defineTool.js +0 -82
- package/src/tools/index.js +0 -11
- package/src/utils/jsonExtractor.js +0 -139
- package/src/workflow/AgentMachine.js +0 -250
- package/src/workflow/WorkflowRuntime.js +0 -63
- package/src/workflow/dsl.js +0 -287
- package/src/workflow/motifs.js +0 -435
- package/src/ziggs/runtime.js +0 -192
- /package/src/adapters/{index.js → index.ts} +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { ConnectionPool } from './ConnectionPool.js';
|
|
2
|
+
import { AgentHost, type AgentHostOptions } from '../AgentHost.js';
|
|
3
|
+
import { createHealthServer } from './createHealthServer.js';
|
|
4
|
+
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
5
|
+
import type { Server } from 'http';
|
|
6
|
+
|
|
7
|
+
type AgentConfig = AgentHostOptions & {
|
|
8
|
+
ownAgentId?: string;
|
|
9
|
+
options?: { ownAgentId?: string; name?: string };
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
interface SingleOptions {
|
|
13
|
+
operatorKey?: string;
|
|
14
|
+
label?: string;
|
|
15
|
+
port?: number | string;
|
|
16
|
+
healthServer?: boolean;
|
|
17
|
+
onShutdown?: () => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface FleetOptions extends SingleOptions {
|
|
21
|
+
wsUrl?: string;
|
|
22
|
+
agentMeta?: Record<string, unknown>[];
|
|
23
|
+
primary?: AgentConfig | ((pool: ConnectionPool) => AgentConfig);
|
|
24
|
+
preWake?: string[];
|
|
25
|
+
poolOptions?: { maxActive?: number; idleTimeoutMs?: number };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function runLauncher(
|
|
29
|
+
configOrConfigs: AgentConfig | AgentConfig[],
|
|
30
|
+
opts: FleetOptions = {},
|
|
31
|
+
): Promise<unknown> {
|
|
32
|
+
if (Array.isArray(configOrConfigs)) return runFleet(configOrConfigs, opts);
|
|
33
|
+
return runSingle(configOrConfigs, opts as SingleOptions);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function runSingle(
|
|
37
|
+
config: AgentConfig,
|
|
38
|
+
{ operatorKey, label = 'agent', port = process.env.PORT || 8080, healthServer = true, onShutdown }: SingleOptions = {},
|
|
39
|
+
): Promise<{ agent: unknown; healthServer?: Server }> {
|
|
40
|
+
const effectiveKey = operatorKey ?? process.env.ZIGGS_OPERATOR_KEY;
|
|
41
|
+
const decorated = effectiveKey && !config.operatorKey ? { ...config, operatorKey: effectiveKey } : config;
|
|
42
|
+
const agent = new AgentHost(decorated);
|
|
43
|
+
try {
|
|
44
|
+
await agent.connectAsync();
|
|
45
|
+
runtimeLog.info(
|
|
46
|
+
label,
|
|
47
|
+
`"${(agent as unknown as Record<string, unknown>)['options'] ? ((agent as unknown as Record<string, {agentId?: string; name?: string | null}>)['options']?.agentId ?? (agent as unknown as Record<string, {name?: string | null}>)['options']?.name) : 'agent'}" connected`,
|
|
48
|
+
);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
runtimeLog.warn(label, `Agent failed to connect at startup: ${(err as Error).message} — will retry`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const server = healthServer ? createHealthServer({ port: Number(port), label }) : null;
|
|
54
|
+
|
|
55
|
+
process.on('SIGTERM', async () => {
|
|
56
|
+
runtimeLog.info(label, 'SIGTERM — shutting down');
|
|
57
|
+
agent.disconnect();
|
|
58
|
+
await agent.drain(30_000);
|
|
59
|
+
try {
|
|
60
|
+
await onShutdown?.();
|
|
61
|
+
} catch (err) {
|
|
62
|
+
runtimeLog.warn(label, `onShutdown error: ${(err as Error).message}`);
|
|
63
|
+
}
|
|
64
|
+
server?.close();
|
|
65
|
+
process.exit(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return { agent, healthServer: server ?? undefined };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function runFleet(
|
|
72
|
+
configs: AgentConfig[],
|
|
73
|
+
{
|
|
74
|
+
operatorKey, wsUrl = process.env.WS_URL, port = process.env.PORT || 8080,
|
|
75
|
+
healthServer = true, label = 'launcher', agentMeta,
|
|
76
|
+
primary: primaryConfig, preWake = [], poolOptions, onShutdown,
|
|
77
|
+
}: FleetOptions = {},
|
|
78
|
+
): Promise<{ pool: ConnectionPool; primary: unknown; healthServer?: Server }> {
|
|
79
|
+
const pool = new ConnectionPool(poolOptions);
|
|
80
|
+
const effectiveKey = operatorKey ?? process.env.ZIGGS_OPERATOR_KEY;
|
|
81
|
+
const decoratedConfigs = effectiveKey
|
|
82
|
+
? configs.map(c => (c.operatorKey ? c : { ...c, operatorKey: effectiveKey }))
|
|
83
|
+
: configs;
|
|
84
|
+
pool.register(decoratedConfigs, agentMeta as Record<string, unknown>[]);
|
|
85
|
+
const server = healthServer ? createHealthServer({ port: Number(port), label, pool }) : null;
|
|
86
|
+
|
|
87
|
+
let resolvedPrimaryConfig: AgentConfig | undefined = typeof primaryConfig === 'function'
|
|
88
|
+
? primaryConfig(pool)
|
|
89
|
+
: primaryConfig;
|
|
90
|
+
if (resolvedPrimaryConfig && effectiveKey && !resolvedPrimaryConfig.operatorKey) {
|
|
91
|
+
resolvedPrimaryConfig = { ...resolvedPrimaryConfig, operatorKey: effectiveKey };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const controlKey = effectiveKey ?? configs.find(c => c.operatorKey)?.operatorKey ?? resolvedPrimaryConfig?.operatorKey;
|
|
95
|
+
if (controlKey) {
|
|
96
|
+
pool.startControl({ wsUrl, operatorKey: controlKey });
|
|
97
|
+
} else {
|
|
98
|
+
runtimeLog.warn(label, 'No operatorKey provided — skipping control socket');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let primary: unknown = null;
|
|
102
|
+
if (resolvedPrimaryConfig) {
|
|
103
|
+
primary = new AgentHost(resolvedPrimaryConfig);
|
|
104
|
+
try {
|
|
105
|
+
await (primary as { connectAsync: () => Promise<void> }).connectAsync();
|
|
106
|
+
runtimeLog.info(
|
|
107
|
+
label,
|
|
108
|
+
`Primary agent "${resolvedPrimaryConfig.name || resolvedPrimaryConfig.ownAgentId || 'primary'}" connected`,
|
|
109
|
+
);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
// Backend may not be ready yet — socket.io reconnects automatically.
|
|
112
|
+
// Don't crash the process; the primary agent will be live once it reconnects.
|
|
113
|
+
runtimeLog.warn(
|
|
114
|
+
label,
|
|
115
|
+
`Primary agent "${resolvedPrimaryConfig.name || resolvedPrimaryConfig.ownAgentId || 'primary'}" failed to connect at startup: ${(err as Error).message} — will retry`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (preWake.length > 0) {
|
|
121
|
+
await Promise.all(preWake.map(id =>
|
|
122
|
+
pool.wake(id)
|
|
123
|
+
.then(() => runtimeLog.debug(label, `Pre-woke "${id}"`))
|
|
124
|
+
.catch(err => runtimeLog.warn(label, `Could not pre-wake "${id}": ${(err as Error).message}`)),
|
|
125
|
+
));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
runtimeLog.info(label, `Ready — ${pool.size} agent(s) registered, ${pool.listActive().length} connected`);
|
|
129
|
+
|
|
130
|
+
process.on('SIGTERM', async () => {
|
|
131
|
+
runtimeLog.info(label, 'SIGTERM — shutting down');
|
|
132
|
+
pool.stopControl();
|
|
133
|
+
(primary as { disconnect?: () => void })?.disconnect?.();
|
|
134
|
+
await (primary as { drain?: (ms: number) => Promise<void> })?.drain?.(30_000);
|
|
135
|
+
await pool.disconnectAll();
|
|
136
|
+
try {
|
|
137
|
+
await onShutdown?.();
|
|
138
|
+
} catch (err) {
|
|
139
|
+
runtimeLog.warn(label, `onShutdown error: ${(err as Error).message}`);
|
|
140
|
+
}
|
|
141
|
+
server?.close();
|
|
142
|
+
process.exit(0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return { pool, primary, healthServer: server ?? undefined };
|
|
146
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTask, getTask, getActiveTasksForAgent, getActiveTasksForChat, getSubtasks,
|
|
3
|
+
updateTaskState, cancelTask,
|
|
4
|
+
claimLedgerTask, reportTask, setTaskSatisfaction, countTasks,
|
|
5
|
+
type Creds, type Task, type TaskState,
|
|
6
|
+
} from '@ziggs-ai/api-client';
|
|
7
|
+
import type { CountTasksFilters } from '@ziggs-ai/api-client';
|
|
8
|
+
import { runtimeLog } from '../../shared/runtimeLog.js';
|
|
9
|
+
|
|
10
|
+
function safeJsonStringify(value: unknown): unknown {
|
|
11
|
+
if (value === null || value === undefined) return value;
|
|
12
|
+
if (typeof value === 'string') return value;
|
|
13
|
+
if (typeof value !== 'object') return String(value);
|
|
14
|
+
const seen = new WeakSet();
|
|
15
|
+
try {
|
|
16
|
+
return JSON.stringify(value, (_key, val) => {
|
|
17
|
+
if (typeof val === 'object' && val !== null) {
|
|
18
|
+
if (seen.has(val)) return '[Circular]';
|
|
19
|
+
seen.add(val);
|
|
20
|
+
}
|
|
21
|
+
return val;
|
|
22
|
+
}, 2);
|
|
23
|
+
} catch {
|
|
24
|
+
return String(value);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class TaskService {
|
|
29
|
+
private creds: Creds;
|
|
30
|
+
|
|
31
|
+
constructor(operatorKey: string, agentId: string) {
|
|
32
|
+
if (!operatorKey) throw new Error('TaskService: operatorKey is required');
|
|
33
|
+
if (!agentId) throw new Error('TaskService: agentId is required (operator-token impersonation)');
|
|
34
|
+
this.creds = { operatorKey, agentId };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async getTask(taskId: string): Promise<Task> {
|
|
38
|
+
return getTask(taskId, this.creds);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async getActiveTasksForAgent(agentId: string): Promise<Task[]> {
|
|
42
|
+
return getActiveTasksForAgent(agentId, this.creds);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async getActiveTasksForChat(chatId: string): Promise<Task[]> {
|
|
46
|
+
return getActiveTasksForChat(chatId, this.creds);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async getSubtasks(parentTaskId: string): Promise<Task[]> {
|
|
50
|
+
return getSubtasks(parentTaskId, this.creds);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async spawnUnderAgreement(agreementId: string, taskData: { description: string; parentTaskId?: string; plan?: unknown; planReviewTiming?: string; requireMidWorkPlanAck?: boolean }): Promise<Task> {
|
|
54
|
+
if (!agreementId) throw new Error('TaskService.spawnUnderAgreement: agreementId is required');
|
|
55
|
+
try {
|
|
56
|
+
const result = await createTask({ ...taskData, agreementId } as Parameters<typeof createTask>[0], this.creds);
|
|
57
|
+
runtimeLog.info('TaskService', `Task spawned under agreement ${agreementId}: ${result?.taskId || 'unknown'}`);
|
|
58
|
+
return result;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
runtimeLog.error('TaskService', `Task spawn failed: ${(error as Error).message}`);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async updateState(taskId: string, state: TaskState, result?: unknown): Promise<Task> {
|
|
66
|
+
try {
|
|
67
|
+
const data = result !== undefined ? { result: safeJsonStringify(result) } : {};
|
|
68
|
+
const updated = await updateTaskState(taskId, state, data, this.creds);
|
|
69
|
+
runtimeLog.info('TaskService', `Task state: ${taskId} → ${state}`);
|
|
70
|
+
return updated;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
runtimeLog.error('TaskService', `Task state update failed: ${(error as Error).message}`);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async cancel(taskId: string): Promise<Task> {
|
|
78
|
+
try {
|
|
79
|
+
const updated = await cancelTask(taskId, this.creds);
|
|
80
|
+
runtimeLog.info('TaskService', `Task cancelled: ${taskId}`);
|
|
81
|
+
return updated;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
runtimeLog.error('TaskService', `Task cancel failed: ${(error as Error).message}`);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async claimLedgerTask(taskId: string): Promise<Task> {
|
|
89
|
+
try {
|
|
90
|
+
const task = await claimLedgerTask(taskId, this.creds);
|
|
91
|
+
runtimeLog.info('TaskService', `[ledger] Claimed task ${taskId}`);
|
|
92
|
+
return task as unknown as Task;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
runtimeLog.warn('TaskService', `[ledger] Claim failed for ${taskId}: ${(error as Error).message}`);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async report(taskId: string, message = ''): Promise<Task> {
|
|
100
|
+
return reportTask(taskId, message, this.creds);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async setSatisfaction(taskId: string, satisfaction: 'positive' | 'negative'): Promise<Task> {
|
|
104
|
+
return setTaskSatisfaction(taskId, satisfaction, this.creds);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async count(filters: CountTasksFilters = {}): Promise<number> {
|
|
108
|
+
return countTasks(filters, this.creds);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TaskService } from './TaskService.js';
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { LlmResponse } from '../types.js';
|
|
2
|
+
import type { TelemetryClient } from '@ziggs-ai/api-client';
|
|
3
|
+
|
|
4
|
+
export function isTelemetryIngestEnabled(
|
|
5
|
+
explicit?: boolean,
|
|
6
|
+
): boolean {
|
|
7
|
+
if (explicit === false) return false;
|
|
8
|
+
if (explicit === true) return true;
|
|
9
|
+
const env = process.env.ZIGGS_TELEMETRY_INGEST;
|
|
10
|
+
if (env === '0' || env === 'false') return false;
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function usageFromLlmResponse(response: LlmResponse): {
|
|
15
|
+
tokensTotal: number;
|
|
16
|
+
tokensInput: number;
|
|
17
|
+
tokensOutput: number;
|
|
18
|
+
} {
|
|
19
|
+
const u = response.usage;
|
|
20
|
+
const tokensInput =
|
|
21
|
+
Number(u?.prompt_tokens ?? u?.promptTokens ?? 0) || 0;
|
|
22
|
+
const tokensOutput =
|
|
23
|
+
Number(u?.completion_tokens ?? u?.completionTokens ?? 0) || 0;
|
|
24
|
+
const tokensTotal =
|
|
25
|
+
Number(u?.total_tokens ?? u?.totalTokens ?? 0) ||
|
|
26
|
+
tokensInput + tokensOutput;
|
|
27
|
+
return { tokensTotal, tokensInput, tokensOutput };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildLlmIngestPayload(
|
|
31
|
+
response: LlmResponse,
|
|
32
|
+
ctx: { sessionId?: string; durationMs: number; runId?: string; stateId?: string },
|
|
33
|
+
): Record<string, unknown> {
|
|
34
|
+
const { tokensTotal, tokensInput, tokensOutput } =
|
|
35
|
+
usageFromLlmResponse(response);
|
|
36
|
+
const chatId = ctx.sessionId ?? null;
|
|
37
|
+
return {
|
|
38
|
+
source: 'agent',
|
|
39
|
+
chatId,
|
|
40
|
+
sessionId: chatId,
|
|
41
|
+
execution: {
|
|
42
|
+
llmCallsTotal: 1,
|
|
43
|
+
tokensTotal,
|
|
44
|
+
tokensInput,
|
|
45
|
+
tokensOutput,
|
|
46
|
+
...(tokensTotal > 0 ? { tokensPerLlmCall: [tokensTotal] } : {}),
|
|
47
|
+
},
|
|
48
|
+
resource: { durationMs: Math.max(0, Math.round(ctx.durationMs)) },
|
|
49
|
+
meta: {
|
|
50
|
+
effect: 'llm-call',
|
|
51
|
+
...(ctx.runId ? { runId: ctx.runId } : {}),
|
|
52
|
+
...(ctx.stateId ? { stateId: ctx.stateId } : {}),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function buildToolIngestPayload(
|
|
58
|
+
ctx: {
|
|
59
|
+
sessionId: string;
|
|
60
|
+
tool: string;
|
|
61
|
+
durationMs: number;
|
|
62
|
+
ok: boolean;
|
|
63
|
+
runId?: string;
|
|
64
|
+
stateId?: string;
|
|
65
|
+
},
|
|
66
|
+
): Record<string, unknown> {
|
|
67
|
+
return {
|
|
68
|
+
source: 'agent',
|
|
69
|
+
chatId: ctx.sessionId,
|
|
70
|
+
sessionId: ctx.sessionId,
|
|
71
|
+
execution: { executionLoops: 1 },
|
|
72
|
+
resource: { durationMs: Math.max(0, Math.round(ctx.durationMs)) },
|
|
73
|
+
meta: {
|
|
74
|
+
effect: 'tool-call',
|
|
75
|
+
tool: ctx.tool,
|
|
76
|
+
ok: ctx.ok,
|
|
77
|
+
...(ctx.runId ? { runId: ctx.runId } : {}),
|
|
78
|
+
...(ctx.stateId ? { stateId: ctx.stateId } : {}),
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Fire-and-forget; never throws to the effect handler. */
|
|
84
|
+
export function sendTelemetry(
|
|
85
|
+
client: TelemetryClient | undefined,
|
|
86
|
+
payload: Record<string, unknown>,
|
|
87
|
+
enabled: boolean,
|
|
88
|
+
): void {
|
|
89
|
+
if (!enabled || !client) return;
|
|
90
|
+
void client.send(payload).catch(() => {});
|
|
91
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool bundles, in two tiers. Single home for everything an agent can
|
|
3
|
+
* "speak" or "do" over the wire.
|
|
4
|
+
*
|
|
5
|
+
* Tier 1 — protocol grammar (tier1/). The verbs every agent uses to participate
|
|
6
|
+
* in the agreement/task system. Framework-managed: attached via the `taskTools`
|
|
7
|
+
* config (default 'all'), dispatched to in-process services (taskService /
|
|
8
|
+
* agreementService) injected into tool context — NOT passed in the user `tools:`
|
|
9
|
+
* array. Publishing open work is part of this grammar:
|
|
10
|
+
* agreement_propose({ proposedTo: "everyone" }) — there is no separate publish tool.
|
|
11
|
+
*
|
|
12
|
+
* Tier 2 — capability bundles (tier2/). Opt-in, HTTP-backed tool bundles an agent
|
|
13
|
+
* chooses to have. Off by default; an agent gains a bundle by spreading it into
|
|
14
|
+
* its `tools:` array. Each tool reads operatorKey/agentId from tool context and
|
|
15
|
+
* wraps a backend HTTP client — no per-agent plumbing.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// ── Tier 1: protocol grammar ────────────────────────────────────────────────
|
|
19
|
+
export {
|
|
20
|
+
PROTOCOL_TOOLS,
|
|
21
|
+
agreementProposeTool, agreementSubcontractTool, agreementRespondTool,
|
|
22
|
+
agreementCounterProposalTool, agreementCheckProposalTool, agreementRevokeTool,
|
|
23
|
+
taskSpawnTool, taskUpdateTool, taskUpdatePlanStepTool,
|
|
24
|
+
} from './tier1/protocolTools.js';
|
|
25
|
+
export { dispatchProtocolOp } from './tier1/protocolRunner.js';
|
|
26
|
+
|
|
27
|
+
// ── Tier 2: capability bundles ──────────────────────────────────────────────
|
|
28
|
+
export { DISCOVERY_TOOLS, agentSearchTool, agentGetTool } from './tier2/discoveryTools.js';
|
|
29
|
+
export { MARKETPLACE_TOOLS, marketplaceViewTool } from './tier2/marketplaceTools.js';
|
|
30
|
+
export {
|
|
31
|
+
PAYMENT_TOOLS,
|
|
32
|
+
paymentBalanceTool, paymentTransferTool, paymentWaitForApprovalTool,
|
|
33
|
+
paymentHoldTool, paymentReleaseTool, paymentResolveWalletTool,
|
|
34
|
+
paymentIssueTokenTool, paymentAttenuateTokenTool, paymentRevokeTokenTool, paymentListTokensTool,
|
|
35
|
+
} from './tier2/paymentTools.js';
|
|
36
|
+
export {
|
|
37
|
+
CONNECTION_TOOLS,
|
|
38
|
+
connectionProxyTool,
|
|
39
|
+
connectionListGrantsTool,
|
|
40
|
+
} from './tier2/connectionTools.js';
|
|
41
|
+
export {
|
|
42
|
+
CONTEXT_TOOLS,
|
|
43
|
+
contextDiscoverTool,
|
|
44
|
+
contextListGrantsTool,
|
|
45
|
+
contextDelegateTool,
|
|
46
|
+
} from './tier2/contextTools.js';
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { AgreementService } from '../../agreements/AgreementService.js';
|
|
2
|
+
import type { TaskService } from '../../tasks/TaskService.js';
|
|
3
|
+
import {
|
|
4
|
+
applyFleetCounterPricing,
|
|
5
|
+
applyFleetPricingForProtocol,
|
|
6
|
+
} from '../../../pricing/fleetDefaults.js';
|
|
7
|
+
|
|
8
|
+
// Map legacy task-state aliases onto the canonical set.
|
|
9
|
+
function normalizeState(state: string): string {
|
|
10
|
+
if (state === 'done') return 'completed';
|
|
11
|
+
if (state === 'error') return 'failed';
|
|
12
|
+
return state;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Services {
|
|
16
|
+
agreementService: AgreementService;
|
|
17
|
+
taskService: TaskService;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ProtocolPayload {
|
|
21
|
+
operation?: string;
|
|
22
|
+
agreementId?: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
proposedTo?: string;
|
|
25
|
+
executorId?: string;
|
|
26
|
+
action?: 'approve' | 'reject';
|
|
27
|
+
price?: number;
|
|
28
|
+
lifecycle?: string;
|
|
29
|
+
expiresAt?: string;
|
|
30
|
+
maxExecutions?: number;
|
|
31
|
+
agreementDescription?: string;
|
|
32
|
+
parentAgreementId?: string;
|
|
33
|
+
parentTaskId?: string;
|
|
34
|
+
taskId?: string;
|
|
35
|
+
status?: string;
|
|
36
|
+
result?: unknown;
|
|
37
|
+
plan?: unknown;
|
|
38
|
+
planReviewTiming?: string;
|
|
39
|
+
requireMidWorkPlanAck?: boolean;
|
|
40
|
+
engagementKind?: 'hire' | 'service';
|
|
41
|
+
subtasks?: (string | { description?: string })[];
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function dispatchProtocolOp(
|
|
46
|
+
services: Services,
|
|
47
|
+
payload: ProtocolPayload,
|
|
48
|
+
chatId: string | null,
|
|
49
|
+
agents: unknown[] = [],
|
|
50
|
+
actingAgentId: string | null = null,
|
|
51
|
+
): Promise<unknown> {
|
|
52
|
+
if (!payload?.operation) return null;
|
|
53
|
+
const { operation } = payload;
|
|
54
|
+
const { agreementService, taskService } = services;
|
|
55
|
+
const actorId = actingAgentId?.trim() || null;
|
|
56
|
+
|
|
57
|
+
switch (operation) {
|
|
58
|
+
case 'agreement-propose':
|
|
59
|
+
return proposeAgreement(services, payload, chatId, agents, actorId);
|
|
60
|
+
case 'agreement-subcontract':
|
|
61
|
+
return subcontractAgreement(services, payload, chatId, agents, actorId);
|
|
62
|
+
case 'agreement-respond':
|
|
63
|
+
if (!payload.agreementId || !payload.action) return null;
|
|
64
|
+
return agreementService.respond(payload.agreementId, payload.action);
|
|
65
|
+
case 'agreement-counter-proposal':
|
|
66
|
+
if (!payload.agreementId) return null;
|
|
67
|
+
{
|
|
68
|
+
const counterPayload =
|
|
69
|
+
actorId != null
|
|
70
|
+
? applyFleetCounterPricing(payload, actorId)
|
|
71
|
+
: payload;
|
|
72
|
+
return agreementService.counter(payload.agreementId, {
|
|
73
|
+
price: counterPayload.price,
|
|
74
|
+
agreementDescription: counterPayload.agreementDescription,
|
|
75
|
+
expiresAt: counterPayload.expiresAt,
|
|
76
|
+
lifecycle: counterPayload.lifecycle,
|
|
77
|
+
maxExecutions: counterPayload.maxExecutions,
|
|
78
|
+
description: counterPayload.description,
|
|
79
|
+
plan: counterPayload.plan,
|
|
80
|
+
planReviewTiming: counterPayload.planReviewTiming as import('@ziggs-ai/api-client').PlanReviewTiming | undefined,
|
|
81
|
+
requireMidWorkPlanAck: counterPayload.requireMidWorkPlanAck,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
case 'agreement-check-proposal':
|
|
85
|
+
if (!payload.agreementId) return null;
|
|
86
|
+
return agreementService.getStatus(payload.agreementId);
|
|
87
|
+
case 'agreement-revoke':
|
|
88
|
+
if (!payload.agreementId) return null;
|
|
89
|
+
return agreementService.revoke(payload.agreementId);
|
|
90
|
+
case 'task-spawn':
|
|
91
|
+
if (!payload.agreementId || !payload.description) return null;
|
|
92
|
+
return taskService.spawnUnderAgreement(payload.agreementId, {
|
|
93
|
+
description: payload.description,
|
|
94
|
+
parentTaskId: payload.parentTaskId,
|
|
95
|
+
plan: payload.plan,
|
|
96
|
+
planReviewTiming: payload.planReviewTiming,
|
|
97
|
+
requireMidWorkPlanAck: payload.requireMidWorkPlanAck,
|
|
98
|
+
});
|
|
99
|
+
case 'task-update':
|
|
100
|
+
if (!payload.taskId || !payload.status) return null;
|
|
101
|
+
return taskService.updateState(payload.taskId, normalizeState(payload.status) as Parameters<typeof taskService.updateState>[1], payload.result);
|
|
102
|
+
default:
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function proposeAgreement(
|
|
108
|
+
services: Services,
|
|
109
|
+
payload: ProtocolPayload,
|
|
110
|
+
chatId: string | null,
|
|
111
|
+
_agents: unknown[],
|
|
112
|
+
actingAgentId: string | null,
|
|
113
|
+
): Promise<unknown> {
|
|
114
|
+
if (!payload.description) throw new Error('agreement-propose requires `description`');
|
|
115
|
+
if (!payload.proposedTo) throw new Error('agreement-propose requires `proposedTo` (userId or agentId). To spawn a task under an existing agreement, use `task_spawn` instead.');
|
|
116
|
+
const priced = applyFleetPricingForProtocol('agreement-propose', payload, actingAgentId);
|
|
117
|
+
const { operation: _op, ...fields } = priced;
|
|
118
|
+
return services.agreementService.propose({ ...fields, chatId } as Parameters<typeof services.agreementService.propose>[0]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function subcontractAgreement(
|
|
122
|
+
services: Services,
|
|
123
|
+
payload: ProtocolPayload,
|
|
124
|
+
chatId: string | null,
|
|
125
|
+
_agents: unknown[],
|
|
126
|
+
actingAgentId: string | null,
|
|
127
|
+
): Promise<unknown> {
|
|
128
|
+
if (!payload.description || !payload.executorId) return null;
|
|
129
|
+
const priced = applyFleetPricingForProtocol('agreement-subcontract', payload, actingAgentId);
|
|
130
|
+
const { operation: _op, ...fields } = priced;
|
|
131
|
+
return services.agreementService.delegate({ ...fields, chatId } as Parameters<typeof services.agreementService.delegate>[0]);
|
|
132
|
+
}
|
|
133
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { defineTool, type ToolDefinition } from '../../../tools/defineTool.js';
|
|
2
|
+
import { dispatchProtocolOp } from './protocolRunner.js';
|
|
3
|
+
import type { AgreementService } from '../../agreements/AgreementService.js';
|
|
4
|
+
import type { TaskService } from '../../tasks/TaskService.js';
|
|
5
|
+
|
|
6
|
+
interface ToolContext extends Record<string, unknown> {
|
|
7
|
+
taskService: TaskService;
|
|
8
|
+
agreementService: AgreementService;
|
|
9
|
+
chatId?: string | null;
|
|
10
|
+
agents?: unknown[];
|
|
11
|
+
/** Acting agent (ZIG-368 default pricing). */
|
|
12
|
+
agentId?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function handlerFor(operation: string) {
|
|
16
|
+
return async (args: Record<string, unknown>, ctx: Record<string, unknown>): Promise<unknown> => {
|
|
17
|
+
const c = ctx as ToolContext;
|
|
18
|
+
if (!c.taskService) throw new Error('taskService missing from tool context (expected when running under Agent)');
|
|
19
|
+
if (!c.agreementService) throw new Error('agreementService missing from tool context (expected when running under Agent)');
|
|
20
|
+
const result = await dispatchProtocolOp(
|
|
21
|
+
{ taskService: c.taskService, agreementService: c.agreementService },
|
|
22
|
+
{ operation, ...args },
|
|
23
|
+
c.chatId ?? null,
|
|
24
|
+
c.agents ?? [],
|
|
25
|
+
typeof c.agentId === 'string' ? c.agentId : null,
|
|
26
|
+
);
|
|
27
|
+
if (result == null) throw new Error(`Operation ${operation} returned no result`);
|
|
28
|
+
return result;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const agreementProposeTool: ToolDefinition = defineTool(
|
|
33
|
+
'agreement_propose',
|
|
34
|
+
{ description: { type: 'string', required: true }, proposedTo: { type: 'string', required: true }, engagementKind: { type: 'string', enum: ['hire', 'service'], description: 'Contract type. Defaults to service when omitted.' }, parentAgreementId: 'string', parentTaskId: 'string', payerId: 'string', providerId: 'string', price: 'number', lifecycle: 'string', expiresAt: 'string', maxExecutions: 'number', agreementDescription: 'string', plan: { type: 'object' }, planReviewTiming: 'string', requireMidWorkPlanAck: 'boolean' },
|
|
35
|
+
handlerFor('agreement-propose'),
|
|
36
|
+
{ description: 'Propose a contract to a user, agent, or everyone (proposedTo="everyone" for open broadcast). engagementKind defaults to service.', isAgreementCreation: true },
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export const agreementSubcontractTool: ToolDefinition = defineTool(
|
|
40
|
+
'agreement_subcontract',
|
|
41
|
+
{ description: { type: 'string', required: true }, executorId: { type: 'string', required: true }, parentAgreementId: { type: 'string', required: true }, parentTaskId: 'string', payerId: 'string', price: 'number', lifecycle: 'string', expiresAt: 'string', maxExecutions: 'number', agreementDescription: 'string', plan: { type: 'object' }, planReviewTiming: 'string', requireMidWorkPlanAck: 'boolean' },
|
|
42
|
+
handlerFor('agreement-subcontract'),
|
|
43
|
+
{ description: 'Delegate work to another agent under an existing parent agreement. Requires parentAgreementId.', isAgreementCreation: true },
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export const agreementRespondTool: ToolDefinition = defineTool(
|
|
47
|
+
'agreement_respond',
|
|
48
|
+
{ agreementId: { type: 'string', required: true }, action: { type: 'string', required: true, enum: ['approve', 'reject'] } },
|
|
49
|
+
handlerFor('agreement-respond'),
|
|
50
|
+
{ description: 'Approve or reject a pending agreement proposal.' },
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
export const taskUpdateTool: ToolDefinition = defineTool(
|
|
54
|
+
'task_update',
|
|
55
|
+
{ taskId: { type: 'string', required: true }, status: { type: 'string', required: true, enum: ['completed', 'failed', 'cancelled'] }, result: 'string' },
|
|
56
|
+
handlerFor('task-update'),
|
|
57
|
+
{ description: 'Transition a task to completed, failed, or cancelled. Include a result string for completed tasks.' },
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export const agreementCounterProposalTool: ToolDefinition = defineTool(
|
|
61
|
+
'agreement_counter_proposal',
|
|
62
|
+
{ agreementId: { type: 'string', required: true }, price: 'number', agreementDescription: 'string', expiresAt: 'string', lifecycle: 'string', maxExecutions: 'number', description: 'string', plan: { type: 'object' }, planReviewTiming: 'string', requireMidWorkPlanAck: 'boolean' },
|
|
63
|
+
handlerFor('agreement-counter-proposal'),
|
|
64
|
+
{ description: 'Counter a pending proposal with revised terms instead of approving or rejecting. Check status first with agreement_check_proposal.' },
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
export const agreementCheckProposalTool: ToolDefinition = defineTool(
|
|
68
|
+
'agreement_check_proposal',
|
|
69
|
+
{ agreementId: { type: 'string', required: true } },
|
|
70
|
+
handlerFor('agreement-check-proposal'),
|
|
71
|
+
{ description: 'Read the current proposal status for an agreement (pending, approved, rejected, countered, expired).' },
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
export const agreementRevokeTool: ToolDefinition = defineTool(
|
|
75
|
+
'agreement_revoke',
|
|
76
|
+
{ agreementId: { type: 'string', required: true } },
|
|
77
|
+
handlerFor('agreement-revoke'),
|
|
78
|
+
{ 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.' },
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
export const taskUpdatePlanStepTool: ToolDefinition = defineTool(
|
|
82
|
+
'task_update_plan_step',
|
|
83
|
+
{ taskId: { type: 'string', required: true }, stepId: { type: 'string', required: true }, status: { type: 'string', required: true }, result: 'string' },
|
|
84
|
+
handlerFor('task-update-plan-step'),
|
|
85
|
+
{ description: "Update the status of a single step within a task's plan." },
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
export const taskSpawnTool: ToolDefinition = defineTool(
|
|
89
|
+
'task_spawn',
|
|
90
|
+
{ agreementId: { type: 'string', required: true }, description: { type: 'string', required: true }, parentTaskId: 'string', plan: { type: 'object' }, planReviewTiming: 'string', requireMidWorkPlanAck: 'boolean' },
|
|
91
|
+
handlerFor('task-spawn'),
|
|
92
|
+
{ description: 'Spawn a new task under an existing approved agreement. Use when the contract is already in place.' },
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
export const PROTOCOL_TOOLS: ToolDefinition[] = [
|
|
96
|
+
agreementProposeTool, agreementSubcontractTool,
|
|
97
|
+
agreementRespondTool, agreementCounterProposalTool, agreementCheckProposalTool, agreementRevokeTool,
|
|
98
|
+
taskSpawnTool, taskUpdateTool, taskUpdatePlanStepTool,
|
|
99
|
+
];
|