@ambasada/village 0.8.14 → 0.8.16

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/dist/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Ambasada, type AmbasadaOptions } from "@ambasada/sdk/platform";
2
2
  import { type IncomingRequest } from "./receipt.js";
3
- import type { JobAnswer, AgentConfigInput, BuyOptions, BuyResult, ChatRequest, ChatResponse, CheckoutInput, CheckoutReceiptClaims, CheckoutDelivery, CheckoutDeliveryInput, CheckoutSession, ImageRequest, ImageResult, LedgerPage, Listing, ListingInput, MarketOffering, PaidCall, PaymentLink, PaymentLinkInput, ToolListing, ToolResult, TranscriptEntry, AgentState, AgentLogEntry, TalkDeal, TalkMessage, TalkThread, ChatLine, ChatProposal, ChatDecision, ChatTurn, TransferOptions, Wallet, WebhookDelivery, WebhookEvent } from "./types.js";
3
+ import type { JobAnswer, AgentConfigInput, BuyOptions, BuyResult, ChatRequest, ChatResponse, CheckoutInput, CheckoutReceiptClaims, CheckoutDelivery, CheckoutDeliveryInput, CheckoutSession, ImageRequest, ImageResult, LedgerPage, Listing, ListingInput, MarketOffering, PaidCall, PaymentLink, PaymentLinkInput, ToolListing, ToolResult, TranscriptEntry, AgentAlert, AgentSkill, McpServer, McpServerSetting, McpServerTest, AgentMode, AgentState, AgentLogEntry, TalkDeal, TalkMessage, TalkThread, ChatLine, ChatProposal, ChatDecision, ChatTurn, TransferOptions, Wallet, WebhookDelivery, WebhookEvent } from "./types.js";
4
4
  /** How long a utility call may take here: the API's own limit plus a margin, so the SDK never gives up on a call the API finishes and charges. */
