@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.
Files changed (108) 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 +75 -0
  43. package/dist/node_modules/@agentorg/looper/dist/src/loop.js +332 -0
  44. package/dist/node_modules/@agentorg/looper/dist/src/looper.d.ts +71 -0
  45. package/dist/node_modules/@agentorg/looper/dist/src/looper.js +282 -0
  46. package/dist/node_modules/@agentorg/looper/dist/src/record.d.ts +99 -0
  47. package/dist/node_modules/{assistant/dist/src/history.js → @agentorg/looper/dist/src/record.js} +89 -46
  48. package/dist/node_modules/@agentorg/looper/dist/src/tools.d.ts +11 -0
  49. package/dist/node_modules/@agentorg/looper/dist/src/tools.js +42 -0
  50. package/dist/node_modules/@agentorg/looper/dist/src/types.d.ts +147 -0
  51. package/dist/node_modules/@agentorg/looper/dist/src/types.js +32 -0
  52. package/dist/node_modules/@agentorg/looper/package.json +24 -0
  53. package/dist/node_modules/@agentorg/manager/dist/src/activity.d.ts +18 -0
  54. package/dist/node_modules/@agentorg/manager/dist/src/activity.js +65 -0
  55. package/dist/node_modules/@agentorg/manager/dist/src/control.d.ts +134 -0
  56. package/dist/node_modules/@agentorg/manager/dist/src/control.js +1115 -0
  57. package/dist/node_modules/@agentorg/manager/dist/src/form.d.ts +40 -0
  58. package/dist/node_modules/@agentorg/manager/dist/src/form.js +191 -0
  59. package/dist/node_modules/@agentorg/manager/dist/src/manager.d.ts +4 -0
  60. package/dist/node_modules/@agentorg/manager/dist/src/manager.js +341 -189
  61. package/dist/node_modules/@agentorg/manager/dist/src/skills.d.ts +2 -0
  62. package/dist/node_modules/@agentorg/manager/dist/src/skills.js +7 -0
  63. package/dist/node_modules/@agentorg/manager/dist/src/stack.d.ts +454 -0
  64. package/dist/node_modules/@agentorg/manager/dist/src/stack.js +917 -0
  65. package/dist/node_modules/@agentorg/mcp/dist/src/connection.d.ts +2 -1
  66. package/dist/node_modules/@agentorg/mcp/dist/src/connection.js +8 -2
  67. package/dist/node_modules/@agentorg/mcp/dist/src/index.d.ts +3 -0
  68. package/dist/node_modules/@agentorg/mcp/dist/src/index.js +3 -0
  69. package/dist/node_modules/@agentorg/mcp/dist/src/kinds.d.ts +108 -0
  70. package/dist/node_modules/@agentorg/mcp/dist/src/kinds.js +95 -0
  71. package/dist/node_modules/@agentorg/mcp/dist/src/provider.d.ts +2 -2
  72. package/dist/node_modules/@agentorg/mcp/dist/src/provider.js +48 -18
  73. package/dist/node_modules/@agentorg/mcp/dist/src/public.d.ts +6 -0
  74. package/dist/node_modules/@agentorg/mcp/dist/src/public.js +72 -0
  75. package/dist/node_modules/@agentorg/sdk/dist/src/filled.d.ts +21 -0
  76. package/dist/node_modules/@agentorg/sdk/dist/src/filled.js +24 -0
  77. package/dist/node_modules/@agentorg/sdk/dist/src/index.d.ts +1 -0
  78. package/dist/node_modules/@agentorg/sdk/dist/src/index.js +1 -0
  79. package/dist/node_modules/@agentorg/shell/dist/app.css +1289 -100
  80. package/dist/node_modules/@agentorg/shell/dist/app.js +207 -166
  81. package/dist/node_modules/@agentorg/shell/package.json +6 -1
  82. package/dist/node_modules/assistant/dist/src/chats.d.ts +92 -0
  83. package/dist/node_modules/assistant/dist/src/chats.js +357 -0
  84. package/dist/node_modules/assistant/dist/src/engine.d.ts +26 -7
  85. package/dist/node_modules/assistant/dist/src/engine.js +107 -35
  86. package/dist/node_modules/assistant/dist/src/local-store.js +1 -1
  87. package/dist/node_modules/assistant/dist/src/provider.d.ts +1 -1
  88. package/dist/node_modules/assistant/dist/src/provider.js +47 -19
  89. package/dist/node_modules/assistant/dist/src/tools.d.ts +11 -28
  90. package/dist/node_modules/assistant/dist/src/tools.js +19 -40
  91. package/dist/node_modules/assistant/package.json +1 -0
  92. package/dist/src/api.js +4 -0
  93. package/dist/src/folders.d.ts +2 -0
  94. package/dist/src/folders.js +45 -0
  95. package/dist/src/gateway.d.ts +7 -1
  96. package/dist/src/gateway.js +81 -12
  97. package/dist/src/live.d.ts +17 -0
  98. package/dist/src/live.js +109 -0
  99. package/dist/src/programs.d.ts +7 -0
  100. package/dist/src/programs.js +29 -2
  101. package/dist/src/signin.d.ts +12 -3
  102. package/dist/src/signin.js +30 -6
  103. package/package.json +2 -1
  104. package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.d.ts +0 -88
  105. package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.js +0 -78
  106. package/dist/node_modules/assistant/dist/src/agent.d.ts +0 -62
  107. package/dist/node_modules/assistant/dist/src/agent.js +0 -226
  108. package/dist/node_modules/assistant/dist/src/history.d.ts +0 -97
