@agentorg/cli 0.0.7 → 0.0.9

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 (33) hide show
  1. package/README.md +3 -3
  2. package/dist/node_modules/@agentorg/computer/dist/types/generated/schema.d.ts +3 -0
  3. package/dist/node_modules/@agentorg/computer/package.json +1 -1
  4. package/dist/node_modules/@agentorg/computer-connector/dist/src/provider.js +3 -3
  5. package/dist/node_modules/@agentorg/computer-connector/dist/src/unit.d.ts +1 -1
  6. package/dist/node_modules/@agentorg/computer-connector/dist/src/unit.js +34 -30
  7. package/dist/node_modules/@agentorg/computer-connector/package.json +1 -1
  8. package/dist/node_modules/@agentorg/interfaces/dist/src/index.d.ts +0 -1
  9. package/dist/node_modules/@agentorg/interfaces/dist/src/index.js +0 -1
  10. package/dist/node_modules/@agentorg/interfaces/dist/src/manager.d.ts +1 -1
  11. package/dist/node_modules/@agentorg/interfaces/dist/src/manager.js +1 -1
  12. package/dist/node_modules/@agentorg/looper/dist/src/loop.d.ts +40 -17
  13. package/dist/node_modules/@agentorg/looper/dist/src/loop.js +142 -69
  14. package/dist/node_modules/@agentorg/looper/dist/src/looper.d.ts +62 -18
  15. package/dist/node_modules/@agentorg/looper/dist/src/looper.js +588 -108
  16. package/dist/node_modules/@agentorg/looper/dist/src/main.d.ts +90 -0
  17. package/dist/node_modules/@agentorg/looper/dist/src/main.js +205 -0
  18. package/dist/node_modules/@agentorg/looper/dist/src/record.d.ts +156 -16
  19. package/dist/node_modules/@agentorg/looper/dist/src/record.js +292 -58
  20. package/dist/node_modules/@agentorg/manager/dist/src/control.d.ts +1 -1
  21. package/dist/node_modules/@agentorg/manager/dist/src/control.js +1 -1
  22. package/dist/node_modules/@agentorg/shell/dist/app.css +290 -375
  23. package/dist/node_modules/@agentorg/shell/dist/app.js +141 -144
  24. package/dist/node_modules/assistant/dist/src/chats.d.ts +14 -7
  25. package/dist/node_modules/assistant/dist/src/chats.js +24 -25
  26. package/dist/node_modules/assistant/dist/src/engine.js +11 -5
  27. package/dist/node_modules/chatgpt/dist/src/account.js +1 -1
  28. package/dist/src/gateway.js +0 -3
  29. package/package.json +1 -1
  30. package/dist/node_modules/@agentorg/interfaces/dist/src/folders.d.ts +0 -19
  31. package/dist/node_modules/@agentorg/interfaces/dist/src/folders.js +0 -25
  32. package/dist/src/folders.d.ts +0 -2
  33. package/dist/src/folders.js +0 -45
package/README.md CHANGED
@@ -18,10 +18,10 @@ AgentOrg creates and manages agents. Each part is a program that joins a Hub as
18
18
  - `@agentorg/hub`: the core. Its state, in `node:sqlite` in a private home: Actors, what each takes and offers, bindings and an audit trail. An Actor is a program that connects itself, with a credential. A binding is the grant, and it needs both ends: the export side (the provider, an Actor already bound to that export, or a manager) and the slot side (the consumer itself, or a manager). Through `hub.grants@1`, a caller with both ends binds; any other is refused. `hub.grants.expose({provider, export, to, as?})` exposes an `rpc` export to one Actor: a slot made on `to` at run time, typed by the export's interface and bound to it in one step, named `as` (one word of at most 48 characters) or by default the provider's name camelCased, `_` and the export's last part, made unique on `to` with `_2`, `_3`…. Its authority is a binding's, `to` being the consumer. Exposing the same export to the same Actor again gives its slot back. The slot goes with its binding: `unexpose({to, slot})`, `unbind`, the export no longer declared or either end closed; `hub.bindings` marks such a binding `exposed: true`. A manager is any Actor bound to `hub.bindings@1`. `hub.actors.create` can bind the new Actor's slots at once, with the creator's export side. The router serves it over WebSocket (`listen`), one yamux channel per Actor: a client's first stream is `hello` with its credential, then every `call` and `subscribe` stream is resolved through its binding, validated against the interface's JSON Schemas in both directions (ajv, 2020-12, in a pool of worker threads, each check under a time budget, since schemas come from Actors), forwarded to the provider with the caller named, and audited. An Actor learns which of its slots are bound from hello's result, then from a `slot` notice on each change. Every Actor's record in `hub.actors` says whether it is `connected`, holding a channel to the Hub now, and `hub.actors`' `changed` event names an Actor whose record may differ. When a redeclaration changes the interface of a bound name, the bindings whose other end doesn't declare the new one are dropped (audited as `binding.drop`). A refusal that names an Actor names it as "Name (id)", and says what the caller can do instead.
