@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
@@ -16,7 +16,7 @@
16
16
  ],
17
17
  "scripts": {
18
18
  "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json && tsc -p tsconfig.test.json && node scripts/build.js",
19
- "test": "node --experimental-strip-types --no-warnings --test --test-timeout=60000 \"test/**/*.test.ts\""
19
+ "test": "node --experimental-strip-types --no-warnings --test --test-concurrency=3 --test-timeout=60000 \"test/**/*.test.ts\""
20
20
  },
21
21
  "devDependencies": {
22
22
  "@agentorg/interfaces": "0.0.0",
@@ -27,6 +27,8 @@
27
27
  "@types/react": "19.3.0",
28
28
  "@types/react-dom": "19.3.0",
29
29
  "@types/ws": "8.18.2",
30
+ "@xterm/addon-fit": "0.10.0",
31
+ "@xterm/xterm": "5.5.0",
30
32
  "class-variance-authority": "0.7.1",
31
33
  "clsx": "2.1.1",
32
34
  "esbuild": "0.28.2",
@@ -40,5 +42,8 @@
40
42
  "tw-animate-css": "1.4.0",
41
43
  "ws": "8.22.1",
42
44
  "zod": "^4.6.7"
45
+ },
46
+ "dependencies": {
47
+ "react-diff-view": "3.3.3"
43
48
  }
44
49
  }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * An agent's chats: each one's entry in the store, and the looper's thread it is. A chat is made by
