@ziggs-ai/agent-sdk 0.1.4 → 0.1.6
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 +7 -5
- package/package.json +2 -2
- package/src/AgentHost.ts +165 -12
- package/src/adapters/OpenAIAdapter.ts +21 -0
- package/src/agent/Agent.ts +5 -2
- package/src/cognition/validateContext.ts +1 -1
- package/src/context/batch.ts +3 -3
- package/src/context/classifyEnvelope.ts +2 -2
- package/src/context/routingLabels.ts +1 -1
- package/src/formatters/AgreementFormatter.ts +5 -5
- package/src/formatters/HistoryFormatter.ts +3 -3
- package/src/index.ts +25 -4
- package/src/ingress/normalizeIncoming.ts +50 -7
- 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 +68 -2
- package/src/runtime/PromptBuilder.ts +25 -23
- package/src/runtime/buildOutcome.ts +33 -3
- package/src/runtime/defaults.ts +3 -0
- package/src/runtime/runTurn.ts +115 -61
- package/src/runtime/validateWorkflow.ts +16 -0
- package/src/server/EventQueue.ts +14 -0
- package/src/server/InboxCatchUp.ts +251 -0
- package/src/server/SeenMessages.ts +27 -0
- package/src/server/ZiggsEffectHandler.ts +82 -8
- package/src/server/agreements/AgreementService.ts +7 -1
- package/src/server/createHealthServer.ts +79 -2
- package/src/server/runLauncher.ts +40 -25
- package/src/server/tasks/TaskService.ts +4 -5
- package/src/server/tasks/index.ts +0 -3
- package/src/server/telemetryIngest.ts +91 -0
- package/src/server/tools/index.ts +46 -0
- package/src/server/{tasks → tools/tier1}/protocolRunner.ts +52 -20
- package/src/server/{tasks → tools/tier1}/protocolTools.ts +6 -3
- 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/{tasks → tools/tier2}/paymentTools.ts +74 -37
- package/src/server/ziggsconnect/ZiggsConnectClient.ts +126 -0
- package/src/server/ziggscontext/ZiggsContextClient.ts +137 -0
- package/src/server/ziggspay/ZiggsPayClient.ts +12 -12
- package/src/shared/types.ts +0 -2
- package/src/tools/index.ts +2 -0
- package/src/tools/recordReport.ts +82 -0
- package/src/types.ts +47 -8
- package/src/tasks/taskCore.ts +0 -139
package/src/server/EventQueue.ts
CHANGED
|
@@ -109,6 +109,20 @@ export class EventQueue {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
get isIdle(): boolean {
|
|
113
|
+
return [...this._state.values()].every(s => !s.processing && s.events.length === 0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Resolves when all lanes finish processing, or after timeoutMs. Best-effort — never throws. */
|
|
117
|
+
async waitForIdle(timeoutMs = 30_000): Promise<void> {
|
|
118
|
+
const deadline = Date.now() + timeoutMs;
|
|
119
|
+
while (Date.now() < deadline) {
|
|
120
|
+
if (this.isIdle) return;
|
|
121
|
+
await new Promise<void>(r => setTimeout(r, 50));
|
|
122
|
+
}
|
|
123
|
+
runtimeLog.warn('EventQueue', `waitForIdle timed out after ${timeoutMs}ms`);
|
|
124
|
+
}
|
|
125
|
+
|
|
112
126
|
getMetrics() {
|
|
113
127
|
return {
|
|
114
128
|
...this._metrics,
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ContextReadClient,
|
|
3
|
+
ContextDiscoveryClient,
|
|
4
|
+
InboxClient,
|
|
5
|
+
getChatsForAgreement,
|
|
6
|
+
type InboxScopeEntry,
|
|
7
|
+
type InboxAck,
|
|
8
|
+
type Creds,
|
|
9
|
+
} from '@ziggs-ai/api-client';
|
|
10
|
+
import { SeenMessages } from './SeenMessages.js';
|
|
11
|
+
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
12
|
+
|
|
13
|
+
export interface InboxCatchUpDeps {
|
|
14
|
+
creds: Creds;
|
|
15
|
+
ownAgentId: string;
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
seen: SeenMessages;
|
|
18
|
+
onMessage: (text: string, metadata: Record<string, unknown>) => Promise<void>;
|
|
19
|
+
onResourceEvent: (event: Record<string, unknown>) => Promise<void>;
|
|
20
|
+
label?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface InboxCatchUpResult {
|
|
24
|
+
scopes: number;
|
|
25
|
+
messagesDelivered: number;
|
|
26
|
+
resourceEvents: number;
|
|
27
|
+
acked: number;
|
|
28
|
+
truncated: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type WireRow = Record<string, unknown>;
|
|
32
|
+
|
|
33
|
+
const READ_PAGE = 100;
|
|
34
|
+
|
|
35
|
+
function log(label: string, msg: string): void {
|
|
36
|
+
runtimeLog.info(label, msg);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function warn(label: string, msg: string): void {
|
|
40
|
+
runtimeLog.warn(label, msg);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Map Mongo message row → wire metadata for normalizeIncomingEvent. */
|
|
44
|
+
function rowToMetadata(row: WireRow, chatId: string): Record<string, unknown> {
|
|
45
|
+
const senderRaw = row.sender as { agentId?: string; id?: string; type?: string } | undefined;
|
|
46
|
+
const senderId = String(senderRaw?.agentId ?? senderRaw?.id ?? '');
|
|
47
|
+
const receiver = row.receiver as { id?: string; type?: string } | undefined;
|
|
48
|
+
const senderType =
|
|
49
|
+
senderRaw?.type ??
|
|
50
|
+
(receiver?.type === 'agent' ? 'agent' : 'user');
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
chatId,
|
|
54
|
+
chat_id: chatId,
|
|
55
|
+
messageId: row.messageId,
|
|
56
|
+
sender: { id: senderId, type: senderType },
|
|
57
|
+
senderId,
|
|
58
|
+
senderType: String(senderType).toUpperCase(),
|
|
59
|
+
receiver: receiver ?? null,
|
|
60
|
+
receiverId: receiver?.id ?? null,
|
|
61
|
+
entryType: row.entryType ?? 'message',
|
|
62
|
+
content_type: row.content_type ?? row.contentType ?? 'text',
|
|
63
|
+
contentType: row.content_type ?? row.contentType ?? 'text',
|
|
64
|
+
task: row.task ?? null,
|
|
65
|
+
agreement: row.agreement ?? null,
|
|
66
|
+
operation: row.operation ?? null,
|
|
67
|
+
agreementId: row.agreementId ?? null,
|
|
68
|
+
timestamp: row.timestamp,
|
|
69
|
+
text: row.text,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function chatIdsForScope(
|
|
74
|
+
scope: InboxScopeEntry,
|
|
75
|
+
creds: Creds,
|
|
76
|
+
baseUrl: string | undefined,
|
|
77
|
+
discovery: ContextDiscoveryClient,
|
|
78
|
+
): Promise<string[]> {
|
|
79
|
+
const { kind, id } = scope.scope;
|
|
80
|
+
switch (kind) {
|
|
81
|
+
case 'chat':
|
|
82
|
+
return [id];
|
|
83
|
+
case 'agreement': {
|
|
84
|
+
const links = (await getChatsForAgreement(id, creds)) as Array<{ chatId?: string }>;
|
|
85
|
+
return [...new Set(links.map((l) => l.chatId).filter(Boolean) as string[])];
|
|
86
|
+
}
|
|
87
|
+
case 'org': {
|
|
88
|
+
const reach = await discovery.discover();
|
|
89
|
+
return [
|
|
90
|
+
...new Set(
|
|
91
|
+
reach
|
|
92
|
+
.filter((r) => r.scope.kind === 'chat')
|
|
93
|
+
.map((r) => r.scope.id),
|
|
94
|
+
),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
default:
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function deliverMessagesForScope(
|
|
103
|
+
deps: InboxCatchUpDeps,
|
|
104
|
+
scope: InboxScopeEntry,
|
|
105
|
+
chatIds: string[],
|
|
106
|
+
reader: ContextReadClient,
|
|
107
|
+
countCap: number,
|
|
108
|
+
): Promise<number> {
|
|
109
|
+
let delivered = 0;
|
|
110
|
+
const label = deps.label ?? 'InboxCatchUp';
|
|
111
|
+
const expected = scope.newMessages;
|
|
112
|
+
let remaining = expected >= countCap ? Number.POSITIVE_INFINITY : expected;
|
|
113
|
+
|
|
114
|
+
for (const chatId of chatIds) {
|
|
115
|
+
if (remaining <= 0) break;
|
|
116
|
+
let after = scope.since;
|
|
117
|
+
let pages = 0;
|
|
118
|
+
const maxPages = 20;
|
|
119
|
+
|
|
120
|
+
while (remaining > 0 && pages < maxPages) {
|
|
121
|
+
const limit = Math.min(READ_PAGE, remaining === Number.POSITIVE_INFINITY ? READ_PAGE : remaining);
|
|
122
|
+
const page = await reader.read<WireRow>('messages', {
|
|
123
|
+
via: `chat:${chatId}`,
|
|
124
|
+
after,
|
|
125
|
+
direction: 'forward',
|
|
126
|
+
limit,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
for (const row of page.items) {
|
|
130
|
+
const messageId = String(row.messageId ?? '');
|
|
131
|
+
if (deps.seen.has(messageId)) continue;
|
|
132
|
+
|
|
133
|
+
const text = typeof row.text === 'string' ? row.text : '';
|
|
134
|
+
if (!text.trim()) continue;
|
|
135
|
+
|
|
136
|
+
const metadata = rowToMetadata(row, chatId);
|
|
137
|
+
await deps.onMessage(text, metadata);
|
|
138
|
+
delivered++;
|
|
139
|
+
if (remaining !== Number.POSITIVE_INFINITY) remaining--;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!page.hasMore || !page.latestSequence) break;
|
|
143
|
+
after = page.latestSequence;
|
|
144
|
+
pages++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (delivered < expected && expected >= countCap) {
|
|
149
|
+
warn(label, `scope ${scope.scope.kind}:${scope.scope.id} may be incomplete (count cap)`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return delivered;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Pull-based recovery after reconnect (ZIG-434 / ZIG-446 / ZIG-454).
|
|
157
|
+
* Inbox → read message deltas → deliver → ack. Push remains a hint;
|
|
158
|
+
* this path survives backend restarts that drop in-memory pending queues.
|
|
159
|
+
*/
|
|
160
|
+
export async function runInboxCatchUp(deps: InboxCatchUpDeps): Promise<InboxCatchUpResult> {
|
|
161
|
+
const label = deps.label ?? 'InboxCatchUp';
|
|
162
|
+
const inbox = new InboxClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
|
|
163
|
+
const reader = new ContextReadClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
|
|
164
|
+
const discovery = new ContextDiscoveryClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
|
|
165
|
+
|
|
166
|
+
const envelope = await inbox.getInbox();
|
|
167
|
+
const countCap = envelope.countCap;
|
|
168
|
+
|
|
169
|
+
let messagesDelivered = 0;
|
|
170
|
+
let resourceEvents = 0;
|
|
171
|
+
const toAck: InboxAck[] = [];
|
|
172
|
+
|
|
173
|
+
if (envelope.truncatedScopes > 0) {
|
|
174
|
+
warn(label, `inbox truncatedScopes=${envelope.truncatedScopes} — skipping ack until narrowed`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const scope of envelope.scopes) {
|
|
178
|
+
const chatIds = await chatIdsForScope(scope, deps.creds, deps.baseUrl, discovery);
|
|
179
|
+
if (!chatIds.length && (scope.newMessages > 0 || scope.newArtifacts > 0)) {
|
|
180
|
+
warn(
|
|
181
|
+
label,
|
|
182
|
+
`no chat ids for scope ${scope.scope.kind}:${scope.scope.id} (${scope.newMessages} msgs)`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (scope.newMessages > 0 && chatIds.length) {
|
|
187
|
+
const n = await deliverMessagesForScope(deps, scope, chatIds, reader, countCap);
|
|
188
|
+
messagesDelivered += n;
|
|
189
|
+
log(label, `scope ${scope.scope.kind}:${scope.scope.id} delivered ${n} message(s)`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (scope.newArtifacts > 0) {
|
|
193
|
+
for (const chatId of chatIds) {
|
|
194
|
+
await deps.onResourceEvent({
|
|
195
|
+
kind: 'artifact',
|
|
196
|
+
ts: scope.latestAt ?? new Date().toISOString(),
|
|
197
|
+
resourceId: chatId,
|
|
198
|
+
chatId,
|
|
199
|
+
change: 'created',
|
|
200
|
+
});
|
|
201
|
+
resourceEvents++;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (
|
|
206
|
+
envelope.truncatedScopes === 0 &&
|
|
207
|
+
scope.latestAt &&
|
|
208
|
+
(scope.newMessages > 0 || scope.newArtifacts > 0)
|
|
209
|
+
) {
|
|
210
|
+
toAck.push({
|
|
211
|
+
kind: scope.scope.kind,
|
|
212
|
+
id: scope.scope.id,
|
|
213
|
+
upTo: scope.latestAt,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
for (const proposal of envelope.proposalsAwaitingMe) {
|
|
219
|
+
await deps.onResourceEvent({
|
|
220
|
+
kind: 'agreement',
|
|
221
|
+
ts: proposal.proposedAt ?? new Date().toISOString(),
|
|
222
|
+
resourceId: proposal.agreementId,
|
|
223
|
+
agreementId: proposal.agreementId,
|
|
224
|
+
change: 'created',
|
|
225
|
+
reason: 'proposal_pending',
|
|
226
|
+
});
|
|
227
|
+
resourceEvents++;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let acked = 0;
|
|
231
|
+
if (toAck.length) {
|
|
232
|
+
await inbox.ack(toAck);
|
|
233
|
+
acked = toAck.length;
|
|
234
|
+
log(label, `acked ${acked} scope(s)`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (messagesDelivered > 0 || resourceEvents > 0) {
|
|
238
|
+
log(
|
|
239
|
+
label,
|
|
240
|
+
`catch-up complete: ${messagesDelivered} message(s), ${resourceEvents} resource hint(s)`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
scopes: envelope.scopes.length,
|
|
246
|
+
messagesDelivered,
|
|
247
|
+
resourceEvents,
|
|
248
|
+
acked,
|
|
249
|
+
truncated: envelope.truncatedScopes > 0,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dedupes delivery across push (WS flush) and pull (inbox catch-up).
|
|
3
|
+
* ZIG-434 / ZIG-454: inbox is reconciliation source; stable message ids
|
|
4
|
+
* prevent double-processing when both paths fire for the same row.
|
|
5
|
+
*/
|
|
6
|
+
export class SeenMessages {
|
|
7
|
+
private readonly order: string[] = [];
|
|
8
|
+
private readonly seen = new Set<string>();
|
|
9
|
+
|
|
10
|
+
constructor(private readonly maxSize = 5_000) {}
|
|
11
|
+
|
|
12
|
+
has(messageId: string | null | undefined): boolean {
|
|
13
|
+
if (!messageId || typeof messageId !== 'string') return false;
|
|
14
|
+
return this.seen.has(messageId);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
mark(messageId: string | null | undefined): void {
|
|
18
|
+
if (!messageId || typeof messageId !== 'string') return;
|
|
19
|
+
if (this.seen.has(messageId)) return;
|
|
20
|
+
this.seen.add(messageId);
|
|
21
|
+
this.order.push(messageId);
|
|
22
|
+
while (this.order.length > this.maxSize) {
|
|
23
|
+
const oldest = this.order.shift();
|
|
24
|
+
if (oldest) this.seen.delete(oldest);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -18,6 +18,7 @@ import { runtimeLog } from '../shared/runtimeLog.js';
|
|
|
18
18
|
/** Minimal LLM adapter contract — OpenAIAdapter satisfies this. */
|
|
19
19
|
interface LlmAdapter {
|
|
20
20
|
chatMessages(messages: LlmMessage[], tools?: LlmToolSchema[], options?: Record<string, unknown>): Promise<LlmResponse>;
|
|
21
|
+
chatMessagesStream?(messages: LlmMessage[], onChunk: (text: string) => void): Promise<string>;
|
|
21
22
|
}
|
|
22
23
|
import type { TaskService } from './tasks/TaskService.js';
|
|
23
24
|
import type { AgreementService } from './agreements/AgreementService.js';
|
|
@@ -28,6 +29,12 @@ import type {
|
|
|
28
29
|
ScopeClient,
|
|
29
30
|
TelemetryClient,
|
|
30
31
|
} from '@ziggs-ai/api-client';
|
|
32
|
+
import {
|
|
33
|
+
buildLlmIngestPayload,
|
|
34
|
+
buildToolIngestPayload,
|
|
35
|
+
isTelemetryIngestEnabled,
|
|
36
|
+
sendTelemetry,
|
|
37
|
+
} from './telemetryIngest.js';
|
|
31
38
|
|
|
32
39
|
export interface ZiggsEffectDeps {
|
|
33
40
|
llm: LlmAdapter;
|
|
@@ -36,11 +43,16 @@ export interface ZiggsEffectDeps {
|
|
|
36
43
|
scopeClient: ScopeClient;
|
|
37
44
|
taskService: TaskService;
|
|
38
45
|
agreementService: AgreementService;
|
|
39
|
-
messageSender: (text: string, receiverId: string, sessionId: string) => Promise<unknown>;
|
|
46
|
+
messageSender: (text: string, receiverId: string, sessionId: string, messageId?: string) => Promise<unknown>;
|
|
47
|
+
/** Sends a streaming chunk to a client. Called per-token during stream-text effects. */
|
|
48
|
+
chunkSender?: (sessionId: string, receiverId: string, text: string, messageId: string) => void;
|
|
40
49
|
toolManager: ToolManager;
|
|
41
50
|
operatorKey: string;
|
|
42
51
|
agentId: string;
|
|
43
52
|
telemetryClient?: TelemetryClient;
|
|
53
|
+
/** When false, skips POST /agents/monitoring/ingest (default: env on). */
|
|
54
|
+
telemetryIngest?: boolean;
|
|
55
|
+
effectTap?: (effect: Effect, response: unknown) => void;
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
const CONTEXT_CACHE_MAX = 256;
|
|
@@ -52,13 +64,13 @@ const CONTEXT_CACHE_MAX = 256;
|
|
|
52
64
|
* change.
|
|
53
65
|
*/
|
|
54
66
|
const annotateAgreement = (ag: Record<string, unknown>, agentId: string): Record<string, unknown> => {
|
|
55
|
-
const parties = (ag?.parties ?? {}) as {
|
|
67
|
+
const parties = (ag?.parties ?? {}) as { creator?: string; provider?: string; payer?: string; proposedTo?: string };
|
|
56
68
|
return {
|
|
57
69
|
...ag,
|
|
58
|
-
creatorIsYou: parties.
|
|
59
|
-
providerIsYou: parties.
|
|
60
|
-
payerIsYou: parties.
|
|
61
|
-
proposedToIsYou: parties.
|
|
70
|
+
creatorIsYou: parties.creator === agentId,
|
|
71
|
+
providerIsYou: parties.provider === agentId,
|
|
72
|
+
payerIsYou: parties.payer === agentId,
|
|
73
|
+
proposedToIsYou: parties.proposedTo === agentId,
|
|
62
74
|
};
|
|
63
75
|
};
|
|
64
76
|
|
|
@@ -194,7 +206,7 @@ export function createZiggsEffectHandler(deps: ZiggsEffectDeps): EffectHandler {
|
|
|
194
206
|
return snap;
|
|
195
207
|
};
|
|
196
208
|
|
|
197
|
-
const
|
|
209
|
+
const dispatch = async (effect: Effect): Promise<unknown> => {
|
|
198
210
|
switch (effect.kind) {
|
|
199
211
|
case 'read-context': {
|
|
200
212
|
// Composite: assembles ContextSnapshot from scope + messages.
|
|
@@ -248,11 +260,38 @@ export function createZiggsEffectHandler(deps: ZiggsEffectDeps): EffectHandler {
|
|
|
248
260
|
}
|
|
249
261
|
|
|
250
262
|
case 'llm-call': {
|
|
263
|
+
const t0 = Date.now();
|
|
251
264
|
const r: LlmResponse = await deps.llm.chatMessages(effect.messages, effect.tools);
|
|
265
|
+
sendTelemetry(
|
|
266
|
+
deps.telemetryClient,
|
|
267
|
+
buildLlmIngestPayload(r, {
|
|
268
|
+
sessionId: effect.sessionId,
|
|
269
|
+
durationMs: Date.now() - t0,
|
|
270
|
+
runId: effect.runId,
|
|
271
|
+
stateId: effect.stateId,
|
|
272
|
+
}),
|
|
273
|
+
isTelemetryIngestEnabled(deps.telemetryIngest),
|
|
274
|
+
);
|
|
252
275
|
return r;
|
|
253
276
|
}
|
|
254
277
|
|
|
278
|
+
case 'stream-text': {
|
|
279
|
+
const { sessionId, receiverId, messageId, messages } = effect;
|
|
280
|
+
let text = '';
|
|
281
|
+
if (deps.llm.chatMessagesStream) {
|
|
282
|
+
text = await deps.llm.chatMessagesStream(messages as LlmMessage[], (chunk) => {
|
|
283
|
+
deps.chunkSender?.(sessionId, receiverId, chunk, messageId);
|
|
284
|
+
});
|
|
285
|
+
} else {
|
|
286
|
+
const r = await deps.llm.chatMessages(messages as LlmMessage[]);
|
|
287
|
+
text = r.content ?? '';
|
|
288
|
+
if (text) deps.chunkSender?.(sessionId, receiverId, text, messageId);
|
|
289
|
+
}
|
|
290
|
+
return { text };
|
|
291
|
+
}
|
|
292
|
+
|
|
255
293
|
case 'tool-call': {
|
|
294
|
+
const toolT0 = Date.now();
|
|
256
295
|
const cached = cacheGet(effect.sessionId);
|
|
257
296
|
const agents = (cached?.agents as unknown[]) || [];
|
|
258
297
|
const users = (cached?.users as unknown[]) || [];
|
|
@@ -264,6 +303,9 @@ export function createZiggsEffectHandler(deps: ZiggsEffectDeps): EffectHandler {
|
|
|
264
303
|
...(argsPreview ? { args: argsPreview } : {}),
|
|
265
304
|
});
|
|
266
305
|
try {
|
|
306
|
+
const lastUser = (users as Array<{ userId?: string; id?: string }>).find(
|
|
307
|
+
(u) => u.userId || u.id,
|
|
308
|
+
);
|
|
267
309
|
const result = await deps.toolManager.executeTool(
|
|
268
310
|
effect.name,
|
|
269
311
|
args,
|
|
@@ -276,6 +318,8 @@ export function createZiggsEffectHandler(deps: ZiggsEffectDeps): EffectHandler {
|
|
|
276
318
|
sendMessage: deps.messageSender,
|
|
277
319
|
agents,
|
|
278
320
|
users,
|
|
321
|
+
senderId: effect.senderId ?? lastUser?.userId ?? lastUser?.id,
|
|
322
|
+
senderType: effect.senderType ?? (lastUser ? 'USER' : undefined),
|
|
279
323
|
memory: effect.memory,
|
|
280
324
|
turnScratch: {},
|
|
281
325
|
},
|
|
@@ -285,6 +329,18 @@ export function createZiggsEffectHandler(deps: ZiggsEffectDeps): EffectHandler {
|
|
|
285
329
|
tool: effect.name,
|
|
286
330
|
result: clipForOperationArtifact(result),
|
|
287
331
|
});
|
|
332
|
+
sendTelemetry(
|
|
333
|
+
deps.telemetryClient,
|
|
334
|
+
buildToolIngestPayload({
|
|
335
|
+
sessionId: effect.sessionId,
|
|
336
|
+
tool: effect.name,
|
|
337
|
+
durationMs: Date.now() - toolT0,
|
|
338
|
+
ok: true,
|
|
339
|
+
runId: effect.runId,
|
|
340
|
+
stateId: effect.stateId,
|
|
341
|
+
}),
|
|
342
|
+
isTelemetryIngestEnabled(deps.telemetryIngest),
|
|
343
|
+
);
|
|
288
344
|
return { ok: true, result } satisfies ToolCallResult;
|
|
289
345
|
} catch (error: unknown) {
|
|
290
346
|
const errMsg = (error as Error)?.message ?? String(error);
|
|
@@ -294,12 +350,24 @@ export function createZiggsEffectHandler(deps: ZiggsEffectDeps): EffectHandler {
|
|
|
294
350
|
tool: effect.name,
|
|
295
351
|
error: errMsg,
|
|
296
352
|
});
|
|
353
|
+
sendTelemetry(
|
|
354
|
+
deps.telemetryClient,
|
|
355
|
+
buildToolIngestPayload({
|
|
356
|
+
sessionId: effect.sessionId,
|
|
357
|
+
tool: effect.name,
|
|
358
|
+
durationMs: Date.now() - toolT0,
|
|
359
|
+
ok: false,
|
|
360
|
+
runId: effect.runId,
|
|
361
|
+
stateId: effect.stateId,
|
|
362
|
+
}),
|
|
363
|
+
isTelemetryIngestEnabled(deps.telemetryIngest),
|
|
364
|
+
);
|
|
297
365
|
return { ok: false, error: errMsg } satisfies ToolCallResult;
|
|
298
366
|
}
|
|
299
367
|
}
|
|
300
368
|
|
|
301
369
|
case 'send-message': {
|
|
302
|
-
await deps.messageSender(effect.text, effect.receiverId, effect.sessionId);
|
|
370
|
+
await deps.messageSender(effect.text, effect.receiverId, effect.sessionId, effect.messageId);
|
|
303
371
|
outbox.track(effect.sessionId, { text: effect.text, receiverId: effect.receiverId });
|
|
304
372
|
return { ok: true };
|
|
305
373
|
}
|
|
@@ -331,5 +399,11 @@ export function createZiggsEffectHandler(deps: ZiggsEffectDeps): EffectHandler {
|
|
|
331
399
|
}
|
|
332
400
|
};
|
|
333
401
|
|
|
402
|
+
const handler = async (effect: Effect): Promise<unknown> => {
|
|
403
|
+
const response = await dispatch(effect);
|
|
404
|
+
deps.effectTap?.(effect, response);
|
|
405
|
+
return response;
|
|
406
|
+
};
|
|
407
|
+
|
|
334
408
|
return handler as EffectHandler;
|
|
335
409
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
proposeAgreement, delegateAgreement, respondToAgreement, counterAgreement,
|
|
3
3
|
getAgreementStatus, listAgreements, getMyAgreements, getAgreement,
|
|
4
|
-
createAgreement, revokeAgreement, getAgreementsByChat, linkAgreementToChat,
|
|
4
|
+
publishOffer as publishOfferApi, createAgreement, revokeAgreement, getAgreementsByChat, linkAgreementToChat,
|
|
5
5
|
getChatsForAgreement, joinAgreement, linkArtifactToAgreement, getArtifactsForAgreement,
|
|
6
6
|
linkUserToAgreement, getUsersForAgreement, createContract,
|
|
7
7
|
OPEN_AGREEMENT_TARGET,
|
|
8
8
|
type Creds, type Agreement,
|
|
9
9
|
type ProposeAgreementData, type ProposeDirectInput, type ProposeBroadcastInput,
|
|
10
|
+
type PublishOfferPayload,
|
|
10
11
|
type DelegateAgreementData, type CounterAgreementData,
|
|
11
12
|
type CreateAgreementBody, type ListAgreementsFilters, type GetMyAgreementsFilters,
|
|
12
13
|
type ChatLinkType, type ArtifactLinkType, type UserRole, type CreateContractInput,
|
|
@@ -41,6 +42,11 @@ export class AgreementService {
|
|
|
41
42
|
return proposeAgreement(payload, this.creds);
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
/** Seller-broadcast marketplace offer (`parties.payer = everyone`). */
|
|
46
|
+
publishOffer(payload: PublishOfferPayload): Promise<Agreement> {
|
|
47
|
+
return publishOfferApi(payload, this.creds);
|
|
48
|
+
}
|
|
49
|
+
|
|
44
50
|
delegate(payload: DelegateAgreementData): Promise<Agreement> {
|
|
45
51
|
return delegateAgreement(payload, this.creds);
|
|
46
52
|
}
|
|
@@ -1,8 +1,85 @@
|
|
|
1
1
|
import { createServer, type Server } from 'http';
|
|
2
|
+
import type { ConnectionPool } from './ConnectionPool.js';
|
|
3
|
+
import type { AgentHost } from '../AgentHost.js';
|
|
2
4
|
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
function readJsonBody(req: import('http').IncomingMessage): Promise<Record<string, unknown>> {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const chunks: Buffer[] = [];
|
|
9
|
+
req.on('data', (c) => chunks.push(c));
|
|
10
|
+
req.on('end', () => {
|
|
11
|
+
try {
|
|
12
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
13
|
+
resolve(raw ? (JSON.parse(raw) as Record<string, unknown>) : {});
|
|
14
|
+
} catch (err) {
|
|
15
|
+
reject(err);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
req.on('error', reject);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createHealthServer({
|
|
23
|
+
port = Number(process.env.PORT) || 8080,
|
|
24
|
+
label = 'agents',
|
|
25
|
+
pool,
|
|
26
|
+
}: {
|
|
27
|
+
port?: number;
|
|
28
|
+
label?: string;
|
|
29
|
+
pool?: ConnectionPool;
|
|
30
|
+
} = {}): Server {
|
|
31
|
+
const server = createServer(async (req, res) => {
|
|
32
|
+
const url = req.url ?? '/';
|
|
33
|
+
const wakeMatch = url.match(/^\/fleet-wake\/([^/?]+)/);
|
|
34
|
+
if (req.method === 'POST' && wakeMatch && pool) {
|
|
35
|
+
const agentId = decodeURIComponent(wakeMatch[1]);
|
|
36
|
+
try {
|
|
37
|
+
await pool.wake(agentId);
|
|
38
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
39
|
+
res.end(JSON.stringify({ ok: true, agentId, active: pool.listActive().includes(agentId) }));
|
|
40
|
+
} catch (err) {
|
|
41
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
42
|
+
res.end(JSON.stringify({ ok: false, error: (err as Error).message }));
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (req.method === 'POST' && url === '/fleet-deliver' && pool) {
|
|
48
|
+
try {
|
|
49
|
+
const body = await readJsonBody(req);
|
|
50
|
+
const agentId = String(body.agentId ?? '');
|
|
51
|
+
const chatId = String(body.chatId ?? '');
|
|
52
|
+
const text = String(body.text ?? '');
|
|
53
|
+
const senderId = String(body.senderId ?? '');
|
|
54
|
+
if (!agentId || !chatId || !text || !senderId) {
|
|
55
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
56
|
+
res.end(JSON.stringify({ ok: false, error: 'agentId, chatId, text, senderId required' }));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const host = (await pool.wake(agentId)) as AgentHost;
|
|
60
|
+
await host.handleMessage(text, {
|
|
61
|
+
chatId,
|
|
62
|
+
entryType: 'message',
|
|
63
|
+
content_type: 'text',
|
|
64
|
+
sender: { id: senderId, type: 'USER' },
|
|
65
|
+
receiver: { id: agentId, type: 'AGENT' },
|
|
66
|
+
});
|
|
67
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
68
|
+
res.end(JSON.stringify({ ok: true, agentId }));
|
|
69
|
+
} catch (err) {
|
|
70
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
71
|
+
res.end(JSON.stringify({ ok: false, error: (err as Error).message }));
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (url === '/health' || url === '/') {
|
|
76
|
+
res.writeHead(200);
|
|
77
|
+
res.end('ok');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
res.writeHead(404);
|
|
81
|
+
res.end();
|
|
82
|
+
});
|
|
6
83
|
server.listen(port, () => runtimeLog.info(label, `health server on port ${port}`));
|
|
7
84
|
return server;
|
|
8
85
|
}
|