@agentorg/cli 0.0.6 → 0.0.8

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 (111) hide show
  1. package/README.md +10 -7
  2. package/dist/node_modules/@agentorg/computer/LICENSE +201 -0
  3. package/dist/node_modules/@agentorg/computer/NOTICE +2 -0
  4. package/dist/node_modules/@agentorg/computer/dist/THIRD_PARTY_LICENSES.txt +1321 -0
  5. package/dist/node_modules/@agentorg/computer/dist/sdk.js +24909 -0
  6. package/dist/node_modules/@agentorg/computer/dist/types/generated/schema.d.ts +860 -0
  7. package/dist/node_modules/@agentorg/computer/dist/types/sdk.d.ts +242 -0
  8. package/dist/node_modules/@agentorg/computer/package.json +12 -0
  9. package/dist/node_modules/@agentorg/computer-connector/dist/src/index.d.ts +1 -0
  10. package/dist/node_modules/@agentorg/computer-connector/dist/src/index.js +1 -0
  11. package/dist/node_modules/@agentorg/computer-connector/dist/src/provider.d.ts +24 -0
  12. package/dist/node_modules/@agentorg/computer-connector/dist/src/provider.js +482 -0
  13. package/dist/node_modules/@agentorg/computer-connector/dist/src/unit.d.ts +19 -0
  14. package/dist/node_modules/@agentorg/computer-connector/dist/src/unit.js +149 -0
  15. package/dist/node_modules/@agentorg/computer-connector/package.json +33 -0
  16. package/dist/node_modules/@agentorg/computer-connector/skills/browser/SKILL.md +31 -0
  17. package/dist/node_modules/@agentorg/computer-connector/skills/computer/SKILL.md +30 -0
  18. package/dist/node_modules/@agentorg/computer-connector/skills/files/SKILL.md +28 -0
  19. package/dist/node_modules/@agentorg/computer-connector/skills/terminal/SKILL.md +39 -0
  20. package/dist/node_modules/@agentorg/computer-connector/skills/web-research/SKILL.md +29 -0
  21. package/dist/node_modules/@agentorg/hub/dist/src/router.d.ts +4 -2
  22. package/dist/node_modules/@agentorg/hub/dist/src/router.js +3 -1
  23. package/dist/node_modules/@agentorg/hub/dist/src/validate.js +16 -2
  24. package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.d.ts +34 -47
  25. package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.js +25 -34
  26. package/dist/node_modules/@agentorg/interfaces/dist/src/chat.d.ts +90 -50
  27. package/dist/node_modules/@agentorg/interfaces/dist/src/chat.js +69 -34
  28. package/dist/node_modules/@agentorg/interfaces/dist/src/connection.d.ts +36 -0
  29. package/dist/node_modules/@agentorg/interfaces/dist/src/connection.js +31 -0
  30. package/dist/node_modules/@agentorg/interfaces/dist/src/folders.d.ts +19 -0
  31. package/dist/node_modules/@agentorg/interfaces/dist/src/folders.js +25 -0
  32. package/dist/node_modules/@agentorg/interfaces/dist/src/index.d.ts +6 -4
  33. package/dist/node_modules/@agentorg/interfaces/dist/src/index.js +6 -4
  34. package/dist/node_modules/@agentorg/interfaces/dist/src/llm-session.d.ts +2 -0
  35. package/dist/node_modules/@agentorg/interfaces/dist/src/llm-session.js +2 -1
  36. package/dist/node_modules/@agentorg/interfaces/dist/src/manager.d.ts +381 -27
  37. package/dist/node_modules/@agentorg/interfaces/dist/src/manager.js +179 -15
  38. package/dist/node_modules/@agentorg/interfaces/dist/src/units.d.ts +460 -0
  39. package/dist/node_modules/@agentorg/interfaces/dist/src/units.js +204 -0
  40. package/dist/node_modules/@agentorg/looper/dist/src/index.d.ts +5 -0
  41. package/dist/node_modules/@agentorg/looper/dist/src/index.js +5 -0
  42. package/dist/node_modules/@agentorg/looper/dist/src/loop.d.ts +89 -0
  43. package/dist/node_modules/@agentorg/looper/dist/src/loop.js +366 -0
  44. package/dist/node_modules/@agentorg/looper/dist/src/looper.d.ts +81 -0
  45. package/dist/node_modules/@agentorg/looper/dist/src/looper.js +574 -0
  46. package/dist/node_modules/@agentorg/looper/dist/src/main.d.ts +34 -0
  47. package/dist/node_modules/@agentorg/looper/dist/src/main.js +116 -0
  48. package/dist/node_modules/@agentorg/looper/dist/src/record.d.ts +181 -0
  49. package/dist/node_modules/@agentorg/looper/dist/src/record.js +433 -0
  50. package/dist/node_modules/@agentorg/looper/dist/src/tools.d.ts +11 -0
  51. package/dist/node_modules/@agentorg/looper/dist/src/tools.js +42 -0
  52. package/dist/node_modules/@agentorg/looper/dist/src/types.d.ts +147 -0
  53. package/dist/node_modules/@agentorg/looper/dist/src/types.js +32 -0
  54. package/dist/node_modules/@agentorg/looper/package.json +24 -0
  55. package/dist/node_modules/@agentorg/manager/dist/src/activity.d.ts +18 -0
  56. package/dist/node_modules/@agentorg/manager/dist/src/activity.js +65 -0
  57. package/dist/node_modules/@agentorg/manager/dist/src/control.d.ts +134 -0
  58. package/dist/node_modules/@agentorg/manager/dist/src/control.js +1115 -0
  59. package/dist/node_modules/@agentorg/manager/dist/src/form.d.ts +40 -0
  60. package/dist/node_modules/@agentorg/manager/dist/src/form.js +191 -0
  61. package/dist/node_modules/@agentorg/manager/dist/src/manager.d.ts +4 -0
  62. package/dist/node_modules/@agentorg/manager/dist/src/manager.js +341 -189
  63. package/dist/node_modules/@agentorg/manager/dist/src/skills.d.ts +2 -0
  64. package/dist/node_modules/@agentorg/manager/dist/src/skills.js +7 -0
  65. package/dist/node_modules/@agentorg/manager/dist/src/stack.d.ts +454 -0
  66. package/dist/node_modules/@agentorg/manager/dist/src/stack.js +917 -0
  67. package/dist/node_modules/@agentorg/mcp/dist/src/connection.d.ts +2 -1
  68. package/dist/node_modules/@agentorg/mcp/dist/src/connection.js +8 -2
  69. package/dist/node_modules/@agentorg/mcp/dist/src/index.d.ts +3 -0
  70. package/dist/node_modules/@agentorg/mcp/dist/src/index.js +3 -0
  71. package/dist/node_modules/@agentorg/mcp/dist/src/kinds.d.ts +108 -0
  72. package/dist/node_modules/@agentorg/mcp/dist/src/kinds.js +95 -0
  73. package/dist/node_modules/@agentorg/mcp/dist/src/provider.d.ts +2 -2
  74. package/dist/node_modules/@agentorg/mcp/dist/src/provider.js +48 -18
  75. package/dist/node_modules/@agentorg/mcp/dist/src/public.d.ts +6 -0
  76. package/dist/node_modules/@agentorg/mcp/dist/src/public.js +72 -0
  77. package/dist/node_modules/@agentorg/sdk/dist/src/filled.d.ts +21 -0
  78. package/dist/node_modules/@agentorg/sdk/dist/src/filled.js +24 -0
  79. package/dist/node_modules/@agentorg/sdk/dist/src/index.d.ts +1 -0
  80. package/dist/node_modules/@agentorg/sdk/dist/src/index.js +1 -0
  81. package/dist/node_modules/@agentorg/shell/dist/app.css +1306 -181
  82. package/dist/node_modules/@agentorg/shell/dist/app.js +207 -166
  83. package/dist/node_modules/@agentorg/shell/package.json +6 -1
  84. package/dist/node_modules/assistant/dist/src/chats.d.ts +94 -0
  85. package/dist/node_modules/assistant/dist/src/chats.js +355 -0
  86. package/dist/node_modules/assistant/dist/src/engine.d.ts +26 -7
  87. package/dist/node_modules/assistant/dist/src/engine.js +107 -35
  88. package/dist/node_modules/assistant/dist/src/local-store.js +1 -1
  89. package/dist/node_modules/assistant/dist/src/provider.d.ts +1 -1
  90. package/dist/node_modules/assistant/dist/src/provider.js +47 -19
  91. package/dist/node_modules/assistant/dist/src/tools.d.ts +11 -28
  92. package/dist/node_modules/assistant/dist/src/tools.js +19 -40
  93. package/dist/node_modules/assistant/package.json +1 -0
  94. package/dist/src/api.js +4 -0
  95. package/dist/src/folders.d.ts +2 -0
  96. package/dist/src/folders.js +45 -0
  97. package/dist/src/gateway.d.ts +7 -1
  98. package/dist/src/gateway.js +81 -12
  99. package/dist/src/live.d.ts +17 -0
  100. package/dist/src/live.js +109 -0
  101. package/dist/src/programs.d.ts +7 -0
  102. package/dist/src/programs.js +29 -2
  103. package/dist/src/signin.d.ts +12 -3
  104. package/dist/src/signin.js +30 -6
  105. package/package.json +2 -1
  106. package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.d.ts +0 -88
  107. package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.js +0 -78
  108. package/dist/node_modules/assistant/dist/src/agent.d.ts +0 -62
  109. package/dist/node_modules/assistant/dist/src/agent.js +0 -226
  110. package/dist/node_modules/assistant/dist/src/history.d.ts +0 -97
  111. package/dist/node_modules/assistant/dist/src/history.js +0 -235