19
19
  - `@agentorg/cli`: AgentOrg as one app. `agentorg` runs a Hub, the person's gateway and the shell on loopback; see "The app: the person's gateway".
20
20
  - `@agentorg/mcp`: the built-in MCP provider, an external Actor exporting `unit.provider@1` as `provider`, and the only code that imports the MCP client (`@modelcontextprotocol/client`). `startMcpProvider({hubUrl, actorId, credential, dir})` runs it; its slot `actors` is bound to `hub.actors@1`. It offers two kinds: `url` attaches to a server over Streamable HTTP, sending `secrets.token` as a bearer token on every request (only over https or to this machine), and `command` starts a local server over stdio, with the basic environment and each of its `secrets` as an environment variable. Each connection joins the Hub as its own Actor exporting `tools@1` as `tools`: `list` and `call` go to the server, a tool's result comes back as text, with `failed` when the tool reported an error, and `changed` fires when the server's tools change or it goes or comes back. While a server is away `list` and `call` are refused `not_connected`, and the provider tries again after a backoff from 1 s doubling to 60 s (starting over after 10 minutes up). `create`'s `secrets` are write-only: the values cross once and go only to the provider's private `dir`, never into `params`, `target`, `detail`, status or a log (what a server prints is scrubbed of them). Each connection's Hub credential, parameters and secrets are kept in that folder, and connections rejoin at its next start. A server started by `command` is an ordinary process with the person's rights, outside the Hub's sandbox.