@@ -4,11 +4,13 @@ import { createRequire } from "node:module";
4
4
  import { dirname, join, normalize, resolve, sep } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { Hub, listen, PERSON } from "@agentorg/hub";
7
- import { accountLogin, agents, connections, hubActors, hubAudit, hubBindings, hubExports, hubGrants, skills, } from "@agentorg/interfaces";
8
- import { Refused } from "@agentorg/sdk";
7
+ import { accountLogin, agents, connections, hubActors, hubAudit, hubBindings, hubExports, hubGrants, llmSession, skills, } from "@agentorg/interfaces";
8
+ import { describeInterface, Refused } from "@agentorg/sdk";
9
9
  import { connect } from "@agentorg/sdk/client";
10
10
  import { WebSocketServer } from "ws";
11
11
  import { serveSocket } from "./api.js";
12
+ import { callFolders } from "./folders.js";
13
+ import { LIVE_MAX, pipeLive } from "./live.js";
12
14
  import { Notes } from "./notes.js";
13
15
  import { oauthRoute } from "./oauth.js";
14
16
  import { closePackageActors, startPrograms, } from "./programs.js";
@@ -39,6 +41,21 @@ const MAX_MESSAGE = 1024 * 1024;
39
41
  const HELLO_TIMEOUT_MS = 10_000;
40
42
  /** The close code for a socket with no live session: the tab signs in again. */
41
43
  const SIGN_IN_AGAIN = 4401;
