@ziggs-ai/agent-sdk 0.1.7 → 0.1.8
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/package.json +2 -1
- package/src/adapters/McpAdapter.ts +173 -0
- package/src/adapters/OpenAIAdapter.ts +70 -34
- package/src/formatters/HistoryFormatter.ts +7 -1
- package/src/index.ts +2 -0
- package/src/pricing/fleetDefaults.ts +12 -5
- package/src/runtime/PromptBuilder.ts +3 -1
- package/src/runtime/runTurn.ts +7 -2
- package/src/server/InboxCatchUp.ts +9 -6
- package/src/server/agreements/AgreementService.ts +2 -7
- package/src/server/tasks/TaskService.ts +2 -13
- package/src/server/tools/tier2/marketplaceTools.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/agent-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Agent framework SDK for building autonomous agents on the Ziggs platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"test:watch": "node --experimental-test-module-mocks --import tsx/esm --test --watch test/*.test.ts"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
19
20
|
"@ziggs-ai/api-client": "^0.1.9",
|
|
20
21
|
"ajv": "^8.17.1",
|
|
21
22
|
"dotenv": "^17.2.3",
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
|
+
import { AgentHost, type AgentHostOptions } from '../AgentHost.js';
|
|
5
|
+
import type { ToolDefinition } from '../tools/defineTool.js';
|
|
6
|
+
import { thinkingDefaults } from '../runtime/defaults.js';
|
|
7
|
+
import type { Workflow } from '../types.js';
|
|
8
|
+
|
|
9
|
+
export type McpTransportConfig =
|
|
10
|
+
| { type: 'stdio'; command: string; args?: string[]; env?: Record<string, string> }
|
|
11
|
+
| { type: 'http'; url: string };
|
|
12
|
+
|
|
13
|
+
export interface CreateMcpAgentOptions extends Omit<AgentHostOptions, 'tools' | 'workflow' | 'states' | 'initial'> {
|
|
14
|
+
/** MCP server connection — stdio (local process) or HTTP (remote). */
|
|
15
|
+
transport: McpTransportConfig;
|
|
16
|
+
/** Additional Ziggs tools to register alongside the MCP-derived ones. */
|
|
17
|
+
extraTools?: ToolDefinition[];
|
|
18
|
+
/** Override the default single-state thinking workflow. */
|
|
19
|
+
workflow?: Workflow;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface McpAgentHandle {
|
|
23
|
+
host: AgentHost;
|
|
24
|
+
/** Disconnect the MCP client (call on shutdown alongside host.disconnect()). */
|
|
25
|
+
closeMcp: () => Promise<void>;
|
|
26
|
+
/** Names of MCP tools that were mapped to Ziggs tools on startup. */
|
|
27
|
+
toolNames: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildTransport(config: McpTransportConfig) {
|
|
31
|
+
if (config.type === 'stdio') {
|
|
32
|
+
return new StdioClientTransport({
|
|
33
|
+
command: config.command,
|
|
34
|
+
args: config.args ?? [],
|
|
35
|
+
env: config.env,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return new StreamableHTTPClientTransport(new URL(config.url));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type McpTool = {
|
|
42
|
+
name: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
inputSchema?: Record<string, unknown>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type McpContent = { type: string; text?: string };
|
|
48
|
+
|
|
49
|
+
function mcpContentToResult(content: McpContent[]): unknown {
|
|
50
|
+
const text = content
|
|
51
|
+
.map(c => (c.type === 'text' ? c.text ?? '' : JSON.stringify(c)))
|
|
52
|
+
.join('\n');
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(text);
|
|
55
|
+
} catch {
|
|
56
|
+
return text;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function mapMcpTools(client: Client, tools: McpTool[]): ToolDefinition[] {
|
|
61
|
+
return tools.map(tool => ({
|
|
62
|
+
schema: {
|
|
63
|
+
type: 'function' as const,
|
|
64
|
+
function: {
|
|
65
|
+
name: tool.name,
|
|
66
|
+
description: tool.description ?? '',
|
|
67
|
+
parameters: tool.inputSchema ?? { type: 'object', properties: {} },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
handler: async (args: Record<string, unknown>) => {
|
|
71
|
+
const result = await client.callTool({ name: tool.name, arguments: args }) as {
|
|
72
|
+
isError?: boolean;
|
|
73
|
+
content: McpContent[];
|
|
74
|
+
};
|
|
75
|
+
if (result.isError) {
|
|
76
|
+
throw new Error(mcpContentToResult(result.content) as string);
|
|
77
|
+
}
|
|
78
|
+
return mcpContentToResult(result.content);
|
|
79
|
+
},
|
|
80
|
+
isGenericFallback: true,
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Wraps any MCP server as a Ziggs AgentHost.
|
|
86
|
+
*
|
|
87
|
+
* 1. Opens a connection to the MCP server (stdio or HTTP).
|
|
88
|
+
* 2. Calls tools/list and maps each tool to a Ziggs ToolDefinition.
|
|
89
|
+
* 3. Returns an AgentHost pre-loaded with those tools, ready to connectAsync().
|
|
90
|
+
*
|
|
91
|
+
* Usage:
|
|
92
|
+
* const { host, closeMcp } = await createMcpAgent({
|
|
93
|
+
* transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'] },
|
|
94
|
+
* agentId: 'my-github-agent',
|
|
95
|
+
* name: 'GitHub Agent',
|
|
96
|
+
* description: 'Wraps the GitHub MCP server',
|
|
97
|
+
* });
|
|
98
|
+
* await host.connectAsync();
|
|
99
|
+
*
|
|
100
|
+
* process.on('SIGTERM', async () => {
|
|
101
|
+
* host.disconnect();
|
|
102
|
+
* await host.drain();
|
|
103
|
+
* await closeMcp();
|
|
104
|
+
* });
|
|
105
|
+
*/
|
|
106
|
+
function buildDefaultWorkflow(description: string): Workflow {
|
|
107
|
+
const dflt = thinkingDefaults({ initial: 'idle' });
|
|
108
|
+
return {
|
|
109
|
+
id: 'mcp-agent',
|
|
110
|
+
description: description || 'MCP-backed agent',
|
|
111
|
+
initial: 'idle',
|
|
112
|
+
states: {
|
|
113
|
+
idle: {
|
|
114
|
+
kind: 'thinking',
|
|
115
|
+
prompt: {
|
|
116
|
+
role: 'You are a helpful agent powered by an MCP server.',
|
|
117
|
+
goal: description || 'Help the user by calling the available tools.',
|
|
118
|
+
constraints: 'Use your tools to answer requests. When no action is needed, wait.',
|
|
119
|
+
},
|
|
120
|
+
actions: { ...dflt.actions },
|
|
121
|
+
transitions: [...dflt.transitions],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Wraps any MCP server as a Ziggs AgentHost.
|
|
129
|
+
*
|
|
130
|
+
* 1. Opens a connection to the MCP server (stdio or HTTP).
|
|
131
|
+
* 2. Calls tools/list and maps each tool to a Ziggs ToolDefinition.
|
|
132
|
+
* 3. Returns an AgentHost pre-loaded with those tools, ready to connectAsync().
|
|
133
|
+
*
|
|
134
|
+
* Usage:
|
|
135
|
+
* const { host, closeMcp } = await createMcpAgent({
|
|
136
|
+
* transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'] },
|
|
137
|
+
* agentId: 'my-github-agent',
|
|
138
|
+
* name: 'GitHub Agent',
|
|
139
|
+
* description: 'Wraps the GitHub MCP server',
|
|
140
|
+
* });
|
|
141
|
+
* await host.connectAsync();
|
|
142
|
+
*
|
|
143
|
+
* process.on('SIGTERM', async () => {
|
|
144
|
+
* host.disconnect();
|
|
145
|
+
* await host.drain();
|
|
146
|
+
* await closeMcp();
|
|
147
|
+
* });
|
|
148
|
+
*/
|
|
149
|
+
export async function createMcpAgent(options: CreateMcpAgentOptions): Promise<McpAgentHandle> {
|
|
150
|
+
const { transport: transportConfig, extraTools = [], workflow, ...hostOptions } = options;
|
|
151
|
+
|
|
152
|
+
const transport = buildTransport(transportConfig);
|
|
153
|
+
const client = new Client({ name: 'ziggs-mcp-agent', version: '1.0.0' });
|
|
154
|
+
|
|
155
|
+
await client.connect(transport);
|
|
156
|
+
|
|
157
|
+
const { tools: rawTools } = await client.listTools();
|
|
158
|
+
const mcpTools = mapMcpTools(client, rawTools as McpTool[]);
|
|
159
|
+
|
|
160
|
+
const resolvedWorkflow = workflow ?? buildDefaultWorkflow(hostOptions.description ?? '');
|
|
161
|
+
|
|
162
|
+
const host = new AgentHost({
|
|
163
|
+
...hostOptions,
|
|
164
|
+
workflow: resolvedWorkflow,
|
|
165
|
+
tools: [...mcpTools, ...extraTools],
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
host,
|
|
170
|
+
closeMcp: () => client.close(),
|
|
171
|
+
toolNames: rawTools.map((t: McpTool) => t.name),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -62,49 +62,85 @@ export class OpenAIAdapter {
|
|
|
62
62
|
: undefined;
|
|
63
63
|
|
|
64
64
|
try {
|
|
65
|
-
const { usage: _u, ...restOptions } = options as Record<string, unknown>;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
65
|
+
const { usage: _u, response_format: _explicitResponseFormat, ...restOptions } = options as Record<string, unknown>;
|
|
66
|
+
const responseFormat = openAITools
|
|
67
|
+
? undefined
|
|
68
|
+
: ((_explicitResponseFormat as OpenAI.ResponseFormatJSONObject) ?? { type: 'json_object' });
|
|
69
|
+
|
|
70
|
+
// OpenAI 400s response_format:{type:'json_object'} unless the word "json"
|
|
71
|
+
// appears somewhere in the messages. Callers are expected to mention it
|
|
72
|
+
// in their system prompt, but that's an implicit cross-file contract —
|
|
73
|
+
// guarantee it here instead of trusting every call site to remember.
|
|
74
|
+
let finalMessages = messages;
|
|
75
|
+
if (
|
|
76
|
+
responseFormat?.type === 'json_object' &&
|
|
77
|
+
!messages.some(m => {
|
|
78
|
+
const c = typeof m.content === 'string' ? m.content : JSON.stringify(m.content ?? '');
|
|
79
|
+
return /json/i.test(c);
|
|
80
|
+
})
|
|
81
|
+
) {
|
|
82
|
+
finalMessages = [...messages, { role: 'system', content: 'Respond with valid JSON.' }];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Verbose LLM request/response logging — enabled for debugging.
|
|
86
|
+
// Comment out again to reduce log noise.
|
|
87
|
+
const divider = '─'.repeat(80);
|
|
88
|
+
console.log(`\n${divider}`);
|
|
89
|
+
console.log(`📤 LLM REQUEST → ${this.model} (${finalMessages.length} msg${finalMessages.length === 1 ? '' : 's'})`);
|
|
90
|
+
console.log(divider);
|
|
91
|
+
for (const m of finalMessages) {
|
|
92
|
+
const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content);
|
|
93
|
+
const len = content?.length ?? 0;
|
|
94
|
+
console.log(`\n[${m.role.toUpperCase()}] ${len} chars`);
|
|
95
|
+
console.log(content ?? '');
|
|
96
|
+
}
|
|
97
|
+
console.log(`${divider}\n`);
|
|
79
98
|
const res = await this.client.chat.completions.create({
|
|
80
99
|
model: this.model,
|
|
81
|
-
messages,
|
|
100
|
+
messages: finalMessages,
|
|
82
101
|
stream: false,
|
|
83
|
-
|
|
84
|
-
|
|
102
|
+
// 25000 reserves room for hidden reasoning tokens on reasoning models
|
|
103
|
+
// (gpt-5.x/o-series) — they're billed against this cap before any
|
|
104
|
+
// visible content, so a low cap can exhaust the budget and return
|
|
105
|
+
// empty content with finish_reason "length".
|
|
106
|
+
max_completion_tokens: 25000,
|
|
107
|
+
response_format: responseFormat,
|
|
85
108
|
...(openAITools && { tools: openAITools }),
|
|
86
109
|
...restOptions,
|
|
87
110
|
} as OpenAI.Chat.ChatCompletionCreateParamsNonStreaming);
|
|
88
111
|
|
|
89
112
|
const msg = res.choices[0].message;
|
|
113
|
+
const finishReason = res.choices[0].finish_reason;
|
|
90
114
|
const content = msg.content?.trim() ?? null;
|
|
91
115
|
const toolCalls = msg.tool_calls?.length ? msg.tool_calls : null;
|
|
92
|
-
// Verbose LLM response logging —
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
// Verbose LLM response logging — enabled for debugging.
|
|
117
|
+
// Comment out again to reduce log noise.
|
|
118
|
+
console.log(`\n${divider}`);
|
|
119
|
+
console.log(`📥 LLM RESPONSE ← ${this.model} finish=${finishReason}`);
|
|
120
|
+
console.log(divider);
|
|
121
|
+
if (content) {
|
|
122
|
+
console.log(`\n[CONTENT] ${content.length} chars`);
|
|
123
|
+
console.log(content);
|
|
124
|
+
}
|
|
125
|
+
if (toolCalls) {
|
|
126
|
+
console.log(`\n[TOOL CALLS] ${toolCalls.length}`);
|
|
127
|
+
for (const tc of toolCalls) {
|
|
128
|
+
console.log(` → ${tc.function.name}(${tc.function.arguments})`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!content && !toolCalls) console.log('\n(no content, no tool calls)');
|
|
132
|
+
if (res.usage) {
|
|
133
|
+
const u = res.usage as OpenAI.CompletionUsage & { completion_tokens_details?: { reasoning_tokens?: number } };
|
|
134
|
+
console.log(`\n[USAGE] prompt=${u.prompt_tokens} completion=${u.completion_tokens}${u.completion_tokens_details?.reasoning_tokens != null ? ` (reasoning=${u.completion_tokens_details.reasoning_tokens})` : ''} total=${u.total_tokens}`);
|
|
135
|
+
}
|
|
136
|
+
console.log(`${divider}\n`);
|
|
137
|
+
if (!content && !toolCalls && finishReason === 'length') {
|
|
138
|
+
const u = res.usage as (OpenAI.CompletionUsage & { completion_tokens_details?: { reasoning_tokens?: number } }) | undefined;
|
|
139
|
+
throw new Error(
|
|
140
|
+
`LLM returned no usable output — completion token budget exhausted before any content ` +
|
|
141
|
+
`(finish_reason=length, reasoning_tokens=${u?.completion_tokens_details?.reasoning_tokens ?? 'unknown'})`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
108
144
|
return {
|
|
109
145
|
content,
|
|
110
146
|
tool_calls: toolCalls,
|
|
@@ -131,7 +167,7 @@ export class OpenAIAdapter {
|
|
|
131
167
|
model: this.model,
|
|
132
168
|
messages,
|
|
133
169
|
stream: true,
|
|
134
|
-
max_completion_tokens:
|
|
170
|
+
max_completion_tokens: 25000,
|
|
135
171
|
});
|
|
136
172
|
let text = '';
|
|
137
173
|
for await (const chunk of stream) {
|
|
@@ -116,7 +116,13 @@ export class HistoryFormatter {
|
|
|
116
116
|
|
|
117
117
|
formatParticipant(participant: AnyObj | null | undefined, agentId: string): string {
|
|
118
118
|
if (!participant) return 'unknown';
|
|
119
|
-
|
|
119
|
+
// Message.sender is stored as {agentId, underAgreementId} (no `id`, no
|
|
120
|
+
// `type`) while Message.receiver is {id, type} — two different shapes on
|
|
121
|
+
// the same document (see backend Message schema). Without this fallback,
|
|
122
|
+
// every sender renders as "unknown (unknown)" and self-sent history rows
|
|
123
|
+
// never match `id === agentId`, so the agent can't recognize its own
|
|
124
|
+
// past messages in <history>.
|
|
125
|
+
const id = (participant['id'] as string) || (participant['agentId'] as string) || 'unknown';
|
|
120
126
|
if (participant['type'] === 'system') return 'System';
|
|
121
127
|
if (id === agentId) return 'You';
|
|
122
128
|
return `${id} (${participant['type'] || 'unknown'})`;
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
export { defineAgent } from './defineAgent.js';
|
|
12
|
+
export { createMcpAgent } from './adapters/McpAdapter.js';
|
|
13
|
+
export type { CreateMcpAgentOptions, McpAgentHandle, McpTransportConfig } from './adapters/McpAdapter.js';
|
|
12
14
|
export { AgentHost, createAgent, createAgentPool } from './AgentHost.js';
|
|
13
15
|
export { Agent } from './agent/Agent.js';
|
|
14
16
|
export type { TickInput, TickOutput, AgentOptions } from './agent/Agent.js';
|
|
@@ -144,11 +144,18 @@ export function applyFleetAgreementPricing(
|
|
|
144
144
|
return next;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
/**
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Default tier price when a fleet agent counters without an explicit `price`.
|
|
149
|
+
*
|
|
150
|
+
* Generic over the payload shape so the precise field types of the caller's
|
|
151
|
+
* payload survive the round-trip — this helper only ever sets `price`, so a
|
|
152
|
+
* richer input type (e.g. protocolRunner's `ProtocolPayload`) must come back
|
|
153
|
+
* out unwidened by `ProtocolPricingPayload`'s `[key: string]: unknown` index.
|
|
154
|
+
*/
|
|
155
|
+
export function applyFleetCounterPricing<T extends ProtocolPricingPayload>(
|
|
156
|
+
payload: T,
|
|
150
157
|
actingAgentId: string,
|
|
151
|
-
):
|
|
158
|
+
): T {
|
|
152
159
|
if (payload.price != null && payload.price !== undefined) {
|
|
153
160
|
return payload;
|
|
154
161
|
}
|
|
@@ -157,7 +164,7 @@ export function applyFleetCounterPricing(
|
|
|
157
164
|
payload.description ?? payload.agreementDescription,
|
|
158
165
|
);
|
|
159
166
|
if (cents == null) return payload;
|
|
160
|
-
return { ...payload, price: cents };
|
|
167
|
+
return { ...payload, price: cents } as T;
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
/** User or agent direct-create / marketplace: default from provider specialist. */
|
|
@@ -208,7 +208,9 @@ ${this._renderDecisionSchemaFromActions(actions, senderMeta, ctxObj, omitMessage
|
|
|
208
208
|
const content = event.text || event.result || event.content;
|
|
209
209
|
if (!content) return `<event type="${eventType}" from="${from}">(Empty)</event>`;
|
|
210
210
|
const str = typeof content === 'object' ? JSON.stringify(content) : String(content);
|
|
211
|
-
|
|
211
|
+
// Matches MAX_MSG_CHARS in runTurn.ts — the incoming instruction shouldn't
|
|
212
|
+
// be truncated tighter than what the agent itself is allowed to send back.
|
|
213
|
+
const display = str.length > 3500 ? str.slice(0, 3500) + '...' : str;
|
|
212
214
|
return `<event type="${eventType}" from="${from}">\n${display}\n</event>`;
|
|
213
215
|
}
|
|
214
216
|
|
package/src/runtime/runTurn.ts
CHANGED
|
@@ -424,9 +424,13 @@ function eventsAreTerminal(events: EmittedEvent[]): boolean {
|
|
|
424
424
|
// (api-client now returns Agreement directly, not wrapped in { agreement }).
|
|
425
425
|
const ag = (r.agreement ?? (r.agreementId ? r : null)) as Record<string, unknown> | null | undefined;
|
|
426
426
|
const task = (r.task || (r.taskId ? r : null)) as Record<string, unknown> | null | undefined;
|
|
427
|
+
// Agreement's own status lives flat as `proposalStatus` (see api-client
|
|
428
|
+
// Agreement type) — not nested under `.proposal.status`. Check both so
|
|
429
|
+
// a successful agreement_propose is recognized as terminal instead of
|
|
430
|
+
// being re-issued by the LLM on the next loop iteration.
|
|
427
431
|
const agProposal = ag?.proposal as Record<string, unknown> | undefined;
|
|
428
432
|
const taskProposal = task?.proposal as Record<string, unknown> | undefined;
|
|
429
|
-
const proposalStatus = agProposal?.status
|
|
433
|
+
const proposalStatus = ag?.proposalStatus ?? agProposal?.status ?? taskProposal?.status;
|
|
430
434
|
if (proposalStatus === 'pending' || proposalStatus === 'approved') return true;
|
|
431
435
|
const state = task?.state || r.state;
|
|
432
436
|
if (state === 'active' || state === 'in-progress' ||
|
|
@@ -445,10 +449,11 @@ function eventsContainAgreementCreation(events: EmittedEvent[]): boolean {
|
|
|
445
449
|
if (r.success === false || r.error) return false;
|
|
446
450
|
// Support both nested { agreement: {...} } and flat Agreement objects.
|
|
447
451
|
const ag = (r.agreement ?? (r.agreementId ? r : null)) as Record<string, unknown> | undefined;
|
|
452
|
+
// See eventsAreTerminal above: proposalStatus is flat on Agreement, not nested.
|
|
448
453
|
const agProposal = ag?.proposal as Record<string, unknown> | undefined;
|
|
449
454
|
const task = r.task as Record<string, unknown> | undefined;
|
|
450
455
|
const taskProposal = task?.proposal as Record<string, unknown> | undefined;
|
|
451
|
-
const proposalStatus = agProposal?.status
|
|
456
|
+
const proposalStatus = ag?.proposalStatus ?? agProposal?.status ?? taskProposal?.status;
|
|
452
457
|
return proposalStatus === 'pending' || proposalStatus === 'approved';
|
|
453
458
|
});
|
|
454
459
|
}
|
|
@@ -171,7 +171,14 @@ export async function runInboxCatchUp(deps: InboxCatchUpDeps): Promise<InboxCatc
|
|
|
171
171
|
const toAck: InboxAck[] = [];
|
|
172
172
|
|
|
173
173
|
if (envelope.truncatedScopes > 0) {
|
|
174
|
-
|
|
174
|
+
// Informational only — does NOT block acking the scopes actually
|
|
175
|
+
// returned below. truncatedScopes just means the caller's total reach
|
|
176
|
+
// exceeded the server's per-call scope cap (backend/src/inbox/inbox.service.ts,
|
|
177
|
+
// INBOX_MAX_SCOPES); each entry in envelope.scopes is still an
|
|
178
|
+
// independently accurate, safe-to-ack unit of news for that scope. Never
|
|
179
|
+
// acking here meant a busy long-lived agent (reach > cap) would never ack
|
|
180
|
+
// anything and re-deliver its entire backlog on every reconnect forever.
|
|
181
|
+
warn(label, `inbox truncatedScopes=${envelope.truncatedScopes} (reach exceeds per-call cap — examined scopes are still acked normally)`);
|
|
175
182
|
}
|
|
176
183
|
|
|
177
184
|
for (const scope of envelope.scopes) {
|
|
@@ -202,11 +209,7 @@ export async function runInboxCatchUp(deps: InboxCatchUpDeps): Promise<InboxCatc
|
|
|
202
209
|
}
|
|
203
210
|
}
|
|
204
211
|
|
|
205
|
-
if (
|
|
206
|
-
envelope.truncatedScopes === 0 &&
|
|
207
|
-
scope.latestAt &&
|
|
208
|
-
(scope.newMessages > 0 || scope.newArtifacts > 0)
|
|
209
|
-
) {
|
|
212
|
+
if (scope.latestAt && (scope.newMessages > 0 || scope.newArtifacts > 0)) {
|
|
210
213
|
toAck.push({
|
|
211
214
|
kind: scope.scope.kind,
|
|
212
215
|
id: scope.scope.id,
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
getAgreementStatus, listAgreements, getMyAgreements, getAgreement,
|
|
4
4
|
publishOffer as publishOfferApi, createAgreement, revokeAgreement, getAgreementsByChat, linkAgreementToChat,
|
|
5
5
|
getChatsForAgreement, joinAgreement, linkArtifactToAgreement, getArtifactsForAgreement,
|
|
6
|
-
linkUserToAgreement, getUsersForAgreement,
|
|
6
|
+
linkUserToAgreement, getUsersForAgreement,
|
|
7
7
|
type Creds, type Agreement,
|
|
8
8
|
type ProposeAgreementData, type ProposeDirectInput, type ProposeBroadcastInput,
|
|
9
9
|
type PublishOfferPayload,
|
|
10
10
|
type DelegateAgreementData, type CounterAgreementData,
|
|
11
11
|
type CreateAgreementBody, type ListAgreementsFilters, type GetMyAgreementsFilters,
|
|
12
|
-
type ChatLinkType, type ArtifactLinkType, type UserRole,
|
|
12
|
+
type ChatLinkType, type ArtifactLinkType, type UserRole,
|
|
13
13
|
} from '@ziggs-ai/api-client';
|
|
14
14
|
|
|
15
15
|
export class AgreementService {
|
|
@@ -21,11 +21,6 @@ export class AgreementService {
|
|
|
21
21
|
this.creds = { operatorKey, agentId };
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/** @deprecated Prefer {@link proposeDirect} or {@link proposeBroadcast}. */
|
|
25
|
-
contract(input: CreateContractInput): Promise<Agreement> {
|
|
26
|
-
return createContract(input, this.creds);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
24
|
/** 1:1 proposal. Server defaults engagementKind to `service` when omitted. */
|
|
30
25
|
proposeDirect(payload: ProposeDirectInput): Promise<Agreement> {
|
|
31
26
|
return proposeAgreement(payload, this.creds);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createTask, getTask, getActiveTasksForAgent, getActiveTasksForChat, getSubtasks,
|
|
3
3
|
updateTaskState, cancelTask,
|
|
4
|
-
|
|
4
|
+
reportTask, setTaskSatisfaction, countTasks,
|
|
5
5
|
type Creds, type Task, type TaskState,
|
|
6
6
|
} from '@ziggs-ai/api-client';
|
|
7
7
|
import type { CountTasksFilters } from '@ziggs-ai/api-client';
|
|
@@ -50,7 +50,7 @@ export class TaskService {
|
|
|
50
50
|
return getSubtasks(parentTaskId, this.creds);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
async spawnUnderAgreement(agreementId: string, taskData: { description: string; parentTaskId?: string; plan?: unknown; planReviewTiming?: string; requireMidWorkPlanAck?: boolean }): Promise<Task> {
|
|
53
|
+
async spawnUnderAgreement(agreementId: string, taskData: { description: string; parentTaskId?: string; plan?: unknown; planReviewTiming?: string; requireMidWorkPlanAck?: boolean; assigneeId?: string }): Promise<Task> {
|
|
54
54
|
if (!agreementId) throw new Error('TaskService.spawnUnderAgreement: agreementId is required');
|
|
55
55
|
try {
|
|
56
56
|
const result = await createTask({ ...taskData, agreementId } as Parameters<typeof createTask>[0], this.creds);
|
|
@@ -85,17 +85,6 @@ export class TaskService {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
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
88
|
async report(taskId: string, message = ''): Promise<Task> {
|
|
100
89
|
return reportTask(taskId, message, this.creds);
|
|
101
90
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineTool, type ToolDefinition } from '../../../tools/defineTool.js';
|
|
2
|
-
import {
|
|
2
|
+
import { pullQuests } from '@ziggs-ai/api-client';
|
|
3
3
|
|
|
4
4
|
type Ctx = Record<string, unknown>;
|
|
5
5
|
|
|
@@ -18,7 +18,7 @@ export const marketplaceViewTool: ToolDefinition = defineTool('marketplace_view'
|
|
|
18
18
|
limit: typeof args['limit'] === 'number' ? args['limit'] as number : 20,
|
|
19
19
|
};
|
|
20
20
|
if (args['since']) options.since = args['since'] as string;
|
|
21
|
-
const quests = await
|
|
21
|
+
const quests = await pullQuests(options, { operatorKey, agentId });
|
|
22
22
|
return { quests, count: quests.length };
|
|
23
23
|
},
|
|
24
24
|
{ description: 'Browse open quests on the marketplace — work buyers have broadcast to willing agents. Returns public quests plus any org-scoped quests in your org. To claim a quest, use agreement_respond on the returned agreementId. To publish your own, use agreement_propose({ proposedTo: "everyone" }) for a public quest or proposedTo: "org" to keep it inside your org.' },
|