@ziggs-ai/agent-sdk 0.1.4 → 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.
Files changed (47) hide show
  1. package/README.md +3 -1
  2. package/package.json +2 -2
  3. package/src/AgentHost.ts +165 -12
  4. package/src/adapters/OpenAIAdapter.ts +21 -0
  5. package/src/agent/Agent.ts +5 -2
  6. package/src/cognition/validateContext.ts +1 -1
  7. package/src/context/batch.ts +3 -3
  8. package/src/context/classifyEnvelope.ts +2 -2
  9. package/src/context/routingLabels.ts +1 -1
  10. package/src/formatters/AgreementFormatter.ts +5 -5
  11. package/src/formatters/HistoryFormatter.ts +3 -3
  12. package/src/index.ts +23 -4
  13. package/src/ingress/normalizeIncoming.ts +50 -7
  14. package/src/pricing/fleetDefaults.ts +218 -0
  15. package/src/pricing/fleetEvalFree.ts +24 -0
  16. package/src/pricing/fleetFreeTierA.gen.ts +12 -0
  17. package/src/pricing/fleetTierByAgentId.gen.ts +1022 -0
  18. package/src/runtime/AgentMachine.ts +68 -2
  19. package/src/runtime/PromptBuilder.ts +25 -23
  20. package/src/runtime/buildOutcome.ts +33 -3
  21. package/src/runtime/defaults.ts +3 -0
  22. package/src/runtime/runTurn.ts +115 -61
  23. package/src/runtime/validateWorkflow.ts +16 -0
  24. package/src/server/EventQueue.ts +14 -0
  25. package/src/server/InboxCatchUp.ts +251 -0
  26. package/src/server/SeenMessages.ts +27 -0
  27. package/src/server/ZiggsEffectHandler.ts +82 -8
  28. package/src/server/agreements/AgreementService.ts +7 -1
  29. package/src/server/createHealthServer.ts +79 -2
  30. package/src/server/runLauncher.ts +40 -25
  31. package/src/server/tasks/TaskService.ts +4 -5
  32. package/src/server/tasks/index.ts +0 -3
  33. package/src/server/telemetryIngest.ts +91 -0
  34. package/src/server/tools/index.ts +46 -0
  35. package/src/server/{tasks → tools/tier1}/protocolRunner.ts +52 -20
  36. package/src/server/{tasks → tools/tier1}/protocolTools.ts +6 -3
  37. package/src/server/tools/tier2/connectionTools.ts +75 -0
  38. package/src/server/tools/tier2/contextTools.ts +74 -0
  39. package/src/server/tools/tier2/discoveryTools.ts +34 -0
  40. package/src/server/tools/tier2/marketplaceTools.ts +25 -0
  41. package/src/server/{tasks → tools/tier2}/paymentTools.ts +74 -37
  42. package/src/server/ziggsconnect/ZiggsConnectClient.ts +126 -0
  43. package/src/server/ziggscontext/ZiggsContextClient.ts +137 -0
  44. package/src/server/ziggspay/ZiggsPayClient.ts +12 -12
  45. package/src/shared/types.ts +0 -2
  46. package/src/types.ts +47 -8
  47. package/src/tasks/taskCore.ts +0 -139
@@ -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 { creatorId?: string; providerId?: string; payerId?: string; proposedToId?: string };
67
+ const parties = (ag?.parties ?? {}) as { creator?: string; provider?: string; payer?: string; proposedTo?: string };
56
68
  return {
57
69
  ...ag,
58
- creatorIsYou: parties.creatorId === agentId,
59
- providerIsYou: parties.providerId === agentId,
60
- payerIsYou: parties.payerId === agentId,
61
- proposedToIsYou: parties.proposedToId === agentId,
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 handler = async (effect: Effect): Promise<unknown> => {
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
- export function createHealthServer({ port = Number(process.env.PORT) || 8080, label = 'agents' } = {}): Server {
5
- const server = createServer((_req, res) => { res.writeHead(200); res.end('ok'); });
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
  }