@repogo/plugins 0.1.42 → 0.1.44

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/define.d.ts CHANGED
@@ -11,34 +11,58 @@ export type StreamDefinition = z.ZodType | {
11
11
  retain: Retain;
12
12
  };
13
13
  /**
14
- * A query only reads, so cards may run it on their own and again when its data
15
- * changes. A mutation writes, and runs only when the user acts. A stream only
16
- * reads too, and sends items as they happen for as long as someone watches.
17
- * Unmarked methods are mutations: repeating one is never assumed safe.
14
+ * What a method does. `read` only reads, so cards may run it on their own and
15
+ * again when its data changes. `write` changes data, and runs only when the
16
+ * user acts. `destructive` can't be undone, and must ask first (`approve`).
17
+ * Unmarked methods write: repeating one is never assumed safe.
18
18
  */
19
- export type MethodKind = "query" | "mutation" | "stream";
19
+ export type Effect = "read" | "write" | "destructive";
20
+ /**
21
+ * The chat's permission modes, strictest first, as RepoGo names them for every
22
+ * agent. `approveUpTo` names the least strict one a method still asks in.
23
+ */
24
+ export declare const permissionModes: readonly ["approval-required", "auto-accept-edits", "full-access"];
25
+ export type PermissionMode = (typeof permissionModes)[number];
20
26
  export type Method<I extends z.ZodType = z.ZodType, O extends z.ZodType = z.ZodType> = {
21
27
  input: I;
22
28
  /** What it returns; the agent and cards read field names from it. */
23
29
  output?: O;
24
- kind: MethodKind;
30
+ effect: Effect;
31
+ /** Set by `stream`: a read that sends items as they happen. */
32
+ stream?: true;
25
33
  description?: string;
26
34
  /**
27
- * For what can't be undone: the question a card's button asks before it
28
- * runs, such as "Delete every item?". Methods without one run on tap.
35
+ * The question asked before it runs, such as "Delete {{name}}?", filled from
36
+ * its input; required for a destructive method. Methods without one run on tap.
37
+ */
38
+ approve?: string;
39
+ /**
40
+ * The least strict chat mode in which an agent's call still asks `approve`:
41
+ * it asks in that mode and every stricter one, and runs unasked in the rest.
42
+ * Unset is "full-access", every mode. A card's tap always asks.
29
43
  */
30
- confirm?: string;
31
- /** Who may call it at the environment's own address; unset, no one. */
32
- http?: HTTPAccess;
33
- /** Set by `cached`: the query answers from what it saved, and checks again when that's old. */
44
+ approveUpTo?: PermissionMode;
45
+ /** Who may call it over HTTP, at each address; an address it doesn't name serves it to no one. */
46
+ http?: HTTPAddresses;
47
+ /** Set by `cached`: the read answers from what it saved, and checks again when that's old. */
34
48
  cache?: CacheOptions;
35
49
  run: (input: z.infer<I>, ctx: Context) => unknown | Promise<unknown>;
36
50
  };
37
51
  /**
38
- * `key`: the host checks a key the caller sends. `none`: anyone reaches the
39
- * method, which checks the caller itself, such as a provider's signature.
52
+ * `key`: the caller sends the plugin's key as a bearer token, which the host
53
+ * checks before the method runs. `none`: anyone who reaches the address may call.
40
54
  */
41
55
  export type HTTPAccess = "key" | "none";
56
+ /**
57
+ * `public`: the environment's own address, through the tunnel. `device`: this
58
+ * machine's loopback and its containers' gateway, where the network is the boundary.
59
+ */
60
+ export type HTTPAddresses = {
61
+ device?: HTTPAccess;
62
+ public?: HTTPAccess;
63
+ };
64
+ /** `true` is `{ public: "key" }`. */
65
+ export type HTTPOption = true | HTTPAddresses;
42
66
  export type Handler = (payload: any, ctx: Context) => unknown | Promise<unknown>;
