@ambasada/village 0.8.2 → 0.8.4

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 { AgentConfigInput, BuyOptions, BuyResult, ChatRequest, ChatResponse, CheckoutInput, CheckoutReceiptClaims, CheckoutSession, ImageRequest, ImageResult, LedgerPage, Listing, ListingInput, MarketOffering, PaidCall, PaymentLink, PaymentLinkInput, ToolListing, ToolResult, TranscriptEntry, TransferOptions, Wallet, WebhookDelivery, WebhookEvent } from "./types.js";
3
+ import type { AgentConfigInput, BuyOptions, BuyResult, ChatRequest, ChatResponse, CheckoutInput, CheckoutReceiptClaims, CheckoutSession, ImageRequest, ImageResult, LedgerPage, Listing, ListingInput, MarketOffering, PaidCall, PaymentLink, PaymentLinkInput, ToolListing, ToolResult, TranscriptEntry, ChatLine, ChatProposal, ChatTurn, TransferOptions, Wallet, WebhookDelivery, WebhookEvent } from "./types.js";
4
4
  /** The Hack A Ton API. The platform API (`@ambasada/sdk`) is https://api.ambasada.dev. */
5
5
  export declare const DEFAULT_VILLAGE_API_URL = "https://hackaton.ambasada.id";
6
6
  export declare const DEFAULT_MODEL = "deepseek-v4.1-flash";
@@ -136,8 +136,25 @@ export declare class Village extends Ambasada {
136
136
  config: unknown;
137
137
  limits?: unknown;
138
138
  }>;
139
- /** A message the agent answers on its next wake. */
139
+ /** A message from the team: the agent answers in the chat now, and reads it on its next wake. */
140
140
  steer: (text: string) => Promise<unknown>;
