@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
@@ -1,13 +1,11 @@
1
1
  import { Refused } from "@agentorg/sdk";
2
- import { createAgent } from "./agent.js";
3
- import { createHistory } from "./history.js";
2
+ import { createChats } from "./chats.js";
4
3
  import { instructionsWith, skillTool } from "./settings.js";
5
- import { listedBy, listsTools, offered } from "./tools.js";
6
- /** Where the agent keeps the model chosen for it, beside its record in the store. */
4
+ import { listedBy, listsTools, offered, relayed } from "./tools.js";
5
+ /** Where the agent keeps the model chosen for it, beside its chats in the store. */
7
6
  const MODEL_KEY = "settings/model";
8
7
  export function startEngine(ctx, options) {
9
8
  const { store } = options;
10
- const history = createHistory(store, ctx.log);
11
9
  /** The model chosen for this agent, if any; read for each request. */
12
10
  const chosen = async () => {
13
11
  const { value } = await store.get({ key: MODEL_KEY });
@@ -25,7 +23,10 @@ export function startEngine(ctx, options) {
25
23
  else
26
24
  ctx.log.info("Everything is offered to the model again.");
27
25
  };
28
- /** What a request says and offers, read afresh: nothing here outlives the request. */
26
+ /**
27
+ * What a request says and offers, and to which model, read afresh: none of it outlives it.
28
+ * `leftOut` is what it couldn't offer, and why.
29
+ */
29
30
  const request = async () => {
30
31
  const settings = ctx.slots.settings.bound ? await ctx.slots.settings.call.get({}) : undefined;
31
32
  const told = settings
@@ -37,52 +38,123 @@ export function startEngine(ctx, options) {
37
38
  .exposed()
38
39
  .filter((slot) => listsTools(slot.described))
39
40
  .map((slot) => listedBy(slot.slot, slot)));
40
- const { tools, leftOut } = offered([...own, ...exposed.flatMap((one) => one.candidates)]);
41
- report([...told.leftOut, ...exposed.flatMap((one) => one.leftOut), ...leftOut]);
42
- return { instructions: told.instructions, tools };
41
+ const fitted = offered([...own, ...exposed.flatMap((one) => one.candidates)]);
42
+ const leftOut = [...told.leftOut, ...exposed.flatMap((one) => one.leftOut), ...fitted.leftOut];
43
+ report(leftOut);
44
+ return { instructions: told.instructions, tools: fitted.tools, model: await chosen(), leftOut };
43
45
  };
44
- const agent = createAgent({
45
- request,
46
- model: ctx.slots.model,
47
- chosenModel: chosen,
48
- history,
49
- chat: {
46
+ /** The model slot as the looper asks it: what it refuses is told with its code. */
47
+ const model = {
48
+ respond: (asked) => relayed(ctx.slots.model.call.respond(asked)),
49
+ // A model that is gone has nothing to stop.
50
+ cancel: async (asked) => (ctx.slots.model.bound ? ctx.slots.model.call.cancel(asked) : {}),
51
+ };
52
+ const chats = createChats({
53
+ store,
54
+ stopped: options.stopped ?? false,
55
+ model,
56
+ request: () => relayed(request()),
57
+ emit: {
50
58
  message: (message) => ctx.exports.chat.emit("message", message),
51
- delta: (id, text) => ctx.exports.chat.emit("delta", { id, text }),
59
+ delta: (delta) => ctx.exports.chat.emit("delta", delta),
52
60
  status: (status) => ctx.exports.chat.emit("status", status),
61
+ chat: (entry) => ctx.exports.chat.emit("chat", entry),
53
62
  },
54
63
  log: ctx.log,
55
64
  });
56
- ctx.slots.model.on("delta", ({ requestId, text }) => agent.delta(requestId, text));
65
+ ctx.slots.model.on("delta", ({ requestId, text }) => chats.delta(requestId, text));
66
+ /**
67
+ * The turns that were cut off when the agent last ran go on once the model is bound, the
68
+ * settings answer, and no tools slot is away. As the agent starts, what it is bound to may not
69
+ * be running yet, so this is looked at again whenever that may have changed: a slot's provider
70
+ * is back, a tools slot is made or gone, or one says its tools changed. A tools slot that stays
71
+ * away holds them back; a message in a chat starts a turn anyway.
72
+ */
73
+ let wentOn = false;
74
+ /** Each tools slot heard for its tools changing, with how to stop hearing it. */
75
+ const heard = new Map();
76
+ const goOn = async () => {
77
+ for (const slot of ctx.exposed())
78
+ if (listsTools(slot.described) && !heard.has(slot))
79
+ heard.set(slot, slot.on("changed", look));
80
+ if (!ctx.slots.model.bound)
81
+ return;
82
+ // Refused while the settings can't answer, as a turn's own request would be.
83
+ const { leftOut } = await request();
84
+ if (leftOut.some((one) => one.reason === "not_connected"))
85
+ return;
86
+ await chats.resume();
87
+ done();
88
+ };
89
+ /** Nothing more is looked for: the turns have gone on, or the agent is stopped or closing. */
90
+ const done = () => {
91
+ wentOn = true;
92
+ for (const stop of heard.values())
93
+ stop();
94
+ heard.clear();
95
+ };
96
+ /** The look running now, and whether to look once more after it. */
97
+ let looking;
98
+ let again = false;
99
+ const look = () => {
100
+ again = true;
101
+ looking ??= (async () => {
102
+ // A start binds its slots one after another in one go, and one not bound yet reads as one
103
+ // the agent doesn't have: so it looks once they all are.
104
+ await Promise.resolve();
105
+ while (again && !wentOn) {
106
+ again = false;
107
+ await goOn().catch((error) => ctx.log.info("Turns that were cut off aren't looked for yet.", { error: String(error) }));
108
+ }
109
+ looking = undefined;
110
+ })();
111
+ };
112
+ ctx.slots.model.onBind(look);
113
+ ctx.slots.settings.onBind(look);
114
+ ctx.onExposed(look);
57
115
  ctx.exports.chat.implement({
58
- send: ({ text }) => agent.send(text),
59
- history: ({ before, limit }) => history.page(before, limit ?? 50),
60
- cancel: async () => {
61
- await agent.cancel();
62
- return {};
63
- },
64
- state: () => agent.state(),
65
- retry: () => agent.retry(),
66
- models: async () => {
116
+ send: (input) => chats.send(input),
117
+ history: (input) => chats.history(input),
118
+ state: () => chats.state(),
119
+ list: (input) => chats.list(input),
120
+ rename: (input) => chats.rename(input),
121
+ archive: (input) => chats.archive(input),
122
+ });
123
+ return {
124
+ /** The models the agent can use, and the one chosen for it. */
125
+ async models() {
67
126
  const [{ models }, current] = await Promise.all([ctx.slots.model.call.models({}), chosen()]);
68
127
  return { models, ...(current ? { current } : {}) };
69
128
  },
70
- setModel: async ({ model }) => {
129
+ /** Choose the model for its next requests, kept with it; with none, the provider's default. */
130
+ async setModel(model) {
71
131
  if (!model) {
72
132
  await store.delete({ key: MODEL_KEY });
73
- return {};
133
+ return;
74
134
  }
75
135
  const { models } = await ctx.slots.model.call.models({});
76
136
  if (!models.some((offered) => offered.id === model))
77
137
  throw new Refused("unknown", `${model} isn't one of the models offered.`);
78
138
  await store.set({ key: MODEL_KEY, value: model });
79
- return {};
80
139
  },
81
- });
82
- return {
83
- /** Stop the turn in progress. */
84
- cancel: () => agent.cancel(),
85
- /** Settles when the latest turn has written all it will. */
86
- settled: () => agent.settled(),
140
+ /** Stop the agent: its turns are cut and none starts until `start`. Its chats stay readable. */
141
+ stop() {
142
+ done();
143
+ return chats.stop();
144
+ },
145
+ start: () => chats.start(),
146
+ /**
147
+ * Close for good: its turns are cut, and none starts after. They go on in the engine that
148
+ * opens this store next.
149
+ */
150
+ close() {
151
+ done();
152
+ return chats.close();
153
+ },
154
+ /** Settles when every chat's latest turn has written all it will. */
155
+ async settled() {
156
+ await looking;
157
+ await chats.settled();
158
+ },
87
159
  };
88
160
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The engine's record when it runs as an ordinary program: one small JSON file per key, in the
3
3
  * agent's own folder. A new message writes one file, however long the record is, and each write
4
- * lands whole. It answers as `kv@1` does, so the history reads it as it reads any store.
4
+ * lands whole. It answers as `kv@1` does, so the engine reads it as it reads any store.
5
5
  */
6
6
  import { randomUUID } from "node:crypto";
7
7
  import { mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
@@ -14,7 +14,7 @@ export interface AgentProviderOptions {
14
14
  log?: Log;
15
15
  }
16
16
  export interface AgentProvider {
17
- /** Leave the Hub and stop every agent. They stay, and those not stopped resume at the next start. */
17
+ /** Leave the Hub, every agent with it. They stay, and rejoin at the next start as each was left. */
18
18
  close(): Promise<void>;
19
19
  }
20
20
  /** Run the bundled agent provider as an Actor exporting `agent.provider@1` as `provider`. */
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * The bundled agent provider: it offers one creatable, Assistant, and runs each agent in this
3
- * process as its own Actor on its own channel. Stopping an agent closes its channel; starting it
4
- * opens one. Each agent's record and model choice are kept in the provider's private folder.
3
+ * process as its own Actor on its own channel. A stopped agent stays on its channel, so its chats
4
+ * can be read, and runs no turn until it is started. Each agent's chats and model choice are kept
5
+ * in the provider's private folder.
5
6
  */
6
7
  import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
7
8
  import { rm } from "node:fs/promises";
8
9
  import { join } from "node:path";
9
10
  import { agentProvider, agentSettings, chat, hubActors, llmSession } from "@agentorg/interfaces";
10
- import { Refused } from "@agentorg/sdk";
11
+ import { NotConnected, Refused } from "@agentorg/sdk";
11
12
  import { connect } from "@agentorg/sdk/client";
12
13
  import { z } from "zod";
13
14
  import { startEngine } from "./engine.js";
@@ -22,8 +23,6 @@ const ASSISTANT = {
22
23
  description: "An agent that chats, and acts through the connections you give it.",
23
24
  instructions: ASSISTANT_INSTRUCTIONS,
24
25
  asks: [],
25
- skills: [],
26
- mcp: [],
27
26
  };
28
27
  /** What is kept of an agent between runs, in the provider's private file. */
29
28
  const Stored = z.object({ credential: z.string(), stopped: z.boolean() });
@@ -70,16 +69,33 @@ export async function startAgentProvider(options) {
70
69
  throw new Refused("unknown", `This provider made no agent ${agent}.`);
71
70
  return found;
72
71
  };
72
+ /** An agent's engine, while it is on its channel: running, or stopped. */
73
+ const engineOf = (agent) => {
74
+ const { channel, engine, detail } = held(agent);
75
+ if (!channel || !engine)
76
+ throw new Refused("unavailable", detail ?? "It isn't running.");
77
+ return engine;
78
+ };
79
+ /**
80
+ * What an agent's model answered. A model that is away is refused as that, where the bare
81
+ * error would reach the caller as its own slot being away.
82
+ */
83
+ const ofModel = (asked) => asked.catch((error) => {
84
+ if (error instanceof NotConnected)
85
+ throw new Refused("not_connected", "Its model isn't connected.");
86
+ throw error;
87
+ });
73
88
  const inTurn = (agent, work) => {
74
89
  const one = held(agent);
75
90
  const done = one.queue.then(() => work(one));
76
91
  one.queue = done.catch(() => { });
77
92
  return done;
78
93
  };
79
- /** Join the Hub as the agent, with its engine running. */
94
+ /** Join the Hub as the agent, with its engine running, or stopped if it was left so. */
80
95
  const open = async (agent, one) => {
81
96
  one.state = "starting";
82
97
  delete one.detail;
98
+ const { stopped } = stored[agent];
83
99
  let engine;
84
100
  try {
85
101
  const channel = await connect({
@@ -93,12 +109,13 @@ export async function startAgentProvider(options) {
93
109
  engine = startEngine(ctx, {
94
110
  store: createLocalStore(folderOf(agent)),
95
111
  instructions: () => ASSISTANT_INSTRUCTIONS,
112
+ stopped,
96
113
  });
97
114
  },
98
115
  });
99
116
  one.channel = channel;
100
117
  one.engine = engine;
101
- one.state = "running";
118
+ one.state = stopped ? "stopped" : "running";
102
119
  void channel.closed.then(() => {
103
120
  if (one.channel !== channel)
104
121
  return;
@@ -113,22 +130,20 @@ export async function startAgentProvider(options) {
113
130
  throw error;
114
131
  }
115
132
  };
116
- /** Cut its turn, close its channel, and wait until its engine has written all it will. */
133
+ /** Cut its turns, close its channel, and wait until its engine has written all it will. */
117
134
  const halt = async (one) => {
118
135
  const { channel, engine } = one;
119
136
  delete one.channel;
120
137
  if (!channel || !engine)
121
138
  return;
122
- await engine.cancel();
139
+ await engine.close();
123
140
  channel.close();
124
141
  await channel.closed;
125
142
  await engine.settled();
126
143
  };
127
- await Promise.all(Object.entries(stored).map(async ([agent, { stopped }]) => {
144
+ await Promise.all(Object.keys(stored).map(async (agent) => {
128
145
  const one = { state: "stopped", queue: Promise.resolve() };
129
146
  agents.set(agent, one);
130
- if (stopped)
131
- return;
132
147
  try {
133
148
  await open(agent, one);
134
149
  }
@@ -153,9 +168,6 @@ export async function startAgentProvider(options) {
153
168
  start(ctx) {
154
169
  ctx.exports.provider.implement({
155
170
  creatable: async () => ({ creatables: [ASSISTANT] }),
156
- skill: async ({ creatable, name, path: file }) => {
157
- throw new Refused("unknown", `${creatable} has no file ${file} of a skill ${name}.`);
158
- },
159
171
  async create({ creatable, name }) {
160
172
  if (creatable !== ASSISTANT.id)
161
173
  throw new Refused("unknown", `This provider offers no ${creatable}.`);
@@ -193,15 +205,31 @@ export async function startAgentProvider(options) {
193
205
  return {};
194
206
  stored[agent].stopped = false;
195
207
  save();
196
- await open(agent, one);
208
+ // One whose connection ended joins again; one that is only stopped goes on.
209
+ if (!one.channel || !one.engine)
210
+ await open(agent, one);
211
+ else {
212
+ one.engine.start();
213
+ one.state = "running";
214
+ }
197
215
  return {};
198
216
  }),
199
217
  stop: ({ agent }) => inTurn(agent, async (one) => {
200
218
  stored[agent].stopped = true;
201
219
  save();
202
- await halt(one);
203
- one.state = "stopped";
204
- delete one.detail;
220
+ // One whose connection ended joins again, stopped, so that it can be read.
221
+ if (!one.channel || !one.engine)
222
+ await open(agent, one);
223
+ else {
224
+ await one.engine.stop();
225
+ one.state = "stopped";
226
+ }
227
+ return {};
228
+ }),
229
+ models: ({ agent }) => ofModel(engineOf(agent).models()),
230
+ // In the agent's turn, so a choice never lands after the agent is removed.
231
+ setModel: ({ agent, model }) => inTurn(agent, async () => {
232
+ await ofModel(engineOf(agent).setModel(model));
205
233
  return {};
206
234
  }),
207
235
  remove: ({ agent }) => inTurn(agent, async (one) => {
@@ -1,30 +1,13 @@
1
- /**
2
- * The assistant's tools, gathered afresh for each model request. A `tools@1` slot gives whatever
3
- * it lists now, with its server's own input schemas. Each is offered as
4
- * `<slot>__<name>`, and a call runs through the slot, so the Hub checks and audits it like any
5
- * other.
6
- */
7
- import { type Tool } from "@agentorg/interfaces";
1
+ import { type Outcome, type Tool } from "@agentorg/looper";
8
2
  import { type DescribedInterface } from "@agentorg/sdk";
9
3
  import type { z } from "zod";
10
- /** How a function call ended, as the model and the record see it. */
11
- export interface Outcome {
12
- output: string;
13
- ok: boolean;
14
- code?: string;
15
- }
16
- interface Found {
17
- tool: z.input<typeof Tool>;
18
- run(input: unknown): Promise<Outcome>;
19
- }
20
- export type Tools = Map<string, Found>;
21
4
  /** A tool as its slot gives it, before it has the name a request offers it under. */
22
5
  export interface Candidate {
23
6
  slot: string;
24
7
  name: string;
25
8
  description?: string;
26
9
  parameters: Record<string, z.core.util.JSONType>;
27
- run: Found["run"];
10
+ run(input: unknown): Promise<Outcome>;
28
11
  }
29
12
  /**
30
13
  * Something the model isn't offered, and why: `name` (past 64 characters as a function name),
@@ -36,18 +19,21 @@ export interface LeftOut {
36
19
  tool?: string;
37
20
  reason: string;
38
21
  }
39
- /** A function's output is cut to this many bytes of UTF-8, so one result can't fill a request. */
40
- export declare const MAX_OUTPUT: number;
41
22
  /** What a request can carry: its tools count, and, with the record and instructions, one frame. */
42
23
  export declare const MAX_TOOLS = 128;
43
24
  export declare const TOOLS_BUDGET: number;
44
- /** The code and message of anything a call or a turn can throw. */
25
+ /** The first `length` UTF-16 units of `text`, never ending inside a surrogate pair. */
26
+ export declare function head(text: string, length: number): string;
27
+ /** The code and message of what a call through a slot threw. */
45
28
  export declare function codeOf(error: unknown): {
46
29
  code: string;
47
30
  message: string;
48
31
  };
49
- /** At most `MAX_OUTPUT` bytes of `text`, cut at a character, saying so when it is cut. */
50
- export declare function capped(text: string): string;
32
+ /**
33
+ * A call through a slot, as the looper is told how it went: what the slot refuses is the looper's
34
+ * `Refusal`, with the code to show and the slot's own error as its cause.
35
+ */
36
+ export declare const relayed: <T>(call: Promise<T>) => Promise<T>;
51
37
  /** Whether a slot is a `tools@1`, whose tools are whatever it lists. */
52
38
  export declare const listsTools: (described: DescribedInterface) => boolean;
53
39
  /**
@@ -66,9 +52,6 @@ export declare function listedBy(name: string, slot: {
66
52
  * when a request is full; what doesn't fit is reported, never sent.
67
53
  */
68
54
  export declare function offered(candidates: Candidate[]): {
69
- tools: Tools;
55
+ tools: Tool[];
70
56
  leftOut: LeftOut[];
71
57
  };
72
- /** Run a function call through its slot: the result, or the refusal, as the function's output. */
73
- export declare function runTool(tools: Tools, name: string, args: string): Promise<Outcome>;
74
- export {};
@@ -5,10 +5,8 @@
5
5
  * other.
6
6
  */
7
7
  import { tools as toolsInterface } from "@agentorg/interfaces";
8
+ import { Refusal } from "@agentorg/looper";
8
9
  import { describeInterface, NotConnected, OutcomeUnknown, Refused, } from "@agentorg/sdk";
9
- import { head } from "./history.js";
10
- /** A function's output is cut to this many bytes of UTF-8, so one result can't fill a request. */
11
- export const MAX_OUTPUT = 16 * 1024;
12
10
  /** What a request can carry: its tools count, and, with the record and instructions, one frame. */
13
11
  export const MAX_TOOLS = 128;
14
12
  export const TOOLS_BUDGET = 256 * 1024;
@@ -16,7 +14,12 @@ const MAX_NAME = 64;
16
14
  const MAX_DESCRIPTION = 1024;
17
15
  const encoder = new TextEncoder();
18
16
  const TOOLS_ID = describeInterface(toolsInterface).id;
19
- /** The code and message of anything a call or a turn can throw. */
17
+ /** The first `length` UTF-16 units of `text`, never ending inside a surrogate pair. */
18
+ export function head(text, length) {
19
+ const cut = text.slice(0, length);
20
+ return /[\uD800-\uDBFF]$/.test(cut) ? cut.slice(0, -1) : cut;
21
+ }
22
+ /** The code and message of what a call through a slot threw. */
20
23
  export function codeOf(error) {
21
24
  if (error instanceof Refused)
22
25
  return { code: error.code, message: error.message };
@@ -26,14 +29,16 @@ export function codeOf(error) {
26
29
  return { code: "outcome_unknown", message: error.message };
27
30
  return { code: "failed", message: "The call failed." };
28
31
  }
29
- /** At most `MAX_OUTPUT` bytes of `text`, cut at a character, saying so when it is cut. */
30
- export function capped(text) {
31
- const bytes = encoder.encode(text);
32
- if (bytes.byteLength <= MAX_OUTPUT)
33
- return text;
34
- const head = new TextDecoder().decode(bytes.subarray(0, MAX_OUTPUT - 16)).replace(/�$/, "");
35
- return `${head}… (cut)`;
36
- }
32
+ /**
33
+ * A call through a slot, as the looper is told how it went: what the slot refuses is the looper's
34
+ * `Refusal`, with the code to show and the slot's own error as its cause.
35
+ */
36
+ export const relayed = (call) => call.catch((error) => {
37
+ const { code, message } = codeOf(error);
38
+ throw code === "failed" && !(error instanceof Refused)
39
+ ? error
40
+ : new Refusal(code, message, { cause: error });
41
+ });
37
42
  /** Whether a slot is a `tools@1`, whose tools are whatever it lists. */
38
43
  export const listsTools = (described) => described.id === TOOLS_ID;
39
44
  /**
@@ -91,33 +96,7 @@ export function offered(candidates) {
91
96
  continue;
92
97
  }
93
98
  bytes += size;
94
- tools.set(toolName, { tool, run });
95
- }
96
- return { tools, leftOut };
97
- }
98
- const failure = ({ code, message }) => JSON.stringify({ error: { code, message } });
99
- /** Run a function call through its slot: the result, or the refusal, as the function's output. */
100
- export async function runTool(tools, name, args) {
101
- const refused = (code, message) => ({
102
- output: failure({ code, message }),
103
- ok: false,
104
- code,
105
- });
106
- const found = tools.get(name);
107
- if (!found)
108
- return refused("unknown_tool", `No tool is named ${name.slice(0, 64)}.`);
109
- let input;
110
- try {
111
- input = args.trim() === "" ? {} : JSON.parse(args);
112
- }
113
- catch {
114
- return refused("invalid", "The arguments aren't JSON.");
115
- }
116
- try {
117
- return await found.run(input);
118
- }
119
- catch (error) {
120
- const { code, message } = codeOf(error);
121
- return refused(code, message);
99
+ tools.set(toolName, { ...tool, run: (input) => relayed(run(input)) });
122
100
  }
101
+ return { tools: [...tools.values()], leftOut };
123
102
  }
@@ -20,6 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@agentorg/interfaces": "0.0.0",
23
+ "@agentorg/looper": "0.0.0",
23
24
  "@agentorg/sdk": "0.0.0",
24
25
  "zod": "^4.6.7"
25
26
  },
package/dist/src/api.js CHANGED
@@ -59,6 +59,10 @@ export function serveSocket(socket, deps) {
59
59
  void (async () => {
60
60
  if (!isName(slot) || !isName(method))
61
61
  throw new Refused("invalid", "A call names its slot and method.");
62
+ // A live ticket is for the gateway's own pipe at `/live`, never for a page to hold, under
63
+ // whatever slot a page might expose it at.
64
+ if (method === "live")
65
+ throw new Refused("forbidden", "Open a live surface at /live.");
62
66
  return deps.person.call(slot, method, input);
63
67
  })()
64
68
  .then((result) => send({ id, result }), (error) => send({ id, error: errorBody(error) }))
@@ -0,0 +1,2 @@
1
+ /** One `folders@1` call, checked as a call through the Hub would be. */
2
+ export declare function callFolders(method: string, input: unknown): Promise<unknown>;
@@ -0,0 +1,45 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, isAbsolute, join, normalize } from "node:path";
4
+ import { FOLDERS_MAX, folders } from "@agentorg/interfaces";
5
+ import { Refused } from "@agentorg/sdk";
6
+ async function isFolder(at, entry) {
7
+ if (entry.isDirectory())
8
+ return true;
9
+ if (!entry.isSymbolicLink())
10
+ return false;
11
+ return stat(join(at, entry.name)).then((found) => found.isDirectory(), () => false);
12
+ }
13
+ async function list(path) {
14
+ if (path !== undefined && !isAbsolute(path))
15
+ throw new Refused("invalid", "A folder's path starts at the top of the disk.");
16
+ const at = path === undefined ? homedir() : normalize(path);
17
+ const entries = await readdir(at, { withFileTypes: true }).catch((error) => {
18
+ if (error.code === "ENOENT" || error.code === "ENOTDIR")
19
+ throw new Refused("unknown", "There's no such folder.");
20
+ throw new Refused("invalid", "That folder can't be read.");
21
+ });
22
+ // Dot folders stay out of the list, as in a file picker; a path can still be typed.
23
+ const shown = entries.filter((entry) => !entry.name.startsWith("."));
24
+ const kept = await Promise.all(shown.map((entry) => isFolder(at, entry)));
25
+ const names = shown
26
+ .filter((_, i) => kept[i])
27
+ .map((entry) => entry.name)
28
+ .sort((a, b) => a.localeCompare(b))
29
+ .slice(0, FOLDERS_MAX);
30
+ const parent = dirname(at);
31
+ return {
32
+ path: at,
33
+ ...(parent === at ? {} : { parent }),
34
+ folders: names.map((name) => ({ name })),
35
+ };
36
+ }
37
+ /** One `folders@1` call, checked as a call through the Hub would be. */
38
+ export function callFolders(method, input) {
39
+ if (method !== "list")
40
+ throw new Refused("invalid", `folders has no method ${method}.`);
41
+ const parsed = folders.methods.list.input.safeParse(input);
42
+ if (!parsed.success)
43
+ throw new Refused("invalid", `folders.list: ${parsed.error.message}`);
44
+ return list(parsed.data.path);
45
+ }
@@ -1,12 +1,18 @@
1
1
  import { type IncomingMessage, type ServerResponse } from "node:http";
2
2
  import { Hub } from "@agentorg/hub";
3
3
  import { type Programs, type ProgramsOptions } from "./programs.js";
4
+ /** A model's reply can take minutes; every other interface keeps the Hub's own limit. */
5
+ export declare const HUB_OPTIONS: {
6
+ answerTimeoutsMs: {
7
+ [x: string]: number;
8
+ };
9
+ };
4
10
  /** A route another part of the app adds, such as the model Actor's sign-in callback (step 5). */
5
11
  export type Route = (request: IncomingMessage, response: ServerResponse, url: URL) => void;
6
12
  export interface AppOptions {
7
13
  /** The Hub home. */
8
14
  home: string;
9
- /** The gateway's port on 127.0.0.1; any free one by default. */
15
+ /** The gateway's port on 127.0.0.1; by default the kept sessions' if it can, else any free one. */
10
16
  port?: number;
11
17
  /** Where the shell's static files are; `@agentorg/shell`'s build by default. */
12
18
  shell?: string;