@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
@@ -0,0 +1,1115 @@
1
+ /**
2
+ * AgentOrg control: what lets an agent reach what the person's own surface reaches, within the
3
+ * scopes the person set on its unit. The agent holds no authority of the Hub's. It calls
4
+ * management's tools, and management, which holds that authority, works out who is calling, what
5
+ * that caller's unit allows, and whether the target is within its reach.
6
+ */
7
+ import { CHAT_TITLE_MAX, } from "@agentorg/interfaces";
8
+ import { Refused } from "@agentorg/sdk";
9
+ import { z } from "zod";
10
+ import { schemaOf, settle } from "./form.js";
11
+ export const AGENTS_UNIT = "control";
12
+ export const AGENTS_SLOT = "control";
13
+ /** Each scope's answers, each containing the one before. */
14
+ const AGENTS = ["none", "own", "all"];
15
+ const REACH = ["none", "see", "use", "add"];
16
+ const atLeast = (answers, value, least) => answers.indexOf(value) >= answers.indexOf(least);
17
+ const COMPUTER = "computer";
18
+ /** The kind of connection an agent may add: one at an address. */
19
+ const URL_KIND = "url";
20
+ /** The place that is the parent's own computer; every other place is a connection's id. */
21
+ const OWN = "own";
22
+ /** A new computer for the child, on the service its parent's computer runs on. */
23
+ const NEW = "new";
24
+ /** The only service a child's computer can go on: elsewhere needs a token no agent may pass on. */
25
+ const LOCAL_SERVICE = "local";
26
+ const LEVELS = ["none", "read", "files", "terminal"];
27
+ /** How many agents one agent may have alive at once, at most and unless the person says. */
28
+ export const LIMIT_MAX = 8;
29
+ const LIMIT_DEFAULT = 3;
30
+ /** How many levels below an agent may exist, at most. */
31
+ export const DEPTH_MAX = 3;
32
+ /** Above this many agents in the home, none is made by an agent. They share one plan. */
33
+ export const HOME_CAP = 24;
34
+ const HOME = "home";
35
+ const OUTPUT_MAX = 200_000;
36
+ const READ_DEFAULT = 20;
37
+ const READ_MAX = 200;
38
+ const SKILL = "delegating";
39
+ const SKILL_FILE = "SKILL.md";
40
+ const SKILL_TEXT = `---
41
+ name: ${SKILL}
42
+ description: How to hand work to agents of your own and see it through, and what else your control tools reach. Use it before creating an agent, and whenever you have work that can run apart from yours.
43
+ ---
44
+ Your \`control\` connection's tools reach what the person allowed you: agents, and perhaps connections, skills and templates. A tool you don't have is one you weren't given.
45
+
46
+ ## Before you create an agent
47
+
48
+ Do the work yourself when it is short. An agent costs model usage from the plan everyone here shares, and you have a limit on how many you can have. Use one when work can run apart from yours, or needs a shape you were not given.
49
+
50
+ There is one create tool per template you may use. Read its description and fields. Give the agent a plain name and a purpose: one sentence on what it is for, which it will be told. Fields you leave out keep the template's value.
51
+
52
+ ## Working with it
53
+
54
+ - \`agent_send\` gives it a message and returns at once. It works on its own after that. Your name is on the message's first line.
55
+ - A message with no chat named starts a new chat with it, and tells you which. Name that chat to go on in it. Start another only for work that is apart from the first.
56
+ - \`agent_chats\` lists its chats and says whether it is working. A chat with a \`problem\` has a message that got no answer: send it another once the problem is dealt with. \`agent_read\` shows what was said in one. Check back when you need the result, rather than after every step.
57
+ - Say what you want back and in what form. It knows only what you send it and its purpose.
58
+
59
+ ## When it is done
60
+
61
+ \`agent_remove\` it when its work is finished, so your limit is free for the next one. \`agent_stop\` it to keep it for later. You can remove only agents you made, and theirs. The person sees every agent you create: it is listed with the person's own agents, and has a page of its own. The person also sees every change you make through these tools, and can undo any of it.
62
+ `;
63
+ export const skillFile = (path) => (path === SKILL_FILE ? SKILL_TEXT : undefined);
64
+ /** The agents unit's form, with what can be chosen today. */
65
+ export function agentsUnit(templates, places) {
66
+ return {
67
+ id: AGENTS_UNIT,
68
+ title: "AgentOrg control",
69
+ description: "It can reach what you reach in AgentOrg, as far as you allow here. Each answer says what it grants.",
70
+ form: [
71
+ {
72
+ id: "agents",
73
+ authority: "setting",
74
+ type: "choice",
75
+ value: "string",
76
+ title: "Agents",
77
+ default: "own",
78
+ options: [
79
+ { value: "none", title: "None", description: "It can't create or manage agents." },
80
+ { value: "own", title: "Its own", description: "It creates and manages its own agents." },
81
+ {
82
+ value: "all",
83
+ title: "All",
84
+ description: "It also sees, messages, stops and starts every other agent.",
85
+ },
86
+ ],
87
+ },
88
+ {
89
+ id: "templates",
90
+ authority: "setting",
91
+ type: "choices",
92
+ value: "strings",
93
+ title: "Templates",
94
+ description: "What its agents can be made from.",
95
+ required: true,
96
+ options: templates,
97
+ },
98
+ {
99
+ id: "saveTemplates",
100
+ authority: "setting",
101
+ type: "toggle",
102
+ value: "boolean",
103
+ title: "Save templates of its own",
104
+ default: false,
105
+ },
106
+ {
107
+ id: "connections",
108
+ authority: "setting",
109
+ type: "choice",
110
+ value: "string",
111
+ title: "Connections",
112
+ default: "none",
113
+ options: [
114
+ { value: "none", title: "None" },
115
+ { value: "see", title: "See", description: "It lists them." },
116
+ {
117
+ value: "use",
118
+ title: "Use",
119
+ description: "It gives those listed below to itself and its agents.",
120
+ },
121
+ { value: "add", title: "Add", description: "It adds new ones by public address." },
122
+ ],
123
+ },
124
+ {
125
+ id: "listed",
126
+ authority: "setting",
127
+ type: "connection",
128
+ value: "strings",
129
+ title: "Connections it may use",
130
+ visible: { field: "connections", in: ["use", "add"] },
131
+ },
132
+ {
133
+ id: "skills",
134
+ authority: "setting",
135
+ type: "choice",
136
+ value: "string",
137
+ title: "Skills",
138
+ default: "none",
139
+ options: [
140
+ { value: "none", title: "None" },
141
+ { value: "see", title: "See", description: "It lists and reads them." },
142
+ { value: "use", title: "Use", description: "It gives them to itself and its agents." },
143
+ { value: "add", title: "Add", description: "It writes new ones." },
144
+ ],
145
+ },
146
+ {
147
+ id: "places",
148
+ authority: "setting",
149
+ type: "choices",
150
+ value: "strings",
151
+ title: "Places",
152
+ description: "Where its agents can work, when their template has a computer.",
153
+ options: [
154
+ { value: OWN, title: "Its own computer" },
155
+ { value: NEW, title: "A new computer on the same service" },
156
+ ...places,
157
+ ],
158
+ },
159
+ {
160
+ id: "access",
161
+ authority: "setting",
162
+ type: "choice",
163
+ value: "string",
164
+ title: "Most a computer can do",
165
+ description: "Never more than its own computer can.",
166
+ default: "none",
167
+ options: LEVELS.map((value) => ({ value, title: value })),
168
+ },
169
+ {
170
+ id: "browser",
171
+ authority: "setting",
172
+ type: "toggle",
173
+ value: "boolean",
174
+ title: "A computer can have a browser",
175
+ description: "Only if its own computer has one.",
176
+ default: false,
177
+ },
178
+ {
179
+ id: "limit",
180
+ authority: "setting",
181
+ type: "number",
182
+ value: "number",
183
+ title: "How many at once",
184
+ default: LIMIT_DEFAULT,
185
+ min: 1,
186
+ max: LIMIT_MAX,
187
+ },
188
+ {
189
+ id: "depth",
190
+ type: "number",
191
+ value: "number",
192
+ title: "Levels below it",
193
+ description: "1 means its agents can't create agents.",
194
+ default: 1,
195
+ min: 1,
196
+ max: DEPTH_MAX,
197
+ },
198
+ ],
199
+ needs: [],
200
+ gives: {
201
+ slots: [{ name: AGENTS_SLOT }],
202
+ skills: [{ name: SKILL, files: [SKILL_FILE] }],
203
+ results: [],
204
+ },
205
+ };
206
+ }
207
+ const rank = (level) => Math.max(LEVELS.indexOf(String(level)), 0);
208
+ const NOWHERE = { access: "none", browser: false, connections: [], skills: [] };
209
+ /** How far an agent's own computer reaches now. */
210
+ export function reachOf(record) {
211
+ const unit = record.units.find((one) => one.unit === COMPUTER);
212
+ if (!unit)
213
+ return record.computer ?? NOWHERE;
214
+ return {
215
+ access: String(unit.values.access ?? "none"),
216
+ browser: unit.values.browser === true,
217
+ connections: unit.connections,
218
+ skills: unit.skills,
219
+ ...(typeof unit.values.service === "string" ? { service: unit.values.service } : {}),
220
+ };
221
+ }
222
+ /** What an agents unit may not ask for, given the computer of the agent that holds it. */
223
+ export function reachProblems(values, reach) {
224
+ const problems = [];
225
+ if (rank(values.access) > rank(reach.access))
226
+ problems.push({ field: "access", message: "Its agents can't do more than its own computer." });
227
+ if (values.browser === true && !reach.browser)
228
+ problems.push({ field: "browser", message: "It has no browser of its own to allow." });
229
+ return problems;
230
+ }
231
+ const boundOf = (values) => ({
232
+ agents: String(values.agents ?? "own"),
233
+ saveTemplates: values.saveTemplates === true,
234
+ connections: String(values.connections ?? "none"),
235
+ listed: (values.listed ?? []),
236
+ skills: String(values.skills ?? "none"),
237
+ templates: (values.templates ?? []),
238
+ places: (values.places ?? []),
239
+ access: String(values.access ?? "none"),
240
+ browser: values.browser === true,
241
+ limit: Number(values.limit ?? LIMIT_DEFAULT),
242
+ depth: Number(values.depth ?? 1),
243
+ });
244
+ const isAgentsUnit = (self, offer) => offer.provider === self && offer.unit.id === AGENTS_UNIT;
245
+ /**
246
+ * How a parent may make a child from a template, or nothing if it can't. The units are the
247
+ * template's: one that can't be applied as it stands rules out the template, not just the unit. A
248
+ * computer isn't applied: the child goes to one of the places allowed.
249
+ */
250
+ function shapeOf(self, template, offers, bound, reach) {
251
+ const units = [];
252
+ const positions = new Map();
253
+ let computer;
254
+ const seen = new Map();
255
+ for (const [position, unit] of template.units.entries()) {
256
+ const offer = offers.find((one) => one.providerName === unit.provider && one.unit.id === unit.unit);
257
+ if (!offer)
258
+ return undefined;
259
+ if (unit.unit === COMPUTER) {
260
+ computer = { offer, values: unit.values ?? {} };
261
+ continue;
262
+ }
263
+ if (isAgentsUnit(self, offer) && bound.depth <= 1)
264
+ return undefined;
265
+ // What an agent can't set must already be there. That includes any secret it would need.
266
+ const { form } = offer.unit;
267
+ const settled = settle(form, unit.values ?? {}, []);
268
+ if (settled.problems.some((one) => !form.find((f) => f.id === one.field && settable(f))))
269
+ return undefined;
270
+ for (const needed of Object.values(unit.needs ?? {}))
271
+ if (!positions.has(needed))
272
+ return undefined;
273
+ positions.set(position, units.length);
274
+ const count = (seen.get(unit.unit) ?? 0) + 1;
275
+ seen.set(unit.unit, count);
276
+ units.push({
277
+ key: count === 1 ? unit.unit : `${unit.unit}_${count}`,
278
+ position,
279
+ offer,
280
+ values: unit.values ?? {},
281
+ ...(unit.needs
282
+ ? {
283
+ needs: Object.fromEntries(Object.entries(unit.needs).map(([kind, from]) => [kind, positions.get(from)])),
284
+ }
285
+ : {}),
286
+ });
287
+ }
288
+ if (!computer)
289
+ return { units };
290
+ // Sharing its own computer gives the child exactly its reach, so the bound must allow that.
291
+ const own = reach.connections.length > 0 &&
292
+ rank(reach.access) <= rank(bound.access) &&
293
+ (!reach.browser || bound.browser);
294
+ // A new one is made within the bounds, on the service its parent is on.
295
+ const fresh = reach.service === LOCAL_SERVICE;
296
+ const places = bound.places.filter((place) => (place !== OWN || own) && (place !== NEW || fresh));
297
+ return places.length ? { units, places, computer } : undefined;
298
+ }
299
+ const settable = (field) => field.authority === "setting";
300
+ /** A field with what may be chosen cut to a limit, by its type; gone if nothing is left. */
301
+ function cutToLimit(field, limit) {
302
+ if (limit === undefined)
303
+ return [field];
304
+ const within = (option) => {
305
+ if (field.type === "choice")
306
+ return indexIn(field, option.value) <= indexIn(field, limit);
307
+ return limit.includes(option.value);
308
+ };
309
+ switch (field.type) {
310
+ case "toggle":
311
+ return limit === true ? [field] : [];
312
+ case "number":
313
+ return [{ ...field, max: Math.min(field.max ?? Number(limit), Number(limit)) }];
314
+ case "choice":
315
+ case "choices":
316
+ case "connection":
317
+ return [{ ...field, ...(field.options ? { options: field.options.filter(within) } : {}) }];
318
+ default:
319
+ return [field];
320
+ }
321
+ }
322
+ const indexIn = (field, value) => Math.max(field.options?.findIndex((option) => option.value === value) ?? 0, 0);
323
+ /** Why a value given for a field is more than a limit allows, if it is. */
324
+ function overLimit(field, given, limit) {
325
+ if (given === undefined || limit === undefined)
326
+ return undefined;
327
+ switch (field.type) {
328
+ case "toggle":
329
+ return given === true && limit !== true ? `This agent may not allow ${field.id}.` : undefined;
330
+ case "number":
331
+ return Number(given) > Number(limit) ? `This agent may allow at most ${limit}.` : undefined;
332
+ case "choice":
333
+ return indexIn(field, given) > indexIn(field, limit)
334
+ ? "That reaches further than this agent may allow."
335
+ : undefined;
336
+ case "choices":
337
+ case "connection":
338
+ return [given].flat().some((one) => !limit.includes(String(one)))
339
+ ? `That is more ${field.id} than this agent has.`
340
+ : undefined;
341
+ default:
342
+ return undefined;
343
+ }
344
+ }
345
+ /** A value brought within a limit, by the field's type. */
346
+ function withinLimit(field, value, limit) {
347
+ if (limit === undefined)
348
+ return value;
349
+ switch (field.type) {
350
+ case "toggle":
351
+ return value === true && limit === true;
352
+ case "number":
353
+ return Math.min(Number(value ?? limit), Number(limit));
354
+ case "choice":
355
+ return field.options?.[Math.min(indexIn(field, value), indexIn(field, limit))]
356
+ ?.value;
357
+ case "choices":
358
+ case "connection":
359
+ return [value ?? []]
360
+ .flat()
361
+ .map(String)
362
+ .filter((one) => limit.includes(one));
363
+ default:
364
+ return value;
365
+ }
366
+ }
367
+ /** The form an agent fills for one unit: what it may set, with the template's values as defaults. */
368
+ function formFor(self, unit, bound, opens = settable) {
369
+ const fields = unit.offer.unit.form.filter(opens);
370
+ const narrowed = isAgentsUnit(self, unit.offer)
371
+ ? fields.flatMap((field) => cutToLimit(field, limitOf(bound, field)))
372
+ : fields;
373
+ return narrowed.map((field) => unit.values[field.id] === undefined ? field : { ...field, default: unit.values[field.id] });
374
+ }
375
+ const limitOf = (bound, field) => Object.hasOwn(bound, field.id)
376
+ ? bound[field.id]
377
+ : undefined;
378
+ /** A new computer is held by how far it reaches, and by nothing else of the unit's bounds. */
379
+ const reachLimits = ({ access, browser }) => ({ access, browser });
380
+ /** The bounds on a new computer: those of the unit, and of its holder's computer as it is now. */
381
+ const withinReach = (bound, reach) => ({
382
+ ...bound,
383
+ access: LEVELS[Math.min(rank(bound.access), rank(reach.access))],
384
+ browser: bound.browser && reach.browser,
385
+ });
386
+ /** What an agent fills for a new computer: what it may set, within the bounds. */
387
+ function newComputerForm(computer, bound) {
388
+ return computer.offer.unit.form
389
+ .filter(settable)
390
+ .flatMap((field) => cutToLimit(field, limitOf(reachLimits(bound), field)));
391
+ }
392
+ /** A new computer's values: the template's, within the bounds, on the parent's service. */
393
+ function newComputer(computer, given, bound, service) {
394
+ const asked = settle(newComputerForm(computer, bound), given, []).problems;
395
+ const values = { ...computer.values, service };
396
+ for (const field of computer.offer.unit.form.filter(settable))
397
+ values[field.id] =
398
+ given[field.id] ??
399
+ withinLimit(field, computer.values[field.id], limitOf(reachLimits(bound), field));
400
+ return { problems: asked.filter((one) => given[one.field ?? ""] !== undefined), values };
401
+ }
402
+ /**
403
+ * A child's agents unit holds no more than its parent's, by each field's type. What was given
404
+ * beyond `bound` is a problem; a template's value is brought within `caps`.
405
+ */
406
+ function narrowedForChild(form, given, merged, bound, caps) {
407
+ const problems = [];
408
+ const values = { ...merged };
409
+ for (const field of form.filter(settable)) {
410
+ const message = overLimit(field, given[field.id], limitOf(bound, field));
411
+ if (message)
412
+ problems.push({ field: field.id, message });
413
+ values[field.id] = withinLimit(field, merged[field.id], limitOf(caps, field));
414
+ }
415
+ return { problems, values };
416
+ }
417
+ const Id = z.string().min(1).max(128);
418
+ const Target = z.object({ agent: Id.describe("An agent's id, from agent_list.") });
419
+ const CreateArgs = z.object({
420
+ name: z.string().min(1).max(128),
421
+ purpose: z.string().min(1).max(2000),
422
+ place: z.string().max(128).optional(),
423
+ units: z.record(z.string(), z.record(z.string(), z.unknown())).optional(),
424
+ });
425
+ /** One of its own agents, or itself when none is named. */
426
+ const OnWhom = z.object({ agent: Id.optional().describe("One of your agents; you, if none.") });
427
+ /** Text that fits a tool's output, saying so when it was cut. */
428
+ const fit = (value) => {
429
+ const text = JSON.stringify(value);
430
+ const notice = "\n[cut to fit]";
431
+ return text.length > OUTPUT_MAX ? `${text.slice(0, OUTPUT_MAX - notice.length)}${notice}` : text;
432
+ };
433
+ export function createControl(parts) {
434
+ const { self, records } = parts;
435
+ /** Its computer's reach, if it still holds that computer: all of it, or it has none. */
436
+ const reachNow = async (agent, record) => {
437
+ const reach = reachOf(record);
438
+ if (!reach.connections.length)
439
+ return reach;
440
+ const bound = await parts.boundTo(agent);
441
+ return reach.connections.every((one) => bound.has(`${one.slot} ${one.connection}`))
442
+ ? reach
443
+ : NOWHERE;
444
+ };
445
+ /**
446
+ * Why a template an agent saved has a value it may not set itself, if one does: the agent's own
447
+ * template is held to what it could have given, at saving and again at creating.
448
+ */
449
+ const notItsOwn = (caller, bound, template, offers) => {
450
+ if (Object.keys(template.base.answers ?? {}).length)
451
+ return "An agent's template answers nothing of its base.";
452
+ for (const unit of template.units) {
453
+ const offer = offers.find((one) => one.providerName === unit.provider && one.unit.id === unit.unit);
454
+ if (!offer)
455
+ return `${unit.provider} offers no unit ${unit.unit}.`;
456
+ for (const [id, value] of Object.entries(unit.values ?? {})) {
457
+ const field = offer.unit.form.find((one) => one.id === id);
458
+ if (field && !mayFill(caller, bound, field, value))
459
+ return `An agent can't set ${field.title}.`;
460
+ }
461
+ }
462
+ return undefined;
463
+ };
464
+ /** A template it may create from: one listed for it, or one it saved. */
465
+ const allowed = (caller, bound, id) => bound.templates.includes(id) || parts.templateAuthor(id) === caller;
466
+ const holdingOf = (caller) => {
467
+ const record = records.agents[caller];
468
+ const unit = record?.units.find((one) => one.provider === self && one.unit === AGENTS_UNIT);
469
+ return record && unit ? { record, bound: boundOf(unit.values) } : undefined;
470
+ };
471
+ const childrenOf = (caller) => Object.entries(records.agents).filter(([, record]) => record.parent?.agent === caller);
472
+ const nobody = (agent) => new Refused("unknown", `You have no agent ${agent}.`);
473
+ /** One of the agents it made: the same refusal whether it doesn't exist or isn't its own. */
474
+ const ownChild = (caller, agent) => {
475
+ if (records.agents[agent]?.parent?.agent !== caller)
476
+ throw nobody(agent);
477
+ return agent;
478
+ };
479
+ /** An agent it may see and talk to: its own, or with `all`, any other. */
480
+ const reachable = (caller, bound, agent) => {
481
+ if (agent === caller || !records.agents[agent])
482
+ throw nobody(agent);
483
+ return bound.agents === "all" ? agent : ownChild(caller, agent);
484
+ };
485
+ /** One it made, or one they made, at any depth: what removing can reach. */
486
+ const descendant = (caller, agent) => {
487
+ const seen = new Set();
488
+ for (let at = records.agents[agent]?.parent?.agent; at && !seen.has(at);) {
489
+ if (at === caller)
490
+ return agent;
491
+ seen.add(at);
492
+ at = records.agents[at]?.parent?.agent;
493
+ }
494
+ throw nobody(agent);
495
+ };
496
+ /** Itself, or one of its own: where it may give what it holds. */
497
+ const selfOrOwn = (caller, agent) => agent === undefined || agent === caller ? caller : ownChild(caller, agent);
498
+ /** Whether it may fill a field: one that is how something works, or what it already holds. */
499
+ const mayFill = (caller, bound, field, value) => {
500
+ if (settable(field))
501
+ return true;
502
+ if (field.authority !== "holds")
503
+ return false;
504
+ // What it holds is a connection, by the field's type, or else a skill there is.
505
+ if (field.type !== "connection")
506
+ return (atLeast(REACH, bound.skills, "use") &&
507
+ [value ?? []].flat().every((one) => parts.hasSkill(String(one), caller)));
508
+ return (atLeast(REACH, bound.connections, "use") &&
509
+ [value ?? []]
510
+ .flat()
511
+ .every((one) => bound.listed.includes(String(one)) || parts.madeBy(String(one)) === caller));
512
+ };
513
+ const createTool = (template, shape, bound, reach, opens) => ({
514
+ name: `create_${template.id}`,
515
+ description: `Create an agent from the template "${template.title}". ${template.description}`.slice(0, 16_384),
516
+ inputSchema: JSON.parse(JSON.stringify({
517
+ type: "object",
518
+ properties: {
519
+ name: { type: "string", maxLength: 128 },
520
+ purpose: { type: "string", maxLength: 2000, description: "What it's for; it is told." },
521
+ ...(shape.places
522
+ ? { place: { type: "string", enum: shape.places, description: "Where it works." } }
523
+ : {}),
524
+ units: {
525
+ type: "object",
526
+ description: "Only what should differ from the template.",
527
+ properties: Object.fromEntries(shape.units
528
+ .filter((unit) => unit.offer.unit.form.some(opens))
529
+ .map((unit) => [unit.key, schemaOf(formFor(self, unit, bound, opens))])
530
+ .concat(shape.computer && shape.places?.includes(NEW)
531
+ ? [
532
+ [
533
+ COMPUTER,
534
+ schemaOf(newComputerForm(shape.computer, withinReach(bound, reach))),
535
+ ],
536
+ ]
537
+ : [])),
538
+ },
539
+ },
540
+ required: ["name", "purpose", ...(shape.places ? ["place"] : [])],
541
+ })),
542
+ });
543
+ const createTools = async (caller, record, bound) => {
544
+ if (!atLeast(AGENTS, bound.agents, "own"))
545
+ return [];
546
+ const offers = await parts.catalog();
547
+ const reach = await reachNow(caller, record);
548
+ const opens = (field) => mayFill(caller, bound, field, undefined);
549
+ return parts.templates().flatMap((template) => {
550
+ if (!allowed(caller, bound, template.id))
551
+ return [];
552
+ const shape = shapeOf(self, template, offers, bound, reach);
553
+ return shape ? [createTool(template, shape, bound, reach, opens)] : [];
554
+ });
555
+ };
556
+ const create = (caller, id, input) =>
557
+ // One at a time across the home, or two at once could each find one place left.
558
+ parts.serial(HOME, async () => {
559
+ const args = CreateArgs.parse(input);
560
+ const holding = holdingOf(caller);
561
+ const template = parts.templates().find((one) => one.id === id);
562
+ if (!holding || !template || !allowed(caller, holding.bound, id))
563
+ throw new Refused("unknown", `You can't create from ${id}.`);
564
+ const { record, bound } = holding;
565
+ if (!atLeast(AGENTS, bound.agents, "own"))
566
+ throw new Refused("unknown", `You can't create from ${id}.`);
567
+ if (childrenOf(caller).length >= bound.limit)
568
+ throw new Refused("limit", `You already have the ${bound.limit} agents you can have.`);
569
+ if (Object.keys(records.agents).length + parts.creatingCount() >= HOME_CAP)
570
+ throw new Refused("limit", "This home has as many agents as agents can make.");
571
+ const reach = await reachNow(caller, record);
572
+ const offers = await parts.catalog();
573
+ const shape = shapeOf(self, template, offers, bound, reach);
574
+ if (!shape)
575
+ throw new Refused("unknown", `You can't create from ${id}.`);
576
+ const theirs = parts.templateAuthor(id) === caller
577
+ ? notItsOwn(caller, bound, template, offers)
578
+ : undefined;
579
+ if (theirs)
580
+ throw new Refused("invalid", theirs);
581
+ // Where it works: its computer is shared, or is a connection the person listed.
582
+ if (shape.places && !args.place)
583
+ throw new Refused("invalid", `Choose a place: ${shape.places.join(", ")}.`);
584
+ if (args.place && !shape.places?.includes(args.place))
585
+ throw new Refused("invalid", "It can't work there.");
586
+ const problems = [];
587
+ const shared = args.place === OWN ? reach : undefined;
588
+ const fresh = args.place === NEW && shape.computer
589
+ ? newComputer(shape.computer, (args.units?.[COMPUTER] ?? {}), withinReach(bound, reach), reach.service)
590
+ : undefined;
591
+ if (fresh)
592
+ problems.push(...fresh.problems);
593
+ const childReach = shared ??
594
+ (fresh
595
+ ? {
596
+ access: String(fresh.values.access),
597
+ browser: fresh.values.browser === true,
598
+ connections: [],
599
+ skills: [],
600
+ service: reach.service,
601
+ }
602
+ : NOWHERE);
603
+ const shares = shared
604
+ ? shared.connections
605
+ : args.place && !fresh
606
+ ? [{ slot: COMPUTER, connection: args.place }]
607
+ : [];
608
+ const opens = (field) => mayFill(caller, bound, field, undefined);
609
+ const known = new Set(shape.units.filter((unit) => unit.offer.unit.form.some(opens)).map((unit) => unit.key));
610
+ if (fresh)
611
+ known.add(COMPUTER);
612
+ for (const key of Object.keys(args.units ?? {}))
613
+ if (!known.has(key))
614
+ throw new Refused("invalid", `This template has no unit ${key}.`);
615
+ const units = shape.units.map((unit) => {
616
+ const given = (args.units?.[unit.key] ?? {});
617
+ const { form } = unit.offer.unit;
618
+ // A secret is refused for what it is when the stack is checked.
619
+ for (const id of Object.keys(given)) {
620
+ const field = form.find((one) => one.id === id);
621
+ if (field && !mayFill(caller, bound, field, given[id]) && field.value !== "secret")
622
+ problems.push({ field: id, message: `An agent can't set ${field.title}.` });
623
+ }
624
+ let values = { ...unit.values, ...given };
625
+ if (isAgentsUnit(self, unit.offer)) {
626
+ // Held to the form first: what follows compares numbers and lists.
627
+ const typed = settle(form, values, []);
628
+ problems.push(...typed.problems);
629
+ if (typed.problems.length === 0) {
630
+ const caps = withinReach(bound, childReach);
631
+ const narrowed = narrowedForChild(form, given, typed.values, bound, caps);
632
+ problems.push(...narrowed.problems);
633
+ values = { ...narrowed.values, depth: bound.depth - 1 };
634
+ }
635
+ }
636
+ return {
637
+ provider: unit.offer.provider,
638
+ unit: unit.offer.unit.id,
639
+ values,
640
+ ...(unit.needs ? { needs: unit.needs } : {}),
641
+ };
642
+ });
643
+ if (problems.length)
644
+ throw new Refused("invalid", problems
645
+ .map((one) => one.message)
646
+ .join(" ")
647
+ .slice(0, 2000));
648
+ const { base } = template;
649
+ return parts.create({
650
+ stack: {
651
+ base: {
652
+ provider: await parts.baseProviderNamed(base.provider),
653
+ creatable: base.creatable,
654
+ ...(base.answers ? { answers: base.answers } : {}),
655
+ },
656
+ units: [
657
+ ...units,
658
+ ...(fresh && shape.computer
659
+ ? [
660
+ {
661
+ provider: shape.computer.offer.provider,
662
+ unit: COMPUTER,
663
+ values: fresh.values,
664
+ },
665
+ ]
666
+ : []),
667
+ ],
668
+ template: template.id,
669
+ },
670
+ name: args.name,
671
+ purpose: args.purpose,
672
+ }, caller, {
673
+ parent: { agent: caller, name: await parts.nameOf(caller) },
674
+ reach: childReach,
675
+ shares,
676
+ skills: shared?.skills ?? [],
677
+ ...(shared ? { computer: shared } : {}),
678
+ });
679
+ });
680
+ const callerName = (caller) => parts.nameOf(caller).catch(() => caller);
681
+ const verb = (one) => one;
682
+ const own = (bound) => atLeast(AGENTS, bound.agents, "own");
683
+ const sees = (scope, least) => (bound) => atLeast(REACH, bound[scope], least);
684
+ /** Something to give: a connection or a skill, to itself or one of its own, as the person would. */
685
+ const giving = {
686
+ connection: z.object({
687
+ agent: OnWhom.shape.agent,
688
+ connection: Id,
689
+ as: z.string().min(1).max(129).optional().describe("The name it is known by."),
690
+ }),
691
+ skill: z.object({ agent: OnWhom.shape.agent, skill: z.string().min(1).max(64) }),
692
+ };
693
+ const VERBS = [
694
+ verb({
695
+ name: "agent_list",
696
+ description: "The agents you can reach, each with what it's for and how it stands.",
697
+ input: z.object({}),
698
+ needs: own,
699
+ changes: false,
700
+ async run(caller, bound) {
701
+ const { agents } = await parts.person("agents", "list", {});
702
+ return {
703
+ agents: agents.filter(({ agent }) => agent !== caller &&
704
+ (bound.agents === "all" || records.agents[agent]?.parent?.agent === caller)),
705
+ };
706
+ },
707
+ }),
708
+ verb({
709
+ name: "agent_get",
710
+ description: "What an agent is made of and how it stands.",
711
+ input: Target,
712
+ needs: own,
713
+ changes: false,
714
+ async run(caller, bound, { agent }) {
715
+ reachable(caller, bound, agent);
716
+ const read = await parts.person("agents", "get", { agent });
717
+ // Its connections are what `connections` may see, and no more.
718
+ const mcp = atLeast(REACH, bound.connections, "see")
719
+ ? read.mcp
720
+ : read.mcp.map(({ slot, status, instance }) => ({ slot, status, ...(instance ? { instance } : {}) }));
721
+ return { ...read, mcp };
722
+ },
723
+ }),
724
+ verb({
725
+ name: "agent_send",
726
+ description: "Send an agent a message, with your name on it, in one of its chats or in a new one. It says which chat, and returns at once.",
727
+ input: Target.extend({
728
+ text: z.string().min(1).max(90_000).regex(/\S/),
729
+ chat: Id.optional().describe("The chat to go on in; a new chat, if none."),
730
+ }),
731
+ needs: own,
732
+ changes: true,
733
+ async run(caller, bound, { agent, text, chat }) {
734
+ reachable(caller, bound, agent);
735
+ // It says who it's from: otherwise it reads as if the person had typed it. The name is
736
+ // on one line, quoted, so that no name can look like someone else.
737
+ const name = (await callerName(caller)).replace(/\s+/g, " ").trim().slice(0, 128);
738
+ const from = `An agent, "${name}", says:\n\n`;
739
+ const one = await parts.chatOf(agent);
740
+ const sent = await one.send({ ...(chat ? { chat } : {}), text: `${from}${text}` });
741
+ // A chat is titled by its first line, which here is whose message it is: the chat it
742
+ // starts is called by what was asked. The message is sent either way.
743
+ if (!chat)
744
+ await one
745
+ .rename({ chat: sent.chat, title: text.trim().slice(0, CHAT_TITLE_MAX) })
746
+ .catch(() => { });
747
+ return sent;
748
+ },
749
+ }),
750
+ verb({
751
+ name: "agent_chats",
752
+ description: "An agent's chats, the one last written in first, a page at a time: each with when it last replied and, if a message in it got no answer, the problem; and whether the agent is working now.",
753
+ input: Target.extend({
754
+ archived: z.boolean().optional().describe("The archived ones instead."),
755
+ before: Id.optional().describe("The last chat of the page before, to list what follows."),
756
+ }),
757
+ needs: own,
758
+ changes: false,
759
+ async run(caller, bound, { agent, archived, before }) {
760
+ reachable(caller, bound, agent);
761
+ const one = await parts.chatOf(agent);
762
+ const { chats, more } = await one.list({
763
+ ...(archived ? { archived } : {}),
764
+ ...(before ? { before } : {}),
765
+ });
766
+ return { working: (await one.state({})).state === "working", chats, more };
767
+ },
768
+ }),
769
+ verb({
770
+ name: "agent_read",
771
+ description: "What was said in one of an agent's chats, oldest first.",
772
+ input: Target.extend({
773
+ chat: Id.describe("The chat, from agent_chats or agent_send."),
774
+ limit: z.number().int().min(1).max(READ_MAX).optional(),
775
+ before: z.string().min(1).max(128).optional().describe("Read messages before this one."),
776
+ }),
777
+ needs: own,
778
+ changes: false,
779
+ async run(caller, bound, { agent, chat, limit, before }) {
780
+ reachable(caller, bound, agent);
781
+ const { messages, more } = await (await parts.chatOf(agent)).history({
782
+ chat,
783
+ limit: limit ?? READ_DEFAULT,
784
+ ...(before ? { before } : {}),
785
+ });
786
+ return {
787
+ messages: messages.map(({ id, role, text, at }) => ({ id, role, text, at })),
788
+ more,
789
+ };
790
+ },
791
+ }),
792
+ verb({
793
+ name: "agent_stop",
794
+ description: "Stop an agent, keeping it.",
795
+ input: Target,
796
+ needs: own,
797
+ changes: true,
798
+ run: async (caller, bound, { agent }) => parts.stop(reachable(caller, bound, agent)),
799
+ }),
800
+ verb({
801
+ name: "agent_start",
802
+ description: "Start an agent again.",
803
+ input: Target,
804
+ needs: own,
805
+ changes: true,
806
+ run: async (caller, bound, { agent }) => parts.start(reachable(caller, bound, agent)),
807
+ }),
808
+ verb({
809
+ name: "agent_remove",
810
+ description: "Remove an agent you made, or one they made, for good. Agents it made stay, as the person's.",
811
+ input: Target,
812
+ needs: own,
813
+ changes: true,
814
+ run: async (caller, _, { agent }) => parts.remove(descendant(caller, agent)),
815
+ }),
816
+ verb({
817
+ name: "agent_add_unit",
818
+ description: "Add a unit to one of your agents, from unit_list. Only fields you may set can be given.",
819
+ input: Target.extend({
820
+ provider: Id,
821
+ unit: z.string().min(1).max(64),
822
+ values: z.record(z.string(), z.unknown()).optional(),
823
+ }),
824
+ needs: own,
825
+ changes: true,
826
+ async run(caller, bound, { agent, provider, unit, values = {} }) {
827
+ ownChild(caller, agent);
828
+ const offer = (await parts.catalog()).find((one) => one.provider === provider && one.unit.id === unit);
829
+ if (!offer)
830
+ throw new Refused("unknown", `${provider} offers no unit ${unit}.`);
831
+ // Each comes only at creation, where its bounds are applied.
832
+ if (offer.unit.id === COMPUTER || isAgentsUnit(self, offer))
833
+ throw new Refused("invalid", `${offer.unit.title} comes only when an agent is made.`);
834
+ for (const [id, value] of Object.entries(values)) {
835
+ const field = offer.unit.form.find((one) => one.id === id);
836
+ if (field && !mayFill(caller, bound, field, value))
837
+ throw new Refused("invalid", `An agent can't set ${field.title}.`);
838
+ }
839
+ return parts.person("agents", "addUnit", { agent, unit: { provider, unit, values } }, caller);
840
+ },
841
+ }),
842
+ verb({
843
+ name: "agent_remove_unit",
844
+ description: "Remove a unit from one of your agents, by its instance, from agent_get.",
845
+ input: Target.extend({ instance: Id }),
846
+ needs: own,
847
+ changes: true,
848
+ run: async (caller, _, { agent, instance }) => parts.person("agents", "removeUnit", { agent: ownChild(caller, agent), instance }),
849
+ }),
850
+ verb({
851
+ name: "template_list",
852
+ description: "The templates there are.",
853
+ input: z.object({}),
854
+ needs: own,
855
+ changes: false,
856
+ run: async () => parts.person("agents", "templates", {}),
857
+ }),
858
+ verb({
859
+ name: "unit_list",
860
+ description: "The units there are, each with its form.",
861
+ input: z.object({}),
862
+ needs: own,
863
+ changes: false,
864
+ async run(_, bound) {
865
+ const listed = await parts.person("agents", "units", {});
866
+ if (atLeast(REACH, bound.connections, "see"))
867
+ return listed;
868
+ // The places its own unit could list are the person's connections, which it may not see.
869
+ return {
870
+ ...listed,
871
+ units: listed.units.map((unit) => unit.provider === self && unit.id === AGENTS_UNIT
872
+ ? {
873
+ ...unit,
874
+ form: unit.form.map((field) => field.id === "places"
875
+ ? {
876
+ ...field,
877
+ options: field.options?.filter((one) => [OWN, NEW].includes(one.value)),
878
+ }
879
+ : field),
880
+ }
881
+ : unit),
882
+ };
883
+ },
884
+ }),
885
+ verb({
886
+ name: "connection_list",
887
+ description: "The person's connections to MCP servers.",
888
+ input: z.object({}),
889
+ needs: sees("connections", "see"),
890
+ changes: false,
891
+ run: async () => parts.person("connections", "list", {}),
892
+ }),
893
+ verb({
894
+ name: "connection_get",
895
+ description: "One connection: where it came from and how it stands.",
896
+ input: z.object({ connection: Id }),
897
+ needs: sees("connections", "see"),
898
+ changes: false,
899
+ run: async (_, __, { connection }) => parts.person("connections", "get", { connection }),
900
+ }),
901
+ verb({
902
+ name: "connection_expose",
903
+ description: "Give a connection you may use to yourself or one of your agents.",
904
+ input: giving.connection,
905
+ needs: sees("connections", "use"),
906
+ changes: true,
907
+ async run(caller, bound, { agent, connection, as }) {
908
+ if (!bound.listed.includes(connection) && parts.madeBy(connection) !== caller)
909
+ throw new Refused("unknown", `You may not use ${connection}.`);
910
+ return parts.person("agents", "expose", { agent: selfOrOwn(caller, agent), connection, ...(as ? { as } : {}) }, caller);
911
+ },
912
+ }),
913
+ verb({
914
+ name: "connection_unexpose",
915
+ description: "Take a connection back from yourself or one of your agents, by its name.",
916
+ input: z.object({ agent: OnWhom.shape.agent, slot: z.string().min(1).max(129) }),
917
+ needs: sees("connections", "use"),
918
+ changes: true,
919
+ run: async (caller, _, { agent, slot }) => parts.person("agents", "unexpose", { agent: selfOrOwn(caller, agent), slot }),
920
+ }),
921
+ verb({
922
+ name: "skill_list",
923
+ description: "The skills there are.",
924
+ input: z.object({}),
925
+ needs: sees("skills", "see"),
926
+ changes: false,
927
+ run: async (caller) => parts.person("skills", "list", {}, caller),
928
+ }),
929
+ verb({
930
+ name: "skill_get",
931
+ description: "One skill's text and files.",
932
+ input: z.object({ name: z.string().min(1).max(64) }),
933
+ needs: sees("skills", "see"),
934
+ changes: false,
935
+ run: async (caller, _, { name }) => parts.person("skills", "get", { name }, caller),
936
+ }),
937
+ verb({
938
+ name: "skill_expose",
939
+ description: "Give a skill to yourself or one of your agents.",
940
+ input: giving.skill,
941
+ needs: sees("skills", "use"),
942
+ changes: true,
943
+ run: async (caller, _, { agent, skill }) => parts.person("agents", "expose", { agent: selfOrOwn(caller, agent), skill }, caller),
944
+ }),
945
+ verb({
946
+ name: "skill_unexpose",
947
+ description: "Take a skill back from yourself or one of your agents.",
948
+ input: giving.skill,
949
+ needs: sees("skills", "use"),
950
+ changes: true,
951
+ run: async (caller, _, { agent, skill }) => parts.person("agents", "unexpose", { agent: selfOrOwn(caller, agent), skill }, caller),
952
+ }),
953
+ verb({
954
+ name: "connection_add",
955
+ description: "Add a connection to an MCP server at a public https address. It takes no token, reaches nothing on this machine or its network, and goes when you do. Give it to yourself or your agents with connection_expose.",
956
+ input: z.object({
957
+ name: z.string().min(1).max(128),
958
+ url: z.string().min(1).max(4096).describe("An https address on the public internet."),
959
+ }),
960
+ needs: sees("connections", "add"),
961
+ changes: true,
962
+ async run(caller, _, { name, url }) {
963
+ const { providers } = await parts.person("connections", "providers", {});
964
+ const at = providers.find((one) => one.kinds.some((kind) => kind.kind === URL_KIND));
965
+ if (!at)
966
+ throw new Refused("unavailable", "Nothing here connects to an address now.");
967
+ return parts.person("connections", "create", { provider: at.provider, kind: URL_KIND, params: { url, public: true }, name }, caller);
968
+ },
969
+ }),
970
+ verb({
971
+ name: "connection_remove",
972
+ description: "Remove a connection you added, for good.",
973
+ input: z.object({ connection: Id }),
974
+ needs: sees("connections", "add"),
975
+ changes: true,
976
+ run: async (caller, _, { connection }) => parts.person("connections", "remove", { connection }, caller),
977
+ }),
978
+ verb({
979
+ name: "skill_add",
980
+ description: "Write a new skill: its SKILL.md, with front matter naming it, and any other files. Its name mustn't be one there is. You and your agents see it.",
981
+ input: z.object({
982
+ files: z
983
+ .record(z.string(), z.string())
984
+ .describe("By path inside the skill, such as SKILL.md."),
985
+ }),
986
+ needs: sees("skills", "add"),
987
+ changes: true,
988
+ run: async (caller, _, { files }) => parts.person("skills", "add", { files }, caller),
989
+ }),
990
+ verb({
991
+ name: "skill_remove",
992
+ description: "Remove a skill you wrote.",
993
+ input: z.object({ name: z.string().min(1).max(64) }),
994
+ needs: sees("skills", "add"),
995
+ changes: true,
996
+ run: async (caller, _, { name }) => parts.person("skills", "remove", { name }, caller),
997
+ }),
998
+ verb({
999
+ name: "template_save",
1000
+ description: "Save a template of your own, from units on unit_list, providers named as there. Its id mustn't be one there is, it can't say what program to run, and a control unit in it stays within yours. You can create from it.",
1001
+ input: z.object({ template: z.record(z.string(), z.unknown()) }),
1002
+ needs: (bound) => own(bound) && bound.saveTemplates,
1003
+ changes: true,
1004
+ async run(caller, bound, { template }) {
1005
+ const wrong = notItsOwn(caller, bound, template, await parts.catalog());
1006
+ if (wrong)
1007
+ throw new Refused("invalid", wrong);
1008
+ const units = (template.units ?? []);
1009
+ const offers = await parts.catalog();
1010
+ for (const unit of units) {
1011
+ const offer = offers.find((one) => one.providerName === unit.provider && one.unit.id === unit.unit);
1012
+ if (!offer || !isAgentsUnit(self, offer))
1013
+ continue;
1014
+ // What it would let an agent do stays within what this one may.
1015
+ for (const field of offer.unit.form.filter(settable)) {
1016
+ const message = overLimit(field, unit.values?.[field.id], limitOf(bound, field));
1017
+ if (message)
1018
+ throw new Refused("invalid", message);
1019
+ }
1020
+ if (Number(unit.values?.depth ?? 1) >= bound.depth)
1021
+ throw new Refused("invalid", "A control unit in it goes less deep than yours.");
1022
+ }
1023
+ return parts.person("agents", "saveTemplate", template, caller);
1024
+ },
1025
+ }),
1026
+ verb({
1027
+ name: "template_remove",
1028
+ description: "Remove a template you saved.",
1029
+ input: z.object({ id: z.string().min(1).max(64) }),
1030
+ needs: (bound) => own(bound) && bound.saveTemplates,
1031
+ changes: true,
1032
+ run: async (caller, _, { id }) => parts.person("agents", "removeTemplate", { id }, caller),
1033
+ }),
1034
+ ];
1035
+ const toolOf = (one) => ({
1036
+ name: one.name,
1037
+ description: one.description,
1038
+ inputSchema: JSON.parse(JSON.stringify(z.toJSONSchema(one.input))),
1039
+ });
1040
+ /** What a call is about, for the log. */
1041
+ const targetOf = (input) => {
1042
+ if (!input || typeof input !== "object")
1043
+ return undefined;
1044
+ const { agent, connection, skill, instance } = input;
1045
+ const found = [agent, connection, skill, instance].find((one) => typeof one === "string");
1046
+ return found === undefined ? undefined : String(found).slice(0, 256);
1047
+ };
1048
+ return {
1049
+ async list(caller) {
1050
+ const holding = holdingOf(caller);
1051
+ if (!holding)
1052
+ return [];
1053
+ const { record, bound } = holding;
1054
+ return [
1055
+ ...(await createTools(caller, record, bound)),
1056
+ ...VERBS.filter((one) => one.needs(bound)).map(toolOf),
1057
+ ];
1058
+ },
1059
+ /** A tool that ran and failed answers `failed`, for the caller's model to read. */
1060
+ async call(caller, name, input) {
1061
+ // The log mustn't turn a call that was done into one that failed.
1062
+ const note = (entry) => {
1063
+ try {
1064
+ parts.log(entry);
1065
+ }
1066
+ catch {
1067
+ // Its failure is the log's own.
1068
+ }
1069
+ };
1070
+ const holding = holdingOf(caller);
1071
+ if (!holding)
1072
+ throw new Refused("unknown", `There is no tool ${name}.`);
1073
+ const creates = name.startsWith("create_");
1074
+ const one = VERBS.find((each) => each.name === name);
1075
+ const target = targetOf(input);
1076
+ try {
1077
+ let output;
1078
+ if (creates)
1079
+ output = await create(caller, name.slice("create_".length), input);
1080
+ else {
1081
+ if (!one?.needs(holding.bound))
1082
+ throw new Refused("unknown", `There is no tool ${name}.`);
1083
+ output = await one.run(caller, holding.bound, one.input.parse(input));
1084
+ }
1085
+ if (creates || one?.changes)
1086
+ note({
1087
+ caller,
1088
+ tool: name,
1089
+ outcome: "done",
1090
+ ...(creates
1091
+ ? { target: output.agent }
1092
+ : target
1093
+ ? { target }
1094
+ : {}),
1095
+ });
1096
+ return { output: fit(output ?? {}) };
1097
+ }
1098
+ catch (error) {
1099
+ const text = error instanceof z.ZodError
1100
+ ? z.prettifyError(error)
1101
+ : error instanceof Error
1102
+ ? error.message
1103
+ : String(error);
1104
+ note({
1105
+ caller,
1106
+ tool: name.slice(0, 128),
1107
+ outcome: "refused",
1108
+ ...(target ? { target } : {}),
1109
+ reason: text.slice(0, 2000),
1110
+ });
1111
+ return { output: text.slice(0, OUTPUT_MAX), failed: true };
1112
+ }
1113
+ },
1114
+ };
1115
+ }