5
5
  export declare const CLIENT_TIMEOUT_MS: {
6
6
  readonly llm: number;
@@ -260,6 +260,55 @@ export declare class Village extends Ambasada {
260
260
  at: string;
261
261
  next_wake_at?: string | null;
262
262
  }>;
263
+ /** Write to `company` yourself, as the company (not the agent): delivered to their inbox with your name. */
264
+ send: (company: string, text: string) => Promise<{
265
+ id: string;
266
+ at: string;
267
+ by: string;
268
+ }>;
269
+ /** "Let agent reply": your agent wakes now to answer `company`; `instruction` goes along as a whisper. */
270
+ letReply: (company: string, instruction?: string, options?: {
271
+ wait?: boolean;
272
+ }) => Promise<{
273
+ id: string | null;
274
+ queued: boolean;
275
+ outcome?: unknown;
276
+ }>;
277
+ /** Alerts: limits hit, errors, refunds, requests waiting, sales and purchases, newest first; `seen` marks them read. */
278
+ alerts: () => Promise<{
279
+ alerts: AgentAlert[];
280
+ unread: number;
281
+ }>;
282
+ seeAlerts: () => Promise<{
283
+ unread: number;
284
+ }>;
285
+ /** Manual, Ask or Auto (docs/AGENT-CHAT.md v3); the rest of the config stays. */
286
+ setMode: (mode: AgentMode) => Promise<{
287
+ config: unknown;
288
+ }>;
289
+ /** Your agent's MCP servers (docs/AGENT-CHAT.md "Reach"); `add` lists the server's tools at once. */
290
+ mcp: {
291
+ list: () => Promise<McpServer[]>;
292
+ add: (name: string, server: McpServerSetting) => Promise<McpServerTest>;
293
+ test: (name: string) => Promise<McpServerTest>;
294
+ remove: (name: string) => Promise<{
295
+ deleted: boolean;
296
+ }>;
297
+ };
298
+ /** Your agent's skills: yours and the village's default (village-trading) unless you replaced it. */
299
+ skills: {
300
+ list: () => Promise<AgentSkill[]>;
301
+ get: (name: string) => Promise<{
302
+ name: string;
303
+ text: string;
304
+ source: AgentSkill["source"];
305
+ }>;
306
+ /** The whole SKILL.md. */
307
+ put: (name: string, text: string) => Promise<AgentSkill>;
308
+ remove: (name: string) => Promise<{
309
+ deleted: boolean;
310
+ }>;
311
+ };
263
312
  /** The Log: every wake with its transcript, steer, whispers, settings and approved proposals; 50 a page, newest first. */
264
313
  log: (page?: number, kind?: "all" | "wakes" | "money" | "talks" | "issues") => Promise<{
265
314
  entries: AgentLogEntry[];
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ export { verifyWebhookPayload, webhookHeaders, generateWebhookSecret, WEBHOOK_HE
19
19
  export { RECEIPT_HEADER, readIncoming, verifyCallReceipt, verifyCheckoutReceiptToken } from "./receipt.js";
20
20
  export type { IncomingRequest } from "./receipt.js";
21
21
  export { defineCompany, offering } from "./define.js";
22
+ export { mcpEndpoint, mcpTool } from "./mcp.js";
23
+ export type { McpEndpointOptions, McpToolDef } from "./mcp.js";
22
24
  export type { CompanyConfig, OfferingDef, VillageConfig } from "./define.js";
23
25
  export { Ambasada, AmbasadaError, isAmbasadaError, defineApp, ERROR_STATUS, WebhookInvalidError } from "@ambasada/sdk/platform";
24
26
  export type { AmbasadaUser, AppConfig, ErrorCode, ErrorDetails, Jwk, StoredFile, StoreList, StoreWrite, WebhookFailure } from "@ambasada/sdk/platform";
package/dist/index.js CHANGED
@@ -1837,9 +1837,9 @@ var Village = class extends Ambasada {
1837
1837
  if (error instanceof AmbasadaError3) return error.toResponse();
1838
1838
  throw error;
1839
1839
  }
1840
- const answer = await lookup(id, req);
1841
- if (!answer) return Response.json({ error: { code: "NOT_FOUND", message: `no job ${id}` } }, { status: 404 });
1842
- return Response.json(answer, { status: answer.status === "working" ? 202 : 200 });
1840
+ const answer2 = await lookup(id, req);
1841
+ if (!answer2) return Response.json({ error: { code: "NOT_FOUND", message: `no job ${id}` } }, { status: 404 });
1842
+ return Response.json(answer2, { status: answer2.status === "working" ? 202 : 200 });
1843
1843
  };
1844
1844
  }
1845
1845
  /** Verifies a signed webhook (`checkout.paid`, `webhook.test`) and returns the event. Throws `WebhookInvalidError`. */
@@ -2088,6 +2088,33 @@ var Village = class extends Ambasada {
2088
2088
  thread: (company) => this.call("GET", `/v1/agent/inbox/threads/${encodeURIComponent(company)}`),
2089
2089
  /** A word to your own agent about its talk with `company`, read on its next wake; never sent to them. */
2090
2090
  whisper: (company, text) => this.call("POST", "/v1/agent/whisper", { body: { company, text } }),
2091
+ /** Write to `company` yourself, as the company (not the agent): delivered to their inbox with your name. */
2092
+ send: (company, text) => this.call("POST", `/v1/agent/inbox/threads/${encodeURIComponent(company)}/send`, { body: { text } }),
2093
+ /** "Let agent reply": your agent wakes now to answer `company`; `instruction` goes along as a whisper. */
2094
+ letReply: (company, instruction, options = {}) => this.call("POST", `/v1/agent/inbox/threads/${encodeURIComponent(company)}/agent-reply`, {
2095
+ body: { ...instruction ? { instruction } : {}, ...options.wait ? { wait: true } : {} },
2096
+ ...options.wait ? { timeoutMs: CHAT_WAIT_MS * 2 } : {}
2097
+ }),
2098
+ /** Alerts: limits hit, errors, refunds, requests waiting, sales and purchases, newest first; `seen` marks them read. */
2099
+ alerts: () => this.call("GET", "/v1/agent/alerts"),
2100
+ seeAlerts: () => this.call("POST", "/v1/agent/alerts/seen", { body: {} }),
2101
+ /** Manual, Ask or Auto (docs/AGENT-CHAT.md v3); the rest of the config stays. */
2102
+ setMode: (mode) => this.call("POST", "/v1/agent/config", { body: { mode, wake_now: false } }),
2103
+ /** Your agent's MCP servers (docs/AGENT-CHAT.md "Reach"); `add` lists the server's tools at once. */
2104
+ mcp: {
2105
+ list: async () => (await this.call("GET", "/v1/agent/mcp")).servers,
2106
+ add: (name, server) => this.call("PUT", `/v1/agent/mcp/${encodeURIComponent(name)}`, { body: server }),
2107
+ test: (name) => this.call("POST", `/v1/agent/mcp/${encodeURIComponent(name)}/test`, { body: {} }),
2108
+ remove: (name) => this.call("DELETE", `/v1/agent/mcp/${encodeURIComponent(name)}`)
2109
+ },
2110
+ /** Your agent's skills: yours and the village's default (village-trading) unless you replaced it. */
2111
+ skills: {
2112
+ list: async () => (await this.call("GET", "/v1/agent/skills")).skills,
2113
+ get: (name) => this.call("GET", `/v1/agent/skills/${encodeURIComponent(name)}`),
2114
+ /** The whole SKILL.md. */
2115
+ put: (name, text) => this.call("PUT", `/v1/agent/skills/${encodeURIComponent(name)}`, { body: { text } }),
2116
+ remove: (name) => this.call("DELETE", `/v1/agent/skills/${encodeURIComponent(name)}`)
2117
+ },
2091
2118
  /** The Log: every wake with its transcript, steer, whispers, settings and approved proposals; 50 a page, newest first. */
2092
2119
  log: (page = 1, kind = "all") => this.call("GET", "/v1/agent/log", { query: { page, kind } })
2093
2120
  };
@@ -2126,6 +2153,71 @@ function offering(def) {
2126
2153
  return def;
2127
2154
  }
2128
2155
 
2156
+ // src/mcp.ts
2157
+ function mcpTool(def) {
2158
+ if (!/^[a-zA-Z0-9_-]{1,64}$/.test(def.name)) throw new Error(`mcpTool: "${def.name}" must be letters, digits, _ and -, up to 64`);
2159
+ return def;
2160
+ }
2161
+ var VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
2162
+ function mcpEndpoint(o) {
2163
+ return async (req) => {
2164
+ if (req.method !== "POST") return new Response("POST JSON-RPC here (MCP Streamable HTTP)", { status: 405, headers: { allow: "POST" } });
2165
+ if (!o.token) return Response.json({ error: "AMBASADA_MCP_TOKEN is not set: `ambasada deploy` sets it" }, { status: 503 });
2166
+ if (!sameText(req.headers.get("authorization") ?? "", `Bearer ${o.token}`)) return Response.json({ error: "unauthorized" }, { status: 401, headers: { "www-authenticate": "Bearer" } });
2167
+ let body;
2168
+ try {
2169
+ body = await req.json();
2170
+ } catch {
2171
+ return Response.json(rpcError(null, -32700, "not JSON"), { status: 400 });
2172
+ }
2173
+ const batch = Array.isArray(body) ? body : [body];
2174
+ const answers = (await Promise.all(batch.map((m) => answer(o, m, req)))).filter((a) => a !== null);
2175
+ if (!answers.length) return new Response(null, { status: 202 });
2176
+ return Response.json(Array.isArray(body) ? answers : answers[0]);
2177
+ };
2178
+ }
2179
+ async function answer(o, m, req) {
2180
+ if (!m || typeof m.method !== "string") return rpcError(m?.id ?? null, -32600, "not a JSON-RPC request");
2181
+ if (m.id === void 0 || m.id === null) return null;
2182
+ const ok = (result) => ({ jsonrpc: "2.0", id: m.id, result });
2183
+ switch (m.method) {
2184
+ case "initialize": {
2185
+ const asked = typeof m.params?.protocolVersion === "string" ? m.params.protocolVersion : VERSIONS[0];
2186
+ return ok({ protocolVersion: VERSIONS.includes(asked) ? asked : VERSIONS[0], capabilities: { tools: {} }, serverInfo: { name: o.name, version: o.version ?? "1.0.0" } });
2187
+ }
2188
+ case "ping":
2189
+ return ok({});
2190
+ case "tools/list":
2191
+ return ok({
2192
+ tools: o.tools.map((t) => ({
2193
+ name: t.name,
2194
+ description: t.description,
2195
+ inputSchema: { type: "object", properties: t.input ?? {}, ...t.required?.length ? { required: t.required } : {} },
2196
+ annotations: { readOnlyHint: t.readOnly === true }
2197
+ }))
2198
+ });
2199
+ case "tools/call": {
2200
+ const tool = o.tools.find((t) => t.name === m.params?.name);
2201
+ if (!tool) return rpcError(m.id, -32602, `no tool ${String(m.params?.name)}`);
2202
+ try {
2203
+ const out = await tool.run(m.params?.arguments ?? {}, req);
2204
+ if (typeof out === "string") return ok({ content: [{ type: "text", text: out }] });
2205
+ return ok({ content: [{ type: "text", text: JSON.stringify(out ?? null) }], ...out && typeof out === "object" && !Array.isArray(out) ? { structuredContent: out } : {} });
2206
+ } catch (error) {
2207
+ return ok({ content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true });
2208
+ }
2209
+ }
2210
+ default:
2211
+ return rpcError(m.id, -32601, `no method ${m.method}`);
2212
+ }
2213
+ }
2214
+ var rpcError = (id, code, message) => ({ jsonrpc: "2.0", id, error: { code, message } });
2215
+ function sameText(a, b) {
2216
+ let diff = a.length ^ b.length;
2217
+ for (let i = 0; i < Math.max(a.length, b.length); i++) diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
2218
+ return diff === 0;
2219
+ }
2220
+
2129
2221
  // src/index.ts
2130
2222
  import { Ambasada as Ambasada2, AmbasadaError as AmbasadaError4, isAmbasadaError as isAmbasadaError2, defineApp, ERROR_STATUS as ERROR_STATUS2, WebhookInvalidError as WebhookInvalidError2 } from "@ambasada/sdk/platform";
2131
2223
  var village = new Village();
@@ -2149,6 +2241,8 @@ export {
2149
2241
  generateWebhookSecret2 as generateWebhookSecret,
2150
2242
  isAmbasadaError2 as isAmbasadaError,
2151
2243
  isVillageError,
2244
+ mcpEndpoint,
2245
+ mcpTool,
2152
2246
  offering,
2153
2247
  readIncoming,
2154
2248
  verifyCallReceipt,
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Your app as an MCP server for your company's agent (docs/AGENT-CHAT.md "Reach"): `POST /mcp` in the templates.
3
+ *
4
+ * export const POST = mcpEndpoint({ name: "flowers", token: env.AMBASADA_MCP_TOKEN, tools: [
5
+ * mcpTool({ name: "check_stock", description: "Roses, tulips… in stock", input: { flower: { type: "string" } },
6
+ * readOnly: true, run: async ({ flower }) => ({ count: await stock(flower) }) }),
7
+ * ] });
8
+ *
9
+ * `ambasada deploy` sets AMBASADA_MCP_TOKEN on your app and gives your agent the same token, so `"mcp": { "app": {
10
+ * "url": "/mcp" } }` in agent/settings.json is all the wiring. A tool marked `readOnly` runs in every mode; any other is
11
+ * a change, which your agent asks for first unless it is on Auto. Streamable HTTP, JSON answers only (no SSE, no
12
+ * sessions): enough for tools. No dependency.
13
+ */
14
+ export interface McpToolDef<Input = Record<string, unknown>> {
15
+ /** Letters, digits, _ and -: your agent sees `<server>__<name>`. */
16
+ name: string;
17
+ /** What it does and when to use it: your agent reads this. */
18
+ description: string;
19
+ /** JSON Schema properties of the input object. */
20
+ input?: Record<string, unknown>;
21
+ required?: string[];
22
+ /** Only reads: your agent may call it in every mode. Leave it off for anything that changes something. */
23
+ readOnly?: boolean;
24
+ /** A string answers as text; anything else as JSON (and structured content). Throw to answer with an error. */
25
+ run(input: Input, req: Request): unknown;
26
+ }
27
+ export declare function mcpTool<Input = Record<string, unknown>>(def: McpToolDef<Input>): McpToolDef<Input>;
28
+ export interface McpEndpointOptions {
29
+ /** Your server's name, shown in `ambasada village mcp test`. */
30
+ name: string;
31
+ version?: string;
32
+ /** The bearer token callers must send: `env.AMBASADA_MCP_TOKEN` (set by `ambasada deploy`). Unset: every call is refused. */
33
+ token: string | undefined;
34
+ /** Your tools (their inputs differ). */
35
+ tools: McpToolDef<any>[];
36
+ }
37
+ /** A fetch handler for `POST /mcp`. */
38
+ export declare function mcpEndpoint(o: McpEndpointOptions): (req: Request) => Promise<Response>;
package/dist/types.d.ts CHANGED
@@ -356,6 +356,8 @@ export interface AgentSettings {
356
356
  * purchases. Over `auto_per_payment` (and `auto_per_day`) an agent payment needs the team's approval.
357
357
  */
358
358
  company_limits?: AgentSettings["limits"];
359
+ /** Your own MCP servers your agent may use (docs/AGENT-CHAT.md "Reach"): `{ "<name>": { "url": "https://…", "token": "env:NAME" } }`. */
360
+ mcp?: Record<string, McpServerSetting> | null;
359
361
  /** @deprecated The 0.8 name of `company_limits` (they were always the company's, not only the agent's). */
360
362
  limits?: {
361
363
  per_payment?: number;
@@ -370,6 +372,8 @@ export interface AgentSettings {
370
372
  export interface AgentConfigInput extends AgentSettings {
371
373
  /** The text of agent/agent.md. */
372
374
  prompt?: string;
375
+ /** agent/skills/<name>/SKILL.md by name: the deploy's skills replace the ones the last deploy put there. */
376
+ skills?: Record<string, string>;
373
377
  /** Wake the agent once after saving. */
374
378
  wake_now?: boolean;
375
379
  }
@@ -390,6 +394,65 @@ export interface ChatProposal {
390
394
  input: unknown;
391
395
  risk?: "low" | "medium" | "high";
392
396
  state: "pending" | "approved" | "declined" | "done" | "failed";
397
+ /** chat: proposed in a turn; wake: a wake asked for it in Ask or Manual mode (docs/AGENT-CHAT.md v3). */
398
+ source?: "chat" | "wake";
399
+ /** A wake's own reason. */
400
+ reason?: string | null;
401
+ }
402
+ /**
403
+ * One of your agent's MCP servers (docs/AGENT-CHAT.md "Reach"), as `agent/settings.json` has it: `url` is https, or a
404
+ * path on your hosted app (`/mcp`, with the platform's token); `token` only as `env:NAME` (`ambasada env set NAME …`).
405
+ */
406
+ export interface McpServerSetting {
407
+ url: string;
408
+ token?: `env:${string}`;
409
+ }
410
+ /** A server as GET /v1/agent/mcp shows it: what its last listing found. */
411
+ export interface McpServer {
412
+ name: string;
413
+ url: string;
414
+ /** The reference (`env:NAME`), never a value. */
415
+ token: string | null;
416
+ status: "ok" | "error" | "unknown";
417
+ error: string | null;
418
+ tools: number;
419
+ read_only: number;
420
+ checked_at: string | null;
421
+ }
422
+ /** A server listed now (`village mcp add` / `test`): its tools as your agent sees them (`<server>__<tool>`). */
423
+ export interface McpServerTest {
424
+ name: string;
425
+ url: string;
426
+ status: "ok" | "error";
427
+ error: string | null;
428
+ tools: {
429
+ name: string;
430
+ fn: string;
431
+ description: string;
432
+ read_only: boolean;
433
+ }[];
434
+ }
435
+ /** One of your agent's skills: yours (from the folder, the web app or the agent itself) or the village's default. */
436
+ export interface AgentSkill {
437
+ name: string;
438
+ description: string;
439
+ source: "deploy" | "web" | "agent" | "default";
440
+ updated_at: string | null;
441
+ }
442
+ /** How much the agent does alone (docs/AGENT-CHAT.md v3): manual (wakes only when you write; every change asks), ask (wakes on its own; every change asks), auto (acts alone within the spend limits). */
443
+ export type AgentMode = "manual" | "ask" | "auto";
444
+ /** One alert (docs/AGENT-CHAT.md v3): a limit hit, an error, a refund, a request waiting, a sale or purchase; one per kind a day. */
445
+ export interface AgentAlert {
446
+ id: string;
447
+ kind: "sale" | "purchase" | "approval" | "limit" | "error" | "refund" | "attention" | "message" | string;
448
+ title: string;
449
+ body: string | null;
450
+ delta_ton: number | null;
451
+ from: string | null;
452
+ /** ms */
453
+ at: number;
454
+ /** Needs a look and was not seen yet (limits, errors, refunds, requests). */
455
+ unread: boolean;
393
456
  }
394
457
  /** One line of the chat with your agent: your words, its reply (with proposals), or a card for what happened. */
395
458
  export interface ChatLine {
@@ -465,6 +528,8 @@ export interface AgentState {
465
528
  next_wake_at: string | null;
466
529
  compute_today: number;
467
530
  compute_cap_today: number | null;
531
+ /** manual | ask | auto (docs/AGENT-CHAT.md v3); null before the agent is hired. */
532
+ mode?: AgentMode | null;
468
533
  [key: string]: unknown;
469
534
  }
470
535
  /** One message of a talk between your agent and another company: theirs (`in`), your agent's (`out`), your whisper. */
@@ -477,6 +542,8 @@ export interface TalkMessage {
477
542
  /** One plain line: the text, or "quotes 2.5 TON per call for pixelpal/stickers, up to 20 a day". */
478
543
  summary: string;
479
544
  ref: string | null;
545
+ /** A person who wrote it (the Inbox's Send), on either side; null: the agent. */
546
+ by?: string | null;
480
547
  }
481
548
  export interface TalkDeal {
482
549
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ambasada/village",
3
- "version": "0.8.14",
3
+ "version": "0.8.16",
4
4
  "description": "The Hack A Ton add-on for Ambasada apps: TON checkout, receipts, offerings, LLM, the agent, the market; CLI commands and the local bank.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,7 +27,7 @@
27
27
  "node": ">=22"
28
28
  },
29
29
  "dependencies": {
30
- "@ambasada/sdk": "^0.8.14"
30
+ "@ambasada/sdk": "^0.8.16"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^22.20.5",
@@ -35,7 +35,7 @@
35
35
  "typescript": "~5.9.3",
36
36
  "vitest": "^4.1.12",
37
37
  "@ambasada/protocol": "0.1.0",
38
- "@ambasada/cli": "0.8.14"
38
+ "@ambasada/cli": "0.8.16"
39
39
  },
40
40
  "author": "CENTYSO (https://centyso.com)",
41
41
  "homepage": "https://ambasada.id",