43
67
  export type PluginDefinition = {
44
68
  /** One line, shown to the agent in the index of plugins. */
@@ -67,22 +91,26 @@ export type PluginDefinition = {
67
91
  private?: string[];
68
92
  };
69
93
  export declare function definePlugin<T extends PluginDefinition>(plugin: T): T;
70
- 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?: {
71
- kind?: MethodKind;
94
+ type MethodOptions<O extends z.ZodType> = {
72
95
  output?: O;
73
96
  description?: string;
74
- confirm?: string;
75
- http?: true | {
76
- auth: HTTPAccess;
77
- };
78
- }): Method<I, O>;
97
+ http?: HTTPOption;
98
+ approveUpTo?: PermissionMode;
99
+ } & ({
100
+ effect: "destructive";
101
+ approve: string;
102
+ } | {
103
+ effect?: "read" | "write";
104
+ approve?: string;
105
+ });
106
+ 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?: MethodOptions<O>): Method<I, O>;
79
107
  /**
80
- * A query that answers at once from what it saved last time, for answers that
108
+ * A read that answers at once from what it saved last time, for answers that
81
109
  * are slow to get: a CLI's list, another service's API. When the saved answer
82
110
  * is older than `fresh`, `run` goes again in the background, and cards showing
83
111
  * it redraw only if the answer changed. With nothing saved, the first call
84
112
  * waits for `run`. Each project keeps its own answers unless `shared`.
85
- * `run` is read-only, like a query's; it has `timeout` (a minute unless it says).
113
+ * `run` is read-only, like any read's; it has `timeout` (a minute unless it says).
86
114
  */
87
115
  export declare function cached<I extends z.ZodType, O extends z.ZodType = z.ZodType>(input: I, run: (input: z.infer<I>, ctx: Context) => unknown | Promise<unknown>, options: {
88
116
  fresh: Duration;
@@ -90,13 +118,15 @@ export declare function cached<I extends z.ZodType, O extends z.ZodType = z.ZodT
90
118
  timeout?: Duration;
91
119
  output?: O;
92
120
  description?: string;
121
+ http?: HTTPOption;
93
122
  }): Method<I, O>;
94
123
  /**
95
124
  * A method that sends items as they happen: a log tail, a build's output. Each
96
125
  * `yield` is one item; a card shows them as a list that grows. It runs while
97
126
  * someone watches and `ctx.signal` aborts when the last one leaves, so pass it
98
- * on (`Bun.spawn(cmd, { signal: ctx.signal })`). Like a query, it may not write.
127
+ * on (`Bun.spawn(cmd, { signal: ctx.signal })`). Like any read, it may not write.
99
128
  */
100
129
  export declare function stream<I extends z.ZodType>(input: I, run: (input: z.infer<I>, ctx: Context) => AsyncIterable<unknown>, options?: {
101
130
  description?: string;
102
131
  }): Method<I>;
132
+ export {};
@@ -1,14 +1,15 @@
1
1
  import { z } from "zod";
2
2
  import type { Bucket } from "./buckets";
3
- import type { MethodKind } from "./define";
3
+ import type { Effect } from "./define";
4
4
  import type { Kv } from "./kv";
5
5
  import type { Stream } from "./streams";
6
6
  export type Generated = {
7
- kind: MethodKind;
7
+ effect: Effect;
8
+ stream?: true;
8
9
  description: string;
9
10
  input: z.ZodType;
10
11
  output?: z.ZodType;
11
- confirm?: string;
12
+ approve?: string;
12
13
  run: (input: any, signal: AbortSignal) => unknown;
13
14
  };
14
15
  export declare function kvMethods(name: string, kv: Kv<any>, schema: z.ZodType): Record<string, Generated>;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { cached, definePlugin, method, stream } from "./define";
2
+ export { remotePlugin } from "./remote";
2
3
  export { upload, type UploadedFile } from "./uploads";
3
- export type { PluginDefinition, Method, MethodKind, HTTPAccess, StreamDefinition } from "./define";
4
+ export type { PluginDefinition, Method, Effect, PermissionMode, HTTPAccess, HTTPAddresses, HTTPOption, StreamDefinition } from "./define";
4
5
  export type { Activity, ActivityProgress, ActivityTimer, ActivityTint, AgentOptions, AgentReply, Caller, Context, CronEntry, CronStatus, Manifest, ManifestCron, ManifestMethod, ManifestStorage } from "./context";
5
6
  export type { LogLevel, LogRecord } from "./logs";