@@ -11,7 +11,8 @@ export interface Timing {
11
11
  }
12
12
  export declare class Connection {
13
13
  #private;
14
- constructor(spec: Spec, secrets: Secrets, log: Log, timing?: Timing);
14
+ /** `hide` names tools of the server that are never offered or run through this connection. */
15
+ constructor(spec: Spec, secrets: Secrets, log: Log, timing?: Timing, hide?: readonly string[]);
15
16
  /** Join the Hub as the connection's Actor, offering its tools. */
16
17
  join(hub: {
17
18
  hubUrl: string;
@@ -3,6 +3,7 @@ import { Refused } from "@agentorg/sdk";
3
3
  import { connect } from "@agentorg/sdk/client";
4
4
  import { Client, ProtocolError, SdkError, SdkErrorCode, StreamableHTTPClientTransport, } from "@modelcontextprotocol/client";
5
5
  import { getDefaultEnvironment, StdioClientTransport } from "@modelcontextprotocol/client/stdio";
6
+ import { publicFetch } from "./public.js";
6
7
  /** How often a connection asks its server whether it is there, and how long it waits. */
7
8
  const PING_EVERY_MS = 30_000;
8
9
  const PING_TIMEOUT_MS = 10_000;
@@ -61,6 +62,8 @@ function transportOf(spec, secrets, log) {
61
62
  const { token } = secrets;
62
63
  return new StreamableHTTPClientTransport(new URL(spec.url), {
63
64
  ...(token ? { requestInit: { headers: { Authorization: `Bearer ${token}` } } } : {}),
65
+ // Checked at each request, so a name that later points home is refused then.
66
+ ...(spec.public ? { fetch: publicFetch } : {}),
64
67
  });
65
68
  }
66
69
  const transport = new StdioClientTransport({
@@ -92,12 +95,15 @@ export class Connection {
92
95
  #lose;
93
96
  #running;
94
97
  #spec;
98
+ #hide;
95
99
  #secrets;
96
100
  #log;
97
101
  #scrub;
98
102
  #pingEveryMs;
99
103
  #firstDelayMs;
100
- constructor(spec, secrets, log, timing = {}) {
104
+ /** `hide` names tools of the server that are never offered or run through this connection. */
105
+ constructor(spec, secrets, log, timing = {}, hide = []) {
106
+ this.#hide = new Set(hide);
101
107
  this.#spec = spec;
102
108
  this.#secrets = secrets;
103
109
  this.#pingEveryMs = timing.pingEveryMs ?? PING_EVERY_MS;
@@ -285,6 +291,6 @@ export class Connection {
285
291
  }
286
292
  async #refresh(mcp) {
287
293
  const { tools: listed } = await mcp.listTools();
288
- this.#tools = fit(listed, this.#log);
294
+ this.#tools = fit(listed.filter((tool) => !this.#hide.has(tool.name)), this.#log);
289
295
  }
290
296
  }
@@ -1 +1,4 @@
1
+ export { Connection, type Timing } from "./connection.js";
2
+ export { parseConnection, type Spec } from "./kinds.js";
1
3
  export { type McpProvider, type McpProviderOptions, startMcpProvider } from "./provider.js";
4
+ export { isPublicAddress, notPublic } from "./public.js";
@@ -1 +1,4 @@
1
+ export { Connection } from "./connection.js";
2
+ export { parseConnection } from "./kinds.js";
1
3
  export { startMcpProvider } from "./provider.js";
4
+ export { isPublicAddress, notPublic } from "./public.js";
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  declare const UrlParams: z.ZodObject<{
3
3
  url: z.ZodURL;
4
+ public: z.ZodOptional<z.ZodBoolean>;
4
5
  }, z.core.$strict>;
5
6
  declare const CommandParams: z.ZodObject<{
6
7
  command: z.ZodString;
@@ -18,6 +19,7 @@ export declare const KINDS: readonly [{
18
19
  readonly title: "A server at an address";
19
20
  readonly params: z.ZodObject<{
20
21
  url: z.ZodURL;
22
+ public: z.ZodOptional<z.ZodBoolean>;
21
23
  }, z.core.$strict>;
22
24
  readonly secrets: z.ZodObject<{
23
25
  token: z.ZodOptional<z.ZodString>;
@@ -32,6 +34,112 @@ export declare const KINDS: readonly [{
32
34
  }, z.core.$strict>;
33
35
  readonly secrets: z.ZodRecord<z.ZodString, z.ZodString>;
34
36
  }];
37
+ /** Each kind as a unit: it gives one connection, under the name it is filled in with. */
38
+ export declare const UNITS: ({
39
+ id: string;
40
+ title: string;
41
+ description: string;
42
+ form: ({
43
+ readonly id: "slot";
44
+ readonly type: "text";
45
+ readonly value: "string";
46
+ readonly title: "Name";
47
+ readonly description: "What the agent knows this connection as.";
48
+ readonly required: true;
49
+ } | {
50
+ id: string;
51
+ authority: "contacts";
52
+ type: string;
53
+ value: "string";
54
+ title: string;
55
+ description: string;
56
+ required: true;
57
+ } | {
58
+ authority?: undefined;
59
+ required?: undefined;
60
+ id: string;
61
+ type: string;
62
+ value: "secret";
63
+ title: string;
64
+ description?: undefined;
65
+ } | {
66
+ authority?: undefined;
67
+ required?: undefined;
68
+ id: string;
69
+ type: string;
70
+ value: "boolean";
71
+ title: string;
72
+ description: string;
73
+ })[];
74
+ needs: never[];
75
+ gives: {
76
+ slots: {
77
+ field: string;
78
+ }[];
79
+ skills: never[];
80
+ results: never[];
81
+ };
82
+ } | {
83
+ id: string;
84
+ title: string;
85
+ description: string;
86
+ form: ({
87
+ readonly id: "slot";
88
+ readonly type: "text";
89
+ readonly value: "string";
90
+ readonly title: "Name";
91
+ readonly description: "What the agent knows this connection as.";
92
+ readonly required: true;
93
+ } | {
94
+ description?: undefined;
95
+ id: string;
96
+ authority: "runs";
97
+ type: string;
98
+ value: "string";
99
+ title: string;
100
+ required: true;
101
+ } | {
102
+ required?: undefined;
103
+ description?: undefined;
104
+ id: string;
105
+ authority: "runs";
106
+ type: string;
107
+ value: "strings";
108
+ title: string;
109
+ } | {
110
+ required?: undefined;
111
+ description?: undefined;
112
+ id: string;
113
+ authority: "runs";
114
+ type: string;
115
+ value: "string";
116
+ title: string;
117
+ } | {
118
+ authority?: undefined;
119
+ required?: undefined;
120
+ id: string;
121
+ type: string;
122
+ value: "secret";
123
+ title: string;
124
+ description: string;
125
+ })[];
126
+ needs: never[];
127
+ gives: {
128
+ slots: {
129
+ field: string;
130
+ }[];
131
+ skills: never[];
132
+ results: never[];
133
+ };
134
+ })[];
135
+ /**
136
+ * What a unit's secrets mean to a connection: the lines are environment variables by name.
137
+ * Secrets that come by name already are as they are. A line is named by position, never by content.
138
+ */
139
+ export declare function namedSecrets(secrets: Secrets): {
140
+ secrets?: Secrets;
141
+ why?: string;
142
+ };
35
143
  /** What a connection's parameters and secrets say, or why they don't fit its kind. */
36
144
  export declare function parseConnection(kind: string, params: unknown, secrets: unknown): {
37
145
  spec: Spec;
@@ -9,10 +9,13 @@ const UrlParams = z.strictObject({
9
9
  .url({ protocol: /^https?$/ })
10
10
  .max(4096)
11
11
  .refine((url) => {
12
+ if (!URL.canParse(url))
13
+ return true; // Said already, by `z.url`.
12
14
  const { username, password } = new URL(url);
13
15
  return !username && !password;
14
16
  }, "An address can't hold a user or a password: give a token as a secret.")
15
17
  .meta({ title: "Address" }),
18
+ public: z.boolean().optional().meta({ title: "Public addresses only" }),
16
19
  });
17
20
  /** The bearer token sent to the server on every request. */
18
21
  const UrlSecrets = z.strictObject({ token: SecretValue.optional().meta({ title: "Token" }) });
@@ -32,6 +35,92 @@ export const KINDS = [
32
35
  secrets: CommandSecrets,
33
36
  },
34
37
  ];
38
+ /** The secret holding a command's environment as lines; named so that no variable is called it. */
39
+ const LINES = "agentorgEnvironmentLines";
40
+ const NAME = {
41
+ id: "slot",
42
+ type: "text",
43
+ value: "string",
44
+ title: "Name",
45
+ description: "What the agent knows this connection as.",
46
+ required: true,
47
+ };
48
+ const GIVES = { slots: [{ field: "slot" }], skills: [], results: [] };
49
+ /** Each kind as a unit: it gives one connection, under the name it is filled in with. */
50
+ export const UNITS = [
51
+ {
52
+ id: "url",
53
+ title: "A server at an address",
54
+ description: "Connects to an MCP server over HTTP.",
55
+ form: [
56
+ {
57
+ id: "url",
58
+ authority: "contacts",
59
+ type: "text",
60
+ value: "string",
61
+ title: "Address",
62
+ description: "It is shown and kept as written: a token goes in Token.",
63
+ required: true,
64
+ },
65
+ { id: "token", type: "secret", value: "secret", title: "Token" },
66
+ {
67
+ id: "public",
68
+ type: "toggle",
69
+ value: "boolean",
70
+ title: "Public addresses only",
71
+ description: "It reaches only https servers on the public internet, never this machine or its network, and follows no redirect.",
72
+ },
73
+ NAME,
74
+ ],
75
+ needs: [],
76
+ gives: GIVES,
77
+ },
78
+ {
79
+ id: "command",
80
+ title: "A local server this machine starts",
81
+ description: "Starts an MCP server with this app's rights.",
82
+ form: [
83
+ {
84
+ id: "command",
85
+ authority: "runs",
86
+ type: "text",
87
+ value: "string",
88
+ title: "Command",
89
+ required: true,
90
+ },
91
+ { id: "args", authority: "runs", type: "lines", value: "strings", title: "Arguments" },
92
+ { id: "cwd", authority: "runs", type: "directory", value: "string", title: "Working folder" },
93
+ {
94
+ id: LINES,
95
+ type: "environment",
96
+ value: "secret",
97
+ title: "Secrets",
98
+ description: "One NAME=value per line: each is an environment variable of the server.",
99
+ },
100
+ NAME,
101
+ ],
102
+ needs: [],
103
+ gives: GIVES,
104
+ },
105
+ ];
106
+ /**
107
+ * What a unit's secrets mean to a connection: the lines are environment variables by name.
108
+ * Secrets that come by name already are as they are. A line is named by position, never by content.
109
+ */
110
+ export function namedSecrets(secrets) {
111
+ const { [LINES]: lines, ...named } = secrets;
112
+ if (lines === undefined)
113
+ return { secrets: named };
114
+ for (const [index, line] of lines.split(/\r?\n/).entries()) {
115
+ if (line.trim() === "")
116
+ continue;
117
+ const split = line.indexOf("=");
118
+ if (split < 1)
119
+ return { why: `Line ${index + 1} of the secrets isn't NAME=value.` };
120
+ named[line.slice(0, split).trim()] = line.slice(split + 1);
121
+ }
122
+ return { secrets: named };
123
+ }
35
124
  const LOOPBACK = new Set(["127.0.0.1", "localhost", "[::1]"]);
36
125
  /** What a connection's parameters and secrets say, or why they don't fit its kind. */
37
126
  export function parseConnection(kind, params, secrets) {
@@ -46,6 +135,12 @@ export function parseConnection(kind, params, secrets) {
46
135
  if (!hidden.success)
47
136
  return { why: `${kind}: its secrets don't fit: ${found.title}.` };
48
137
  const spec = { kind, ...parsed.data };
138
+ if (spec.kind === "url" && spec.public) {
139
+ if (new URL(spec.url).protocol !== "https:")
140
+ return { why: "A public address is https." };
141
+ if ("token" in hidden.data)
142
+ return { why: "A public-only connection takes no token." };
143
+ }
49
144
  if (spec.kind === "url" && "token" in hidden.data) {
50
145
  const url = new URL(spec.url);
51
146
  if (url.protocol === "http:" && !LOOPBACK.has(url.hostname))
@@ -16,7 +16,7 @@ export interface McpProvider {
16
16
  close(): Promise<void>;
17
17
  }
18
18
  /**
19
- * Run the built-in MCP provider as an Actor exporting `mcp.provider@1` as `provider`. It offers two
20
- * kinds, `url` and `command`; a server it starts runs with this process's rights. Connections made earlier are joined again at the start.
19
+ * Run the built-in MCP provider as an Actor exporting `unit.provider@1` as `provider`. It offers two
20
+ * units, `url` and `command`; a server it starts runs with this process's rights. Connections made earlier are joined again at the start.
21
21
  */
22
22
  export declare function startMcpProvider(options: McpProviderOptions): Promise<McpProvider>;
@@ -1,11 +1,12 @@
1
1
  import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { hubActors, mcpProvider } from "@agentorg/interfaces";
3
+ import { hubActors, unitProvider } from "@agentorg/interfaces";
4
4
  import { Refused } from "@agentorg/sdk";
5
5
  import { connect } from "@agentorg/sdk/client";
6
6
  import { z } from "zod";
7
7
  import { Connection } from "./connection.js";
8
- import { KINDS, parseConnection, targetOf } from "./kinds.js";
8
+ import { namedSecrets, parseConnection, targetOf, UNITS, } from "./kinds.js";
9
+ import { notPublic } from "./public.js";
9
10
  /** What is kept of a connection between runs, in the provider's private file. */
10
11
  const Stored = z.object({
11
12
  credential: z.string(),
@@ -14,10 +15,12 @@ const Stored = z.object({
14
15
  secrets: z.record(z.string(), z.string()),
15
16
  });
16
17
  const FILE = "connections.json";
18
+ /** What a unit's values come to as a connection's parameters: its name isn't one. */
19
+ const paramsOf = ({ slot: _, ...params }) => params;
17
20
  const quiet = { info() { }, warn() { }, error() { } };
18
21
  /**
19
- * Run the built-in MCP provider as an Actor exporting `mcp.provider@1` as `provider`. It offers two
20
- * kinds, `url` and `command`; a server it starts runs with this process's rights. Connections made earlier are joined again at the start.
22
+ * Run the built-in MCP provider as an Actor exporting `unit.provider@1` as `provider`. It offers two
23
+ * units, `url` and `command`; a server it starts runs with this process's rights. Connections made earlier are joined again at the start.
21
24
  */
22
25
  export async function startMcpProvider(options) {
23
26
  const log = options.log ?? quiet;
@@ -102,22 +105,32 @@ export async function startMcpProvider(options) {
102
105
  actorId: options.actorId,
103
106
  credential: options.credential,
104
107
  slots: { actors: hubActors },
105
- exports: { provider: mcpProvider },
108
+ exports: { provider: unitProvider },
106
109
  log,
107
110
  start(ctx) {
108
111
  ctx.exports.provider.implement({
109
- kinds: () => ({
110
- kinds: KINDS.map(({ kind, title, params, secrets }) => ({
111
- kind,
112
- title,
113
- params: JSON.parse(JSON.stringify(z.toJSONSchema(params))),
114
- secrets: JSON.parse(JSON.stringify(z.toJSONSchema(secrets))),
115
- })),
116
- }),
117
- async create({ kind, params, secrets, name }) {
118
- const { spec, secrets: hidden, why } = parseConnection(kind, params, secrets);
112
+ units: () => ({ units: UNITS }),
113
+ skill: () => {
114
+ throw new Refused("unknown", "These units come with no skill.");
115
+ },
116
+ check({ unit, values, secrets }) {
117
+ // Only whether a token is there matters, and a check never holds its value.
118
+ const given = secrets.includes("token") ? { token: "given" } : {};
119
+ const { why } = parseConnection(unit, paramsOf(values), given);
120
+ return { problems: why ? [{ message: why }] : [] };
121
+ },
122
+ async apply({ unit: kind, values, secrets, name }) {
123
+ const params = paramsOf(values);
124
+ const named = namedSecrets(secrets);
125
+ if (!named.secrets)
126
+ throw new Refused("invalid", named.why ?? "Its secrets don't fit.");
127
+ const { spec, secrets: hidden, why } = parseConnection(kind, params, named.secrets);
119
128
  if (!spec)
120
129
  throw new Refused("invalid", why);
130
+ // Said now, as well as at each request.
131
+ const away = spec.kind === "url" && spec.public ? await notPublic(spec.url) : undefined;
132
+ if (away)
133
+ throw new Refused("invalid", away);
121
134
  const { actorId, credential } = await ctx.slots.actors.call.create({ name });
122
135
  let connection;
123
136
  try {
@@ -135,10 +148,27 @@ export async function startMcpProvider(options) {
135
148
  await ctx.slots.actors.call.close({ actorId }).catch(() => { });
136
149
  throw error;
137
150
  }
138
- return { connection: actorId, target: targetOf(spec) };
151
+ // The instance is the connection: one made before units had no other id.
152
+ return {
153
+ instance: actorId,
154
+ connections: [
155
+ { slot: String(values.slot), connection: actorId, target: targetOf(spec) },
156
+ ],
157
+ results: [],
158
+ };
159
+ },
160
+ async status({ instance }) {
161
+ const status = await held(instance).status();
162
+ return {
163
+ state: status.state,
164
+ ...(status.detail ? { detail: status.detail } : {}),
165
+ connections: [{ ...status, connection: instance }],
166
+ };
167
+ },
168
+ live: () => {
169
+ throw new Refused("unknown", "A connection has nothing to open live.");
139
170
  },
140
- status: ({ connection }) => held(connection).status(),
141
- async remove({ connection }) {
171
+ async remove({ instance: connection }) {
142
172
  const found = held(connection);
143
173
  await ctx.slots.actors.call.close({ actorId: connection });
144
174
  connections.delete(connection);
@@ -0,0 +1,6 @@
1
+ /** Whether an IP address is one anyone on the internet could reach. */
2
+ export declare function isPublicAddress(address: string): boolean;
3
+ /** Why an address isn't a public https one, or nothing when it is. */
4
+ export declare function notPublic(url: string): Promise<string | undefined>;
5
+ /** A fetch for a public server: it checks where each request goes, and never follows a redirect. */
6
+ export declare const publicFetch: typeof fetch;
@@ -0,0 +1,72 @@
1
+ // An address on the public internet, and a fetch that reaches nothing else: not this machine, not
2
+ // its network, and no redirect, which could lead anywhere.
3
+ import { lookup } from "node:dns/promises";
4
+ import { BlockList, isIP } from "node:net";
5
+ const NOT_PUBLIC = new BlockList();
6
+ for (const [address, prefix] of [
7
+ ["0.0.0.0", 8],
8
+ ["10.0.0.0", 8],
9
+ ["100.64.0.0", 10],
10
+ ["127.0.0.0", 8],
11
+ ["169.254.0.0", 16],
12
+ ["172.16.0.0", 12],
13
+ ["192.0.0.0", 24],
14
+ ["192.0.2.0", 24],
15
+ ["192.168.0.0", 16],
16
+ ["198.18.0.0", 15],
17
+ ["198.51.100.0", 24],
18
+ ["203.0.113.0", 24],
19
+ ["224.0.0.0", 4],
20
+ ["240.0.0.0", 4],
21
+ ])
22
+ NOT_PUBLIC.addSubnet(address, prefix, "ipv4");
23
+ for (const [address, prefix] of [
24
+ ["::", 128],
25
+ ["::1", 128],
26
+ ["64:ff9b::", 96],
27
+ ["64:ff9b:1::", 48],
28
+ ["100::", 64],
29
+ ["2001:db8::", 32],
30
+ ["fc00::", 7],
31
+ ["fe80::", 10],
32
+ ["ff00::", 8],
33
+ ])
34
+ NOT_PUBLIC.addSubnet(address, prefix, "ipv6");
35
+ /** Whether an IP address is one anyone on the internet could reach. */
36
+ export function isPublicAddress(address) {
37
+ const family = isIP(address);
38
+ if (family === 0)
39
+ return false;
40
+ // An IPv4 address written as IPv6 is the IPv4 address.
41
+ const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i.exec(address)?.[1];
42
+ if (mapped)
43
+ return isPublicAddress(mapped);
44
+ return !NOT_PUBLIC.check(address, family === 4 ? "ipv4" : "ipv6");
45
+ }
46
+ /** Why an address isn't a public https one, or nothing when it is. */
47
+ export async function notPublic(url) {
48
+ if (!URL.canParse(url))
49
+ return "That isn't an address.";
50
+ const { protocol, hostname } = new URL(url);
51
+ if (protocol !== "https:")
52
+ return "Only an https address can be used.";
53
+ const host = hostname.replace(/^\[|\]$/g, "");
54
+ let addresses;
55
+ try {
56
+ addresses = isIP(host) ? [host] : (await lookup(host, { all: true })).map((one) => one.address);
57
+ }
58
+ catch {
59
+ return `${hostname} can't be found.`;
60
+ }
61
+ return addresses.length && addresses.every(isPublicAddress)
62
+ ? undefined
63
+ : `${hostname} isn't on the public internet.`;
64
+ }
65
+ /** A fetch for a public server: it checks where each request goes, and never follows a redirect. */
66
+ export const publicFetch = async (input, init) => {
67
+ const url = input instanceof Request ? input.url : String(input);
68
+ const why = await notPublic(url);
69
+ if (why)
70
+ throw new Error(why);
71
+ return fetch(input, { ...init, redirect: "error" });
72
+ };
@@ -0,0 +1,21 @@
1
+ /** One field of a unit's form, as far as the reader needs it. */
2
+ interface FormField {
3
+ id: string;
4
+ value: "string" | "number" | "boolean" | "strings" | "secret";
5
+ }
6
+ type Value = string | number | boolean | string[];
7
+ /**
8
+ * What a unit's logic reads of what was filled in, by field id, one method per value kind. It has
9
+ * already been settled: checked against the form, defaults applied, a field not shown absent. The
10
+ * logic can't tell who filled it. Asking for a field the form doesn't have, or as the wrong kind,
11
+ * throws at once: that is a mistake in the unit, not in what was filled.
12
+ */
13
+ export declare function filled(form: readonly FormField[], values: Readonly<Record<string, Value>>, secrets?: Readonly<Record<string, string>>): {
14
+ string: (id: string) => string | undefined;
15
+ number: (id: string) => number | undefined;
16
+ boolean: (id: string) => boolean | undefined;
17
+ strings: (id: string) => string[] | undefined;
18
+ /** Read the same way, and kept apart: only `apply` has them. */
19
+ secret: (id: string) => string | undefined;
20
+ };
21
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * What a unit's logic reads of what was filled in, by field id, one method per value kind. It has
3
+ * already been settled: checked against the form, defaults applied, a field not shown absent. The
4
+ * logic can't tell who filled it. Asking for a field the form doesn't have, or as the wrong kind,
5
+ * throws at once: that is a mistake in the unit, not in what was filled.
6
+ */
7
+ export function filled(form, values, secrets = {}) {
8
+ const read = (id, kind) => {
9
+ const field = form.find((one) => one.id === id);
10
+ if (!field)
11
+ throw new TypeError(`This unit's form has no field ${id}.`);
12
+ if (field.value !== kind)
13
+ throw new TypeError(`${id} holds a ${field.value}, not a ${kind}.`);
14
+ return (kind === "secret" ? secrets[id] : values[id]);
15
+ };
16
+ return {
17
+ string: (id) => read(id, "string"),
18
+ number: (id) => read(id, "number"),
19
+ boolean: (id) => read(id, "boolean"),
20
+ strings: (id) => read(id, "strings"),
21
+ /** Read the same way, and kept apart: only `apply` has them. */
22
+ secret: (id) => read(id, "secret"),
23
+ };
24
+ }
@@ -1,4 +1,5 @@
1
1
  export { canonicalJson, hashOf, sha256 } from "./canonical.js";
2
2
  export { NotConnected, OutcomeUnknown, Refused, type RefusedCode } from "./errors.js";
3
+ export { filled } from "./filled.js";
3
4
  export { compareInterfaces, type DescribedInterface, defineInterface, describeInterface, type EventSpec, type Events, event, hashInterface, type Interface, type InterfaceSchemas, interfaceId, type JsonSchema, type MethodSpec, type Methods, method, normalizeSchema, type RpcInterface, } from "./interface.js";
4
5
  export { type CallInfo, type Context, defineActor, type EmitOptions, type Export, type ExportMap, type ExposedSlot, type Handlers, type InterfaceMap, type Log, type ModuleDefinition, type ModuleSpec, type RpcExport, type RpcSlot, type Slot, } from "./module.js";
@@ -1,4 +1,5 @@
1
1
  export { canonicalJson, hashOf, sha256 } from "./canonical.js";
2
2
  export { NotConnected, OutcomeUnknown, Refused } from "./errors.js";
3
+ export { filled } from "./filled.js";
3
4
  export { compareInterfaces, defineInterface, describeInterface, event, hashInterface, interfaceId, method, normalizeSchema, } from "./interface.js";
4
5
  export { defineActor, } from "./module.js";