3
+ * the message that starts it and is never deleted. The entries are read when a chat is first asked
4
+ * for, all of them, and then stay in memory.
5
+ */
6
+ import { type ChatEntry, type chat, type kv } from "@agentorg/interfaces";
7
+ import { type Model, type Tool } from "@agentorg/looper";
8
+ import { type Log, type Slot } from "@agentorg/sdk";
9
+ import { z } from "zod";
10
+ type Store = Slot<typeof kv>["call"];
11
+ type Listed = z.input<typeof ChatEntry>;
12
+ type Input<M extends keyof (typeof chat)["methods"]> = z.output<(typeof chat)["methods"][M]["input"]>;
13
+ type Emitted<E extends keyof (typeof chat)["events"]> = z.input<(typeof chat)["events"][E]["payload"]>;
14
+ export declare function createChats(options: {
15
+ store: Store;
16
+ /** The agent's model, and what a request to it says and offers, as its looper asks for them. */
17
+ model: Model;
18
+ request(): Promise<{
19
+ instructions: string;
20
+ tools: Tool[];
21
+ model?: string | undefined;
22
+ }>;
23
+ /** Whether the agent is stopped when its chats are first held. */
24
+ stopped: boolean;
25
+ emit: {
26
+ message(message: Emitted<"message">): void;
27
+ delta(delta: Emitted<"delta">): void;
28
+ status(status: Emitted<"status">): void;
29
+ chat(entry: Listed): void;
30
+ };
31
+ log: Log;
32
+ }): {
33
+ /**
34
+ * Say a message in a chat, or in a new chat it starts. With no turn in the chat it starts one;
35
+ * a turn running there takes it at its next model request.
36
+ */
37
+ send({ chat, text }: Input<"send">): Promise<{
38
+ chat: string;
39
+ messageId: string;
40
+ }>;
41
+ history: ({ chat, before, limit }: Input<"history">) => Promise<{
42
+ messages: import("@agentorg/looper").Shown[];
43
+ more: boolean;
44
+ }>;
45
+ /** What the agent is doing now. */
46
+ state: () => {
47
+ state: "idle" | "working";
48
+ };
49
+ /**
50
+ * The chats after `before` in the list, the one a message was last sent in first. `before`
51
+ * holds its place whether it is archived or not, so a page never leaves out a chat that hasn't
52
+ * moved since the page before it.
53
+ */
54
+ list({ archived, before, limit }: Input<"list">): Promise<{
55
+ chats: {
56
+ id: string;
57
+ title: string;
58
+ at: string;
59
+ latest?: {
60
+ id: string;
61
+ at: string;
62
+ } | undefined;
63
+ problem?: {
64
+ code: string;
65
+ message: string;
66
+ } | undefined;
67
+ archived: boolean;
68
+ }[];
69
+ more: boolean;
70
+ }>;
71
+ rename({ chat, title }: Input<"rename">): Promise<{}>;
72
+ archive({ chat, archived }: Input<"archive">): Promise<{}>;
73
+ /**
74
+ * Stop the agent: its turns are cut, and none starts until it is started. A call already
75
+ * running finishes first, so this answers when nothing more will be written. Its chats stay as
76
+ * they are, to be read.
77
+ */
78
+ stop: () => Promise<void>;
79
+ start: () => void;
80
+ /** Go on with the turns that were cut off when the agent last ran, each once. */
81
+ resume(): Promise<void>;
82
+ /**
83
+ * Close for good: its turns are cut, and none starts after. They stay marked, so the engine
84
+ * that opens this store next goes on with them.
85
+ */
86
+ close: () => Promise<void>;
87
+ /** Settles when every chat's latest turn has written all it will. */
88
+ settled(): Promise<void>;
89
+ /** Streamed text of the model request running now. */
90
+ delta(requestId: string, text: string): void;
91
+ };
92
+ export {};
@@ -0,0 +1,357 @@
1
+ /**
2
+ * An agent's chats: each one's entry in the store, and the looper's thread it is. A chat is made by
3
+ * the message that starts it and is never deleted. The entries are read when a chat is first asked
4
+ * for, all of them, and then stay in memory.
5
+ */
6
+ import { CHAT_TITLE_MAX, } from "@agentorg/interfaces";
7
+ import { createLooper, Entry, Refusal, textOf, } from "@agentorg/looper";
8
+ import { Refused } from "@agentorg/sdk";
9
+ import { z } from "zod";
10
+ import { head, relayed } from "./tools.js";
11
+ /** A chat's entry is kept under `chats/<id>`. */
12
+ const ENTRIES = "chats/";
13
+ /**
14
+ * The looper's keys in the agent's store. A chat is a thread by its id, and a thread's record is
15
+ * where a chat's has always been, under `chat/<id>/`; anything else the looper keeps is under
16
+ * `loop/`. A list is of one or of the other, never across both. What the store refuses, the looper
17
+ * is told as its own refusal.
18
+ */
19
+ const THREADS = "thread/";
20
+ const CHATS = "chat/";
21
+ const REST = "loop/";
22
+ const toStore = (key) => key.startsWith(THREADS) ? CHATS + key.slice(THREADS.length) : REST + key;
23
+ const fromStore = (key) => key.startsWith(CHATS) ? THREADS + key.slice(CHATS.length) : key.slice(REST.length);
24
+ function forLooper(store) {
25
+ return {
26
+ get: ({ key }) => relayed(store.get({ key: toStore(key) })),
27
+ set: ({ key, value }) => relayed(store.set({ key: toStore(key), value })),
28
+ delete: ({ key }) => relayed(store.delete({ key: toStore(key) })),
29
+ async list({ prefix = "", after, limit }) {
30
+ const page = await relayed(store.list({
31
+ prefix: toStore(prefix),
32
+ ...(after === undefined ? {} : { after: toStore(after) }),
33
+ ...(limit === undefined ? {} : { limit }),
34
+ }));
35
+ return {
36
+ keys: page.keys.map(fromStore),
37
+ next: page.next === null ? null : fromStore(page.next),
38
+ };
39
+ },
40
+ };
41
+ }
42
+ /**
43
+ * The looper as the chats call it: every call into it passes here. What the looper refuses is
44
+ * refused as the chat's, and what the store refused comes back as the store's own error.
45
+ */
46
+ function forChats(looper) {
47
+ const outward = (error) => {
48
+ if (!(error instanceof Refusal))
49
+ throw error;
50
+ throw error.cause ?? new Refused(error.code, error.message);
51
+ };
52
+ return new Proxy(looper, {
53
+ get(target, name, receiver) {
54
+ const member = Reflect.get(target, name, receiver);
55
+ if (typeof member !== "function")
56
+ return member;
57
+ return (...given) => {
58
+ try {
59
+ const result = member.apply(target, given);
60
+ return result instanceof Promise ? result.catch(outward) : result;
61
+ }
62
+ catch (error) {
63
+ return outward(error);
64
+ }
65
+ };
66
+ },
67
+ });
68
+ }
69
+ /** Where the one conversation an agent had before it had chats was kept. */
70
+ const LEGACY = "msg/";
71
+ /** Stored values read at once when the entries, or that conversation, are first loaded. */
72
+ const READS = 16;
73
+ /** The chat that the one conversation an agent had before it had chats becomes. */
74
+ const FIRST = "first";
75
+ const PAGE = 50;
76
+ /** What is kept of a chat beside its record. */
77
+ const Stored = z.object({
78
+ title: z.string(),
79
+ at: z.string(),
80
+ /** Its place in the list: the highest is the chat a message was last sent in. */
81
+ order: z.number(),
82
+ latest: z.object({ id: z.string(), at: z.string() }).optional(),
83
+ /** Why a message in it got no answer, until its next reply. */
84
+ problem: z.object({ code: z.string(), message: z.string() }).optional(),
85
+ archived: z.boolean(),
86
+ });
87
+ /** A title from a message: its first line with anything in it, on one line. */
88
+ const titleOf = (text) => head((text.split("\n").find((line) => /\S/.test(line)) ?? "").replace(/\s+/g, " ").trim(), CHAT_TITLE_MAX);
89
+ /** Every key under `prefix`, in order. */
90
+ async function keysUnder(store, prefix) {
91
+ const keys = [];
92
+ let after;
93
+ do {
94
+ const page = await store.list({ prefix, limit: 1000, ...(after ? { after } : {}) });
95
+ keys.push(...page.keys);
96
+ after = page.next ?? undefined;
97
+ } while (after);
98
+ return keys;
99
+ }
100
+ export function createChats(options) {
101
+ const { store, emit, log } = options;
102
+ /** The highest place in the list given so far. */
103
+ let top = 0;
104
+ /** Entries being written, one after another, so the latest written is the latest said. */
105
+ let writing = Promise.resolve();
106
+ /** The chats, by id, once their entries are read. */
107
+ const chats = new Map();
108
+ let loaded;
109
+ /** The chats whose loops are at work now: the agent is working while there is one. */
110
+ const atWork = new Set();
111
+ const stateNow = () => ({ state: atWork.size > 0 ? "working" : "idle" });
112
+ /** The chats' loops: a chat is the looper's thread of the same name. */
113
+ const looper = forChats(createLooper({
114
+ store: forLooper(store),
115
+ model: options.model,
116
+ request: options.request,
117
+ out: {
118
+ message(thread, message) {
119
+ const held = chats.get(thread);
120
+ if (held)
121
+ sent(held, message);
122
+ emit.message({ ...message, chat: thread });
123
+ },
124
+ delta: (thread, id, text) => emit.delta({ chat: thread, id, text }),
125
+ status(thread, status) {
126
+ const was = atWork.size > 0;
127
+ if (status.state === "thinking" || status.state === "tool")
128
+ atWork.add(thread);
129
+ else
130
+ atWork.delete(thread);
131
+ // A turn that failed left its chat's message with no answer: the chat is told why,
132
+ // before the agent is said to be idle.
133
+ const held = chats.get(thread);
134
+ if (status.state === "error" && held) {
135
+ const { code, message } = status.detail;
136
+ held.stored.problem = { code: head(code, 128), message: head(message, 2000) };
137
+ told(held);
138
+ }
139
+ if (atWork.size > 0 !== was)
140
+ emit.status(stateNow());
141
+ },
142
+ },
143
+ stopped: options.stopped,
144
+ log,
145
+ }));
146
+ const entryOf = ({ id, stored }) => ({
147
+ id,
148
+ title: stored.title,
149
+ at: stored.at,
150
+ ...(stored.latest ? { latest: stored.latest } : {}),
151
+ ...(stored.problem ? { problem: stored.problem } : {}),
152
+ archived: stored.archived,
153
+ });
154
+ /** Write a chat's entry as it now is, after any write before it. */
155
+ const save = (held) => {
156
+ const value = { ...held.stored };
157
+ const written = writing.then(() => store.set({ key: ENTRIES + held.id, value }));
158
+ writing = written.catch(() => { });
159
+ return written;
160
+ };
161
+ /** Say a chat's entry as it now is; its write follows. */
162
+ const told = (held) => {
163
+ // What it tells is already so: an entry that can't be written is behind until the next.
164
+ save(held).catch((error) => log.error("A chat's entry couldn't be stored.", { chat: held.id, error: String(error) }));
165
+ emit.chat(entryOf(held));
166
+ };
167
+ /**
168
+ * A message sent in a chat moves it to the front of the list; a reply is what is new in it, and
169
+ * its message has its answer.
170
+ */
171
+ const sent = (held, message) => {
172
+ if (message.role === "tool")
173
+ return;
174
+ held.stored.order = ++top;
175
+ if (message.role === "assistant") {
176
+ held.stored.latest = { id: message.id, at: message.at };
177
+ delete held.stored.problem;
178
+ }
179
+ told(held);
180
+ };
181
+ /** Change what a chat is called or where it is listed: a change the store refuses isn't made. */
182
+ const keep = async (held, key, value) => {
183
+ const was = held.stored[key];
184
+ held.stored[key] = value;
185
+ try {
186
+ await save(held);
187
+ }
188
+ catch (error) {
189
+ held.stored[key] = was;
190
+ throw error;
191
+ }
192
+ emit.chat(entryOf(held));
193
+ };
194
+ /**
195
+ * The conversation an agent had before it had chats becomes its first chat. Its entry is written
196
+ * before any message moves, from all of them, so a move cut short goes on from where it stopped
197
+ * with the entry it began with.
198
+ */
199
+ const adopt = async () => {
200
+ const keys = await keysUnder(store, LEGACY);
201
+ if (keys.length === 0)
202
+ return;
203
+ const values = [];
204
+ for (let at = 0; at < keys.length; at += READS)
205
+ values.push(...(await Promise.all(keys.slice(at, at + READS).map((key) => store.get({ key })))).map((read) => read.value));
206
+ if (!Stored.safeParse((await store.get({ key: ENTRIES + FIRST })).value).success) {
207
+ const read = values.flatMap((value) => {
208
+ const entry = Entry.safeParse(value);
209
+ return entry.success ? [entry.data] : [];
210
+ });
211
+ const reply = read.findLast((entry) => entry.role === "assistant");
212
+ const opening = read.find((entry) => entry.role === "user") ?? read[0];
213
+ const stored = {
214
+ title: opening ? titleOf(textOf(opening)) : "",
215
+ at: read[0]?.at ?? new Date().toISOString(),
216
+ // Behind every chat started since.
217
+ order: 0,
218
+ ...(reply ? { latest: { id: reply.id, at: reply.at } } : {}),
219
+ archived: false,
220
+ };
221
+ await store.set({ key: ENTRIES + FIRST, value: stored });
222
+ }
223
+ for (const [i, key] of keys.entries()) {
224
+ if (values[i] !== undefined)
225
+ await store.set({
226
+ key: `${CHATS}${FIRST}/${key}`,
227
+ value: values[i],
228
+ });
229
+ await store.delete({ key });
230
+ }
231
+ log.info("The conversation it had is now its first chat.", { messages: keys.length });
232
+ };
233
+ const load = () => {
234
+ if (loaded)
235
+ return loaded;
236
+ const attempt = (async () => {
237
+ await adopt();
238
+ const keys = await keysUnder(store, ENTRIES);
239
+ for (let at = 0; at < keys.length; at += READS) {
240
+ const some = keys.slice(at, at + READS);
241
+ const values = await Promise.all(some.map((key) => store.get({ key })));
242
+ some.forEach((key, i) => {
243
+ const stored = Stored.safeParse(values[i]?.value);
244
+ const id = key.slice(ENTRIES.length);
245
+ if (!stored.success) {
246
+ log.warn("A chat's entry didn't read back; the chat is left out.", { key });
247
+ return;
248
+ }
249
+ chats.set(id, { id, stored: stored.data });
250
+ top = Math.max(top, stored.data.order);
251
+ });
252
+ }
253
+ return chats;
254
+ })();
255
+ loaded = attempt;
256
+ attempt.catch(() => {
257
+ if (loaded === attempt)
258
+ loaded = undefined;
259
+ });
260
+ return attempt;
261
+ };
262
+ const heldOf = async (id) => {
263
+ const held = (await load()).get(id);
264
+ if (!held)
265
+ throw new Refused("unknown", "This agent has no such chat.");
266
+ return held;
267
+ };
268
+ return {
269
+ /**
270
+ * Say a message in a chat, or in a new chat it starts. With no turn in the chat it starts one;
271
+ * a turn running there takes it at its next model request.
272
+ */
273
+ async send({ chat, text }) {
274
+ if (chat !== undefined) {
275
+ await heldOf(chat);
276
+ const { messageId } = await looper.send(chat, text);
277
+ return { chat, messageId };
278
+ }
279
+ const found = await load();
280
+ const held = {
281
+ id: crypto.randomUUID(),
282
+ stored: {
283
+ title: titleOf(text),
284
+ at: new Date().toISOString(),
285
+ order: ++top,
286
+ archived: false,
287
+ },
288
+ };
289
+ try {
290
+ // Its entry first: a record with no entry would be a chat nobody can find.
291
+ await store.set({ key: ENTRIES + held.id, value: held.stored });
292
+ found.set(held.id, held);
293
+ const { messageId } = await looper.send(held.id, text);
294
+ return { chat: held.id, messageId };
295
+ }
296
+ catch (error) {
297
+ found.delete(held.id);
298
+ await store.delete({ key: ENTRIES + held.id }).catch(() => { });
299
+ throw error;
300
+ }
301
+ },
302
+ history: async ({ chat, before, limit }) => looper.page((await heldOf(chat)).id, before, limit ?? PAGE),
303
+ /** What the agent is doing now. */
304
+ state: stateNow,
305
+ /**
306
+ * The chats after `before` in the list, the one a message was last sent in first. `before`
307
+ * holds its place whether it is archived or not, so a page never leaves out a chat that hasn't
308
+ * moved since the page before it.
309
+ */
310
+ async list({ archived = false, before, limit = PAGE }) {
311
+ const found = await load();
312
+ const mark = before === undefined ? undefined : found.get(before);
313
+ if (before !== undefined && !mark)
314
+ return { chats: [], more: false };
315
+ const rest = [...found.values()]
316
+ .filter(({ stored }) => stored.archived === archived && (!mark || stored.order < mark.stored.order))
317
+ .sort((a, b) => b.stored.order - a.stored.order);
318
+ return { chats: rest.slice(0, limit).map(entryOf), more: rest.length > limit };
319
+ },
320
+ async rename({ chat, title }) {
321
+ await keep(await heldOf(chat), "title", titleOf(title));
322
+ return {};
323
+ },
324
+ async archive({ chat, archived }) {
325
+ await keep(await heldOf(chat), "archived", archived);
326
+ return {};
327
+ },
328
+ /**
329
+ * Stop the agent: its turns are cut, and none starts until it is started. A call already
330
+ * running finishes first, so this answers when nothing more will be written. Its chats stay as
331
+ * they are, to be read.
332
+ */
333
+ stop: () => looper.stop(),
334
+ start: () => looper.start(),
335
+ /** Go on with the turns that were cut off when the agent last ran, each once. */
336
+ async resume() {
337
+ // The chats first: what a turn says moves its chat in the list.
338
+ await load();
339
+ await looper.resume();
340
+ },
341
+ /**
342
+ * Close for good: its turns are cut, and none starts after. They stay marked, so the engine
343
+ * that opens this store next goes on with them.
344
+ */
345
+ close: () => looper.close(),
346
+ /** Settles when every chat's latest turn has written all it will. */
347
+ async settled() {
348
+ await loaded?.catch(() => undefined);
349
+ await looper.settled();
350
+ await writing;
351
+ },
352
+ /** Streamed text of the model request running now. */
353
+ delta(requestId, text) {
354
+ looper.delta(requestId, text);
355
+ },
356
+ };
357
+ }
@@ -1,6 +1,7 @@
1
1
  /**
2
- * The engine: one agent's chat, its loop, and what a request says and offers. Its host says where
3
- * the record is kept and what the agent is told when no `settings` is bound.
2
+ * The engine: the looper bound to one agent. Its chats are the looper's threads, its model is the
3
+ * agent's `model` slot, and what a request says and offers is read from what the agent is bound to.
4
+ * Its host says where the records are kept and what the agent is told when no `settings` is bound.
4
5
  */