6
7
  export type { BucketFile, FileStatus, PutFile } from "./buckets";
package/dist/index.js CHANGED
@@ -13,11 +13,12 @@ var __export = (target, all) => {
13
13
  // src/index.ts
14
14
  var exports_src = {};
15
15
  __export(exports_src, {
16
- z: () => z3,
16
+ z: () => z4,
17
17
  workflow: () => workflow,
18
18
  upload: () => upload,
19
19
  toCallError: () => toCallError,
20
20
  stream: () => stream,
21
+ remotePlugin: () => remotePlugin,
21
22
  method: () => method,
22
23
  definePlugin: () => definePlugin,
23
24
  cached: () => cached,
@@ -43,45 +44,54 @@ function parseDuration(duration) {
43
44
  }
44
45
 
45
46
  // src/define.ts
47
+ var permissionModes = ["approval-required", "auto-accept-edits", "full-access"];
48
+ function httpAddresses(option) {
49
+ return option === true ? { public: "key" } : option;
50
+ }
46
51
  function definePlugin(plugin) {
47
52
  return plugin;
48
53
  }
49
54
  function method(input, run, options = {}) {
55
+ if ("kind" in options)
56
+ throw new Error('method: kind is now effect: "read", "write" or "destructive"');
57
+ if ("confirm" in options)
58
+ throw new Error('method: confirm is now approve: "Delete {{id}}?"');
59
+ if (options.approveUpTo !== undefined && !permissionModes.includes(options.approveUpTo)) {
60
+ throw new Error(`method: approveUpTo is one of ${permissionModes.join(", ")}`);
61
+ }
62
+ if (options.approveUpTo !== undefined && !options.approve)
63
+ throw new Error("method: approveUpTo needs an approve question to ask");
50
64
  return {
51
65
  input,
52
66
  run,
53
- kind: options.kind ?? "mutation",
67
+ effect: options.effect ?? "write",
54
68
  output: options.output,
55
69
  description: options.description,
56
- confirm: options.confirm,
57
- http: options.http === true ? "key" : options.http?.auth
70
+ approve: options.approve,
71
+ approveUpTo: options.approveUpTo,
72
+ http: httpAddresses(options.http)
58
73
  };
59
74
  }
60
75
  function cached(input, run, options) {
61
76
  return {
62
77
  input,
63
78
  run,
64
- kind: "query",
79
+ effect: "read",
65
80
  output: options.output,
66
81
  description: options.description,
82
+ http: httpAddresses(options.http),
67
83
  cache: { fresh: parseDuration(options.fresh), shared: options.shared ?? false, timeout: parseDuration(options.timeout ?? "1m") }
68
84
  };
69
85
  }
70
86
  function stream(input, run, options = {}) {
71
- return { input, run, kind: "stream", description: options.description };
72
- }
73
- // src/uploads.ts
74
- import { AsyncLocalStorage } from "async_hooks";
75
- import { z } from "zod";
76
- var callFiles = new AsyncLocalStorage;
77
- function upload() {
78
- return z.object({ id: z.string(), name: z.string(), type: z.string(), size: z.number(), path: z.string() }).refine((file) => (callFiles.getStore() ?? []).some((held) => held.id === file.id && held.path === file.path), {
79
- message: "Not a file uploaded for this call."
80
- });
87
+ return { input, run, effect: "read", stream: true, description: options.description };
81
88
  }
82
- // src/errors.ts
89
+ // src/remote.ts
83
90
  import { z as z2 } from "zod";
84
91
 
92
+ // src/errors.ts
93
+ import { z } from "zod";
94
+
85
95
  class MethodError extends Error {
86
96
  code;
87
97
  fieldErrors;
@@ -95,7 +105,7 @@ function toCallError(error) {
95
105
  if (error instanceof MethodError) {
96
106
  return { code: error.code, message: error.message, ...error.fieldErrors ? { fieldErrors: error.fieldErrors } : {} };
97
107
  }
98
- if (error instanceof z2.ZodError) {
108
+ if (error instanceof z.ZodError) {
99
109
  const fieldErrors = {};
100
110
  for (const issue of error.issues)
101
111
  fieldErrors["/" + issue.path.join("/")] ??= issue.message;
@@ -106,8 +116,43 @@ function toCallError(error) {
106
116
  return { code: "INTERNAL", message: error instanceof Error ? error.message : String(error) };
107
117
  }
108
118
 
109
- // src/index.ts
119
+ // src/remote.ts
120
+ var codes = { 400: "VALIDATION", 403: "FORBIDDEN", 404: "NOT_FOUND" };
121
+ async function remotePlugin(url, name) {
122
+ const base = `${url.replace(/\/$/, "")}/${name}`;
123
+ const response = await fetch(base);
124
+ if (!response.ok)
125
+ throw new Error(`remotePlugin: ${base} answered ${response.status}`);
126
+ const listing = await response.json();
127
+ const methods = {};
128
+ for (const m of listing.methods) {
129
+ methods[m.name] = method(z2.fromJSONSchema(m.input), (input) => call(`${base}/${m.name}`, input), {
130
+ effect: m.effect,
131
+ description: m.description ?? undefined,
132
+ output: m.output ? z2.fromJSONSchema(m.output) : undefined
133
+ });
134
+ }
135
+ return { description: listing.description ?? undefined, methods };
136
+ }
137
+ async function call(url, input) {
138
+ const response = await fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(input) });
139
+ const answer = await response.json().catch(() => null);
140
+ if (response.ok)
141
+ return answer;
142
+ throw new MethodError(codes[response.status] ?? "INTERNAL", answer?.error ?? `${url} answered ${response.status}`, answer?.fields ?? undefined);
143
+ }
144
+ // src/uploads.ts
145
+ import { AsyncLocalStorage } from "async_hooks";
110
146
  import { z as z3 } from "zod";
147
+ var callFiles = new AsyncLocalStorage;
148
+ function upload() {
149
+ return z3.object({ id: z3.string(), name: z3.string(), type: z3.string(), size: z3.number(), path: z3.string() }).refine((file) => (callFiles.getStore() ?? []).some((held) => held.id === file.id && held.path === file.path), {
150
+ message: "Not a file uploaded for this call."
151
+ });
152
+ }
153
+
154
+ // src/index.ts
155
+ import { z as z4 } from "zod";
111
156
 
112
157
  // src/workflow/scope.ts
113
158
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
@@ -136,15 +181,16 @@ class NonRetryable extends Error {
136
181
  }
137
182
  }
138
183
  export {
139
- z3 as z,
184
+ z4 as z,
140
185
  workflow,
141
186
  upload,
142
187
  toCallError,
143
188
  stream,
189
+ remotePlugin,
144
190
  method,
145
191
  definePlugin,
146
192
  cached,
147
193
  NonRetryable,
148
194
  MethodError
149
195
  };
150
- export { parseDuration, callFiles, MethodError, toCallError, attemptScope, workflowScope, workflowContext, exports_src };
196
+ export { parseDuration, MethodError, toCallError, callFiles, attemptScope, workflowScope, workflowContext, exports_src };
@@ -0,0 +1,7 @@
1
+ import { type PluginDefinition } from "./define";
2
+ /**
3
+ * A plugin whose methods run on another host: each method `name` opens to
4
+ * `device` at `url` (such as http://192.168.64.1:47444), with its schemas, as
5
+ * a request there. That host keeps the plugin's code, keys and data.
6
+ */
7
+ export declare function remotePlugin(url: string, name: string): Promise<PluginDefinition>;
package/dist/runtime.d.ts CHANGED
@@ -2,17 +2,19 @@ import { z } from "zod";
2
2
  import { type Schedule } from "./cron";
3
3
  import type { Caller, CronEntry, CronStatus, Manifest, Notice } from "./context";
4
4
  export type { Manifest, ManifestCron, ManifestMethod } from "./context";
5
- import type { HTTPAccess, PluginDefinition, MethodKind } from "./define";
5
+ import type { Effect, HTTPAddresses, PermissionMode, PluginDefinition } from "./define";
6
6
  import { EventBus } from "./events";
7
7
  import type { WorkflowBridge } from "./workflow/contracts";
8
8
  import { type UploadedFile } from "./uploads";
9
9
  export type RegisteredMethod = {
10
10
  name: string;
11
11
  plugin: string;
12
- kind: MethodKind;
12
+ effect: Effect;
13
+ stream?: true;
13
14
  description?: string;
14
- confirm?: string;
15
- http?: HTTPAccess;
15
+ approve?: string;
16
+ approveUpTo?: PermissionMode;
17
+ http?: HTTPAddresses;
16
18
  input: z.ZodType;
17
19
  output?: z.ZodType;
18
20
  /** `raw` is the input as sent, before defaults filled it in. */
@@ -35,9 +37,11 @@ type NoticeListener = (notice: SentNotice) => void;
35
37
  export type HostCaller = (plugin: string, method: string, params: unknown) => Promise<unknown>;
36
38
  /** A schedule as the runtime keeps it: read once, with its last run. */
37
39
  type CronJob = {
38
- /** The plugin, method, schedule and zone: an entry the host sends again keeps its job, and its state. */
40
+ /** The plugin, method, schedule, zone and root: an entry the host sends again keeps its job, and its state. */
39
41
  signature: string;
40
42
  plugin: string;
43
+ /** The project the run gets as `ctx.root`; none keeps the Worker's own. */
44
+ root?: string;
41
45
  /** `<plugin>.<method>`. */
42
46
  method: string;
43
47
  schedule: string;
@@ -71,11 +75,11 @@ export declare class Runtime {
71
75
  private constructor();
72
76
  /** The caller's project while a call runs, for plugins that serve every project. */
73
77
  private callRoot;
74
- /** A running stream's signal, `ctx.signal` to the code it runs. */
78
+ /** A running stream's or cancelable call's signal, `ctx.signal` to the code it runs. */
75
79
  private callSignal;
76
80
  /** Who started the outermost call, `ctx.caller` to the code it runs. */
77
81
  private callCaller;
78
- /** Whether the user answered a `confirm` for this call or one it runs within, which a destructive MCP tool needs. */
82
+ /** Whether the user answered an `approve` for this call or one it runs within, which a destructive MCP tool needs. */
79
83
  private callConfirmed;
80
84
  /** Read through `ctx.plugins()` and written as each cron run starts and ends; this runtime's own, so other Workers aren't stirred. */
81
85
  private cronsTag;
@@ -98,9 +102,9 @@ export declare class Runtime {
98
102
  caller: Caller;
99
103
  }): Promise<unknown>;
100
104
  /**
101
- * Calls a method and reports what it read and wrote. `expect: "query"` is how
105
+ * Calls a method and reports what it read and wrote. `expect: "read"` is how
102
106
  * a card runs a method on its own: it refuses anything that might write, and
103
- * so does everything the query calls.
107
+ * so does everything the read calls.
104
108
  */
105
109
  /**
106
110
  * `confirmed` is false for a tap the user hasn't answered; `ctx.call` leaves it out.
@@ -108,12 +112,13 @@ export declare class Runtime {
108
112
  * pass on to the code the method calls.
109
113
  */
110
114
  call(name: string, input?: unknown, options?: {
111
- expect?: MethodKind;
115
+ expect?: "read";
112
116
  confirmed?: boolean;
113
117
  root?: string;
114
118
  caller?: Caller;
115
119
  chat?: string;
116
120
  files?: UploadedFile[];
121
+ signal?: AbortSignal;
117
122
  }): Promise<CallResult>;
118
123
  /**
119
124
  * Runs a stream method, passing each item it yields to `onItem` until it
package/dist/testing.d.ts CHANGED
@@ -41,7 +41,7 @@ export type TestMCP = (call: TestMCPCall) => unknown;
41
41
  /** Stands in for the host's methods (`chats.start`, `git.status`, …) behind `ctx.host` and `ctx.agent`; throw a MethodError to refuse. */
42
42
  export type TestHost = (method: string, params: unknown) => unknown;
43
43
  /**
44
- * `const app = await testRuntime({ notes })`, then `await app.call("notes.add", { text: "hi" })`.
44
+ * `const app = await testRuntime({ notes })`, then `await app.call("notes.notes.create", { text: "hi" })`.
45
45
  * `ctx.root` is an empty temporary folder unless `root` names a fixture project;
46
46
  * `mcp` answers `ctx.mcp`, `host` the other host methods. Console lines still print.
47
47
  */