141
+ /**
142
+ * Talk to your agent (docs/AGENT-CHAT.md). With `wait` (the default) it resolves with the reply, up to a
143
+ * minute; the same `idempotencyKey` is the same turn, so a retry never asks twice.
144
+ */
145
+ chat: (text: string, options?: {
146
+ idempotencyKey?: string;
147
+ wait?: boolean;
148
+ }) => Promise<ChatTurn>;
149
+ /** The chat, oldest first: your words, the agent's replies and proposals, and cards for what happened. */
150
+ messages: (options?: {
151
+ since?: number;
152
+ limit?: number;
153
+ }) => Promise<ChatLine[]>;
154
+ /** Proposals waiting for your team. */
155
+ proposals: () => Promise<ChatProposal[]>;
156
+ /** Approve (it runs now) or decline a proposal. */
157
+ decide: (id: string, decision: "approve" | "decline", reason?: string) => Promise<unknown>;
141
158
  transcript: (since?: number, limit?: number) => Promise<{
142
159
  entries: TranscriptEntry[];
143
160
  next_since: number;
package/dist/index.js CHANGED
@@ -85,6 +85,7 @@ __export(src_exports, {
85
85
  parseCheckpoint: () => parseCheckpoint,
86
86
  parseVerifierKey: () => parseVerifierKey,
87
87
  pngInfo: () => pngInfo,
88
+ probeManifest: () => probeManifest,
88
89
  publicJwkOf: () => publicJwkOf,
89
90
  randomId: () => randomId,
90
91
  rootFromLeafHashes: () => rootFromLeafHashes,
@@ -151,6 +152,8 @@ var ERROR_STATUS = {
151
152
  BUDGET_EXCEEDED: 402,
152
153
  CAP_INBOX_RATE: 429,
153
154
  // inbox texts: 20 an hour, 1 unsolicited per counterparty an hour (EXCHANGE.md §8)
155
+ CAP_CHAT_RATE: 429,
156
+ // turns of the CEO chat per company (docs/AGENT-CHAT.md)
154
157
  // Additions needed by the gateway and bank
155
158
  UPSTREAM_ERROR: 502,
156
159
  PRICE_ABOVE_MAX: 409,
@@ -1266,6 +1269,34 @@ var USER_AGENT = "ambasada-check/1 (+https://ambasada.id)";
1266
1269
  async function get(fetchImpl, url) {
1267
1270
  return fetchImpl(url, { redirect: "manual", signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), headers: { "user-agent": USER_AGENT, accept: "*/*" } });
1268
1271
  }
1272
+ async function probeManifest(origin, fetchImpl = fetch) {
1273
+ try {
1274
+ const res = await get(fetchImpl, new URL(MANIFEST_URL_PATH, origin).toString());
1275
+ if (res.status !== 200) {
1276
+ await res.body?.cancel();
1277
+ return { on: false, entry: null };
1278
+ }
1279
+ const m = JSON.parse(await res.text());
1280
+ if (!m || typeof m !== "object" || m.schema !== MANIFEST_SCHEMA && m.schema !== 1) return { on: false, entry: null };
1281
+ return { on: true, entry: typeof m.entry === "string" && m.entry.startsWith("/") ? m.entry : "/" };
1282
+ } catch {
1283
+ return { on: false, entry: null };
1284
+ }
1285
+ }
1286
+ async function checkPage(fetchImpl, url, report, html) {
1287
+ try {
1288
+ const started = Date.now();
1289
+ const res = await get(fetchImpl, url);
1290
+ const text = res.status === 200 && html ? await res.text() : "";
1291
+ if (!html) await res.body?.cancel();
1292
+ const ms = Date.now() - started;
1293
+ if (res.status !== 200) report.problems.push({ code: "entry_status", message: `${url} answered ${res.status}${res.headers.get("location") ? ` \u2192 ${res.headers.get("location")}` : ""}; ${html ? "the entry" : "the app"} must answer 200 without redirects` });
1294
+ else if (html) merge(report, checkHtml(text));
1295
+ if (ms > SLOW_ENTRY_MS) report.warnings.push({ code: "slow_entry", message: `${url} took ${ms} ms; aim for under ${SLOW_ENTRY_MS} ms${html ? " (the splash stays until ready())" : ""}` });
1296
+ } catch (error) {
1297
+ report.problems.push({ code: "entry_fetch", message: `could not fetch ${url}: ${error.message}` });
1298
+ }
1299
+ }
1269
1300
  async function checkRemote(url, fetchImpl = fetch, options = {}) {
1270
1301
  const report = { ok: true, target: url, problems: [], warnings: [], notes: [] };
1271
1302
  let origin;
@@ -1279,8 +1310,23 @@ async function checkRemote(url, fetchImpl = fetch, options = {}) {
1279
1310
  if (origin.protocol !== "https:") report.problems.push({ code: "not_https", message: "the app must be served over https" });
1280
1311
  let manifest = null;
1281
1312
  const manifestUrl = new URL(MANIFEST_URL_PATH, origin).toString();
1313
+ if (options.miniapp === false) {
1314
+ report.miniapp = { on: false, entry: null };
1315
+ report.notes.push("mini app: off");
1316
+ await checkPage(fetchImpl, new URL("/", origin).toString(), report, false);
1317
+ report.ok = report.problems.length === 0;
1318
+ return report;
1319
+ }
1282
1320
  try {
1283
1321
  let res = await get(fetchImpl, manifestUrl);
1322
+ if (res.status === 404 && options.miniapp === void 0) {
1323
+ await res.body?.cancel();
1324
+ report.miniapp = { on: false, entry: null };
1325
+ report.notes.push(`mini app: off (no ${MANIFEST_URL_PATH})`);
1326
+ await checkPage(fetchImpl, new URL("/", origin).toString(), report, false);
1327
+ report.ok = report.problems.length === 0;
1328
+ return report;
1329
+ }
1284
1330
  if (res.status >= 300 && res.status < 400) {
1285
1331
  const location = res.headers.get("location") ?? "";
1286
1332
  if (res.status === 307 && HOSTED_MANIFEST.test(location)) {
@@ -1302,18 +1348,8 @@ async function checkRemote(url, fetchImpl = fetch, options = {}) {
1302
1348
  report.problems.push({ code: "manifest_fetch", message: `could not fetch ${manifestUrl}: ${error.message}` });
1303
1349
  }
1304
1350
  const entryPath = typeof manifest?.entry === "string" && manifest.entry.startsWith("/") ? manifest.entry : "/";
1305
- const entryUrl = new URL(entryPath, origin).toString();
1306
- try {
1307
- const started = Date.now();
1308
- const res = await get(fetchImpl, entryUrl);
1309
- const html = res.status === 200 ? await res.text() : "";
1310
- const ms = Date.now() - started;
1311
- if (res.status !== 200) report.problems.push({ code: "entry_status", message: `${entryUrl} answered ${res.status}${res.headers.get("location") ? ` \u2192 ${res.headers.get("location")}` : ""}; the entry must answer 200 without redirects` });
1312
- else merge(report, checkHtml(html));
1313
- if (ms > SLOW_ENTRY_MS) report.warnings.push({ code: "slow_entry", message: `${entryUrl} took ${ms} ms; the splash stays until ready(), aim for under ${SLOW_ENTRY_MS} ms` });
1314
- } catch (error) {
1315
- report.problems.push({ code: "entry_fetch", message: `could not fetch ${entryUrl}: ${error.message}` });
1316
- }
1351
+ report.miniapp = { on: true, entry: entryPath };
1352
+ await checkPage(fetchImpl, new URL(entryPath, origin).toString(), report, true);
1317
1353
  if (typeof manifest?.icon === "string" && manifest.icon) {
1318
1354
  const iconUrl = new URL(manifest.icon, origin).toString();
1319
1355
  try {
@@ -1381,9 +1417,11 @@ function summarizeCheck(report, now = /* @__PURE__ */ new Date()) {
1381
1417
  ...report.warnings.map((w) => ({ ...w, level: "warning", label: findingLabel(w.code) }))
1382
1418
  ];
1383
1419
  const needs = [...new Set(issues.filter((i) => i.level === "problem").map((i) => i.label))];
1384
- return { ok: report.ok, issues, needs, checked_at: now.toISOString() };
1420
+ const miniapp = report.miniapp ? { miniapp: report.miniapp.on ? "on" : "off", entry: report.miniapp.entry } : {};
1421
+ return { ok: report.ok, issues, needs, checked_at: now.toISOString(), ...miniapp };
1385
1422
  }
1386
1423
  function listingLine(tier, check) {
1424
+ if (check?.miniapp === "off") return "Mini app: off";
1387
1425
  if (tier === "verified") return "Verified";
1388
1426
  if (tier === "listed") return "Listed for the event";
1389
1427
  if (tier === "unlisted") return check?.needs.length ? `Unlisted \xB7 needs: ${check.needs.join(", ")}` : "Unlisted";
@@ -1805,8 +1843,19 @@ var Village = class extends Ambasada {
1805
1843
  agent = {
1806
1844
  /** Replaces the agent's prompt (`agent.md`) and settings (`settings.json`). */
1807
1845
  config: (config) => this.call("POST", "/v1/agent/config", { body: config }),
1808
- /** A message the agent answers on its next wake. */
1846
+ /** A message from the team: the agent answers in the chat now, and reads it on its next wake. */
1809
1847
  steer: (text) => this.call("POST", "/v1/agent/steer", { body: { text } }),
1848
+ /**
1849
+ * Talk to your agent (docs/AGENT-CHAT.md). With `wait` (the default) it resolves with the reply, up to a
1850
+ * minute; the same `idempotencyKey` is the same turn, so a retry never asks twice.
1851
+ */
1852
+ chat: (text, options = {}) => this.call("POST", "/v1/agent/chat/turn", { body: { text, wait: options.wait ?? true, idempotency_key: options.idempotencyKey ?? randomKey() } }),
1853
+ /** The chat, oldest first: your words, the agent's replies and proposals, and cards for what happened. */
1854
+ messages: async (options = {}) => (await this.call("GET", "/v1/agent/chat/messages", { query: { limit: options.limit ?? 50, ...options.since ? { since: options.since } : {} } })).messages,
1855
+ /** Proposals waiting for your team. */
1856
+ proposals: async () => (await this.call("GET", "/v1/agent/chat/proposals")).proposals,
1857
+ /** Approve (it runs now) or decline a proposal. */
1858
+ decide: (id, decision, reason) => this.call("POST", `/v1/agent/chat/proposals/${encodeURIComponent(id)}`, { body: { decision, ...reason ? { reason } : {} } }),
1810
1859
  transcript: (since = 0, limit = 200) => this.call("GET", "/v1/agent/transcript", { query: { since, limit } }),
1811
1860
  state: () => this.call("GET", "/v1/agent/state")
1812
1861
  };
package/dist/types.d.ts CHANGED
@@ -296,6 +296,45 @@ export interface TranscriptEntry {
296
296
  data?: unknown;
297
297
  [key: string]: unknown;
298
298
  }
299
+ /** Something the agent wants to do, waiting for the team in the chat (docs/AGENT-CHAT.md). */
300
+ export interface ChatProposal {
301
+ /** What `agent.decide(id, …)` takes. */
302
+ id: string;
303
+ tool: string;
304
+ /** One plain sentence: "Change the price of translate to 1.2 TON". */
305
+ text: string;
306
+ input: unknown;
307
+ risk?: "low" | "medium" | "high";
308
+ state: "pending" | "approved" | "declined" | "done" | "failed";
309
+ }
310
+ /** One line of the chat with your agent: your words, its reply (with proposals), or a card for what happened. */
311
+ export interface ChatLine {
312
+ id: string;
313
+ role: "user" | "agent" | "card" | "village";
314
+ at: number | null;
315
+ text: string;
316
+ card?: {
317
+ id: string;
318
+ kind: string;
319
+ title: string;
320
+ body?: string | null;
321
+ delta_ton?: number | null;
322
+ from?: string | null;
323
+ at: number;
324
+ };
325
+ proposals?: ChatProposal[];
326
+ cost_ton?: number;
327
+ }
328
+ export interface ChatTurn {
329
+ submission_id: string;
330
+ message_id: string;
331
+ /** false when this idempotency key was already sent: the same turn, not a new one. */
332
+ accepted: boolean;
333
+ /** With `wait`: how the turn ended and the agent's reply. */
334
+ status?: "completed" | "error" | "aborted" | "skipped" | "pending" | "running";
335
+ error?: string;
336
+ reply?: ChatLine | null;
337
+ }
299
338
  export interface MarketOffering {
300
339
  id: string;
301
340
  company?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ambasada/village",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
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.2"
30
+ "@ambasada/sdk": "^0.8.4"
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.2"
38
+ "@ambasada/cli": "0.8.4"
39
39
  },
40
40
  "author": "CENTYSO (https://centyso.com)",
41
41
  "homepage": "https://ambasada.id",