21
- - `@agentorg/manager`: management, an external Actor exporting `agents@1`, `connections@1`, `skills@1` and, as `settings`, `agent.settings@1`. `startManager({hubUrl, actorId, credential, dir})` runs it; bound to `hub.actors@1`, `hub.grants@1` and `hub.bindings@1`, it is a manager. A provider of either sort is connected by exposing its export to it: the Hub's slots are the only list. It keeps what the Hub can't know, in its private `dir`: each connection's origin, each agent's provider, creatable, instructions and skills, and the skills as `SKILL.md` folders. Status is read when asked, what a connection is exposed to is the Hub's bindings, and `secrets` go straight to the provider. `agents.create` is all or none: the provider makes the agent, its `settings` are bound, its skills added (one whose name is here keeps the one here), each connection it needs made or picked and exposed under its slot, and its chats exposed to the person; if a step fails, what this creation made is removed. The Hub drops that exposure when an agent comes back offering its chats in another shape, as after an upgrade, so management makes it again each time an agent connects. An agent reads only its own instructions and skills, and only it hears that they changed. `agents.models` and `agents.setModel` pass to the agent's provider, as `start` and `stop` do; stopping an agent is the one way to stop its work.
21
+ - `@agentorg/manager`: management, an external Actor exporting `agents@1`, `connections@1`, `skills@1` and, as `settings`, `agent.settings@1`. `startManager({hubUrl, actorId, credential, dir})` runs it; bound to `hub.actors@1`, `hub.grants@1` and `hub.bindings@1`, it is a manager. A provider of either sort is connected by exposing its export to it: the Hub's slots are the only list. It keeps what the Hub can't know, in its private `dir`: each connection's origin, each agent's provider, creatable, instructions and skills, and the skills as `SKILL.md` folders. Status is read when asked, what a connection is exposed to is the Hub's bindings, and `secrets` go straight to the provider. `agents.create` is all or none: the provider makes the agent, its `settings` are bound, its skills added (one whose name is here keeps the one here), each connection it needs made or picked and exposed under its slot, and its chats exposed to the person; if a step fails, what this creation made is removed. The Hub drops that exposure when an agent comes back offering its chats in another shape, as after an upgrade, so management makes it again each time an agent connects. An agent reads only its own instructions and skills, and only it hears that they changed. `agents.models` and `agents.setModel` pass to the agent's provider, as `start` and `stop` do; stopping an agent stops all of its work.
22
22
  - `chatgpt`: the model, a program. It signs in with ChatGPT for the person's plan (`account.login@1`: OpenAI's OAuth for open-source apps, with dynamic registration, PKCE and a verified id_token; refresh tokens rotate, one refresh at a time) and serves `llm.session@1` on that plan through the Responses API, streamed and stored nowhere. The tokens never leave it: never in a reply, an event, a log or a refusal. It never uses an API key or API billing: a usage limit is refused `usage_limit`, with where to manage it. `startChatGPT({hubUrl, actorId, credential, dir})` runs it as an Actor exporting `login` and `session` with no slots at all: the sign-in is in `sign-in.json` in its private `dir` (mode 0600, written through before a change is acknowledged, since a rotated refresh token can't be had again), and it reaches OpenAI over the network directly, https only, with redirects returned as they are. Its tests run against an in-process fake of OpenAI, so none reaches OpenAI.
23
- - `@agentorg/looper`: an agent's loops, knowing nothing of what hosts them. `createLooper` is given a model client, the tools a request may call, a store for its records and where to say what it does, each as a plain type of its own, and takes a message with the name of the thread it came in on. A thread has its record and its loop: a turn sends the record and the tools to the model, runs what the model calls, and goes on until the model answers or the turn has made 200 calls. Turns of different threads run side by side, four at once, and one more that only a thread already begun may take; a turn past that waits for a place, its message already recorded and said, and while begun threads keep five turns running a new thread goes on waiting. Sixteen turns may wait; past that a message that would start a turn is refused `busy`. A message said in a thread whose turn is running or waiting is recorded and said at once, and that turn takes it at its next model request. A thread is marked in the store while a turn of it hasn't ended: `resume` goes on, once, with each turn that was cut off by a looper closing or going without closing, and the model is told to check what is already done. Stopped, it cuts every turn and takes no message until it is started. It imports no other package of this repository, and a test of its own fails if a file under its `src` does.
24
- - `assistant`: the agent engine and the bundled agent provider. The engine binds the looper to one agent: a chat is a thread, the model and the tools are the agent's slots, and the records go where the agent's are kept. So an agent works in several chats at once, as the looper's places allow. `startAgentProvider` runs an external Actor exporting `agent.provider@1` as `provider`, which offers one creatable, Assistant. Each agent is its own Actor on its own channel in that process, exporting `chat@1`, with the model behind `model: llm.session@1`, instructions and skills from an optional `settings: agent.settings@1`, and tools from each `tools@1` slot exposed to it. Its chats and model choice stay in the provider's private folder; the model is chosen through the provider's `models` and `setModel`, which name the agent. The agent is `working` while any chat's turn runs or waits. A stopped agent stays on its channel and runs no turn: its chats can still be read, and a message is refused `busy`. A message sent in a chat whose turn is running is recorded and shown at once, and that turn takes it at its next model request. A turn that fails leaves its chat with a `problem`, until that chat's next reply, and a message sent after it starts a turn that reads what went unanswered too. A turn that a restart cut off goes on once when the agent is back, its chat showing one line that says so and its model told to check what is already done; one that ended, failed or was stopped doesn't.
23
+ - `@agentorg/looper`: an agent's loops, knowing nothing of what hosts them. `createLooper` is given a model client, the tools a request may call, a store for its records and where to say what it does, each as a plain type of its own, and takes a message with the name of the thread it came in on. What answers is a piece of work, with a loop of its own, or, when a message needs no work, the main loop. A turn of a piece of work sends the work's messages and the tools to the model, runs what the model calls, and goes on until the model answers or the turn has made 200 calls. A message in a thread that a piece of work follows is given to that work, in code. One in a thread nothing follows is recorded and said at once, and the main loop decides on the thread: one model request with one function, `route`, and no other tool. It is told the work there is, each piece by id and title, whether it is in a turn or at rest, and whether its last turn failed, and the head of what it last said (twelve at most: those in a turn, then the latest at rest), and then the thread as a conversation (its newest eight messages, the first 4,000 characters of each). It says what came in is new work, with a title, or points at work there is, which then follows that thread too and, if at rest, starts a turn; or it answers itself, in text, when the message needs no work: that text is kept and said as the thread's reply, streamed as it is written, and the thread stays no work's, so its next message is decided the same way. Text beside a call is said too, and what came in is placed all the same, unless the call names work to stop. It is told to give a message to work only when the message points at that work explicitly, and to answer only what needs no tools and never to promise work. It speaks as whoever hosts it says (`voice`), told after its own words. It decides for one thread at a time, on all that waits in the thread, and keeps nothing between decisions but what the thread holds; an answer that says and calls nothing, or places with no work it was told of, starts new work. When a turn ends, the work's record is given the time, the head of what it last said, and whether the turn failed. One piece of work is stopped by saying so. The main loop may answer a message in a thread nothing follows by stopping work in a turn that the message asks to stop, when exactly one fits: it calls `route` with `stop`, which places nothing, and what it wrote is that thread's answer, or, when it wrote nothing, `Stopped.` if a turn was cut and `Nothing was stopped.` if not. Only that thread is told, and it stays no work's. And a message said in a thread a piece of work follows, which joins a turn of that work, is, beside that turn taking it, put to the main loop with that work alone, unless that turn has ended by then: one more request, in the looper's own words without `voice`, in which it may only stop that turn. These requests take their place among the decisions, one at a time; a message that starts a turn makes none, nor does one said before the work followed its thread, which the work takes with the thread. Stopping cuts the turn the main loop was told of, as the looper's own stop does for all, and no turn that began after it: a call already running finishes and its result is kept, and the work is at rest, not failed. Where the message joined the turn, what the main loop says of stopping it is kept at once in that thread as the work's own, and the work reads it after whatever the stopped turn still writes; if the store refuses it, the turn is cut all the same and the thread is told it failed. A message said to work whose stopped turn is still writing (a running call's result, or a reply being kept) is kept and said at once, as part of that turn's tail: the turn it starts takes over the stopped turn's place and shows as running, and makes its first request once the stopped turn has written what it will, so the model reads that call's result before the message. What the main loop was to hear of is not kept: a stop, a close or a restart before it has answered drops it, and the worker has the message all the same. A message is kept once, in the thread it is shown in; a piece of work reads its own across the threads it follows, in the order it took them, and what comes of a model request is written in the thread of the newest message that request read. Turns of different pieces of work run side by side, four at once, and one more that only work that has already done something may take; a turn past that waits for a place, and while such work keeps five turns running new work goes on waiting. Sixteen turns may wait; past that a message that would start a turn is refused `busy`, or, when the main loop had it, kept and told to have failed `busy`. The main loop takes no place. A message said to work whose turn is running or waiting is recorded and said at once, and that turn takes it at its next model request. A turn that fails tells every thread it had taken a message in. A piece of work is marked in the store while a turn of it hasn't ended, and a thread while it holds messages and no work has taken them: `resume` goes on, once, with each turn and each decision that was cut off by a looper closing or going without closing, and the model of a turn that goes on is told to check what is already done. A thread the main loop answered, couldn't place, or was stopped over, waits for its next message, and the main loop is put what the thread holds. Stopped, it cuts every turn and the main loop, and takes no message until it is started. It imports no other package of this repository, and a test of its own fails if a file under its `src` does.
24
+ - `assistant`: the agent engine and the bundled agent provider. The engine binds the looper to one agent: a chat is a thread, the model and the tools are the agent's slots, and the records go where the agent's are kept. Saying stop in a chat whose work is in a turn stops that turn, and no other work's; the agent's own Stop stops all. A message in a chat no work follows is read by the agent's main loop. It answers a plain message itself, as the agent, and that chat stays no work's; or it starts new work for it, or, when the message points at work there is, gives it to that work, which reads it with all it holds. A piece of work answers in the chat of the newest message it has read, and the chat is that work's from then on. The main loop's request is the looper's own words, then the agent's own instructions, which it speaks as, and one function: none of the agent's skills or tools. The request that only hears of a message said to work in a turn has the looper's words alone. So an agent works on several things at once, as the looper's places allow. `startAgentProvider` runs an external Actor exporting `agent.provider@1` as `provider`, which offers one creatable, Assistant. Each agent is its own Actor on its own channel in that process, exporting `chat@1`, with the model behind `model: llm.session@1`, instructions and skills from an optional `settings: agent.settings@1`, and tools from each `tools@1` slot exposed to it. Its chats and model choice stay in the provider's private folder; the model is chosen through the provider's `models` and `setModel`, which name the agent. The agent is `working` while its main loop decides or any work's turn runs or waits. A stopped agent stays on its channel and runs no turn: its chats can still be read, and a message is refused `busy`. A message sent in a chat whose work is in a turn is recorded and shown at once, and that turn takes it at its next model request. A message that gets no answer leaves its chat with a `problem`, until that chat's next reply, and a message sent after it is read with what went unanswered. A turn that a restart cut off goes on once when the agent is back, the chat of the message it was answering showing one line that says so and its model told to check what is already done; one that ended, failed or was stopped doesn't.
25
25
 
26
26
  ## Errors
27
27
 
@@ -487,6 +487,9 @@ export interface components {
487
487
  /** @constant */
488
488
  type: "entries";
489
489
  id: string;
490
+ /** @description What the workspace is called: its folder's own name, and nothing of where it is. It comes with every entries answer, whichever folder was asked for, so that with path it names the folder. A computer in a container made before `workspace` was added sends none, and there path alone names the folder. It is `workspace` for a workspace the service made, and empty for one at the top of a disk. */
491
+ workspace?: string;
492
+ /** @description The folder as it was asked for: `.` for the workspace itself. */
490
493
  path: string;
491
494
  entries: {
492
495
  name: string;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentorg/computer",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -90,7 +90,7 @@ function problems(unit, values, found, secrets) {
90
90
  wrong.push({ field: "address", message: `That address won't do. ${why}` });
91
91
  }
92
92
  else if (values.service !== LOCAL)
93
- wrong.push({ field: "service", message: "Choose a service." });
93
+ wrong.push({ field: "service", message: "Choose where it resides." });
94
94
  if (values.access === "none") {
95
95
  if (values.browser !== true)
96
96
  wrong.push({
@@ -99,7 +99,7 @@ function problems(unit, values, found, secrets) {
99
99
  });
100
100
  }
101
101
  else if (!LEVELS.includes(values.access))
102
- wrong.push({ field: "access", message: "Choose what it can do." });
102
+ wrong.push({ field: "access", message: "Choose what it reaches." });
103
103
  const workspace = fromHome(values.workspace);
104
104
  if (values.service === LOCAL && workspace !== undefined) {
105
105
  let folder = false;
@@ -313,7 +313,7 @@ export async function startComputerProvider(options) {
313
313
  // The folder, so the person can find it: the service says where one it made is.
314
314
  const folder = workspace ??
315
315
  (await computer.getInfo().then((info) => info.workspace, () => null));
316
- const where = local ? "this machine" : new URL(reached.apiUrl).host;
316
+ const where = local ? "this computer" : new URL(reached.apiUrl).host;
317
317
  const on = isolation === "container" ? `${where}, in a container` : `${where}, no isolation`;
318
318
  const servers = [];
319
319
  if (access !== "none")
@@ -7,7 +7,7 @@ export type Access = "read" | "files" | "terminal";
7
7
  export declare const LOCAL = "local";
8
8
  export declare const ANOTHER = "another";
9
9
  /** What to do when there is no service here: the app never starts or ships one. */
10
- export declare const INSTALL_STEPS = "No AgentComputer is running on this machine. Install it with npm install -g @agentorg/computer, start it with agentcomputer serve, and try again.";
10
+ export declare const INSTALL_STEPS = "No AgentComputer is running on this computer. Install it with npm install -g @agentorg/computer, start it with agentcomputer serve, and try again.";
11
11
  /** How the service on this machine isolates its computers, if one is running. */
12
12
  export type Found = {
13
13
  isolation: "none" | "container";
@@ -7,12 +7,12 @@ const WITH_FILES = { slots: [{ name: COMPUTER }], skills: [skill("files"), skill
7
7
  export const LOCAL = "local";
8
8
  export const ANOTHER = "another";
9
9
  /** What to do when there is no service here: the app never starts or ships one. */
10
- export const INSTALL_STEPS = "No AgentComputer is running on this machine. Install it with npm install -g @agentorg/computer, start it with agentcomputer serve, and try again.";
10
+ export const INSTALL_STEPS = "No AgentComputer is running on this computer. Install it with npm install -g @agentorg/computer, start it with agentcomputer serve, and try again.";
11
11
  const localLine = (found) => !found
12
12
  ? INSTALL_STEPS
13
13
  : found.isolation === "container"
14
- ? "On this machine, started by you. Each computer is a container of its own."
15
- : "On this machine, started by you. No isolation: it acts as you, and with a terminal it reaches everything you can.";
14
+ ? "On this computer, by a service you started. Each agent's computer is a container of its own."
15
+ : "On this computer, by a service you started. No isolation: it acts as you, and with a terminal it reaches everything you can.";
16
16
  /** The form as it stands now, with what was found on this machine. */
17
17
  export const computerUnit = (found) => ({
18
18
  id: COMPUTER,
@@ -23,12 +23,12 @@ export const computerUnit = (found) => ({
23
23
  id: "service",
24
24
  type: "choice",
25
25
  value: "string",
26
- title: "Service",
26
+ title: "Where it resides",
27
27
  description: "Where its computer runs, and how that's kept apart from everything else.",
28
28
  // Not required: with none found, `check` says what to do, which is more than "fill this in".
29
29
  ...(found ? { default: LOCAL } : {}),
30
30
  options: [
31
- { value: LOCAL, title: "This machine", description: localLine(found) },
31
+ { value: LOCAL, title: "This computer", description: localLine(found) },
32
32
  {
33
33
  value: ANOTHER,
34
34
  title: "Another address",
@@ -41,7 +41,7 @@ export const computerUnit = (found) => ({
41
41
  type: "text",
42
42
  value: "string",
43
43
  title: "Address",
44
- description: "The service's address, over https unless it's on this machine.",
44
+ description: "The service's address, over https unless it's on this computer.",
45
45
  required: true,
46
46
  visible: { field: "service", in: [ANOTHER] },
47
47
  },
@@ -54,25 +54,46 @@ export const computerUnit = (found) => ({
54
54
  required: true,
55
55
  visible: { field: "service", in: [ANOTHER] },
56
56
  },
57
+ {
58
+ id: "workspace",
59
+ type: "directory",
60
+ value: "string",
61
+ title: "Workspace directory",
62
+ description: "The folder it works in. Left empty, it's a folder with the agent's name in ~/Agents, made if it's missing. Its file tools stay inside it by a check, not a sandbox. Removing the agent leaves it.",
63
+ placeholder: "~/Agents/{name}",
64
+ visible: { field: "service", in: [LOCAL] },
65
+ },
66
+ {
67
+ id: "path",
68
+ type: "text",
69
+ value: "string",
70
+ title: "Workspace directory",
71
+ description: "A path on the service's machine. Removing the agent leaves it.",
72
+ visible: { field: "service", in: [ANOTHER] },
73
+ },
57
74
  {
58
75
  id: "access",
59
76
  authority: "setting",
60
77
  type: "choice",
61
78
  value: "string",
62
- title: "What it can do",
79
+ title: "What it reaches",
63
80
  required: true,
64
81
  options: [
65
- { value: "none", title: "Nothing", description: "It has no files and can't run anything." },
82
+ {
83
+ value: "none",
84
+ title: "No files or terminal",
85
+ description: "It has no files and can't run anything.",
86
+ },
66
87
  {
67
88
  value: "read",
68
- title: "Read files",
69
- description: "It can read files in its working directory, but can't change them or run anything.",
89
+ title: "Read-only files",
90
+ description: "It can read files in its workspace directory, but can't change them or run anything.",
70
91
  adds: WITH_FILES,
71
92
  },
72
93
  {
73
94
  value: "files",
74
- title: "Read and change files",
75
- description: "It can read and change files in its working directory, but can't run anything.",
95
+ title: "Files",
96
+ description: "It can read and change files in its workspace directory, but can't run anything.",
76
97
  adds: WITH_FILES,
77
98
  },
78
99
  {
@@ -91,28 +112,11 @@ export const computerUnit = (found) => ({
91
112
  authority: "setting",
92
113
  type: "toggle",
93
114
  value: "boolean",
94
- title: "Browser",
115
+ title: "With a browser",
95
116
  description: "It can open web pages and act on them, in a browser of its own.",
96
117
  default: false,
97
118
  adds: { slots: [{ name: BROWSER_SLOT }], skills: [skill("browser")] },
98
119
  },
99
- {
100
- id: "workspace",
101
- type: "directory",
102
- value: "string",
103
- title: "Working directory",
104
- description: "The folder it works in. Left empty, it's a folder with the agent's name in ~/Agents, made if it's missing. Its file tools stay inside it by a check, not a sandbox. Removing the agent leaves it.",
105
- placeholder: "~/Agents/{name}",
106
- visible: { field: "service", in: [LOCAL] },
107
- },
108
- {
109
- id: "path",
110
- type: "text",
111
- value: "string",
112
- title: "Working directory",
113
- description: "A path on the service's machine. Removing the agent leaves it.",
114
- visible: { field: "service", in: [ANOTHER] },
115
- },
116
120
  ],
117
121
  needs: [],
118
122
  gives: { slots: [], skills: [], results: [] },
@@ -20,7 +20,7 @@
20
20
  "test": "node --test \"dist/test/**/*.test.js\""
21
21
  },
22
22
  "dependencies": {
23
- "@agentorg/computer": "0.0.10",
23
+ "@agentorg/computer": "0.0.11",
24
24
  "@agentorg/interfaces": "0.0.0",
25
25
  "@agentorg/mcp": "0.0.0",
26
26
  "@agentorg/sdk": "0.0.0",
@@ -3,7 +3,6 @@ export { AgentStatus, Answers, agentProvider, Creatable, CreatableId, } from "./
3
3
  export { agentSettings, SKILL_FILE_MAX, SkillEntry, SkillName, SkillPath, } from "./agent-settings.js";
4
4
  export { CHAT_TITLE_MAX, ChatEntry, ChatMessage, ChatStatus, ChatTool, chat, } from "./chat.js";
5
5
  export { ConnectionKind, ConnectionParams, ConnectionSecrets, ConnectionState, ConnectionStatus, ConnectionTarget, ConnectionTool, } from "./connection.js";
6
- export { FOLDERS_MAX, folders } from "./folders.js";
7
6
  export { Actor, ActorState, AuditEntry, Binding, ExportEntry, hubActors, hubAudit, hubBindings, hubExports, hubGrants, SlotName, } from "./hub.js";
8
7
  export { kv } from "./kv.js";
9
8
  export { Item, llmSession, OutputItem, Tool } from "./llm-session.js";
@@ -3,7 +3,6 @@ export { AgentStatus, Answers, agentProvider, Creatable, CreatableId, } from "./
3
3
  export { agentSettings, SKILL_FILE_MAX, SkillEntry, SkillName, SkillPath, } from "./agent-settings.js";
4
4
  export { CHAT_TITLE_MAX, ChatEntry, ChatMessage, ChatStatus, ChatTool, chat, } from "./chat.js";
5
5
  export { ConnectionKind, ConnectionParams, ConnectionSecrets, ConnectionState, ConnectionStatus, ConnectionTarget, ConnectionTool, } from "./connection.js";
6
- export { FOLDERS_MAX, folders } from "./folders.js";
7
6
  export { Actor, ActorState, AuditEntry, Binding, ExportEntry, hubActors, hubAudit, hubBindings, hubExports, hubGrants, SlotName, } from "./hub.js";
8
7
  export { kv } from "./kv.js";
9
8
  export { Item, llmSession, OutputItem, Tool } from "./llm-session.js";
@@ -410,7 +410,7 @@ export declare const agents: import("@agentorg/sdk").RpcInterface<"agents", {
410
410
  }, z.core.$strip>, z.ZodObject<{}, z.core.$strip>>;
411
411
  /**
412
412
  * Stops the agent: whatever it was doing ends, and it does nothing until started; all else
413
- * stays. This is the one way to stop an agent's work.
413
+ * stays.
414
414
  */
415
415
  stop: import("@agentorg/sdk").MethodSpec<z.ZodObject<{
416
416
  agent: z.ZodString;
@@ -207,7 +207,7 @@ export const agents = defineInterface({
207
207
  start: method({ input: OnAgent, output: z.object({}) }),
208
208
  /**
209
209
  * Stops the agent: whatever it was doing ends, and it does nothing until started; all else
210
- * stays. This is the one way to stop an agent's work.
210
+ * stays.
211
211
  */
212
212
  stop: method({ input: OnAgent, output: z.object({}) }),
213
213
  /** The models the agent can use, and the one chosen, as its provider says. */
@@ -2,18 +2,26 @@
2
2
  * A loop: a turn sends its record and the tools to the model, runs each function the model calls,
3
3
  * and sends the results back, until the model answers without calling anything, or the turn has
4
4
  * made its `MAX_CALLS` calls. A message said while a turn runs is recorded at once, and the turn
5
- * takes it at its next model request.
5
+ * takes it at its next model request. A loop is one piece of work's: its messages come in the
6
+ * threads the work follows, and what comes of a request is kept in the thread of the newest
7
+ * message that request read.
6
8
  */
7
9
  import { type LoopRecord } from "./record.js";
8
10
  import { type Log, type Model, type Shown, type Status, type Tool } from "./types.js";
9
11
  /** A brake on a turn, not a budget: nothing approves a call, and the person can stop it. */
10
12
  export declare const MAX_CALLS = 200;
11
- /** Where a loop says what it does. */
13
+ /** Where a loop says what it does, each message with the thread it is shown in. */
12
14
  export interface LoopOut {
13
- message(message: Shown): void;
15
+ message(thread: string, message: Shown): void;
14
16
  /** Text of the reply `id` as the model writes it. */
15
- delta(id: string, text: string): void;
17
+ delta(thread: string, id: string, text: string): void;
18
+ /** What it is doing. */
16
19
  status(status: Status): void;
20
+ /** A message in `thread` got no answer, and why: said before the `error` status. */
21
+ failed(thread: string, problem: {
22
+ code: string;
23
+ message: string;
24
+ }): void;
17
25
  }
18
26
  /** A turn's place among the turns running: `entered` settles when it has it, or has left. */
19
27
  export interface Place {
@@ -42,33 +50,48 @@ export declare function createLoop(options: {
42
50
  log: Log;
43
51
  }): {
44
52
  /**
45
- * Say a message. With no turn it starts one, whose progress is said through `out`. In a turn,
46
- * running or waiting for its place, it is recorded and said at once, and the turn takes it at
47
- * its next model request.
53
+ * Say a message in a thread the work follows. It is recorded and said at once. In a turn,
54
+ * running or waiting for its place, the turn takes it at its next model request: `turn` is
55
+ * then that turn's number. With no turn it starts one, whose progress is said through `out`.
48
56
  */
49
- send: (text: string) => Promise<{
57
+ send: (text: string, thread: string) => Promise<{
50
58
  messageId: string;
59
+ turn?: number;
51
60
  }>;
52
61
  /**
53
- * Go on with a turn that was cut off before it ended, as by a restart: the thread shows one
54
- * line saying so, and the model is told once to check what is already done. Nothing goes on in
55
- * a thread with nothing kept, in one that has had a turn since the loop was made, or when a
56
- * cancel comes first.
62
+ * Take the messages waiting in `thread`, which the work has come to follow, as its own, and
63
+ * answer them: in the turn running, or in one this starts. With none waiting no turn starts.
64
+ * When a cancel came first nothing is taken, and it says so with `false`.
65
+ */
66
+ adopt: (thread: string) => Promise<boolean>;
67
+ /**
68
+ * Go on with a turn that was cut off before it ended, as by a restart: the thread it was
69
+ * answering in shows one line saying so, and the model is told once to check what is already
70
+ * done. Nothing goes on in work with nothing kept, in work that has had a turn since the loop
71
+ * was made, or when a cancel comes first.
57
72
  */
58
73
  resume: () => Promise<void>;
59
74
  /** Whether the turn running now goes on from one that was cut off. */
60
75
  resumed: () => boolean;
61
76
  /** The status now. */
62
77
  state: () => Status;
78
+ /** The number of the turn running or waiting for its place, if one is. */
79
+ turn: () => number | undefined;
63
80
  /**
64
- * Stop the turn now: its model request is cancelled, no further call starts, and a new turn
65
- * may begin at once. A call already running finishes, and its result is kept. A turn waiting
66
- * for its place never starts.
81
+ * Stop the turn now, or with `only`, the turn of that number if it is still the one: its
82
+ * model request is cancelled and no further call starts. A call already running finishes,
83
+ * and its result is kept; a new turn may be started at once, and reads the record after
84
+ * that. A turn waiting for its place never starts. It says whether it cut a turn. `line` is
85
+ * what the main loop says as it stops it, kept in that thread in order with what was asked
86
+ * before.
67
87
  */
68
- cancel(): Promise<void>;
88
+ cancel(only?: number, line?: {
89
+ text: string;
90
+ thread: string;
91
+ }): Promise<boolean>;
69
92
  /** Settles when the latest turn has written all it will. */
70
93
  settled: () => Promise<void>;
71
- /** Settles when a stopped turn's running call has ended: at once, if none is running. */
94
+ /** Settles when a stopped turn has written what it will: at once, if none is writing. */
72
95
  drained: () => Promise<void>;
73
96
  /** Streamed text of the model request running now, for the reply it becomes. */
74
97
  delta(requestId: string, text: string): void;