@repogo/plugins 0.1.33 → 0.1.35

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 CHANGED
@@ -10,10 +10,12 @@ phone draw its data live and run its methods when you tap.
10
10
  - [Methods](#methods): queries, mutations, streams
11
11
  - [Data](#data): namespaces, collections, kv, streams, buckets
12
12
  - [Crons](#crons)
13
+ - [Logging](#logging)
13
14
  - [The context](#the-context)
14
15
  - [Events](#events)
15
16
  - [How cards and agents reach a plugin](#how-cards-and-agents-reach-a-plugin)
16
17
  - [Errors and confirmations](#errors-and-confirmations)
18
+ - [Testing](#testing)
17
19
 
18
20
  ## Quick start
19
21
 
@@ -62,9 +64,9 @@ project plugin hides an environment one of the same name. Each folder's plugins
62
64
  run together in one Worker, so a plugin's blocking code stalls the others in its
63
65
  folder: use async APIs.
64
66
 
65
- Pages beside an environment plugin (`pages/*.md`) open from any project as
66
- `~/.repogo/plugins/<name>/pages/<page>.md`, and `~/.repogo/plugins/tab.json`
67
- names the tab of a project without its own.
67
+ Pages beside an environment plugin (`pages/*.ui`, or `pages/*.md`) open from
68
+ any project as `~/.repogo/plugins/<name>/pages/<page>.ui`. A
69
+ `.ui` page is card markup with no fence; a `.md` page is writing with cards.
68
70
 
69
71
  ## Methods
70
72
 
@@ -125,8 +127,9 @@ plugin, another plugin, or another project.
125
127
 
126
128
  `namespace` names where a plugin's data lives: the folder
127
129
  `~/.repogo/plugin-data/<namespace>/`, holding `data.db` and its streams' and
128
- buckets' files. Left out, it's the plugin's name. Plugins naming the same
129
- namespace share its data, in any project, so they must declare the same schemas.
130
+ buckets' files. A plugin that keeps data (any collection, kv, stream or bucket)
131
+ must name one, or it fails to load. Plugins naming the same namespace share its
132
+ data, in any project, so they must declare the same schemas.
130
133
 
131
134
  By convention a project's own data is `<project>.<name>` (`myproject.notes`),
132
135
  and data meant for every project a bare name (`todos`). That's only a
@@ -145,10 +148,14 @@ await ctx.collections.tasks.get(task.id); // or n
145
148
  await ctx.collections.tasks.update(task.id, { status: "done" }); // only the fields given change
146
149
  await ctx.collections.tasks.list({ where: { status: "open" }, orderBy: "updatedAt", order: "desc", limit: 50 });
147
150
  await ctx.collections.tasks.delete(task.id);
151
+ await ctx.collections.tasks.deleteMany({ where: { status: "done" }, offset: 100, limit: 500 }); // done ones past the newest 100, 500 at a time
148
152
  ```
149
153
 
150
154
  `list` is newest first by default, at most 100 unless `limit` says (up to 500).
151
155
  `where` matches top-level fields exactly; `null` matches a missing one.
156
+ `orderBy` is `id`, `createdAt`, `updatedAt` or one of the record's own fields, and
157
+ `offset` passes over that many first. `deleteMany` takes the same options and
158
+ deletes exactly what `list` would return, in one transaction, returning how many.
152
159
 
153
160
  ### Kv
154
161
 
@@ -265,28 +272,64 @@ A bucket's `put` takes `text`, or `from`, an absolute path on the environment to
265
272
  copy: bytes never travel in a call. A method of your own with the same name as a
266
273
  generated one fails the plugin's load.
267
274
 
275
+ Data named in `private` gets none of these, so cards and agents reach it only
276
+ through the plugin's own methods: `private: ["actions", "lines"]` for an audit
277
+ log no tap can edit. It hides methods; it isn't a lock. Another plugin naming
278
+ the same namespace and store can still read and write it.
279
+
268
280
  ## Crons
269
281
 
270
- ```ts
271
- export default definePlugin({
272
- methods: { watch: method(z.object({}), async (_, ctx) => { /* … */ }) },
273
- crons: [
274
- { method: "watch", schedule: "*/5 * * * *" },
275
- { method: "digest", schedule: "0 9 * * 1-5", timezone: "America/New_York" },
276
- ],
277
- });
282
+ What runs with no one looking is listed in one file the user owns,
283
+ `~/.repogo/crons.json`, not in a plugin's code: a repo you clone can't schedule
284
+ itself. Each line names a plugin, one of its methods and a schedule:
285
+
286
+ ```json
287
+ [
288
+ { "plugin": "incidents", "method": "watch", "schedule": "*/5 * * * *" },
289
+ { "project": "~/code/shop", "plugin": "backup", "method": "run", "schedule": "0 3 * * *", "timezone": "America/New_York" },
290
+ { "plugin": "digest", "method": "send", "schedule": "0 9 * * 1-5", "enabled": false }
291
+ ]
278
292
  ```
279
293
 
280
- The host calls the method with `{}` when it comes due, as a tap would, whether
281
- or not anyone is looking. `schedule` is five fields (minute hour day-of-month
282
- month day-of-week), or six with seconds first (`*/5 * * * * *`); each is `*`, a
283
- number, a range, a list or a step. `timezone` defaults to the environment's.
294
+ | Field | What |
295
+ | --- | --- |
296
+ | `plugin`, `method` | The plugin's folder name and one of its methods, called with `{}` as a tap would |
297
+ | `project` | The project whose `.repogo/plugins` holds it, an absolute path or one starting `~/`; left out, `~/.repogo/plugins` |
298
+ | `schedule` | A cron: five fields (minute hour day-of-month month day-of-week), or six with seconds first (`*/5 * * * * *`); each `*`, a number, a range, a list or a step |
299
+ | `timezone` | Defaults to the environment's |
300
+ | `enabled` | `false` keeps the line without running it |
301
+
302
+ The host reads the file every minute, and again when the agent's `plugins` tool
303
+ lists what's there, so an edit shows at once. A folder opened only for its
304
+ schedules imports just the plugins they name; the others beside them don't run.
305
+ A line removed or disabled is never started again; a run already going finishes.
284
306
 
285
307
  A job still running isn't started again, a time the environment slept through
286
- is skipped, and nothing is retried. A cron naming a missing method, a stream, a
287
- method that needs input or asks first, or a schedule never due fails the
288
- plugin's load. The manifest shows each cron's `nextRunAt`, `lastRunAt`,
289
- `lastError` and `runs`.
308
+ is skipped, and nothing is retried. A line naming something missing, a stream, a
309
+ method that needs input or asks first, a bad schedule, or the same method twice
310
+ says why in `plugins` (each line's `error`), and the others run. A file that
311
+ isn't valid JSON pauses every line until it's fixed. Each line also shows
312
+ `nextRunAt`, `lastRunAt`, `lastError`, `lastDurationMs` and `runs`; `ctx.plugins()`
313
+ has the same for a plugin's own, and a query reading them re-runs around each run.
314
+
315
+ Schedule something only when the user asks for it. A cron's `ctx.root` is its
316
+ project, or the home folder for a plugin in `~/.repogo/plugins`. Plugins in one
317
+ folder share a thread: code that blocks it holds up every other cron there, and
318
+ the host's log names the crons that were running when it happened.
319
+
320
+ ## Logging
321
+
322
+ Write `console.log` (or `warn`, `error`, `debug`); nothing else is needed. The
323
+ host keeps every line with the plugin, the method and the call that wrote it,
324
+ who called (`card`, `agent`, `cron`), and the chat for an agent's call, plus
325
+ the arguments as JSON. An error a method throws, or one nothing caught, is
326
+ kept too, with its stack. Read them with the agent's `plugin_logs` tool, or on
327
+ a card with `<Data method="repogo:plugins.logs">`, which reloads as lines land:
328
+ `plugin`, `level` (that level and worse), `method`, `since`, `until`, `query`
329
+ (a substring) and `limit` narrow it. The `plugins` tool shows each plugin's
330
+ `errors` in the last hour. Lines older than 7 days go, and the oldest hours of
331
+ the noisiest plugin once the log passes 2 GB. A line is cut at 8 KB: a payload
332
+ belongs in the plugin's own data, not its log.
290
333
 
291
334
  ## The context
292
335
 
@@ -295,18 +338,63 @@ Every method, handler and cron gets `ctx`:
295
338
  | Field | What |
296
339
  | --- | --- |
297
340
  | `plugin` | The plugin's name |
341
+ | `caller` | Who started the call: `card`, `agent`, `cron`, or `code` |
298
342
  | `root` | The calling project's checkout |
299
343
  | `collections`, `kv`, `streams`, `buckets` | The plugin's data |
300
344
  | `call(method, input)` | Runs a method of a plugin in the same folder; `"open"` is this plugin's own |
301
345
  | `plugins()` | The plugins in the same folder, with their methods |
302
346
  | `host(method, params)` | Runs one of the host's methods that agents and cards may call (`git.status`, `chats.list`, `actions.run`, …); others fail with `FORBIDDEN` |
347
+ | `mcp(server, tool, input)` | Runs a tool of an MCP server the user connected in RepoGo and switched on for the calling project; see below |
303
348
  | `notify({ title, body, page?, collapse? })` | Alerts every paired phone; a tap opens `page`, and alerts sharing `collapse` replace each other |
349
+ | `agent(prompt, { agent, project?, title?, timeout? })` | Starts a chat with `prompt` and waits for the agent's whole answer: `{ chatId, text }`; see below |
304
350
  | `emit(event, payload)` | Sends `<plugin>.<event>` to handlers |
305
351
  | `signal` | Aborts when a stream's card goes |
306
- | `log(...)` | Writes to the host's plugin log |
352
+ | `log(...)` | The same as `console.log` |
307
353
 
308
354
  A query doesn't re-run when what a `ctx.host` method read changes.
309
355
 
356
+ ### An agent's answer
357
+
358
+ `ctx.agent` hands a question to Claude or Codex and answers with what it said,
359
+ so a cron can watch, investigate and notify with the finding:
360
+
361
+ ```ts
362
+ watch: method(z.object({}), async (_, ctx) => {
363
+ const down = await failing(ctx);
364
+ if (!down.length) return null;
365
+ const { text, chatId } = await ctx.agent(`Say in two sentences why ${down[0]} is down.`, { agent: "claude" });
366
+ await ctx.notify({ title: `${down[0]} is down`, body: text, page: `chat:${chatId}` });
367
+ return text;
368
+ }),
369
+ ```
370
+
371
+ The chat runs in `project` (the calling project unless said), under the
372
+ calling chat when an agent called the plugin. It waits up to `timeout`
373
+ (10 minutes unless said) and then throws, naming the chat, which keeps
374
+ running. The same two host methods are there by hand: `ctx.host("chats.start", …)`
375
+ and `ctx.host("chats.wait", …)`, for a chat you want to link to rather than wait for.
376
+
377
+ ### MCP servers
378
+
379
+ `ctx.mcp` reaches the MCP servers the user connected in RepoGo, by the key
380
+ cards name them with, using the user's own sign-in, so a plugin needs no key
381
+ or CLI of its own. It answers the tool's structured content, else its text
382
+ (parsed when it's JSON):
383
+
384
+ ```ts
385
+ customers: method(z.object({}), async (_, ctx) => {
386
+ const list = await ctx.mcp("stripe", "stripe_api_read", { stripe_api_operation_id: "GetCustomers", /* … */ });
387
+ return list.data.map((c) => ({ id: c.id, email: c.email }));
388
+ }, { kind: "query" }),
389
+ ```
390
+
391
+ A query may run only the server's read-only tools, as a card's `Data` may. A
392
+ tool marked destructive runs only from a method with a `confirm`, or one
393
+ called from it with `ctx.call`: the user's answer to that question is its
394
+ yes. A refusal throws `FORBIDDEN`; a tool's own error throws `INTERNAL` with
395
+ its message. Only servers switched on for `ctx.root` answer, so a cron of an
396
+ environment plugin, whose root is the home folder, reaches none.
397
+
310
398
  ## Events
311
399
 
312
400
  Handlers in `on` run after the events they name:
@@ -330,8 +418,12 @@ Events run in memory: none is retried, and a handler that throws is logged.
330
418
 
331
419
  ## How cards and agents reach a plugin
332
420
 
333
- **Agents** use two tools: `plugins` lists every plugin with its methods, input
334
- schemas, crons and `storage` (each bucket's and stream's folder), and
421
+ **Agents** use two tools. `plugins` lists every plugin with a line per method
422
+ (its kind, whether it asks first, and its input's fields, optional ones ending
423
+ in `?`), and every line of `~/.repogo/crons.json` with how it's going.
424
+ `plugins` with `plugin` set answers that plugin in full: each method's input
425
+ and output JSON Schema and confirm, its `storage` (each bucket's and stream's
426
+ folder), the pages that show its methods, and the cron lines naming it.
335
427
  `plugin_call` runs one. Call a new method once before a card names it.
336
428
 
337
429
  **Cards** name methods as `plugin:<plugin>.<method>`. A `Data` element runs a
@@ -359,4 +451,26 @@ schema answers `VALIDATION` with a message per field. Anything else thrown is
359
451
  A method's `confirm` is asked before a card's tap runs it. An agent's
360
452
  `plugin_call` gets it back as a `CONFIRM` error until it calls again with
361
453
  `confirmed: true`, once the user agrees. Only `ctx.call` from plugin code skips
362
- it. Give one to anything that can't be undone.
454
+ it. Give one to anything that can't be undone. `{{field}}` in the question is
455
+ filled from the call's input, so it can say what it acts on:
456
+ `confirm: "Restart {{machine}}?"`.
457
+
458
+ ## Testing
459
+
460
+ `@repogo/plugins/testing` runs plugins as the host does, with real data in a
461
+ temporary folder; `host` and `mcp` stand in for the host's methods and the
462
+ user's MCP servers:
463
+
464
+ ```ts
465
+ import { testRuntime } from "@repogo/plugins/testing";
466
+ import notes from "./app";
467
+
468
+ const app = await testRuntime({ notes });
469
+ await app.call("notes.add", { text: "hi" });
470
+ await app.call("notes.clear", {}, { confirmed: false }); // throws CONFIRM, as an unanswered tap
471
+ app.setCrons([{ plugin: "notes", method: "tidy", schedule: "* * * * *" }]); // as a crons.json line would
472
+ await app.tickCrons(Date.now() + 60_000); // runs whatever is due by then
473
+ app.notices; // every ctx.notify so far
474
+ app.logs; // every console line so far, with its method and caller
475
+ await app.close();
476
+ ```
@@ -8,9 +8,12 @@ export type Record_<T> = T & {
8
8
  };
9
9
  export type ListOptions = {
10
10
  where?: Record<string, string | number | boolean | null>;
11
- orderBy?: "createdAt" | "updatedAt";
11
+ /** `id`, `createdAt`, `updatedAt`, or one of the record's own top-level fields. */
12
+ orderBy?: "createdAt" | "updatedAt" | (string & {});
12
13
  order?: "asc" | "desc";
13
14
  limit?: number;
15
+ /** Records to pass over first, in the same order: `offset: 300` is everything past the newest 300. */
16
+ offset?: number;
14
17
  };
15
18
  export declare class Collection<S extends z.ZodObject> {
16
19
  private database;
@@ -28,6 +31,13 @@ export declare class Collection<S extends z.ZodObject> {
28
31
  update(id: string, patch: Partial<z.input<S>>): Promise<Record_<z.infer<S>>>;
29
32
  delete(id: string): Promise<void>;
30
33
  list(options?: ListOptions): Promise<Record_<z.infer<S>>[]>;
34
+ /**
35
+ * Deletes the records `list(options)` would return, with its same defaults,
36
+ * in one transaction, and returns how many. Each emits its `deleted` event.
37
+ */
38
+ deleteMany(options?: ListOptions): Promise<number>;
39
+ /** The query `list` and `deleteMany` share, so both pick out the same records. */
40
+ private select;
31
41
  private eventName;
32
42
  private toRecord;
33
43
  }
package/dist/context.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { Collection } from "./collections";
3
3
  import type { Kv } from "./kv";
4
4
  import type { Stream } from "./streams";
5
5
  import type { MethodKind } from "./define";
6
+ import type { Duration } from "./duration";
6
7
  /** An alert for the user's phones: what it says, and the page a tap opens. */
7
8
  export type Notice = {
8
9
  title: string;
@@ -32,6 +33,8 @@ export type ManifestCron = {
32
33
  nextRunAt: number | null;
33
34
  lastRunAt: number | null;
34
35
  lastError: string | null;
36
+ /** How long its last run took: a run far past its interval blocked or waited on something. */
37
+ lastDurationMs: number | null;
35
38
  running: boolean;
36
39
  runs: number;
37
40
  };
@@ -49,6 +52,21 @@ export type ManifestStorage = {
49
52
  path: string;
50
53
  }[];
51
54
  };
55
+ /**
56
+ * One schedule the host gives a Worker, from a line of `~/.repogo/crons.json`: call
57
+ * `<plugin>.<method>` with `{}` when `schedule` comes due in `timezone` (the environment's).
58
+ */
59
+ export type CronEntry = {
60
+ plugin: string;
61
+ method: string;
62
+ schedule: string;
63
+ timezone?: string;
64
+ };
65
+ /** A schedule and how it's going; `error` says why it couldn't be set, and then nothing runs. */
66
+ export type CronStatus = ManifestCron & {
67
+ plugin: string;
68
+ error: string | null;
69
+ };
52
70
  export type Manifest = {
53
71
  plugins: {
54
72
  name: string;
@@ -58,8 +76,27 @@ export type Manifest = {
58
76
  storage: ManifestStorage;
59
77
  }[];
60
78
  };
79
+ /** Who started a call: a tap or a card's query, an agent's `plugin_call`, a cron, or plugin code with none of those behind it. */
80
+ export type Caller = "card" | "agent" | "cron" | "code";
81
+ /** How `ctx.agent` runs a chat: which agent, in which project, and how long to wait for its answer. */
82
+ export type AgentOptions = {
83
+ /** `claude` or `codex`, as `agents.list` names them. */
84
+ agent: string;
85
+ /** The project the chat runs in; the calling project unless said. */
86
+ project?: string;
87
+ title?: string;
88
+ /** How long to wait for the answer; 10 minutes unless said. */
89
+ timeout?: Duration;
90
+ };
91
+ /** What the agent answered, and the chat it answered in, to open or message later. */
92
+ export type AgentReply = {
93
+ chatId: string;
94
+ text: string;
95
+ };
61
96
  export type Context = {
62
97
  plugin: string;
98
+ /** Who started the outermost call; `ctx.call` passes it on. */
99
+ caller: Caller;
63
100
  /** The project's checkout, for a plugin that reads the project's files. */
64
101
  root: string;
65
102
  collections: Record<string, Collection<any>>;
@@ -73,6 +110,13 @@ export type Context = {
73
110
  * `ctx.host("git.status", { path: ctx.root })`. Its answer doesn't re-run a query.
74
111
  */
75
112
  host: (method: string, params?: unknown) => Promise<unknown>;
113
+ /**
114
+ * Runs a tool of an MCP server the user switched on for the calling project:
115
+ * `ctx.mcp("stripe", "stripe_api_read", { … })`, answering the tool's result.
116
+ * A query may run only read-only tools; a destructive tool runs only from a
117
+ * method with a `confirm`. Its answer doesn't re-run a query.
118
+ */
119
+ mcp: (server: string, tool: string, input?: unknown) => Promise<unknown>;
76
120
  /**
77
121
  * The plugins beside this one, in the same folder (the project's, or the
78
122
  * environment's), with their methods: what `call` can reach.
@@ -82,5 +126,12 @@ export type Context = {
82
126
  signal: AbortSignal;
83
127
  /** Sends an alert to every phone paired with this environment. */
84
128
  notify: (notice: Notice) => void;
129
+ /**
130
+ * Starts a chat with `prompt` and waits for the agent's whole answer:
131
+ * `await ctx.agent("Why is the build failing?", { agent: "claude" })`.
132
+ * Throws once `timeout` passes, naming the chat, which keeps running.
133
+ */
134
+ agent: (prompt: string, options: AgentOptions) => Promise<AgentReply>;
135
+ /** The same as `console.log`: every console line is kept with the plugin and the call that wrote it. */
85
136
  log: (...args: unknown[]) => void;
86
137
  };
package/dist/cron.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ export type Schedule = {
2
+ seconds: Set<number> | null;
3
+ minutes: Set<number>;
4
+ hours: Set<number>;
5
+ days: Set<number>;
6
+ months: Set<number>;
7
+ weekdays: Set<number>;
8
+ /** Day fields given as other than `*`, for cron's either-day rule. */
9
+ daysRestricted: boolean;
10
+ weekdaysRestricted: boolean;
11
+ };
12
+ /** Reads a schedule, or throws saying what's wrong with it. */
13
+ export declare function parseSchedule(source: string): Schedule;
14
+ /** Throws unless `zone` is a time zone this machine knows, such as "America/New_York". */
15
+ export declare function checkTimezone(zone: string): void;
16
+ /**
17
+ * The first due time strictly after `after` (ms), in `zone`, or null when there
18
+ * is none within four years (`0 0 31 2 *`). A wall time that a fall-back night
19
+ * shows twice is due only the first time.
20
+ */
21
+ export declare function nextDue(s: Schedule, after: number, zone: string): number | null;
package/dist/define.d.ts CHANGED
@@ -28,23 +28,13 @@ export type Method<I extends z.ZodType = z.ZodType, O extends z.ZodType = z.ZodT
28
28
  run: (input: z.infer<I>, ctx: Context) => unknown | Promise<unknown>;
29
29
  };
30
30
  export type Handler = (payload: any, ctx: Context) => unknown | Promise<unknown>;
31
- /**
32
- * Runs one of the plugin's methods on a schedule, called with `{}` as a tap
33
- * would: `{ method: "watch", schedule: "*\/5 * * * *" }`. `schedule` is a cron,
34
- * five fields or six with seconds first; `timezone` defaults to the environment's.
35
- */
36
- export type Cron = {
37
- method: string;
38
- schedule: string;
39
- timezone?: string;
40
- };
41
31
  export type PluginDefinition = {
42
32
  /** One line, shown to the agent in the index of plugins. */
43
33
  description?: string;
44
34
  /**
45
35
  * Where the plugin's data lives: `<namespace>/` in the data dir, holding `data.db`
46
36
  * and its buckets' and streams' files. Plugins naming the same namespace share its
47
- * data. The plugin's folder name when left out; by convention a project's own is
37
+ * data. Required once the plugin keeps data; by convention a project's own is
48
38
  * `<project>.<name>` (`repogo.plans`) and data for every project a bare name (`todos`).
49
39
  */
50
40
  namespace?: string;
@@ -57,7 +47,11 @@ export type PluginDefinition = {
57
47
  streams?: Record<string, StreamDefinition>;
58
48
  methods?: Record<string, Method<any, any>>;
59
49
  on?: Record<string, Handler>;
60
- crons?: Cron[];
50
+ /**
51
+ * Data named here gets no generated methods (list, create, delete…), so cards
52
+ * and agents reach it only through the plugin's own: an audit log, say.
53
+ */
54
+ private?: string[];
61
55
  };
62
56
  export declare function definePlugin<T extends PluginDefinition>(plugin: T): T;
63
57
  export declare function method<I extends z.ZodType, O extends z.ZodType = z.ZodType>(input: I, run: (input: z.infer<I>, ctx: Context) => unknown | Promise<unknown>, options?: {
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ import type { Bucket } from "./buckets";
3
+ import type { MethodKind } from "./define";
4
+ import type { Kv } from "./kv";
5
+ import type { Stream } from "./streams";
6
+ export type Generated = {
7
+ kind: MethodKind;
8
+ description: string;
9
+ input: z.ZodType;
10
+ output?: z.ZodType;
11
+ confirm?: string;
12
+ run: (input: any, signal: AbortSignal) => unknown;
13
+ };
14
+ export declare function kvMethods(name: string, kv: Kv<any>, schema: z.ZodType): Record<string, Generated>;
15
+ export declare function streamMethods(name: string, stream: Stream<any>, schema: z.ZodType): Record<string, Generated>;
16
+ export declare function bucketMethods(name: string, bucket: Bucket<any>, schema: z.ZodObject): Record<string, Generated>;
@@ -0,0 +1,28 @@
1
+ // src/errors.ts
2
+ import { z } from "zod";
3
+
4
+ class MethodError extends Error {
5
+ code;
6
+ fieldErrors;
7
+ constructor(code, message, fieldErrors) {
8
+ super(message);
9
+ this.code = code;
10
+ this.fieldErrors = fieldErrors;
11
+ }
12
+ }
13
+ function toCallError(error) {
14
+ if (error instanceof MethodError) {
15
+ return { code: error.code, message: error.message, ...error.fieldErrors ? { fieldErrors: error.fieldErrors } : {} };
16
+ }
17
+ if (error instanceof z.ZodError) {
18
+ const fieldErrors = {};
19
+ for (const issue of error.issues)
20
+ fieldErrors["/" + issue.path.join("/")] ??= issue.message;
21
+ const messages = Object.values(fieldErrors);
22
+ const message = messages.length === 1 ? messages[0] : "Check the highlighted fields.";
23
+ return { code: "VALIDATION", message, fieldErrors };
24
+ }
25
+ return { code: "INTERNAL", message: error instanceof Error ? error.message : String(error) };
26
+ }
27
+
28
+ export { MethodError, toCallError };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { definePlugin, method, stream } from "./define";
2
- export type { PluginDefinition, Method, MethodKind, Cron, StreamDefinition } from "./define";
3
- export type { Context, Manifest, ManifestCron, ManifestMethod, ManifestStorage } from "./context";
2
+ export type { PluginDefinition, Method, MethodKind, StreamDefinition } from "./define";
3
+ export type { AgentOptions, AgentReply, Caller, Context, CronEntry, CronStatus, Manifest, ManifestCron, ManifestMethod, ManifestStorage } from "./context";
4
+ export type { LogLevel, LogRecord } from "./logs";
4
5
  export type { BucketFile, FileStatus, PutFile } from "./buckets";
5
6
  export type { KvEntry, SetOptions } from "./kv";
6
7
  export type { Retain, StreamEntry, StreamKey } from "./streams";
package/dist/index.js CHANGED
@@ -1,4 +1,9 @@
1
1
  // @bun
2
+ import {
3
+ MethodError,
4
+ toCallError
5
+ } from "./index-83w1fk10.js";
6
+
2
7
  // src/define.ts
3
8
  function definePlugin(plugin) {
4
9
  return plugin;
@@ -16,37 +21,11 @@ function method(input, run, options = {}) {
16
21
  function stream(input, run, options = {}) {
17
22
  return { input, run, kind: "stream", description: options.description };
18
23
  }
19
- // src/errors.ts
20
- import { z } from "zod";
21
-
22
- class MethodError extends Error {
23
- code;
24
- fieldErrors;
25
- constructor(code, message, fieldErrors) {
26
- super(message);
27
- this.code = code;
28
- this.fieldErrors = fieldErrors;
29
- }
30
- }
31
- function toCallError(error) {
32
- if (error instanceof MethodError) {
33
- return { code: error.code, message: error.message, ...error.fieldErrors ? { fieldErrors: error.fieldErrors } : {} };
34
- }
35
- if (error instanceof z.ZodError) {
36
- const fieldErrors = {};
37
- for (const issue of error.issues)
38
- fieldErrors["/" + issue.path.join("/")] ??= issue.message;
39
- const messages = Object.values(fieldErrors);
40
- const message = messages.length === 1 ? messages[0] : "Check the highlighted fields.";
41
- return { code: "VALIDATION", message, fieldErrors };
42
- }
43
- return { code: "INTERNAL", message: error instanceof Error ? error.message : String(error) };
44
- }
45
24
 
46
25
  // src/index.ts
47
- import { z as z2 } from "zod";
26
+ import { z } from "zod";
48
27
  export {
49
- z2 as z,
28
+ z,
50
29
  toCallError,
51
30
  stream,
52
31
  method,
package/dist/logs.d.ts ADDED
@@ -0,0 +1,55 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import type { Caller } from "./context";
3
+ export type LogLevel = "debug" | "info" | "warn" | "error";
4
+ export type LogRecord = {
5
+ /** ms since the epoch. */
6
+ at: number;
7
+ plugin: string;
8
+ level: LogLevel;
9
+ /** `<plugin>.<method>` of the run the line was written in; empty outside a run. */
10
+ method: string;
11
+ caller: Caller | "";
12
+ /** One id per outermost call, so a call's lines read together. */
13
+ callId: string;
14
+ /** The chat whose agent made the call, for `caller: "agent"`. */
15
+ chatId: string;
16
+ /** The arguments formatted as the console prints them, at most `textLimit` characters. */
17
+ text: string;
18
+ /** The arguments as given, as JSON can carry them. */
19
+ data: unknown[];
20
+ };
21
+ /** What a line is attributed to: the plugin, and the run when there is one. */
22
+ export type LogContext = {
23
+ plugin: string;
24
+ method?: string;
25
+ caller?: Caller;
26
+ callId?: string;
27
+ chatId?: string;
28
+ };
29
+ /** The run the code writing a line is in; the runtime sets it around every method, handler and cron. */
30
+ export declare const logContext: AsyncLocalStorage<LogContext>;
31
+ /**
32
+ * Runs `work` as an outermost run under `context`. An error nothing catches
33
+ * reaches its handler with no async context, so the newest run going is the
34
+ * best guess at whose it is.
35
+ */
36
+ export declare function runWith<T>(context: LogContext, work: () => Promise<T>): Promise<T>;
37
+ /** A line longer than this is cut: a dumped payload is read in the plugin's own data, not its log. */
38
+ export declare const textLimit: number;
39
+ type Sink = (record: LogRecord) => void;
40
+ /**
41
+ * Replaces `console.log`, `info`, `debug`, `warn` and `error` with ones that
42
+ * record each call. `passThrough` keeps printing them too, for tests.
43
+ */
44
+ export declare function captureConsole(to: Sink, options?: {
45
+ passThrough?: boolean;
46
+ }): void;
47
+ /** Puts the console back and stops recording. */
48
+ export declare function releaseConsole(): void;
49
+ /**
50
+ * Records one line under `context`, else the current run, else the newest run
51
+ * going, as an error nothing caught lands in. With no capture installed the
52
+ * line prints as the console would.
53
+ */
54
+ export declare function record(level: LogLevel, args: unknown[], context?: LogContext): void;
55
+ export {};