44
+ /** A model's reply can take minutes; every other interface keeps the Hub's own limit. */
45
+ export const HUB_OPTIONS = {
46
+ answerTimeoutsMs: { [describeInterface(llmSession).id]: 10 * 60_000 },
47
+ };
48
+ /** The port the kept sessions were given at, if any are kept. */
49
+ function keptPort(sessions) {
50
+ try {
51
+ const { at } = JSON.parse(readFileSync(sessions, "utf8"));
52
+ const port = /^http:\/\/127\.0\.0\.1:(\d+)$/.exec(String(at))?.[1];
53
+ return port === undefined ? undefined : Number(port);
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
42
59
  /** Write a file only its owner reads, all at once. */
43
60
  function writePrivate(file, text) {
44
61
  writeFileSync(`${file}.tmp`, text, { mode: 0o600 });
@@ -55,13 +72,38 @@ export async function startApp(options) {
55
72
  const home = resolve(options.home);
56
73
  mkdirSync(home, { recursive: true, mode: 0o700 });
57
74
  closePackageActors(home);
58
- const hub = Hub.open(home);
75
+ const hub = Hub.open(home, HUB_OPTIONS);
59
76
  const listener = await listen(hub, {}).catch((error) => {
60
77
  hub.close();
61
78
  throw error;
62
79
  });
63
80
  writePrivate(join(home, "url"), `${listener.url}\n`);
64
- const signIn = new SignIn();
81
+ // A tab signed in before the app last stopped is still signed in, if the app is at the address
82
+ // it was then: what is kept is each session's hash, in a file only its owner reads. At another
83
+ // address they are dropped. A tab keeps its session by address, so it couldn't say one here, and
84
+ // a tab left open on the old address goes on saying its session to whatever answers there.
85
+ const sessions = join(home, "sessions.json");
86
+ const signIn = new SignIn({
87
+ kept: {
88
+ read() {
89
+ try {
90
+ return JSON.parse(readFileSync(sessions, "utf8")).hashes;
91
+ }
92
+ catch {
93
+ // None kept, or none that reads back.
94
+ return undefined;
95
+ }
96
+ },
97
+ write(hashes) {
98
+ try {
99
+ writePrivate(sessions, `${JSON.stringify({ at: origin, hashes })}\n`);
100
+ }
101
+ catch {
102
+ // A home that can't be written still signs the tab in: only the next start forgets it.
103
+ }
104
+ },
105
+ },
106
+ });
65
107
  const credential = readFileSync(join(home, "person-credential"), "utf8").trim();
66
108
  /** The app's own notes: what the shell remembers. */
67
109
  const notes = new Notes(join(home, "app-notes.json"));
@@ -81,6 +123,8 @@ export async function startApp(options) {
81
123
  async call(slot, method, input) {
82
124
  if (slot === "store")
83
125
  return notes.call(method, input);
126
+ if (slot === "folders")
127
+ return callFolders(method, input);
84
128
  const exposed = Object.hasOwn(SLOTS, slot) ? undefined : exposedSlot(slot);
85
129
  if (!exposed && !Object.hasOwn(SLOTS, slot))
86
130
  throw new Refused("invalid", `The person has no slot ${slot}.`);
@@ -197,18 +241,32 @@ export async function startApp(options) {
197
241
  response.end(request.method === "HEAD" ? undefined : body);
198
242
  };
199
243
  const sockets = new WebSocketServer({ noServer: true, maxPayload: MAX_MESSAGE });
244
+ const lives = new WebSocketServer({ noServer: true, maxPayload: LIVE_MAX });
200
245
  server.on("upgrade", (request, socket, head) => {
201
246
  // An upgraded socket has no error handler of its own: a client that resets would crash the app.
202
247
  socket.on("error", () => socket.destroy());
203
248
  const url = new URL(request.url ?? "/", origin);
204
249
  const cli = signIn.isCli(request.headers.authorization);
205
250
  const allowed = request.headers.host === host() &&
206
- url.pathname === "/api" &&
251
+ (url.pathname === "/api" || url.pathname === "/live") &&
207
252
  (cli || request.headers.origin === origin);
208
253
  if (!allowed) {
209
254
  socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
210
255
  return;
211
256
  }
257
+ if (url.pathname === "/live") {
258
+ const query = url.searchParams;
259
+ return lives.handleUpgrade(request, socket, head, (ws) => pipeLive(ws, {
260
+ cli,
261
+ isSession: (session) => signIn.isSession(session),
262
+ ticket: async () => (await personSide.call("agents", "live", {
263
+ agent: query.get("agent"),
264
+ instance: query.get("instance"),
265
+ surface: query.get("surface"),
266
+ mode: query.get("mode") ?? "watch",
267
+ })),
268
+ }));
269
+ }
212
270
  sockets.handleUpgrade(request, socket, head, (ws) => {
213
271
  let served;
214
272
  const serve = () => {
@@ -244,16 +302,25 @@ export async function startApp(options) {
244
302
  });
245
303
  });
246
304
  });
247
- await new Promise((done, failed) => {
305
+ const listenOn = (port) => new Promise((done, failed) => {
248
306
  server.once("error", failed);
249
- server.listen(options.port ?? 0, "127.0.0.1", () => done());
250
- }).catch(async (error) => {
307
+ server.listen(port, "127.0.0.1", () => done());
308
+ });
309
+ // With no port asked for, the one the kept sessions were given at, so a tab left open is still
310
+ // signed in. If that one can't be had, any free one, as on a first start.
311
+ await (options.port === undefined
312
+ ? listenOn(keptPort(sessions) ?? 0).catch(() => listenOn(0))
313
+ : listenOn(options.port)).catch(async (error) => {
251
314
  person.close();
252
315
  await listener.close();
253
316
  hub.close();
254
317
  throw error;
255
318
  });
256
- origin = `http://127.0.0.1:${server.address().port}`;
319
+ const { port } = server.address();
320
+ // At another address the kept sessions are dropped, before anything is answered.
321
+ if (keptPort(sessions) !== port)
322
+ rmSync(sessions, { force: true });
323
+ origin = `http://127.0.0.1:${port}`;
257
324
  writePrivate(join(home, "cli-token"), `${signIn.cliToken}\n`);
258
325
  writePrivate(join(home, "app.json"), `${JSON.stringify({ url: origin, pid: process.pid })}\n`);
259
326
  let closing;
@@ -294,9 +361,11 @@ export async function startApp(options) {
294
361
  catch {
295
362
  // Already gone.
296
363
  }
297
- for (const client of sockets.clients)
298
- client.terminate();
299
- sockets.close();
364
+ for (const server of [sockets, lives]) {
365
+ for (const client of server.clients)
366
+ client.terminate();
367
+ server.close();
368
+ }
300
369
  // server.close waits for every open connection; a tab still connected (a keep-alive request,
301
370
  // a socket reconnecting) would hold the app open, so they are all closed now.
302
371
  const serverClosed = new Promise((done) => server.close(() => done()));
@@ -0,0 +1,17 @@
1
+ import WebSocket from "ws";
2
+ /** What one live frame can be, either way. */
3
+ export declare const LIVE_MAX: number;
4
+ export interface Ticket {
5
+ url: string;
6
+ protocol: string;
7
+ }
8
+ /**
9
+ * Pipe a page's socket to a surface. A tab says its session first, as on `/api`, and is then
10
+ * answered `{"op":"live","protocol"}`; after that every frame is the surface's own. The CLI is in
11
+ * by its token.
12
+ */
13
+ export declare function pipeLive(page: WebSocket, options: {
14
+ cli: boolean;
15
+ isSession(session: unknown): boolean;
16
+ ticket(): Promise<Ticket>;
17
+ }): void;
@@ -0,0 +1,109 @@
1
+ // A live surface of an agent's unit, for a page: the page opens a socket here, same-origin, and
2
+ // this asks management for a one-time ticket, dials it, and pipes the bytes both ways. The ticket
3
+ // and every token stay out of the page.
4
+ import WebSocket from "ws";
5
+ /** What one live frame can be, either way. */
6
+ export const LIVE_MAX = 4 * 1024 * 1024;
7
+ /** The page didn't say its session, or the session is gone. */
8
+ const SIGN_IN_AGAIN = 4401;
9
+ /** The surface couldn't be opened; the reason says why. */
10
+ const NOT_OPENED = 4404;
11
+ const HELLO_TIMEOUT_MS = 10_000;
12
+ /** What a page may send before the surface opens; past it, the socket closes rather than drop. */
13
+ const WAITING_MAX = 1024 * 1024;
14
+ /** Past this much unsent to the page, the surface waits; it goes on below the lower mark. */
15
+ const BEHIND = 8 * 1024 * 1024;
16
+ const CAUGHT_UP = 1024 * 1024;
17
+ /** A close code a page may be given: the peer's when it's an ordinary one. */
18
+ const codeOf = (code) => code === 1000 || code === 1001 || (code >= 3000 && code <= 4999) ? code : 1011;
19
+ /** A close reason fits in 123 bytes. */
20
+ const reasonOf = (text) => {
21
+ let reason = text;
22
+ while (Buffer.byteLength(reason) > 123)
23
+ reason = reason.slice(0, -1);
24
+ return reason;
25
+ };
26
+ /**
27
+ * Pipe a page's socket to a surface. A tab says its session first, as on `/api`, and is then
28
+ * answered `{"op":"live","protocol"}`; after that every frame is the surface's own. The CLI is in
29
+ * by its token.
30
+ */
31
+ export function pipeLive(page, options) {
32
+ let upstream;
33
+ let opening = false;
34
+ const waiting = [];
35
+ let waitingBytes = 0;
36
+ const sizeOf = (data) => Array.isArray(data)
37
+ ? data.reduce((sum, one) => sum + one.length, 0)
38
+ : data.byteLength;
39
+ const open = async () => {
40
+ opening = true;
41
+ let ticket;
42
+ try {
43
+ ticket = await options.ticket();
44
+ }
45
+ catch (error) {
46
+ return page.close(NOT_OPENED, reasonOf(error instanceof Error ? error.message : "Not opened."));
47
+ }
48
+ if (page.readyState !== WebSocket.OPEN)
49
+ return;
50
+ let socket;
51
+ try {
52
+ // A server's socket, dialled from here: no browser Origin goes with it.
53
+ socket = new WebSocket(ticket.url, { maxPayload: LIVE_MAX });
54
+ }
55
+ catch {
56
+ // Its message would hold the ticket.
57
+ return page.close(NOT_OPENED, "The surface didn't open.");
58
+ }
59
+ upstream = socket;
60
+ socket.on("open", () => {
61
+ page.send(JSON.stringify({ op: "live", protocol: ticket.protocol }));
62
+ for (const [data, binary] of waiting.splice(0))
63
+ socket.send(data, { binary });
64
+ });
65
+ socket.on("message", (data, binary) => {
66
+ page.send(data, { binary }, () => {
67
+ if (page.bufferedAmount < CAUGHT_UP)
68
+ socket.resume();
69
+ });
70
+ // A page that can't keep up isn't sent more until it has.
71
+ if (page.bufferedAmount > BEHIND)
72
+ socket.pause();
73
+ });
74
+ socket.on("close", (code, reason) => page.close(codeOf(code), reasonOf(String(reason))));
75
+ socket.on("error", () => page.close(NOT_OPENED, "The surface didn't open."));
76
+ };
77
+ const timer = options.cli
78
+ ? undefined
79
+ : setTimeout(() => page.close(SIGN_IN_AGAIN, "No session"), HELLO_TIMEOUT_MS);
80
+ const opened = () => open().catch(() => page.close(NOT_OPENED, "The surface didn't open."));
81
+ if (options.cli)
82
+ void opened();
83
+ page.on("message", (data, binary) => {
84
+ if (!opening) {
85
+ clearTimeout(timer);
86
+ let hello;
87
+ try {
88
+ hello = binary ? undefined : JSON.parse(String(data));
89
+ }
90
+ catch {
91
+ // Not a hello.
92
+ }
93
+ if (hello?.op !== "hello" || !options.isSession(hello.session))
94
+ return page.close(SIGN_IN_AGAIN, "Sign in again");
95
+ return void opened();
96
+ }
97
+ if (upstream?.readyState === WebSocket.OPEN)
98
+ return upstream.send(data, { binary });
99
+ waitingBytes += sizeOf(data);
100
+ if (waitingBytes > WAITING_MAX)
101
+ return page.close(1009, "Too much before the surface opened.");
102
+ waiting.push([data, binary]);
103
+ });
104
+ page.on("close", () => {
105
+ clearTimeout(timer);
106
+ upstream?.close();
107
+ });
108
+ page.on("error", () => page.terminate());
109
+ }
@@ -6,12 +6,19 @@ export interface ProgramsOptions {
6
6
  log?: (message: string) => void;
7
7
  /** How ChatGPT's requests leave, instead of the network. For tests. */
8
8
  fetch?: (request: Request, init: RequestInit) => Promise<Response>;
9
+ /** Where the computer connector looks for a local service, the web unit's server, and agents' folders. For tests. */
10
+ computer?: {
11
+ dataFolder?: string;
12
+ webUrl?: string;
13
+ agentsFolder?: string;
14
+ };
9
15
  }
10
16
  export interface Programs {
11
17
  /** The Actors running now, by program. */
12
18
  chatgpt?: string;
13
19
  agents?: string;
14
20
  mcp?: string;
21
+ computer?: string;
15
22
  management?: string;
16
23
  /** Leave the Hub, in the order that lets each finish. What each keeps stays. */
17
24
  close(): Promise<void>;
@@ -1,8 +1,9 @@
1
- // The app's programs: ChatGPT, the agent provider, the MCP provider and management, each an Actor
1
+ // The app's programs: ChatGPT, the agent provider, the MCP provider, the computer connector and management, each an Actor
2
2
  // of its own that this process runs on its own channel, with its own private folder under the home.
3
3
  // Every step reads what is there before it acts, so each start converges on the same system.
4
4
  import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
5
5
  import { join } from "node:path";
6
+ import { startComputerProvider } from "@agentorg/computer-connector";
6
7
  import { HubState, PERSON } from "@agentorg/hub";
7
8
  import { startManager } from "@agentorg/manager";
8
9
  import { startMcpProvider } from "@agentorg/mcp";
@@ -25,6 +26,17 @@ export function closePackageActors(home) {
25
26
  state.close();
26
27
  }
27
28
  }
29
+ /** The bundled Assistant: the base, a computer on a service the person chooses, and the web. */
30
+ const ASSISTANT_TEMPLATE = {
31
+ id: "assistant",
32
+ title: "Assistant",
33
+ description: "An agent with a computer of its own and the web.",
34
+ base: { provider: "AgentOrg", creatable: "assistant" },
35
+ units: [
36
+ { provider: "Computers", unit: "computer", values: { access: "terminal", browser: true } },
37
+ { provider: "Computers", unit: "web", values: { service: "exa" } },
38
+ ],
39
+ };
28
40
  /** The Hub's exports each program holds. */
29
41
  const hubBinding = (name) => ({ slot: name, provider: "hub", export: name });
30
42
  /**
@@ -131,6 +143,19 @@ export async function startPrograms(person, options) {
131
143
  closers.unshift(() => running.close());
132
144
  return actorId;
133
145
  });
146
+ const computer = await attempt("the computer connector", async () => {
147
+ const { actorId, credential } = await identity("computer", "Computers", [hubBinding("actors")]);
148
+ const running = await startComputerProvider({
149
+ hubUrl,
150
+ actorId,
151
+ credential,
152
+ dir: dirOf("computer"),
153
+ log: programLog,
154
+ ...options.computer,
155
+ });
156
+ closers.unshift(() => running.close());
157
+ return actorId;
158
+ });
134
159
  const management = await attempt("management", async () => {
135
160
  const { actorId, credential } = await identity("management", "Management", [
136
161
  hubBinding("actors"),
@@ -144,6 +169,7 @@ export async function startPrograms(person, options) {
144
169
  credential,
145
170
  dir: dirOf("management"),
146
171
  log: programLog,
172
+ templates: [ASSISTANT_TEMPLATE],
147
173
  });
148
174
  closers.push(() => running.close());
149
175
  for (const [slot, exported] of [
@@ -155,13 +181,14 @@ export async function startPrograms(person, options) {
155
181
  return actorId;
156
182
  });
157
183
  if (management)
158
- for (const provider of [agents, mcp])
184
+ for (const provider of [agents, mcp, computer])
159
185
  if (provider)
160
186
  await attempt("connecting a provider to management", () => call("grants", "expose", { provider, export: "provider", to: management }));
161
187
  return {
162
188
  ...(chatgpt ? { chatgpt } : {}),
163
189
  ...(agents ? { agents } : {}),
164
190
  ...(mcp ? { mcp } : {}),
191
+ ...(computer ? { computer } : {}),
165
192
  ...(management ? { management } : {}),
166
193
  async close() {
167
194
  for (const close of closers)
@@ -1,15 +1,24 @@
1
+ /** Where the sessions' hashes outlast the app: read once, when first needed, and written at each sign-in. */
2
+ export interface KeptSessions {
3
+ /** What was written last; anything that isn't a list is read as none. */
4
+ read(): unknown;
5
+ write(hashes: string[]): void;
6
+ }
1
7
  /**
2
8
  * Who may use the gateway: browser tabs with a session, made from a one-time code, and the CLI
3
9
  * with the token in the home. A code works once, within 60 s, and travels only in a URL's
4
- * fragment, which no request carries. A session is a random id the gateway holds in memory, so a
5
- * restart signs every tab out. It is no cookie, which every port on 127.0.0.1 would receive: the
6
- * shell keeps it in its origin's own storage and says it in its socket's first message.
10
+ * fragment, which no request carries. A session is a random id of which the gateway holds only a
11
+ * hash. With `kept`, the hashes outlast the app, so a tab is still signed in when the app starts
12
+ * again; a session ends only when `SESSIONS_MAX` newer ones have been given.
13
+ * It is no cookie, which every port on 127.0.0.1 would receive: the shell keeps it in its origin's
14
+ * own storage and says it in its socket's first message.
7
15
  */
8
16
  export declare class SignIn {
9
17
  #private;
10
18
  readonly cliToken: string;
11
19
  constructor(options?: {
12
20
  now?: () => number;
21
+ kept?: KeptSessions;
13
22
  });
14
23
  /** A new one-time code, for `/signin#<code>`. */
15
24
  code(): string;
@@ -1,10 +1,12 @@
1
- import { randomBytes, timingSafeEqual } from "node:crypto";
1
+ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
2
2
  /** How long a sign-in code works. */
3
3
  const CODE_TTL_MS = 60_000;
4
4
  /** Codes not yet used; past it, the oldest go, so nothing grows without bound. */
5
5
  const MAX_CODES = 16;
6
6
  const SESSIONS_MAX = 256;
7
7
  const token = () => randomBytes(32).toString("base64url");
8
+ /** What is kept of a session: enough to know it again, and nothing that signs anyone in. */
9
+ const hashOf = (session) => createHash("sha256").update(session).digest("base64url");
8
10
  const same = (a, b) => {
9
11
  const left = Buffer.from(a);
10
12
  const right = Buffer.from(b);
@@ -13,17 +15,34 @@ const same = (a, b) => {
13
15
  /**
14
16
  * Who may use the gateway: browser tabs with a session, made from a one-time code, and the CLI
15
17
  * with the token in the home. A code works once, within 60 s, and travels only in a URL's
16
- * fragment, which no request carries. A session is a random id the gateway holds in memory, so a
17
- * restart signs every tab out. It is no cookie, which every port on 127.0.0.1 would receive: the
18
- * shell keeps it in its origin's own storage and says it in its socket's first message.
18
+ * fragment, which no request carries. A session is a random id of which the gateway holds only a
19
+ * hash. With `kept`, the hashes outlast the app, so a tab is still signed in when the app starts
20
+ * again; a session ends only when `SESSIONS_MAX` newer ones have been given.
21
+ * It is no cookie, which every port on 127.0.0.1 would receive: the shell keeps it in its origin's
22
+ * own storage and says it in its socket's first message.
19
23
  */
20
24
  export class SignIn {
21
25
  cliToken = token();
22
26
  #codes = new Map();
27
+ /** The hash of each session, the oldest first. */
23
28
  #sessions = new Set();
24
29
  #now;
30
+ #kept;
31
+ #restored = false;
25
32
  constructor(options = {}) {
26
33
  this.#now = options.now ?? Date.now;
34
+ this.#kept = options.kept;
35
+ }
36
+ /** Take back what was kept, the first time a session is given or asked about. */
37
+ #restore() {
38
+ if (this.#restored)
39
+ return;
40
+ this.#restored = true;
41
+ const read = this.#kept?.read();
42
+ const hashes = Array.isArray(read) ? read : [];
43
+ for (const hash of hashes.slice(-SESSIONS_MAX))
44
+ if (typeof hash === "string")
45
+ this.#sessions.add(hash);
27
46
  }
28
47
  /** A new one-time code, for `/signin#<code>`. */
29
48
  code() {
@@ -48,17 +67,22 @@ export class SignIn {
48
67
  if (this.#now() > expires)
49
68
  return undefined;
50
69
  const session = token();
51
- this.#sessions.add(session);
70
+ this.#restore();
71
+ this.#sessions.add(hashOf(session));
52
72
  while (this.#sessions.size > SESSIONS_MAX)
53
73
  this.#sessions.delete(this.#sessions.values().next().value);
74
+ // Kept before it is given: a session the tab holds is one a restart still knows.
75
+ this.#kept?.write([...this.#sessions]);
54
76
  return session;
55
77
  }
56
78
  /** Whether a session is live. */
57
79
  isSession(session) {
58
80
  if (typeof session !== "string")
59
81
  return false;
82
+ this.#restore();
83
+ const hash = hashOf(session);
60
84
  for (const known of this.#sessions)
61
- if (same(known, session))
85
+ if (same(known, hash))
62
86
  return true;
63
87
  return false;
64
88
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentorg/cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "AgentOrg as one app: the Hub, the person's gateway and the shell, on loopback.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,6 +29,7 @@
29
29
  "dependencies": {
30
30
  "@modelcontextprotocol/client": "2.4.0",
31
31
  "ajv": "^8.20.0",
32
+ "react-diff-view": "3.3.3",
32
33
  "ws": "^8.22.1",
33
34
  "yaml": "2.9.1",
34
35
  "zod": "^4.6.7"
@@ -1,88 +0,0 @@
1
- import { z } from "zod";
2
- /** A way a provider makes connections, such as `url` or `command`. */
3
- export declare const ConnectionKind: z.ZodString;
4
- /** `connecting` before its first success, `connected`, or `down` and being tried again. */
5
- export declare const ConnectionState: z.ZodEnum<{
6
- connected: "connected";
7
- connecting: "connecting";
8
- down: "down";
9
- }>;
10
- /** A connection's tools, for people to read; a provider cuts a long description to fit. */
11
- export declare const ConnectionTool: z.ZodObject<{
12
- name: z.ZodString;
13
- description: z.ZodOptional<z.ZodString>;
14
- }, z.core.$strip>;
15
- /** How a connection stands now: `detail` says why it is down, and never holds a secret. */
16
- export declare const ConnectionStatus: z.ZodObject<{
17
- state: z.ZodEnum<{
18
- connected: "connected";
19
- connecting: "connecting";
20
- down: "down";
21
- }>;
22
- detail: z.ZodOptional<z.ZodString>;
23
- tools: z.ZodArray<z.ZodObject<{
24
- name: z.ZodString;
25
- description: z.ZodOptional<z.ZodString>;
26
- }, z.core.$strip>>;
27
- }, z.core.$strip>;
28
- /** The parameters of a connection, fitting its kind's `params` schema. */
29
- export declare const ConnectionParams: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
30
- /**
31
- * What a server needs and shouldn't be seen, by name, such as a bearer token. Write-only: the values
32
- * cross once, in `create`, and go only to the provider's own private file.
33
- */
34
- export declare const ConnectionSecrets: z.ZodRecord<z.ZodString, z.ZodString>;
35
- /** What a connection attaches to or runs, for people to read: never a secret. */
36
- export declare const ConnectionTarget: z.ZodString;
37
- /**
38
- * `mcp.provider@1`: a program that makes connections to MCP servers. Each connection joins the
39
- * Hub as its own Actor, whose id is `connection`, exporting `tools@1` as `tools`. MCP itself stays
40
- * behind this interface.
41
- *
42
- * A secret a server needs is given once, in `create`'s `secrets`, and stays with the provider: its
43
- * value is never in `params`, `target`, `detail`, or anything else read back. `status` and `remove` are refused `unknown` for a connection the
44
- * provider doesn't hold.
45
- */
46
- export declare const mcpProvider: import("@agentorg/sdk").RpcInterface<"mcp.provider", {
47
- /** The kinds it offers; `params` and `secrets` are the JSON Schemas of what each one takes. */
48
- kinds: import("@agentorg/sdk").MethodSpec<z.ZodObject<{}, z.core.$strip>, z.ZodObject<{
49
- kinds: z.ZodArray<z.ZodObject<{
50
- kind: z.ZodString;
51
- title: z.ZodString;
52
- params: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
53
- secrets: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
54
- }, z.core.$strip>>;
55
- }, z.core.$strip>>;
56
- /**
57
- * Make a connection and its Actor, named `name`. It returns once the Actor has joined the Hub,
58
- * whether or not the server has answered yet; `status` says how it stands. Refused `invalid`
59
- * when `kind` isn't offered or `params` or `secrets` don't fit it.
60
- */
61
- create: import("@agentorg/sdk").MethodSpec<z.ZodObject<{
62
- kind: z.ZodString;
63
- params: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
64
- secrets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
65
- name: z.ZodString;
66
- }, z.core.$strip>, z.ZodObject<{
67
- connection: z.ZodString;
68
- target: z.ZodString;
69
- }, z.core.$strip>>;
70
- status: import("@agentorg/sdk").MethodSpec<z.ZodObject<{
71
- connection: z.ZodString;
72
- }, z.core.$strip>, z.ZodObject<{
73
- state: z.ZodEnum<{
74
- connected: "connected";
75
- connecting: "connecting";
76
- down: "down";
77
- }>;
78
- detail: z.ZodOptional<z.ZodString>;
79
- tools: z.ZodArray<z.ZodObject<{
80
- name: z.ZodString;
81
- description: z.ZodOptional<z.ZodString>;
82
- }, z.core.$strip>>;
83
- }, z.core.$strip>>;
84
- /** End the connection and close its Actor, which removes what was bound to it. */
85
- remove: import("@agentorg/sdk").MethodSpec<z.ZodObject<{
86
- connection: z.ZodString;
87
- }, z.core.$strip>, z.ZodObject<{}, z.core.$strip>>;
88
- }, {}, 1>;