@agentorg/cli 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -7
- package/dist/node_modules/@agentorg/computer/LICENSE +201 -0
- package/dist/node_modules/@agentorg/computer/NOTICE +2 -0
- package/dist/node_modules/@agentorg/computer/dist/THIRD_PARTY_LICENSES.txt +1321 -0
- package/dist/node_modules/@agentorg/computer/dist/sdk.js +24909 -0
- package/dist/node_modules/@agentorg/computer/dist/types/generated/schema.d.ts +860 -0
- package/dist/node_modules/@agentorg/computer/dist/types/sdk.d.ts +242 -0
- package/dist/node_modules/@agentorg/computer/package.json +12 -0
- package/dist/node_modules/@agentorg/computer-connector/dist/src/index.d.ts +1 -0
- package/dist/node_modules/@agentorg/computer-connector/dist/src/index.js +1 -0
- package/dist/node_modules/@agentorg/computer-connector/dist/src/provider.d.ts +24 -0
- package/dist/node_modules/@agentorg/computer-connector/dist/src/provider.js +482 -0
- package/dist/node_modules/@agentorg/computer-connector/dist/src/unit.d.ts +19 -0
- package/dist/node_modules/@agentorg/computer-connector/dist/src/unit.js +149 -0
- package/dist/node_modules/@agentorg/computer-connector/package.json +33 -0
- package/dist/node_modules/@agentorg/computer-connector/skills/browser/SKILL.md +31 -0
- package/dist/node_modules/@agentorg/computer-connector/skills/computer/SKILL.md +30 -0
- package/dist/node_modules/@agentorg/computer-connector/skills/files/SKILL.md +28 -0
- package/dist/node_modules/@agentorg/computer-connector/skills/terminal/SKILL.md +39 -0
- package/dist/node_modules/@agentorg/computer-connector/skills/web-research/SKILL.md +29 -0
- package/dist/node_modules/@agentorg/hub/dist/src/router.d.ts +4 -2
- package/dist/node_modules/@agentorg/hub/dist/src/router.js +3 -1
- package/dist/node_modules/@agentorg/hub/dist/src/validate.js +16 -2
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.d.ts +34 -47
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.js +25 -34
- package/dist/node_modules/@agentorg/interfaces/dist/src/chat.d.ts +90 -50
- package/dist/node_modules/@agentorg/interfaces/dist/src/chat.js +69 -34
- package/dist/node_modules/@agentorg/interfaces/dist/src/connection.d.ts +36 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/connection.js +31 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/folders.d.ts +19 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/folders.js +25 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/index.d.ts +6 -4
- package/dist/node_modules/@agentorg/interfaces/dist/src/index.js +6 -4
- package/dist/node_modules/@agentorg/interfaces/dist/src/llm-session.d.ts +2 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/llm-session.js +2 -1
- package/dist/node_modules/@agentorg/interfaces/dist/src/manager.d.ts +381 -27
- package/dist/node_modules/@agentorg/interfaces/dist/src/manager.js +179 -15
- package/dist/node_modules/@agentorg/interfaces/dist/src/units.d.ts +460 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/units.js +204 -0
- package/dist/node_modules/@agentorg/looper/dist/src/index.d.ts +5 -0
- package/dist/node_modules/@agentorg/looper/dist/src/index.js +5 -0
- package/dist/node_modules/@agentorg/looper/dist/src/loop.d.ts +75 -0
- package/dist/node_modules/@agentorg/looper/dist/src/loop.js +332 -0
- package/dist/node_modules/@agentorg/looper/dist/src/looper.d.ts +71 -0
- package/dist/node_modules/@agentorg/looper/dist/src/looper.js +282 -0
- package/dist/node_modules/@agentorg/looper/dist/src/record.d.ts +99 -0
- package/dist/node_modules/{assistant/dist/src/history.js → @agentorg/looper/dist/src/record.js} +89 -46
- package/dist/node_modules/@agentorg/looper/dist/src/tools.d.ts +11 -0
- package/dist/node_modules/@agentorg/looper/dist/src/tools.js +42 -0
- package/dist/node_modules/@agentorg/looper/dist/src/types.d.ts +147 -0
- package/dist/node_modules/@agentorg/looper/dist/src/types.js +32 -0
- package/dist/node_modules/@agentorg/looper/package.json +24 -0
- package/dist/node_modules/@agentorg/manager/dist/src/activity.d.ts +18 -0
- package/dist/node_modules/@agentorg/manager/dist/src/activity.js +65 -0
- package/dist/node_modules/@agentorg/manager/dist/src/control.d.ts +134 -0
- package/dist/node_modules/@agentorg/manager/dist/src/control.js +1115 -0
- package/dist/node_modules/@agentorg/manager/dist/src/form.d.ts +40 -0
- package/dist/node_modules/@agentorg/manager/dist/src/form.js +191 -0
- package/dist/node_modules/@agentorg/manager/dist/src/manager.d.ts +4 -0
- package/dist/node_modules/@agentorg/manager/dist/src/manager.js +341 -189
- package/dist/node_modules/@agentorg/manager/dist/src/skills.d.ts +2 -0
- package/dist/node_modules/@agentorg/manager/dist/src/skills.js +7 -0
- package/dist/node_modules/@agentorg/manager/dist/src/stack.d.ts +454 -0
- package/dist/node_modules/@agentorg/manager/dist/src/stack.js +917 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/connection.d.ts +2 -1
- package/dist/node_modules/@agentorg/mcp/dist/src/connection.js +8 -2
- package/dist/node_modules/@agentorg/mcp/dist/src/index.d.ts +3 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/index.js +3 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/kinds.d.ts +108 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/kinds.js +95 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/provider.d.ts +2 -2
- package/dist/node_modules/@agentorg/mcp/dist/src/provider.js +48 -18
- package/dist/node_modules/@agentorg/mcp/dist/src/public.d.ts +6 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/public.js +72 -0
- package/dist/node_modules/@agentorg/sdk/dist/src/filled.d.ts +21 -0
- package/dist/node_modules/@agentorg/sdk/dist/src/filled.js +24 -0
- package/dist/node_modules/@agentorg/sdk/dist/src/index.d.ts +1 -0
- package/dist/node_modules/@agentorg/sdk/dist/src/index.js +1 -0
- package/dist/node_modules/@agentorg/shell/dist/app.css +1289 -100
- package/dist/node_modules/@agentorg/shell/dist/app.js +207 -166
- package/dist/node_modules/@agentorg/shell/package.json +6 -1
- package/dist/node_modules/assistant/dist/src/chats.d.ts +92 -0
- package/dist/node_modules/assistant/dist/src/chats.js +357 -0
- package/dist/node_modules/assistant/dist/src/engine.d.ts +26 -7
- package/dist/node_modules/assistant/dist/src/engine.js +107 -35
- package/dist/node_modules/assistant/dist/src/local-store.js +1 -1
- package/dist/node_modules/assistant/dist/src/provider.d.ts +1 -1
- package/dist/node_modules/assistant/dist/src/provider.js +47 -19
- package/dist/node_modules/assistant/dist/src/tools.d.ts +11 -28
- package/dist/node_modules/assistant/dist/src/tools.js +19 -40
- package/dist/node_modules/assistant/package.json +1 -0
- package/dist/src/api.js +4 -0
- package/dist/src/folders.d.ts +2 -0
- package/dist/src/folders.js +45 -0
- package/dist/src/gateway.d.ts +7 -1
- package/dist/src/gateway.js +81 -12
- package/dist/src/live.d.ts +17 -0
- package/dist/src/live.js +109 -0
- package/dist/src/programs.d.ts +7 -0
- package/dist/src/programs.js +29 -2
- package/dist/src/signin.d.ts +12 -3
- package/dist/src/signin.js +30 -6
- package/package.json +2 -1
- package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.d.ts +0 -88
- package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.js +0 -78
- package/dist/node_modules/assistant/dist/src/agent.d.ts +0 -62
- package/dist/node_modules/assistant/dist/src/agent.js +0 -226
- package/dist/node_modules/assistant/dist/src/history.d.ts +0 -97
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { agentProvider, agentSettings, agents,
|
|
3
|
+
import { agentProvider, agentSettings, agents, chat, connections, hubActors, hubBindings, hubGrants, skills, tools, unitProvider, } from "@agentorg/interfaces";
|
|
4
4
|
import { describeInterface, Refused } from "@agentorg/sdk";
|
|
5
5
|
import { connect } from "@agentorg/sdk/client";
|
|
6
|
-
import {
|
|
6
|
+
import { createActivity } from "./activity.js";
|
|
7
|
+
import { createControl } from "./control.js";
|
|
8
|
+
import { schemaOf, secretsSchemaOf, settle } from "./form.js";
|
|
7
9
|
import { createSkills, readSkillFolder } from "./skills.js";
|
|
8
|
-
|
|
9
|
-
const AgentRecord = z.object({
|
|
10
|
-
provider: z.string(),
|
|
11
|
-
creatable: z.string(),
|
|
12
|
-
instructions: z.string(),
|
|
13
|
-
skills: z.array(z.string()),
|
|
14
|
-
});
|
|
15
|
-
const Records = z.object({
|
|
16
|
-
agents: z.record(z.string(), AgentRecord),
|
|
17
|
-
connections: z.record(z.string(), ConnectionOrigin),
|
|
18
|
-
});
|
|
10
|
+
import { createStacks, Records, slotFieldOf } from "./stack.js";
|
|
19
11
|
const FILE = "manager.json";
|
|
20
|
-
const
|
|
21
|
-
|
|
12
|
+
const ACTIVITY = "activity.jsonl";
|
|
13
|
+
/** Who a control call is made as, when it isn't on an agent's own behalf. */
|
|
14
|
+
const PERSON_CALLER = "person";
|
|
15
|
+
const CHAT_WAIT_MS = 2000;
|
|
16
|
+
const CHAT = "chat@1";
|
|
17
|
+
/** What one agent may add of its own, at most. */
|
|
18
|
+
const AGENT_CONNECTIONS = 16;
|
|
19
|
+
const AGENT_SKILLS = 32;
|
|
20
|
+
const AGENT_SKILL_BYTES = 1024 * 1024;
|
|
22
21
|
const TOOLS_EXPORT = "tools";
|
|
23
22
|
const quiet = { info() { }, warn() { }, error() { } };
|
|
24
23
|
const messageOf = (error) => (error instanceof Error ? error.message : String(error));
|
|
24
|
+
/** The slot a connection is made for by the person: no agent has it yet, so it names nothing. */
|
|
25
|
+
const PERSON_SLOT = "tools";
|
|
25
26
|
const alreadyGone = (error) => error instanceof Refused && error.code === "unknown";
|
|
26
27
|
/**
|
|
27
28
|
* Run management as an Actor exporting `agents@1`, `connections@1`, `skills@1` and, as `settings`,
|
|
@@ -36,7 +37,13 @@ export async function startManager(options) {
|
|
|
36
37
|
chmodSync(dir, 0o700);
|
|
37
38
|
const path = join(dir, FILE);
|
|
38
39
|
const read = () => {
|
|
39
|
-
const empty = {
|
|
40
|
+
const empty = {
|
|
41
|
+
agents: {},
|
|
42
|
+
connections: {},
|
|
43
|
+
templates: {},
|
|
44
|
+
instances: {},
|
|
45
|
+
authors: { skills: {}, templates: {} },
|
|
46
|
+
};
|
|
40
47
|
let raw;
|
|
41
48
|
try {
|
|
42
49
|
raw = readFileSync(path, "utf8");
|
|
@@ -61,6 +68,7 @@ export async function startManager(options) {
|
|
|
61
68
|
renameSync(`${path}.tmp`, path);
|
|
62
69
|
};
|
|
63
70
|
const library = createSkills(join(dir, "skills"));
|
|
71
|
+
const activity = createActivity(join(dir, ACTIVITY));
|
|
64
72
|
/** Agents being made: they can read their settings, and nothing else can see them yet. */
|
|
65
73
|
const creating = new Map();
|
|
66
74
|
const client = await connect({
|
|
@@ -68,7 +76,7 @@ export async function startManager(options) {
|
|
|
68
76
|
actorId: options.actorId,
|
|
69
77
|
credential: options.credential,
|
|
70
78
|
slots: { actors: hubActors, grants: hubGrants, bindings: hubBindings },
|
|
71
|
-
exports: { agents, connections, skills, settings: agentSettings },
|
|
79
|
+
exports: { agents, connections, skills, settings: agentSettings, tools },
|
|
72
80
|
log,
|
|
73
81
|
start(ctx) {
|
|
74
82
|
const hub = {
|
|
@@ -85,7 +93,7 @@ export async function startManager(options) {
|
|
|
85
93
|
.map(({ provider, call }) => ({ actor: provider.actor, call: call }));
|
|
86
94
|
};
|
|
87
95
|
const agentProviders = () => exposed(agentProvider);
|
|
88
|
-
const
|
|
96
|
+
const unitProviders = () => exposed(unitProvider);
|
|
89
97
|
const named = (list, actor, what) => {
|
|
90
98
|
const found = list.find((one) => one.actor === actor);
|
|
91
99
|
if (!found)
|
|
@@ -110,6 +118,30 @@ export async function startManager(options) {
|
|
|
110
118
|
throw new Refused("unknown", `Management has no agent ${agent}.`);
|
|
111
119
|
return record;
|
|
112
120
|
};
|
|
121
|
+
/** The caller when it's an agent, which reaches these only through its control unit. */
|
|
122
|
+
const agentOf = (caller) => records.agents[caller] || creating.has(caller) ? caller : undefined;
|
|
123
|
+
/** A skill an agent added is seen by it and the agents below it, until the person shares it. */
|
|
124
|
+
const seesSkill = (caller, name) => {
|
|
125
|
+
const author = records.authors.skills[name];
|
|
126
|
+
if (!author || !agentOf(caller))
|
|
127
|
+
return true;
|
|
128
|
+
const seen = new Set();
|
|
129
|
+
for (let at = caller; at && !seen.has(at);) {
|
|
130
|
+
if (at === author)
|
|
131
|
+
return true;
|
|
132
|
+
seen.add(at);
|
|
133
|
+
at = records.agents[at]?.parent?.agent;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
};
|
|
137
|
+
const parentOf = ({ parent }) => parent
|
|
138
|
+
? {
|
|
139
|
+
parent: {
|
|
140
|
+
...parent,
|
|
141
|
+
...(records.agents[parent.agent] ? {} : { gone: true }),
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
: {};
|
|
113
145
|
const settingsOf = (agent) => creating.get(agent) ?? recordOf(agent);
|
|
114
146
|
const originOf = (connection) => {
|
|
115
147
|
const origin = records.connections[connection];
|
|
@@ -125,32 +157,22 @@ export async function startManager(options) {
|
|
|
125
157
|
return { state: "down", detail: "Its provider can't be reached." };
|
|
126
158
|
}
|
|
127
159
|
};
|
|
160
|
+
const instanceOf = (connection) => records.instances[connection] ?? connection;
|
|
161
|
+
const forget = (connection) => {
|
|
162
|
+
delete records.connections[connection];
|
|
163
|
+
delete records.instances[connection];
|
|
164
|
+
};
|
|
128
165
|
const connectionStatus = async (connection) => {
|
|
166
|
+
const unit = stacks.unitMaking(connection);
|
|
167
|
+
if (unit)
|
|
168
|
+
return stacks.connectionStatus(unit, connection);
|
|
129
169
|
const origin = records.connections[connection];
|
|
130
170
|
if (!origin) {
|
|
131
171
|
// Exposed by hand, so no provider is known: the Hub says whether it is there.
|
|
132
172
|
const { connected } = await hub.actors.get({ actorId: connection });
|
|
133
173
|
return { state: connected ? "connected" : "down", tools: [] };
|
|
134
174
|
}
|
|
135
|
-
|
|
136
|
-
const mcp = named(mcpProviders(), origin.provider, "MCP provider");
|
|
137
|
-
return await mcp.call.status({ connection });
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
return { state: "down", detail: "Its provider can't be reached.", tools: [] };
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
const offering = async (kind) => {
|
|
144
|
-
const providers = mcpProviders();
|
|
145
|
-
const answers = await fromEach(providers, async ({ actor, call }) => ({
|
|
146
|
-
actor,
|
|
147
|
-
call,
|
|
148
|
-
kinds: (await call.kinds({})).kinds,
|
|
149
|
-
}));
|
|
150
|
-
const found = answers.find((one) => one.kinds.some((k) => k.kind === kind));
|
|
151
|
-
if (!found)
|
|
152
|
-
throw new Refused("unknown", `No connected MCP provider offers ${kind}.`);
|
|
153
|
-
return found;
|
|
175
|
+
return stacks.connectionStatus({ provider: origin.provider, providerInstance: instanceOf(connection) }, connection);
|
|
154
176
|
};
|
|
155
177
|
const exposeConnection = async (agent, connection, as) => {
|
|
156
178
|
const { slot } = await hub.grants.expose({
|
|
@@ -163,10 +185,10 @@ export async function startManager(options) {
|
|
|
163
185
|
};
|
|
164
186
|
/** Remove a connection through its provider; one that has lost it leaves only the Actor. */
|
|
165
187
|
const removeConnection = async (connection, provider) => {
|
|
166
|
-
const
|
|
188
|
+
const maker = unitProviders().find((one) => one.actor === provider);
|
|
167
189
|
try {
|
|
168
|
-
if (
|
|
169
|
-
return void (await
|
|
190
|
+
if (maker)
|
|
191
|
+
return void (await maker.call.remove({ instance: instanceOf(connection) }));
|
|
170
192
|
}
|
|
171
193
|
catch (error) {
|
|
172
194
|
if (!alreadyGone(error))
|
|
@@ -197,14 +219,154 @@ export async function startManager(options) {
|
|
|
197
219
|
for (const [agent, record] of Object.entries(records.agents))
|
|
198
220
|
if (record.skills.includes(name)) {
|
|
199
221
|
record.skills = record.skills.filter((skill) => skill !== name);
|
|
222
|
+
for (const unit of record.units)
|
|
223
|
+
unit.skills = unit.skills.filter((skill) => skill !== name);
|
|
200
224
|
changed(agent);
|
|
201
225
|
}
|
|
202
226
|
};
|
|
227
|
+
const stacks = createStacks({
|
|
228
|
+
self: options.actorId,
|
|
229
|
+
hub,
|
|
230
|
+
records,
|
|
231
|
+
save,
|
|
232
|
+
library,
|
|
233
|
+
creating,
|
|
234
|
+
log,
|
|
235
|
+
shipped: options.templates ?? [],
|
|
236
|
+
agentProviders,
|
|
237
|
+
unitProviders,
|
|
238
|
+
recordOf,
|
|
239
|
+
openActors: open,
|
|
240
|
+
nameOf,
|
|
241
|
+
changed,
|
|
242
|
+
fromEach,
|
|
243
|
+
exposeConnection,
|
|
244
|
+
closing,
|
|
245
|
+
removeAgent,
|
|
246
|
+
});
|
|
247
|
+
/** An agent's own provider, which runs it and keeps its model. */
|
|
248
|
+
const makerOf = (agent) => named(agentProviders(), recordOf(agent).provider, "agent provider").call;
|
|
249
|
+
const startAgent = async (agent) => makerOf(agent).start({ agent });
|
|
250
|
+
const stopAgent = async (agent) => makerOf(agent).stop({ agent });
|
|
251
|
+
/** Its units and connections go first: if one can't, the agent is here to be tried again. */
|
|
252
|
+
const removeWhole = (agent) => stacks.serial(agent, async () => {
|
|
253
|
+
const record = recordOf(agent);
|
|
254
|
+
await stacks.removeAll(agent, record);
|
|
255
|
+
for (const [connection, origin] of Object.entries(records.connections)) {
|
|
256
|
+
if (origin.madeFor !== agent)
|
|
257
|
+
continue;
|
|
258
|
+
await removeConnection(connection, origin.provider);
|
|
259
|
+
forget(connection);
|
|
260
|
+
save();
|
|
261
|
+
}
|
|
262
|
+
await removeAgent(agent, record.provider);
|
|
263
|
+
delete records.agents[agent];
|
|
264
|
+
// What it wrote or saved is the person's now.
|
|
265
|
+
for (const kept of [records.authors.skills, records.authors.templates])
|
|
266
|
+
for (const [name, author] of Object.entries(kept))
|
|
267
|
+
if (author === agent)
|
|
268
|
+
delete kept[name];
|
|
269
|
+
save();
|
|
270
|
+
return {};
|
|
271
|
+
});
|
|
272
|
+
/** A child's chat: exposed to management when it is made, which takes a moment to arrive. */
|
|
273
|
+
const chatOf = async (agent) => {
|
|
274
|
+
const exposedChat = () => exposed(chat).find((one) => one.actor === agent);
|
|
275
|
+
if (!exposedChat()) {
|
|
276
|
+
// One the person made: its chat is reached as the person reaches it.
|
|
277
|
+
const { bindings } = await hub.bindings.list({ actor: PERSON_CALLER });
|
|
278
|
+
const theirs = bindings.find((one) => one.provider === agent && one.interface === CHAT && one.consumer === PERSON_CALLER);
|
|
279
|
+
if (theirs)
|
|
280
|
+
await hub.grants
|
|
281
|
+
.expose({ provider: agent, export: theirs.export, to: options.actorId })
|
|
282
|
+
.catch((error) => log.warn(`Reaching a chat: ${messageOf(error)}`));
|
|
283
|
+
}
|
|
284
|
+
for (let waited = 0; waited < CHAT_WAIT_MS; waited += 20) {
|
|
285
|
+
const found = exposed(chat).find((one) => one.actor === agent);
|
|
286
|
+
if (found)
|
|
287
|
+
return found.call;
|
|
288
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
289
|
+
}
|
|
290
|
+
throw new Refused("not_connected", "That agent's chat can't be reached now.");
|
|
291
|
+
};
|
|
292
|
+
/**
|
|
293
|
+
* An agent's chats reach the person, and management too for an agent another agent made. The
|
|
294
|
+
* Hub drops both when the agent comes back offering its chats in another shape, as after an
|
|
295
|
+
* upgrade, so they are made again each time it connects: nothing changes when they are there.
|
|
296
|
+
*/
|
|
297
|
+
const reachChats = async (agent) => {
|
|
298
|
+
const record = records.agents[agent];
|
|
299
|
+
if (!record || !(await hub.actors.get({ actorId: agent })).connected)
|
|
300
|
+
return;
|
|
301
|
+
for (const to of [PERSON_CALLER, ...(record.parent ? [options.actorId] : [])])
|
|
302
|
+
await hub.grants.expose({ provider: agent, export: record.chat, to });
|
|
303
|
+
};
|
|
304
|
+
const reaching = (agent) => void reachChats(agent).catch((error) => log.warn(`Reaching a chat: ${messageOf(error)}`));
|
|
305
|
+
ctx.slots.actors.on("changed", ({ actor }) => reaching(actor));
|
|
306
|
+
// And for those that connected while management was away, once it can ask.
|
|
307
|
+
ctx.slots.actors.onBind(() => {
|
|
308
|
+
for (const agent of Object.keys(records.agents))
|
|
309
|
+
reaching(agent);
|
|
310
|
+
});
|
|
311
|
+
/** The person's own handlers, which an agent's control tools call after their checks. */
|
|
312
|
+
const personHandlers = {};
|
|
313
|
+
const control = createControl({
|
|
314
|
+
self: options.actorId,
|
|
315
|
+
records,
|
|
316
|
+
creatingCount: () => creating.size,
|
|
317
|
+
catalog: stacks.catalog,
|
|
318
|
+
boundTo: async (agent) => new Set((await hub.bindings.list({ actor: agent })).bindings
|
|
319
|
+
.filter((binding) => binding.consumer === agent)
|
|
320
|
+
.map((binding) => `${binding.slot} ${binding.provider}`)),
|
|
321
|
+
templates: stacks.allTemplates,
|
|
322
|
+
baseProviderNamed: stacks.baseProviderNamed,
|
|
323
|
+
serial: stacks.serial,
|
|
324
|
+
create: (input, caller, child) => stacks.create(input, caller, child),
|
|
325
|
+
nameOf,
|
|
326
|
+
chatOf,
|
|
327
|
+
statusOf: agentStatus,
|
|
328
|
+
start: startAgent,
|
|
329
|
+
stop: stopAgent,
|
|
330
|
+
remove: removeWhole,
|
|
331
|
+
// Held to the interface's own input, as the person's calls are by the Hub.
|
|
332
|
+
person: async (iface, method, input, caller = PERSON_CALLER) => {
|
|
333
|
+
const handler = personHandlers[iface]?.[method];
|
|
334
|
+
const spec = { agents, connections, skills }[iface].methods[method];
|
|
335
|
+
if (!handler || !spec)
|
|
336
|
+
throw new Refused("unimplemented", `${iface} has no ${method}.`);
|
|
337
|
+
return handler(spec.input.parse(input), { caller });
|
|
338
|
+
},
|
|
339
|
+
hasSkill: (name, caller) => library.has(name) && seesSkill(caller, name),
|
|
340
|
+
templateAuthor: (id) => records.authors.templates[id],
|
|
341
|
+
madeBy: (connection) => records.connections[connection]?.madeBy,
|
|
342
|
+
log: (entry) => activity.add(entry),
|
|
343
|
+
});
|
|
344
|
+
/** Those who call management: the person. An agent comes through its control unit instead. */
|
|
345
|
+
const personOnly = (exported, name) => ({
|
|
346
|
+
implement(handlers) {
|
|
347
|
+
const held = handlers;
|
|
348
|
+
personHandlers[name] = held;
|
|
349
|
+
exported.implement(Object.fromEntries(Object.entries(held).map(([method, handler]) => [
|
|
350
|
+
method,
|
|
351
|
+
(input, context) => {
|
|
352
|
+
if (records.agents[context.caller] || creating.has(context.caller))
|
|
353
|
+
throw new Refused("forbidden", "An agent reaches this through its control unit.");
|
|
354
|
+
return handler(input, context);
|
|
355
|
+
},
|
|
356
|
+
])));
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
ctx.exports.tools.implement({
|
|
360
|
+
list: async (_, { caller }) => ({ tools: await control.list(caller) }),
|
|
361
|
+
call: ({ name, arguments: input }, { caller }) => control.call(caller, name, input),
|
|
362
|
+
});
|
|
203
363
|
ctx.exports.settings.implement({
|
|
204
364
|
get: (_, { caller }) => {
|
|
205
|
-
const { instructions, skills: names } = settingsOf(caller);
|
|
365
|
+
const { instructions, purpose, skills: names } = settingsOf(caller);
|
|
206
366
|
return {
|
|
207
|
-
instructions
|
|
367
|
+
instructions: purpose
|
|
368
|
+
? `${instructions}\n\nWhat you are for: ${purpose}`
|
|
369
|
+
: instructions,
|
|
208
370
|
skills: names.flatMap((name) => library.entry(name) ?? []),
|
|
209
371
|
};
|
|
210
372
|
},
|
|
@@ -217,7 +379,7 @@ export async function startManager(options) {
|
|
|
217
379
|
return { text };
|
|
218
380
|
},
|
|
219
381
|
});
|
|
220
|
-
ctx.exports.agents.implement({
|
|
382
|
+
personOnly(ctx.exports.agents, "agents").implement({
|
|
221
383
|
creatable: async () => ({
|
|
222
384
|
creatables: (await fromEach(agentProviders(), async ({ actor, call }) => (await call.creatable({})).creatables.map((one) => ({ ...one, provider: actor })))).flat(),
|
|
223
385
|
}),
|
|
@@ -227,99 +389,23 @@ export async function startManager(options) {
|
|
|
227
389
|
name: await nameOf(actor),
|
|
228
390
|
}))),
|
|
229
391
|
}),
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
creatable: id,
|
|
248
|
-
instructions: creatable.instructions,
|
|
249
|
-
skills: [],
|
|
250
|
-
};
|
|
251
|
-
creating.set(agent, record);
|
|
252
|
-
undo.push(() => creating.delete(agent));
|
|
253
|
-
const origins = {};
|
|
254
|
-
await hub.grants.bind({
|
|
255
|
-
consumer: agent,
|
|
256
|
-
slot: made.settings,
|
|
257
|
-
provider: options.actorId,
|
|
258
|
-
export: "settings",
|
|
259
|
-
});
|
|
260
|
-
for (const skill of creatable.skills) {
|
|
261
|
-
if (!library.has(skill.name)) {
|
|
262
|
-
const files = {};
|
|
263
|
-
for (const file of skill.files)
|
|
264
|
-
files[file] = (await provider.call.skill({ creatable: id, name: skill.name, path: file })).text;
|
|
265
|
-
library.add(files, skill.name);
|
|
266
|
-
// Another agent may have been given it meanwhile, and then it stays.
|
|
267
|
-
undo.push(() => {
|
|
268
|
-
if (!Object.values(records.agents).some((r) => r.skills.includes(skill.name)))
|
|
269
|
-
library.remove(skill.name);
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
record.skills.push(skill.name);
|
|
273
|
-
}
|
|
274
|
-
for (const need of creatable.mcp) {
|
|
275
|
-
let connection = picked[need.slot];
|
|
276
|
-
if (need.how === "create") {
|
|
277
|
-
const mcp = await offering(need.kind);
|
|
278
|
-
const params = { ...need.params };
|
|
279
|
-
for (const [param, ask] of Object.entries(need.fromAnswers ?? {}))
|
|
280
|
-
if (answers[ask] !== undefined)
|
|
281
|
-
params[param] = answers[ask];
|
|
282
|
-
const { connection: id2, target } = await mcp.call.create({
|
|
283
|
-
kind: need.kind,
|
|
284
|
-
params,
|
|
285
|
-
name: `${name}: ${need.title}`.slice(0, 128),
|
|
286
|
-
});
|
|
287
|
-
connection = id2;
|
|
288
|
-
undo.push(() => closing(id2, () => removeConnection(id2, mcp.actor)));
|
|
289
|
-
origins[id2] = {
|
|
290
|
-
provider: mcp.actor,
|
|
291
|
-
kind: need.kind,
|
|
292
|
-
target,
|
|
293
|
-
madeBy: caller,
|
|
294
|
-
madeFor: agent,
|
|
295
|
-
at: new Date().toISOString(),
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
await exposeConnection(agent, connection, need.slot);
|
|
299
|
-
}
|
|
300
|
-
await hub.grants.expose({
|
|
301
|
-
provider: agent,
|
|
302
|
-
export: made.chat,
|
|
303
|
-
to: PERSON,
|
|
304
|
-
});
|
|
305
|
-
records.agents[agent] = record;
|
|
306
|
-
Object.assign(records.connections, origins);
|
|
307
|
-
undo.push(() => {
|
|
308
|
-
delete records.agents[agent];
|
|
309
|
-
for (const connection of Object.keys(origins))
|
|
310
|
-
delete records.connections[connection];
|
|
311
|
-
});
|
|
312
|
-
save();
|
|
313
|
-
creating.delete(agent);
|
|
314
|
-
return { agent };
|
|
315
|
-
}
|
|
316
|
-
catch (error) {
|
|
317
|
-
for (const step of undo.reverse())
|
|
318
|
-
await Promise.resolve()
|
|
319
|
-
.then(step)
|
|
320
|
-
.catch((failed) => log.error(`Undoing a creation: ${messageOf(failed)}`));
|
|
321
|
-
throw error;
|
|
322
|
-
}
|
|
392
|
+
units: () => stacks.units(),
|
|
393
|
+
templates: () => stacks.templates(),
|
|
394
|
+
saveTemplate: (template, { caller }) => stacks.saveTemplate(template, agentOf(caller)),
|
|
395
|
+
removeTemplate: (input, { caller }) => stacks.removeTemplate(input, agentOf(caller)),
|
|
396
|
+
check: (input) => stacks.check(input),
|
|
397
|
+
create: (input, { caller }) => stacks.create(input, caller),
|
|
398
|
+
addUnit: (input, { caller }) => stacks.serial(input.agent, () => stacks.addUnit(input, caller)),
|
|
399
|
+
removeUnit: (input) => stacks.serial(input.agent, () => stacks.removeUnit(input)),
|
|
400
|
+
activity: (input) => activity.read(input),
|
|
401
|
+
async live({ agent, instance, surface, mode }) {
|
|
402
|
+
const unit = recordOf(agent).units.find((one) => one.instance === instance);
|
|
403
|
+
if (!unit || unit.provider === options.actorId)
|
|
404
|
+
throw new Refused("unknown", `${agent} has nothing like that to open live.`);
|
|
405
|
+
const provider = unitProviders().find((one) => one.actor === unit.provider);
|
|
406
|
+
if (!provider)
|
|
407
|
+
throw new Refused("not_connected", "Its provider can't be reached now.");
|
|
408
|
+
return provider.call.live({ instance: unit.providerInstance, surface, mode });
|
|
323
409
|
},
|
|
324
410
|
async list() {
|
|
325
411
|
const actors = await open();
|
|
@@ -334,6 +420,9 @@ export async function startManager(options) {
|
|
|
334
420
|
status,
|
|
335
421
|
provider: record.provider,
|
|
336
422
|
creatable: record.creatable,
|
|
423
|
+
...(record.purpose ? { purpose: record.purpose } : {}),
|
|
424
|
+
...(record.template ? { template: record.template } : {}),
|
|
425
|
+
...parentOf(record),
|
|
337
426
|
})),
|
|
338
427
|
]
|
|
339
428
|
: [];
|
|
@@ -342,19 +431,18 @@ export async function startManager(options) {
|
|
|
342
431
|
},
|
|
343
432
|
async get({ agent }) {
|
|
344
433
|
const record = recordOf(agent);
|
|
345
|
-
const {
|
|
434
|
+
const { units, tools, instanceOfSkill, instanceOfSlot } = await stacks.read(agent, record, connectionStatus);
|
|
346
435
|
const [name, status, mcp] = await Promise.all([
|
|
347
436
|
nameOf(agent),
|
|
348
437
|
agentStatus(agent, record.provider),
|
|
349
|
-
Promise.all(
|
|
350
|
-
.filter((b) => b.consumer === agent && b.interface === TOOLS)
|
|
351
|
-
.map(async (b) => ({
|
|
438
|
+
Promise.all(tools.map(async (b) => ({
|
|
352
439
|
slot: b.slot,
|
|
353
440
|
connection: b.provider,
|
|
354
441
|
...(records.connections[b.provider]
|
|
355
442
|
? { origin: records.connections[b.provider] }
|
|
356
443
|
: {}),
|
|
357
444
|
status: await connectionStatus(b.provider),
|
|
445
|
+
instance: instanceOfSlot(b.slot),
|
|
358
446
|
}))),
|
|
359
447
|
]);
|
|
360
448
|
return {
|
|
@@ -363,34 +451,42 @@ export async function startManager(options) {
|
|
|
363
451
|
status,
|
|
364
452
|
provider: record.provider,
|
|
365
453
|
creatable: record.creatable,
|
|
454
|
+
...(record.purpose ? { purpose: record.purpose } : {}),
|
|
455
|
+
...(record.template ? { template: record.template } : {}),
|
|
456
|
+
...parentOf(record),
|
|
366
457
|
instructions: record.instructions,
|
|
367
|
-
skills: record.skills.flatMap((skill) =>
|
|
458
|
+
skills: record.skills.flatMap((skill) => {
|
|
459
|
+
const entry = library.entry(skill);
|
|
460
|
+
return entry ? [{ ...entry, instance: instanceOfSkill(skill) }] : [];
|
|
461
|
+
}),
|
|
368
462
|
mcp,
|
|
463
|
+
units,
|
|
369
464
|
};
|
|
370
465
|
},
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const { provider } = recordOf(agent);
|
|
377
|
-
return named(agentProviders(), provider, "agent provider").call.stop({ agent });
|
|
378
|
-
},
|
|
379
|
-
async expose(input) {
|
|
466
|
+
start: ({ agent }) => startAgent(agent),
|
|
467
|
+
stop: ({ agent }) => stopAgent(agent),
|
|
468
|
+
models: ({ agent }) => makerOf(agent).models({ agent }),
|
|
469
|
+
setModel: (input) => makerOf(input.agent).setModel(input),
|
|
470
|
+
expose: (input, { caller }) => stacks.serial(input.agent, async () => {
|
|
380
471
|
const record = recordOf(input.agent);
|
|
381
472
|
if ("connection" in input)
|
|
382
473
|
return { slot: await exposeConnection(input.agent, input.connection, input.as) };
|
|
383
|
-
if (!library.has(input.skill))
|
|
474
|
+
if (!library.has(input.skill) || !seesSkill(caller, input.skill))
|
|
384
475
|
throw new Refused("unknown", `Management has no skill ${input.skill}.`);
|
|
476
|
+
// The person giving an agent's skill to anyone shares it: it's the person's now.
|
|
477
|
+
if (!agentOf(caller))
|
|
478
|
+
delete records.authors.skills[input.skill];
|
|
385
479
|
if (!record.skills.includes(input.skill)) {
|
|
386
480
|
record.skills.push(input.skill);
|
|
387
481
|
save();
|
|
388
482
|
changed(input.agent);
|
|
389
483
|
}
|
|
390
484
|
return {};
|
|
391
|
-
},
|
|
392
|
-
|
|
485
|
+
}),
|
|
486
|
+
unexpose: (input) => stacks.serial(input.agent, async () => {
|
|
393
487
|
const record = recordOf(input.agent);
|
|
488
|
+
if (stacks.fromUnit(record, input))
|
|
489
|
+
throw new Refused("state", "This came with a unit: remove the unit.");
|
|
394
490
|
if ("slot" in input) {
|
|
395
491
|
await hub.grants.unexpose({ to: input.agent, slot: input.slot });
|
|
396
492
|
return {};
|
|
@@ -401,53 +497,80 @@ export async function startManager(options) {
|
|
|
401
497
|
changed(input.agent);
|
|
402
498
|
}
|
|
403
499
|
return {};
|
|
404
|
-
},
|
|
405
|
-
/**
|
|
406
|
-
|
|
407
|
-
const record = recordOf(agent);
|
|
408
|
-
for (const [connection, origin] of Object.entries(records.connections)) {
|
|
409
|
-
if (origin.madeFor !== agent)
|
|
410
|
-
continue;
|
|
411
|
-
await removeConnection(connection, origin.provider);
|
|
412
|
-
delete records.connections[connection];
|
|
413
|
-
save();
|
|
414
|
-
}
|
|
415
|
-
await removeAgent(agent, record.provider);
|
|
416
|
-
delete records.agents[agent];
|
|
417
|
-
save();
|
|
418
|
-
return {};
|
|
419
|
-
},
|
|
500
|
+
}),
|
|
501
|
+
/** Units and connections go first: if one can't, the agent is still here to be tried again. */
|
|
502
|
+
remove: ({ agent }) => removeWhole(agent),
|
|
420
503
|
});
|
|
421
|
-
ctx.exports.connections.implement({
|
|
504
|
+
personOnly(ctx.exports.connections, "connections").implement({
|
|
422
505
|
providers: async () => ({
|
|
423
|
-
providers: await fromEach(
|
|
506
|
+
providers: (await fromEach(unitProviders(), async ({ actor, call }) => ({
|
|
424
507
|
provider: actor,
|
|
425
508
|
name: await nameOf(actor),
|
|
426
|
-
kinds: (await call.
|
|
427
|
-
|
|
509
|
+
kinds: (await call.units({})).units.flatMap((unit) => {
|
|
510
|
+
const slot = slotFieldOf(unit);
|
|
511
|
+
if (!slot)
|
|
512
|
+
return [];
|
|
513
|
+
const fields = unit.form.filter((field) => field.id !== slot);
|
|
514
|
+
return [
|
|
515
|
+
{
|
|
516
|
+
kind: unit.id,
|
|
517
|
+
title: unit.title,
|
|
518
|
+
params: JSON.parse(JSON.stringify(schemaOf(fields))),
|
|
519
|
+
secrets: JSON.parse(JSON.stringify(secretsSchemaOf(fields))),
|
|
520
|
+
},
|
|
521
|
+
];
|
|
522
|
+
}),
|
|
523
|
+
}))).filter(({ kinds }) => kinds.length > 0),
|
|
428
524
|
}),
|
|
429
525
|
// `secrets` go straight to the provider: nothing here keeps or logs them.
|
|
430
|
-
async create({ provider, kind, params, secrets, name }, { caller }) {
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
526
|
+
async create({ provider, kind, params, secrets = {}, name }, { caller }) {
|
|
527
|
+
const agent = agentOf(caller);
|
|
528
|
+
if (agent &&
|
|
529
|
+
Object.values(records.connections).filter((one) => one.madeBy === agent).length >=
|
|
530
|
+
AGENT_CONNECTIONS)
|
|
531
|
+
throw new Refused("limit", `An agent can add at most ${AGENT_CONNECTIONS} connections.`);
|
|
532
|
+
const { call } = named(unitProviders(), provider, "connection provider");
|
|
533
|
+
const unit = (await call.units({})).units.find((one) => one.id === kind);
|
|
534
|
+
const slot = unit && slotFieldOf(unit);
|
|
535
|
+
if (!unit || !slot)
|
|
536
|
+
throw new Refused("invalid", `${provider} offers no ${kind}.`);
|
|
537
|
+
const declared = Object.keys(secrets).filter((id) => unit.form.some((field) => field.id === id && field.value === "secret"));
|
|
538
|
+
const settled = settle(unit.form, { ...params, [slot]: PERSON_SLOT }, declared);
|
|
539
|
+
if (settled.problems.length)
|
|
540
|
+
throw new Refused("invalid", settled.problems.map((one) => one.message).join(" "));
|
|
541
|
+
const applied = await call.apply({
|
|
542
|
+
unit: kind,
|
|
543
|
+
values: settled.values,
|
|
544
|
+
secrets,
|
|
545
|
+
needs: {},
|
|
546
|
+
agent: "person",
|
|
435
547
|
name,
|
|
436
|
-
...(secrets ? { secrets } : {}),
|
|
437
548
|
});
|
|
549
|
+
const [made] = applied.connections;
|
|
550
|
+
if (!made || applied.connections.length !== 1) {
|
|
551
|
+
await call.remove({ instance: applied.instance }).catch(async () => {
|
|
552
|
+
for (const one of applied.connections)
|
|
553
|
+
await hub.actors.close({ actorId: one.connection });
|
|
554
|
+
});
|
|
555
|
+
throw new Refused("failed", `${kind} didn't make one connection.`);
|
|
556
|
+
}
|
|
557
|
+
const { connection, target } = made;
|
|
438
558
|
try {
|
|
439
559
|
records.connections[connection] = {
|
|
440
560
|
provider,
|
|
441
561
|
kind,
|
|
442
562
|
target,
|
|
443
563
|
madeBy: caller,
|
|
564
|
+
// One an agent made goes with it.
|
|
565
|
+
...(agent ? { madeFor: agent } : {}),
|
|
444
566
|
at: new Date().toISOString(),
|
|
445
567
|
};
|
|
568
|
+
records.instances[connection] = applied.instance;
|
|
446
569
|
save();
|
|
447
570
|
}
|
|
448
571
|
catch (error) {
|
|
449
|
-
delete records.connections[connection];
|
|
450
572
|
await closing(connection, () => removeConnection(connection, provider));
|
|
573
|
+
forget(connection);
|
|
451
574
|
throw error;
|
|
452
575
|
}
|
|
453
576
|
return { connection };
|
|
@@ -479,25 +602,52 @@ export async function startManager(options) {
|
|
|
479
602
|
status: await connectionStatus(connection),
|
|
480
603
|
};
|
|
481
604
|
},
|
|
482
|
-
async remove({ connection }) {
|
|
605
|
+
async remove({ connection }, { caller }) {
|
|
483
606
|
const origin = originOf(connection);
|
|
607
|
+
if (agentOf(caller) && origin.madeBy !== caller)
|
|
608
|
+
throw new Refused("unknown", `You have no connection ${connection}.`);
|
|
609
|
+
if (stacks.unitMaking(connection))
|
|
610
|
+
throw new Refused("state", "This connection came with a unit: remove the unit.");
|
|
484
611
|
await removeConnection(connection, origin.provider);
|
|
485
|
-
|
|
612
|
+
forget(connection);
|
|
486
613
|
save();
|
|
487
614
|
return {};
|
|
488
615
|
},
|
|
489
616
|
});
|
|
490
|
-
ctx.exports.skills.implement({
|
|
491
|
-
add: (input) => {
|
|
617
|
+
personOnly(ctx.exports.skills, "skills").implement({
|
|
618
|
+
add: (input, { caller }) => {
|
|
619
|
+
const agent = agentOf(caller);
|
|
620
|
+
if (agent) {
|
|
621
|
+
// An agent writes a skill of its own: it can't read a folder, or replace one there is.
|
|
622
|
+
if ("path" in input)
|
|
623
|
+
throw new Refused("forbidden", "An agent gives a skill as files.");
|
|
624
|
+
const named = library.nameOf(input.files);
|
|
625
|
+
if (library.has(named))
|
|
626
|
+
throw new Refused("state", `There is a skill ${named} already.`);
|
|
627
|
+
if (Object.values(records.authors.skills).filter((one) => one === agent).length >=
|
|
628
|
+
AGENT_SKILLS)
|
|
629
|
+
throw new Refused("limit", `An agent can write at most ${AGENT_SKILLS} skills.`);
|
|
630
|
+
const bytes = Object.values(input.files).reduce((sum, text) => sum + Buffer.byteLength(text), 0);
|
|
631
|
+
if (bytes > AGENT_SKILL_BYTES)
|
|
632
|
+
throw new Refused("limit", "An agent's skill can be at most 1 MB.");
|
|
633
|
+
const added = library.add(input.files, named);
|
|
634
|
+
records.authors.skills[added.name] = agent;
|
|
635
|
+
save();
|
|
636
|
+
return added;
|
|
637
|
+
}
|
|
492
638
|
const added = library.add("path" in input ? readSkillFolder(input.path) : input.files);
|
|
639
|
+
// Replaced by the person, it's the person's.
|
|
640
|
+
delete records.authors.skills[added.name];
|
|
493
641
|
for (const [agent, record] of Object.entries(records.agents))
|
|
494
642
|
if (record.skills.includes(added.name))
|
|
495
643
|
changed(agent);
|
|
496
644
|
return added;
|
|
497
645
|
},
|
|
498
|
-
list: () => ({
|
|
499
|
-
|
|
500
|
-
|
|
646
|
+
list: (_, { caller }) => ({
|
|
647
|
+
skills: library.list().filter((one) => seesSkill(caller, one.name)),
|
|
648
|
+
}),
|
|
649
|
+
get: ({ name }, { caller }) => {
|
|
650
|
+
const entry = seesSkill(caller, name) ? library.entry(name) : undefined;
|
|
501
651
|
if (!entry)
|
|
502
652
|
throw new Refused("unknown", `Management has no skill ${name}.`);
|
|
503
653
|
return {
|
|
@@ -509,10 +659,12 @@ export async function startManager(options) {
|
|
|
509
659
|
.map(([agent]) => agent),
|
|
510
660
|
};
|
|
511
661
|
},
|
|
512
|
-
remove: ({ name }) => {
|
|
513
|
-
|
|
662
|
+
remove: ({ name }, { caller }) => {
|
|
663
|
+
const agent = agentOf(caller);
|
|
664
|
+
if (!library.has(name) || (agent && records.authors.skills[name] !== agent))
|
|
514
665
|
throw new Refused("unknown", `Management has no skill ${name}.`);
|
|
515
666
|
dropSkill(name);
|
|
667
|
+
delete records.authors.skills[name];
|
|
516
668
|
save();
|
|
517
669
|
return {};
|
|
518
670
|
},
|