5
6
  import type { agentSettings, chat, kv, llmSession } from "@agentorg/interfaces";
6
7
  import { type Context, type Slot } from "@agentorg/sdk";
@@ -9,15 +10,33 @@ type Host = Pick<Context<{
9
10
  settings: typeof agentSettings;
10
11
  }, {
11
12
  chat: typeof chat;
12
- }>, "slots" | "exports" | "exposed" | "log">;
13
+ }>, "slots" | "exports" | "exposed" | "onExposed" | "log">;
13
14
  export declare function startEngine(ctx: Host, options: {
14
15
  store: Slot<typeof kv>["call"];
15
16
  /** What the agent is told while no `settings` is bound. */
16
17
  instructions(): string;
18
+ /** Whether it starts stopped, as it was left. */
19
+ stopped?: boolean;
17
20
  }): {
18
- /** Stop the turn in progress. */
19
- cancel: () => Promise<void>;
20
- /** Settles when the latest turn has written all it will. */
21
- settled: () => Promise<void>;
21
+ /** The models the agent can use, and the one chosen for it. */
22
+ models(): Promise<{
23
+ models: {
24
+ id: string;
25
+ name: string;
26
+ }[];
27
+ current?: string | undefined;
28
+ }>;
29
+ /** Choose the model for its next requests, kept with it; with none, the provider's default. */
30
+ setModel(model: string | undefined): Promise<void>;
31
+ /** Stop the agent: its turns are cut and none starts until `start`. Its chats stay readable. */
32
+ stop(): Promise<void>;
33
+ start: () => void;
34
+ /**
35
+ * Close for good: its turns are cut, and none starts after. They go on in the engine that
36
+ * opens this store next.
37
+ */
38
+ close(): Promise<void>;
39
+ /** Settles when every chat's latest turn has written all it will. */
40
+ settled(): Promise<void>;
22
41
  };
23
42
  export {};