@ai-ecoverse/slicc-agent 3.5.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -4
- package/bin/agent +76 -0
- package/dist/agent.js +7 -14
- package/dist/agent.js.map +2 -2
- package/dist/agents.js +133 -0
- package/dist/agents.js.map +7 -0
- package/dist/client.js +3 -1
- package/dist/client.js.map +2 -2
- package/dist/cone.js +2 -16
- package/dist/cone.js.map +2 -2
- package/dist/control.js +68 -10
- package/dist/control.js.map +2 -2
- package/dist/host.js +11 -2
- package/dist/host.js.map +2 -2
- package/dist/kernel/env.js +49 -5
- package/dist/kernel/env.js.map +2 -2
- package/dist/kernel/exec.js +2 -1
- package/dist/kernel/exec.js.map +2 -2
- package/dist/kernel/groups.js +31 -0
- package/dist/kernel/groups.js.map +7 -0
- package/dist/kernel/watch.js +86 -0
- package/dist/kernel/watch.js.map +2 -2
- package/dist/licks/index.js +16 -1
- package/dist/licks/index.js.map +2 -2
- package/dist/licks/lick.js.map +1 -1
- package/dist/licks/licks.js +12 -1
- package/dist/licks/licks.js.map +2 -2
- package/dist/main.js +38 -9
- package/dist/main.js.map +2 -2
- package/dist/roles/frontmatter.js +59 -0
- package/dist/roles/frontmatter.js.map +7 -0
- package/dist/roles/roles.js +204 -0
- package/dist/roles/roles.js.map +7 -0
- package/dist/scoops/cli.js +391 -0
- package/dist/scoops/cli.js.map +7 -0
- package/dist/scoops/extension.js +301 -0
- package/dist/scoops/extension.js.map +7 -0
- package/dist/scoops/identity.js +33 -0
- package/dist/scoops/identity.js.map +7 -0
- package/dist/scoops/index.js +120 -0
- package/dist/scoops/index.js.map +7 -0
- package/dist/scoops/requests.js +136 -0
- package/dist/scoops/requests.js.map +7 -0
- package/dist/scoops/service.js +409 -0
- package/dist/scoops/service.js.map +7 -0
- package/dist/scoops/sync.js +252 -0
- package/dist/scoops/sync.js.map +7 -0
- package/dist/scoops/visible.js +15 -0
- package/dist/scoops/visible.js.map +7 -0
- package/dist/services.js.map +2 -2
- package/dist/spectrum/agent-port.js +137 -54
- package/dist/spectrum/agent-port.js.map +2 -2
- package/dist/spectrum/messages.js +19 -10
- package/dist/spectrum/messages.js.map +2 -2
- package/dist/types/agent.d.ts +3 -1
- package/dist/types/agents.d.ts +54 -0
- package/dist/types/client.d.ts +3 -1
- package/dist/types/cone.d.ts +1 -3
- package/dist/types/control.d.ts +7 -1
- package/dist/types/host.d.ts +2 -1
- package/dist/types/kernel/client.d.ts +13 -0
- package/dist/types/kernel/env.d.ts +13 -2
- package/dist/types/kernel/exec.d.ts +2 -2
- package/dist/types/kernel/groups.d.ts +7 -0
- package/dist/types/kernel/watch.d.ts +12 -1
- package/dist/types/licks/index.d.ts +5 -0
- package/dist/types/licks/lick.d.ts +1 -1
- package/dist/types/licks/licks.d.ts +2 -1
- package/dist/types/main.d.ts +3 -0
- package/dist/types/roles/frontmatter.d.ts +7 -0
- package/dist/types/roles/roles.d.ts +48 -0
- package/dist/types/scoops/cli.d.ts +66 -0
- package/dist/types/scoops/extension.d.ts +63 -0
- package/dist/types/scoops/identity.d.ts +6 -0
- package/dist/types/scoops/index.d.ts +37 -0
- package/dist/types/scoops/requests.d.ts +32 -0
- package/dist/types/scoops/service.d.ts +116 -0
- package/dist/types/scoops/sync.d.ts +22 -0
- package/dist/types/scoops/visible.d.ts +4 -0
- package/dist/types/services.d.ts +30 -0
- package/dist/types/spectrum/agent-port.d.ts +13 -11
- package/dist/types/views.d.ts +12 -0
- package/dist/views.js +89 -0
- package/dist/views.js.map +7 -0
- package/package.json +6 -4
- package/packages/vfs-root/agents/delegate.md +8 -0
- package/packages/vfs-root/agents/oracle.md +25 -0
- package/packages/vfs-root/agents/reviewer.md +23 -0
- package/packages/vfs-root/agents/scout.md +30 -0
- package/packages/vfs-root/agents/worker.md +23 -0
- package/packages/vfs-root/skills/agent/SKILL.md +72 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { answerText } from "../agent.js";
|
|
2
|
+
import { resolve } from "../kernel/paths.js";
|
|
3
|
+
import {
|
|
4
|
+
choices,
|
|
5
|
+
fail,
|
|
6
|
+
flags,
|
|
7
|
+
isAnswer,
|
|
8
|
+
last,
|
|
9
|
+
MAX_DEPTH,
|
|
10
|
+
prompts,
|
|
11
|
+
readText,
|
|
12
|
+
role,
|
|
13
|
+
unsupported
|
|
14
|
+
} from "./cli.js";
|
|
15
|
+
import { SCOOPS_ROOT } from "./service.js";
|
|
16
|
+
const SESSIONS = { tmp: "/tmp/agent-sessions", durable: "/home/sessions" };
|
|
17
|
+
const SYNC_VALUES = [
|
|
18
|
+
"--name",
|
|
19
|
+
"--agent",
|
|
20
|
+
"--provider",
|
|
21
|
+
"--model",
|
|
22
|
+
"--thinking",
|
|
23
|
+
"--effort",
|
|
24
|
+
"--tools",
|
|
25
|
+
"--prompt",
|
|
26
|
+
"--file",
|
|
27
|
+
"--read-only",
|
|
28
|
+
"--system-prompt",
|
|
29
|
+
"--system-prompt-file",
|
|
30
|
+
"--background-after"
|
|
31
|
+
];
|
|
32
|
+
const SYNC_SWITCHES = /* @__PURE__ */ new Set([
|
|
33
|
+
"--persist-session",
|
|
34
|
+
"--no-persist-session",
|
|
35
|
+
"--no-extensions",
|
|
36
|
+
"--no-skills",
|
|
37
|
+
"--no-context-files"
|
|
38
|
+
]);
|
|
39
|
+
function persistOf(switches) {
|
|
40
|
+
if (switches.has("--no-persist-session")) return "none";
|
|
41
|
+
return switches.has("--persist-session") ? "durable" : "tmp";
|
|
42
|
+
}
|
|
43
|
+
function block(text) {
|
|
44
|
+
return typeof text === "string" ? text : JSON.stringify(text, null, 2);
|
|
45
|
+
}
|
|
46
|
+
function entryMarkdown(entry) {
|
|
47
|
+
const message = entry.model?.[0];
|
|
48
|
+
if (entry.kind === "pi.user") return `## Request
|
|
49
|
+
|
|
50
|
+
${answerText(message?.content)}`;
|
|
51
|
+
if (entry.kind === "pi.tool-result")
|
|
52
|
+
return `### Result
|
|
53
|
+
|
|
54
|
+
\`\`\`
|
|
55
|
+
${answerText(message?.content)}
|
|
56
|
+
\`\`\``;
|
|
57
|
+
if (entry.kind !== "pi.assistant") return void 0;
|
|
58
|
+
const parts = Array.isArray(message?.content) ? message.content : [];
|
|
59
|
+
const calls = parts.filter((part) => part.type === "toolCall").map((part) => `\`${part.name}\` ${block(part.arguments)}`);
|
|
60
|
+
return [`## Answer
|
|
61
|
+
|
|
62
|
+
${answerText(message?.content)}`, ...calls].join("\n\n");
|
|
63
|
+
}
|
|
64
|
+
function transcript(record, entries) {
|
|
65
|
+
const body = entries.map(entryMarkdown).filter(Boolean);
|
|
66
|
+
return `# ${record.name}${record.role ? ` (${record.role})` : ""}
|
|
67
|
+
|
|
68
|
+
${body.join("\n\n")}
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
async function archive(deps, record, persist, context) {
|
|
72
|
+
if (persist === "none") return null;
|
|
73
|
+
const conversation = await deps.harness.conversation(
|
|
74
|
+
record.conversation,
|
|
75
|
+
context
|
|
76
|
+
);
|
|
77
|
+
if (!conversation) return null;
|
|
78
|
+
const { entries } = await conversation.context(context);
|
|
79
|
+
const path = `${SESSIONS[persist]}/${record.folder}.md`;
|
|
80
|
+
await deps.env.writeFile(path, transcript(record, entries), context);
|
|
81
|
+
return path;
|
|
82
|
+
}
|
|
83
|
+
async function finish(deps, id, persist, context) {
|
|
84
|
+
const record = deps.agents.state().scoops[id];
|
|
85
|
+
if (!record) return;
|
|
86
|
+
await archive(deps, record, persist, context).catch(() => null);
|
|
87
|
+
await deps.scoops.stop(id, false, context);
|
|
88
|
+
await deps.env.remove(
|
|
89
|
+
`${SCOOPS_ROOT}/${record.folder}`,
|
|
90
|
+
{ recursive: true, force: true },
|
|
91
|
+
context
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
async function callerRunning(deps, who, context) {
|
|
95
|
+
if (!who.fromAgent) return false;
|
|
96
|
+
const state = deps.agents.state();
|
|
97
|
+
const conversation = who.scoop ? state.scoops[who.scoop]?.conversation : state.cones[who.cone]?.conversation;
|
|
98
|
+
return conversation !== void 0 && deps.scoops.busy(conversation, context);
|
|
99
|
+
}
|
|
100
|
+
async function keep(deps, id, tasks, target, context) {
|
|
101
|
+
if (target) await deps.scoops.detach(tasks, target, context);
|
|
102
|
+
await deps.agents.update((_tx, state) => {
|
|
103
|
+
const record = state.scoops[id];
|
|
104
|
+
if (record) record.kind = "async";
|
|
105
|
+
}, context);
|
|
106
|
+
}
|
|
107
|
+
function watchPid(deps, pid, end) {
|
|
108
|
+
const alive = deps.alive;
|
|
109
|
+
if (pid === null || !alive) return () => void 0;
|
|
110
|
+
const timer = setInterval(() => {
|
|
111
|
+
void alive(pid).then(
|
|
112
|
+
(yes) => yes || end("cancel"),
|
|
113
|
+
() => void 0
|
|
114
|
+
);
|
|
115
|
+
}, 1e3);
|
|
116
|
+
return () => clearInterval(timer);
|
|
117
|
+
}
|
|
118
|
+
async function plan(deps, request, parsed, context) {
|
|
119
|
+
const given = await prompts(deps, request.argv, request, context);
|
|
120
|
+
let workdir;
|
|
121
|
+
const notes = [];
|
|
122
|
+
if (!given.length && parsed.rest.length === 3) {
|
|
123
|
+
const [cwd, allowed, prompt] = parsed.rest;
|
|
124
|
+
workdir = resolve(request.cwd, cwd);
|
|
125
|
+
if (allowed && allowed !== "*")
|
|
126
|
+
notes.push(
|
|
127
|
+
`Use only these commands in bash: ${allowed}. This is not enforced; keep to it anyway.`
|
|
128
|
+
);
|
|
129
|
+
given.push(prompt);
|
|
130
|
+
} else if (parsed.rest.length) return fail(`unexpected argument ${parsed.rest[0]}`, 2);
|
|
131
|
+
if (!given.length)
|
|
132
|
+
return fail("agent needs --prompt, --file, - or <cwd> <allowed-commands> <prompt>", 2);
|
|
133
|
+
const systemFile = last(parsed, "--system-prompt-file");
|
|
134
|
+
const system = last(parsed, "--system-prompt") ?? (systemFile ? await readText(deps, systemFile, request, context) : void 0);
|
|
135
|
+
if (system) notes.push(system);
|
|
136
|
+
if (workdir) notes.push(`Your working folder is ${workdir}; you may change files there.`);
|
|
137
|
+
const readOnly = (last(parsed, "--read-only") ?? "").split(",").map((path) => path.trim()).filter(Boolean).map((path) => resolve(request.cwd, path));
|
|
138
|
+
const result = {
|
|
139
|
+
given,
|
|
140
|
+
instructions: notes.join("\n\n"),
|
|
141
|
+
readOnly,
|
|
142
|
+
persist: persistOf(parsed.switches)
|
|
143
|
+
};
|
|
144
|
+
if (workdir) result.workdir = workdir;
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
async function outcome(deps, tasks, context) {
|
|
148
|
+
let result = { text: "", failed: "no request", path: null };
|
|
149
|
+
for (const task of tasks) {
|
|
150
|
+
const settled = await deps.harness.waitForTask(task, context);
|
|
151
|
+
const outcome2 = settled.state.outcome;
|
|
152
|
+
result = outcome2.status === "completed" ? outcome2.result : { text: "", failed: outcome2.status, path: null };
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
function answerOf(result) {
|
|
157
|
+
if (result.failed) return fail(`the scoop's request ended without an answer: ${result.failed}`);
|
|
158
|
+
return { code: 0, out: result.text.endsWith("\n") ? result.text : `${result.text}
|
|
159
|
+
` };
|
|
160
|
+
}
|
|
161
|
+
async function settle(deps, who, id, tasks, ending, context) {
|
|
162
|
+
const handle = id.slice("scoop:".length);
|
|
163
|
+
if (ending === "detach") {
|
|
164
|
+
await keep(deps, id, tasks, who.id, context);
|
|
165
|
+
const how = who.id ? "its answer arrives as a lick" : `follow it with agent wait ${handle}`;
|
|
166
|
+
return { code: 0, out: `${handle} continues in the background; ${how}
|
|
167
|
+
` };
|
|
168
|
+
}
|
|
169
|
+
if (await callerRunning(deps, who, context)) {
|
|
170
|
+
await keep(deps, id, tasks, who.id, context);
|
|
171
|
+
return {
|
|
172
|
+
code: 143,
|
|
173
|
+
out: `agent: the call was interrupted; ${handle} continues and its answer arrives as a lick
|
|
174
|
+
`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return { code: 130, out: "agent: stopped\n" };
|
|
178
|
+
}
|
|
179
|
+
async function runSync(deps, request, who, context) {
|
|
180
|
+
const refused = unsupported(request.argv);
|
|
181
|
+
if (refused) return refused;
|
|
182
|
+
if (who.depth >= MAX_DEPTH) return fail(`agents nest at most ${MAX_DEPTH} deep`);
|
|
183
|
+
const parsed = flags(request.argv, SYNC_VALUES);
|
|
184
|
+
const unknown = [...parsed.switches].find((flag) => !SYNC_SWITCHES.has(flag));
|
|
185
|
+
if (unknown) return fail(`unknown option ${unknown}`, 2);
|
|
186
|
+
const planned = await plan(deps, request, parsed, context);
|
|
187
|
+
if (isAnswer(planned)) return planned;
|
|
188
|
+
const chosen = choices(parsed);
|
|
189
|
+
if (isAnswer(chosen)) return chosen;
|
|
190
|
+
const found = await role(deps, parsed);
|
|
191
|
+
if (found.error) return found.error;
|
|
192
|
+
const spawned = await deps.scoops.spawn(
|
|
193
|
+
{
|
|
194
|
+
cone: who.cone,
|
|
195
|
+
kind: "sync",
|
|
196
|
+
parent: who.scoop ?? who.cone,
|
|
197
|
+
depth: who.depth + 1,
|
|
198
|
+
roots: { write: planned.workdir ? [planned.workdir] : [], read: planned.readOnly },
|
|
199
|
+
...planned.workdir ? { cwd: planned.workdir } : {},
|
|
200
|
+
...planned.instructions ? { instructions: planned.instructions } : {},
|
|
201
|
+
target: null,
|
|
202
|
+
name: last(parsed, "--name") ?? found.role?.name ?? "agent",
|
|
203
|
+
...found.role ? { role: found.role } : {},
|
|
204
|
+
...chosen.model ? { model: chosen.model } : {},
|
|
205
|
+
...chosen.thinking ? { thinking: chosen.thinking } : {},
|
|
206
|
+
...chosen.tools ? { tools: chosen.tools } : {},
|
|
207
|
+
prompts: planned.given,
|
|
208
|
+
fromAgent: who.fromAgent,
|
|
209
|
+
request: request.id,
|
|
210
|
+
limits: found.roles.limits
|
|
211
|
+
},
|
|
212
|
+
context
|
|
213
|
+
);
|
|
214
|
+
if (spawned.code !== 0) return spawned;
|
|
215
|
+
const id = `scoop:${spawned.out.trim()}`;
|
|
216
|
+
const tasks = spawned.tasks ?? [];
|
|
217
|
+
let end = () => void 0;
|
|
218
|
+
const ended = new Promise((done) => {
|
|
219
|
+
end = done;
|
|
220
|
+
});
|
|
221
|
+
deps.sync.pending.set(request.id, { end });
|
|
222
|
+
const unwatch = watchPid(deps, request.pid, end);
|
|
223
|
+
try {
|
|
224
|
+
const raced = await Promise.race([outcome(deps, tasks, context), ended]);
|
|
225
|
+
if (typeof raced === "string") {
|
|
226
|
+
const answer = await settle(deps, who, id, tasks, raced, context);
|
|
227
|
+
if (answer.code === 130) await finish(deps, id, planned.persist, context);
|
|
228
|
+
return answer;
|
|
229
|
+
}
|
|
230
|
+
await finish(deps, id, planned.persist, context);
|
|
231
|
+
return answerOf(raced);
|
|
232
|
+
} finally {
|
|
233
|
+
unwatch();
|
|
234
|
+
deps.sync.pending.delete(request.id);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function cancelSync(deps, requestId, _context) {
|
|
238
|
+
deps.sync.pending.get(requestId)?.end("cancel");
|
|
239
|
+
return { code: 0, out: "" };
|
|
240
|
+
}
|
|
241
|
+
async function detachSync(deps, requestId, _context) {
|
|
242
|
+
deps.sync.pending.get(requestId)?.end("detach");
|
|
243
|
+
return { code: 0, out: "" };
|
|
244
|
+
}
|
|
245
|
+
export {
|
|
246
|
+
SESSIONS,
|
|
247
|
+
cancelSync,
|
|
248
|
+
detachSync,
|
|
249
|
+
runSync,
|
|
250
|
+
transcript
|
|
251
|
+
};
|
|
252
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/scoops/sync.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { ConversationId, EntryRecord, TaskId } from '@earendil-works/pi-durable';\nimport { answerText } from '../agent.ts';\nimport type { ScoopRecord } from '../agents.ts';\nimport { resolve } from '../kernel/paths.ts';\nimport {\n type Caller,\n choices,\n type Deps,\n fail,\n flags,\n isAnswer,\n last,\n MAX_DEPTH,\n prompts,\n readText,\n role,\n unsupported,\n} from './cli.ts';\nimport type { ReportResult } from './extension.ts';\nimport type { Request } from './requests.ts';\nimport { type Answer, SCOOPS_ROOT } from './service.ts';\n\nexport const SESSIONS = { tmp: '/tmp/agent-sessions', durable: '/home/sessions' };\n\ntype Ending = 'cancel' | 'detach';\n\ninterface Pending {\n end: (ending: Ending) => void;\n}\n\nexport interface SyncState {\n pending: Map<string, Pending>;\n}\n\nconst SYNC_VALUES = [\n '--name',\n '--agent',\n '--provider',\n '--model',\n '--thinking',\n '--effort',\n '--tools',\n '--prompt',\n '--file',\n '--read-only',\n '--system-prompt',\n '--system-prompt-file',\n '--background-after',\n];\nconst SYNC_SWITCHES = new Set([\n '--persist-session',\n '--no-persist-session',\n '--no-extensions',\n '--no-skills',\n '--no-context-files',\n]);\n\ntype Persist = 'tmp' | 'durable' | 'none';\n\nfunction persistOf(switches: Set<string>): Persist {\n if (switches.has('--no-persist-session')) return 'none';\n return switches.has('--persist-session') ? 'durable' : 'tmp';\n}\n\nfunction block(text: unknown): string {\n return typeof text === 'string' ? text : JSON.stringify(text, null, 2);\n}\n\nfunction entryMarkdown(entry: EntryRecord): string | undefined {\n const message = entry.model?.[0] as { content?: unknown; toolCallId?: string } | undefined;\n if (entry.kind === 'pi.user') return `## Request\\n\\n${answerText(message?.content)}`;\n if (entry.kind === 'pi.tool-result')\n return `### Result\\n\\n\\`\\`\\`\\n${answerText(message?.content)}\\n\\`\\`\\``;\n if (entry.kind !== 'pi.assistant') return undefined;\n const parts = Array.isArray(message?.content)\n ? (message.content as { type: string; name?: string; arguments?: unknown }[])\n : [];\n const calls = parts\n .filter((part) => part.type === 'toolCall')\n .map((part) => `\\`${part.name}\\` ${block(part.arguments)}`);\n return [`## Answer\\n\\n${answerText(message?.content)}`, ...calls].join('\\n\\n');\n}\n\nexport function transcript(record: ScoopRecord, entries: readonly EntryRecord[]): string {\n const body = entries.map(entryMarkdown).filter(Boolean);\n return `# ${record.name}${record.role ? ` (${record.role})` : ''}\\n\\n${body.join('\\n\\n')}\\n`;\n}\n\nasync function archive(\n deps: Deps,\n record: ScoopRecord,\n persist: Persist,\n context: Context\n): Promise<string | null> {\n if (persist === 'none') return null;\n const conversation = await deps.harness.conversation(\n record.conversation as ConversationId,\n context\n );\n if (!conversation) return null;\n const { entries } = await conversation.context(context);\n const path = `${SESSIONS[persist]}/${record.folder}.md`;\n await deps.env.writeFile(path, transcript(record, entries), context);\n return path;\n}\n\nasync function finish(deps: Deps, id: string, persist: Persist, context: Context): Promise<void> {\n const record = deps.agents.state().scoops[id];\n if (!record) return;\n await archive(deps, record, persist, context).catch(() => null);\n await deps.scoops.stop(id, false, context);\n await deps.env.remove(\n `${SCOOPS_ROOT}/${record.folder}`,\n { recursive: true, force: true },\n context\n );\n}\n\nasync function callerRunning(deps: Deps, who: Caller, context: Context): Promise<boolean> {\n if (!who.fromAgent) return false;\n const state = deps.agents.state();\n const conversation = who.scoop\n ? state.scoops[who.scoop]?.conversation\n : state.cones[who.cone]?.conversation;\n return conversation !== undefined && deps.scoops.busy(conversation, context);\n}\n\nasync function keep(\n deps: Deps,\n id: string,\n tasks: readonly number[],\n target: string | null,\n context: Context\n) {\n if (target) await deps.scoops.detach(tasks, target, context);\n await deps.agents.update((_tx, state) => {\n const record = state.scoops[id];\n if (record) record.kind = 'async';\n }, context);\n}\n\nfunction watchPid(deps: Deps, pid: number | null, end: (ending: Ending) => void): () => void {\n const alive = deps.alive;\n if (pid === null || !alive) return () => undefined;\n const timer = setInterval(() => {\n void alive(pid).then(\n (yes) => yes || end('cancel'),\n () => undefined\n );\n }, 1000);\n return () => clearInterval(timer);\n}\n\ninterface Plan {\n given: string[];\n workdir?: string;\n instructions: string;\n readOnly: string[];\n persist: Persist;\n}\n\nasync function plan(\n deps: Deps,\n request: Request,\n parsed: ReturnType<typeof flags>,\n context: Context\n) {\n const given = await prompts(deps, request.argv, request, context);\n let workdir: string | undefined;\n const notes: string[] = [];\n if (!given.length && parsed.rest.length === 3) {\n const [cwd, allowed, prompt] = parsed.rest as [string, string, string];\n workdir = resolve(request.cwd, cwd);\n if (allowed && allowed !== '*')\n notes.push(\n `Use only these commands in bash: ${allowed}. This is not enforced; keep to it anyway.`\n );\n given.push(prompt);\n } else if (parsed.rest.length) return fail(`unexpected argument ${parsed.rest[0]}`, 2);\n if (!given.length)\n return fail('agent needs --prompt, --file, - or <cwd> <allowed-commands> <prompt>', 2);\n const systemFile = last(parsed, '--system-prompt-file');\n const system =\n last(parsed, '--system-prompt') ??\n (systemFile ? await readText(deps, systemFile, request, context) : undefined);\n if (system) notes.push(system);\n if (workdir) notes.push(`Your working folder is ${workdir}; you may change files there.`);\n const readOnly = (last(parsed, '--read-only') ?? '')\n .split(',')\n .map((path) => path.trim())\n .filter(Boolean)\n .map((path) => resolve(request.cwd, path));\n const result: Plan = {\n given,\n instructions: notes.join('\\n\\n'),\n readOnly,\n persist: persistOf(parsed.switches),\n };\n if (workdir) result.workdir = workdir;\n return result;\n}\n\nasync function outcome(\n deps: Deps,\n tasks: readonly number[],\n context: Context\n): Promise<ReportResult> {\n let result: ReportResult = { text: '', failed: 'no request', path: null };\n for (const task of tasks) {\n const settled = await deps.harness.waitForTask(task as TaskId<ReportResult>, context);\n const outcome = settled.state.outcome;\n result =\n outcome.status === 'completed'\n ? outcome.result\n : { text: '', failed: outcome.status, path: null };\n }\n return result;\n}\n\nfunction answerOf(result: ReportResult): Answer {\n if (result.failed) return fail(`the scoop's request ended without an answer: ${result.failed}`);\n return { code: 0, out: result.text.endsWith('\\n') ? result.text : `${result.text}\\n` };\n}\n\nasync function settle(\n deps: Deps,\n who: Caller,\n id: string,\n tasks: readonly number[],\n ending: Ending,\n context: Context\n): Promise<Answer> {\n const handle = id.slice('scoop:'.length);\n if (ending === 'detach') {\n await keep(deps, id, tasks, who.id, context);\n const how = who.id ? 'its answer arrives as a lick' : `follow it with agent wait ${handle}`;\n return { code: 0, out: `${handle} continues in the background; ${how}\\n` };\n }\n if (await callerRunning(deps, who, context)) {\n await keep(deps, id, tasks, who.id, context);\n return {\n code: 143,\n out: `agent: the call was interrupted; ${handle} continues and its answer arrives as a lick\\n`,\n };\n }\n return { code: 130, out: 'agent: stopped\\n' };\n}\n\nexport async function runSync(\n deps: Deps,\n request: Request,\n who: Caller,\n context: Context\n): Promise<Answer> {\n const refused = unsupported(request.argv);\n if (refused) return refused;\n if (who.depth >= MAX_DEPTH) return fail(`agents nest at most ${MAX_DEPTH} deep`);\n const parsed = flags(request.argv, SYNC_VALUES);\n const unknown = [...parsed.switches].find((flag) => !SYNC_SWITCHES.has(flag));\n if (unknown) return fail(`unknown option ${unknown}`, 2);\n const planned = await plan(deps, request, parsed, context);\n if (isAnswer(planned)) return planned;\n const chosen = choices(parsed);\n if (isAnswer(chosen)) return chosen;\n const found = await role(deps, parsed);\n if (found.error) return found.error;\n const spawned = await deps.scoops.spawn(\n {\n cone: who.cone,\n kind: 'sync',\n parent: who.scoop ?? who.cone,\n depth: who.depth + 1,\n roots: { write: planned.workdir ? [planned.workdir] : [], read: planned.readOnly },\n ...(planned.workdir ? { cwd: planned.workdir } : {}),\n ...(planned.instructions ? { instructions: planned.instructions } : {}),\n target: null,\n name: last(parsed, '--name') ?? found.role?.name ?? 'agent',\n ...(found.role ? { role: found.role } : {}),\n ...(chosen.model ? { model: chosen.model } : {}),\n ...(chosen.thinking ? { thinking: chosen.thinking as never } : {}),\n ...(chosen.tools ? { tools: chosen.tools } : {}),\n prompts: planned.given,\n fromAgent: who.fromAgent,\n request: request.id,\n limits: found.roles.limits,\n },\n context\n );\n if (spawned.code !== 0) return spawned;\n const id = `scoop:${spawned.out.trim()}`;\n const tasks = spawned.tasks ?? [];\n let end: (ending: Ending) => void = () => undefined;\n const ended = new Promise<Ending>((done) => {\n end = done;\n });\n deps.sync.pending.set(request.id, { end });\n const unwatch = watchPid(deps, request.pid, end);\n try {\n const raced = await Promise.race([outcome(deps, tasks, context), ended]);\n if (typeof raced === 'string') {\n const answer = await settle(deps, who, id, tasks, raced, context);\n if (answer.code === 130) await finish(deps, id, planned.persist, context);\n return answer;\n }\n await finish(deps, id, planned.persist, context);\n return answerOf(raced);\n } finally {\n unwatch();\n deps.sync.pending.delete(request.id);\n }\n}\n\nexport async function cancelSync(\n deps: Deps,\n requestId: string,\n _context: Context\n): Promise<Answer> {\n deps.sync.pending.get(requestId)?.end('cancel');\n return { code: 0, out: '' };\n}\n\nexport async function detachSync(\n deps: Deps,\n requestId: string,\n _context: Context\n): Promise<Answer> {\n deps.sync.pending.get(requestId)?.end('detach');\n return { code: 0, out: '' };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["outcome"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
async function visible(read, conversation, mark) {
|
|
2
|
+
let current = conversation;
|
|
3
|
+
let cap = Number.POSITIVE_INFINITY;
|
|
4
|
+
for (; ; ) {
|
|
5
|
+
if (current === mark.in) return mark.at <= cap;
|
|
6
|
+
const parent = (await read(current))?.parent;
|
|
7
|
+
if (!parent) return false;
|
|
8
|
+
cap = Math.min(cap, Number(parent.at));
|
|
9
|
+
current = Number(parent.conversationId);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
visible
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=visible.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/scoops/visible.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ConversationRecord } from '@earendil-works/pi-durable';\nimport type { Mark } from '../agents.ts';\n\nexport type ReadConversation = (id: number) => Promise<ConversationRecord | undefined>;\n\nexport async function visible(\n read: ReadConversation,\n conversation: number,\n mark: Mark\n): Promise<boolean> {\n let current = conversation;\n let cap = Number.POSITIVE_INFINITY;\n for (;;) {\n if (current === mark.in) return mark.at <= cap;\n const parent = (await read(current))?.parent;\n if (!parent) return false;\n cap = Math.min(cap, Number(parent.at));\n current = Number(parent.conversationId);\n }\n}\n"],
|
|
5
|
+
"mappings": "AAKA,eAAsB,QACpB,MACA,cACA,MACkB;AAClB,MAAI,UAAU;AACd,MAAI,MAAM,OAAO;AACjB,aAAS;AACP,QAAI,YAAY,KAAK,GAAI,QAAO,KAAK,MAAM;AAC3C,UAAM,UAAU,MAAM,KAAK,OAAO,IAAI;AACtC,QAAI,CAAC,OAAQ,QAAO;AACpB,UAAM,KAAK,IAAI,KAAK,OAAO,OAAO,EAAE,CAAC;AACrC,cAAU,OAAO,OAAO,cAAc;AAAA,EACxC;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/services.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/services.ts"],
|
|
4
|
-
"sourcesContent": ["import { type Context, defineService, type ReplicatedState } from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n}\n\nexport interface OperationError {\n code: string;\n message: string;\n}\n\nexport type SendResponse =\n | { accepted: true; submissionId: string; error: null }\n | { accepted: false; submissionId: null; error: OperationError };\n\nexport type PromptResult =\n | { status: 'done'; text: string; reason: null }\n | { status: 'unanswered'; text: null; reason: string };\n\nexport interface AgentControl {\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n wait(submissionId: string, context: Context): Promise<PromptResult>;\n withdraw(\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n abort(context: Context): Promise<void>;\n compact(instructions: string | null, context: Context): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n rewind(messageId: string | null, context: Context): Promise<Rewound>;\n resolveLick(\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n webhook(\n name: string,\n delivery: WebhookRequest,\n context: Context\n ): Promise<{ delivered: boolean }>;\n}\n\nexport type LickResolved =\n | { done: true; text: string; error: null }\n | { done: false; text: null; error: string };\n\nexport interface WebhookRequest {\n id: string | null;\n headers: Record<string, string>;\n body: string;\n}\n\nexport type Rewound =\n | { done: true; text: string; reason: null }\n | { done: false; text: null; reason: 'busy' | 'no-turn' };\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport type Delivered = 'run' | 'steer' | 'follow-up';\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n}\n\nexport const AgentTranscript = defineService<AgentTranscript>('slicc.agent.transcript');\n\nexport interface AgentSessions {\n attach(sessionId: string, context: Context): Promise<void>;\n detach(context: Context): Promise<void>;\n}\n\nexport const AgentSessions = defineService<AgentSessions>('slicc.agent.sessions');\n\nexport const AGENT_SESSION = 'agent';\n\nexport interface ModelChoice {\n id: string;\n label: string;\n provider: string;\n kind: 'chat' | 'classifier';\n reasoning: boolean;\n contextWindow: number;\n}\n\nexport interface AccountState {\n id: string;\n provider: string;\n identity: string;\n status: 'connected' | 'disconnected';\n auth: 'api-key' | 'oauth';\n needs: 'cors-free-transport' | null;\n}\n\nexport interface BudgetState {\n provider: string;\n percent: number;\n window: 'weekly';\n resets: string;\n}\n\nexport interface SignIn {\n clientId: string;\n scopes: string;\n imsEnvironment: string;\n}\n\nexport interface SettingsState {\n models: ModelChoice[];\n accounts: AccountState[];\n budget?: BudgetState | null;\n}\n\nexport interface AgentSettings {\n readonly state: ReplicatedState<SettingsState>;\n connect(\n providerId: string,\n secret: string,\n region: string | null,\n context: Context\n ): Promise<void>;\n disconnect(providerId: string, context: Context): Promise<void>;\n signIn(providerId: string, context: Context): Promise<SignIn | null>;\n}\n\nexport const AgentSettings = defineService<AgentSettings>('slicc.agent.settings');\n"],
|
|
5
|
-
"mappings": "AAAA,SAAuB,qBAA2C;
|
|
4
|
+
"sourcesContent": ["import { type Context, defineService, type ReplicatedState } from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n agentId?: string | null;\n}\n\nexport interface OperationError {\n code: string;\n message: string;\n}\n\nexport type SendResponse =\n | { accepted: true; submissionId: string; error: null }\n | { accepted: false; submissionId: null; error: OperationError };\n\nexport type PromptResult =\n | { status: 'done'; text: string; reason: null }\n | { status: 'unanswered'; text: null; reason: string };\n\nexport interface AgentControl {\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n wait(submissionId: string, context: Context): Promise<PromptResult>;\n withdraw(\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n abort(context: Context): Promise<void>;\n compact(instructions: string | null, context: Context): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n rewind(messageId: string | null, context: Context): Promise<Rewound>;\n resolveLick(\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n webhook(\n name: string,\n delivery: WebhookRequest,\n context: Context\n ): Promise<{ delivered: boolean }>;\n stopAgent(agentId: string, context: Context): Promise<void>;\n selectCone(agentId: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<Created>;\n createScoop(parentId: string, name: string, context: Context): Promise<Created>;\n drop(agentId: string, context: Context): Promise<Created>;\n unqueue(\n agentId: string | null,\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n}\n\nexport type Created = { id: string; error: null } | { id: null; error: string };\n\nexport interface AgentSummary {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n role: string | null;\n}\n\nexport interface AgentsSummary {\n active: string;\n agents: AgentSummary[];\n}\n\nexport type LickResolved =\n | { done: true; text: string; error: null }\n | { done: false; text: null; error: string };\n\nexport interface WebhookRequest {\n id: string | null;\n headers: Record<string, string>;\n body: string;\n}\n\nexport type Rewound =\n | { done: true; text: string; reason: null }\n | { done: false; text: null; reason: 'busy' | 'no-turn' };\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n agentId?: string | null;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport type Delivered = 'run' | 'steer' | 'follow-up';\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents: ReplicatedState<AgentsSummary>;\n readonly views: ReplicatedState<Record<string, ConversationView>>;\n}\n\nexport const AgentTranscript = defineService<AgentTranscript>('slicc.agent.transcript');\n\nexport interface AgentSessions {\n attach(sessionId: string, context: Context): Promise<void>;\n detach(context: Context): Promise<void>;\n}\n\nexport const AgentSessions = defineService<AgentSessions>('slicc.agent.sessions');\n\nexport const AGENT_SESSION = 'agent';\n\nexport interface ModelChoice {\n id: string;\n label: string;\n provider: string;\n kind: 'chat' | 'classifier';\n reasoning: boolean;\n contextWindow: number;\n}\n\nexport interface AccountState {\n id: string;\n provider: string;\n identity: string;\n status: 'connected' | 'disconnected';\n auth: 'api-key' | 'oauth';\n needs: 'cors-free-transport' | null;\n}\n\nexport interface BudgetState {\n provider: string;\n percent: number;\n window: 'weekly';\n resets: string;\n}\n\nexport interface SignIn {\n clientId: string;\n scopes: string;\n imsEnvironment: string;\n}\n\nexport interface SettingsState {\n models: ModelChoice[];\n accounts: AccountState[];\n budget?: BudgetState | null;\n}\n\nexport interface AgentSettings {\n readonly state: ReplicatedState<SettingsState>;\n connect(\n providerId: string,\n secret: string,\n region: string | null,\n context: Context\n ): Promise<void>;\n disconnect(providerId: string, context: Context): Promise<void>;\n signIn(providerId: string, context: Context): Promise<SignIn | null>;\n}\n\nexport const AgentSettings = defineService<AgentSettings>('slicc.agent.settings');\n"],
|
|
5
|
+
"mappings": "AAAA,SAAuB,qBAA2C;AA8F3D,MAAM,eAAe,cAA4B,qBAAqB;AAWtE,MAAM,kBAAkB,cAA+B,wBAAwB;AAO/E,MAAM,gBAAgB,cAA6B,sBAAsB;AAEzE,MAAM,gBAAgB;AAmDtB,MAAM,gBAAgB,cAA6B,sBAAsB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -23,73 +23,107 @@ function status(view, messages) {
|
|
|
23
23
|
class AgentAdapter extends Emitter {
|
|
24
24
|
#connection;
|
|
25
25
|
#ready;
|
|
26
|
-
#messages =
|
|
26
|
+
#messages = /* @__PURE__ */ new Map();
|
|
27
|
+
#active = null;
|
|
28
|
+
#creating = /* @__PURE__ */ new Map();
|
|
27
29
|
constructor(connection) {
|
|
28
30
|
super();
|
|
29
31
|
this.#connection = connection;
|
|
30
32
|
this.#ready = new Promise((resolve) => {
|
|
31
|
-
connection.transcript.subscribe((
|
|
32
|
-
this.#
|
|
33
|
+
connection.transcript.subscribe(() => {
|
|
34
|
+
this.#refresh();
|
|
33
35
|
resolve();
|
|
34
36
|
});
|
|
35
|
-
connection.deliveries.subscribe(() =>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
});
|
|
37
|
+
connection.deliveries.subscribe(() => this.#refresh());
|
|
38
|
+
connection.views?.subscribe(() => this.#refresh());
|
|
39
|
+
connection.agents?.subscribe(() => this.#refresh());
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
|
-
#
|
|
42
|
-
|
|
42
|
+
#summary() {
|
|
43
|
+
const agents = this.#connection.agents?.value?.agents;
|
|
44
|
+
return agents?.length ? agents : [{ id: CONE, name: "sliccy", kind: "cone", parentId: null, role: null }];
|
|
45
|
+
}
|
|
46
|
+
#view(agentId = this.active()) {
|
|
47
|
+
const views = this.#connection.views?.value;
|
|
48
|
+
const view = views?.[agentId];
|
|
49
|
+
if (view) return view;
|
|
50
|
+
const cone = this.#connection.agents?.value?.active ?? CONE;
|
|
51
|
+
return agentId === cone ? this.#connection.transcript.value : void 0;
|
|
43
52
|
}
|
|
44
|
-
#
|
|
45
|
-
const before = this.#messages;
|
|
46
|
-
this.#messages = toMessages(view, this.#connection.deliveries.value);
|
|
53
|
+
#refresh() {
|
|
47
54
|
this.emit("agents", this.list());
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
for (const summary of this.#summary()) this.#update(summary.id);
|
|
56
|
+
for (const id of [...this.#messages.keys()])
|
|
57
|
+
if (!this.#summary().some((summary) => summary.id === id)) this.#messages.delete(id);
|
|
58
|
+
}
|
|
59
|
+
#update(agentId) {
|
|
60
|
+
const view = this.#view(agentId);
|
|
61
|
+
if (!view) return;
|
|
62
|
+
const before = this.#messages.get(agentId) ?? [];
|
|
63
|
+
const after = toMessages(view, this.#connection.deliveries.value);
|
|
64
|
+
this.#messages.set(agentId, after);
|
|
65
|
+
if (before.length !== after.length) {
|
|
66
|
+
this.emit("messages", agentId);
|
|
50
67
|
return;
|
|
51
68
|
}
|
|
52
|
-
const changed =
|
|
69
|
+
const changed = after.filter(
|
|
53
70
|
(message, index) => JSON.stringify(message) !== JSON.stringify(before[index])
|
|
54
71
|
);
|
|
55
|
-
if (changed.length === 1)
|
|
56
|
-
|
|
57
|
-
else if (changed.length > 1) this.emit("messages", CONE);
|
|
72
|
+
if (changed.length === 1) this.emit("message", { agentId, message: changed[0] });
|
|
73
|
+
else if (changed.length > 1) this.emit("messages", agentId);
|
|
58
74
|
}
|
|
59
75
|
ready() {
|
|
60
76
|
return this.#ready;
|
|
61
77
|
}
|
|
62
78
|
list() {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
return this.#summary().map((summary) => {
|
|
80
|
+
const view = this.#view(summary.id);
|
|
81
|
+
const doc = view?.docs["pi.agent"];
|
|
82
|
+
return {
|
|
83
|
+
id: summary.id,
|
|
84
|
+
name: summary.role ? `${summary.name} \xB7 ${summary.role}` : summary.name,
|
|
85
|
+
kind: summary.kind,
|
|
86
|
+
parentId: summary.parentId,
|
|
87
|
+
status: status(view, this.#messages.get(summary.id) ?? []),
|
|
71
88
|
model: doc?.model ? `${doc.model.provider}/${doc.model.modelId}` : "",
|
|
72
89
|
contextFill: 0,
|
|
73
90
|
unread: 0,
|
|
74
91
|
thinking: thinking[doc?.thinkingLevel ?? "off"] ?? "off"
|
|
75
|
-
}
|
|
76
|
-
|
|
92
|
+
};
|
|
93
|
+
});
|
|
77
94
|
}
|
|
78
95
|
active() {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
96
|
+
const known = this.#summary();
|
|
97
|
+
if (this.#active && this.#creating.has(this.#active)) return this.#active;
|
|
98
|
+
if (this.#active && known.some((summary) => summary.id === this.#active)) return this.#active;
|
|
99
|
+
return this.#connection.agents?.value?.active ?? CONE;
|
|
100
|
+
}
|
|
101
|
+
select(id) {
|
|
102
|
+
if (this.#creating.has(id)) {
|
|
103
|
+
this.#active = id;
|
|
104
|
+
this.emit("active", id);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const found = this.#summary().find((summary) => summary.id === id);
|
|
108
|
+
if (!found) return;
|
|
109
|
+
this.#active = id;
|
|
110
|
+
if (found.kind === "cone") void this.#connection.control.selectCone(id, BACKGROUND_CONTEXT);
|
|
111
|
+
this.emit("active", id);
|
|
112
|
+
this.emit("messages", id);
|
|
82
113
|
}
|
|
83
|
-
messages() {
|
|
84
|
-
return this.#messages;
|
|
114
|
+
messages(agentId = this.active()) {
|
|
115
|
+
return this.#messages.get(agentId) ?? [];
|
|
85
116
|
}
|
|
86
|
-
async send(
|
|
117
|
+
async send(agentId, input) {
|
|
87
118
|
const outgoing = typeof input === "string" ? { text: input } : input;
|
|
119
|
+
const target = this.#creating.has(agentId) ? await this.#creating.get(agentId) : agentId;
|
|
120
|
+
if (target === null) throw new Error("The scoop could not be created.");
|
|
88
121
|
const sent = await this.#connection.control.send(
|
|
89
122
|
{
|
|
90
123
|
text: outgoing.text,
|
|
91
124
|
whenBusy: outgoing.mode === "queue" ? "followUp" : "steer",
|
|
92
|
-
requestId: crypto.randomUUID()
|
|
125
|
+
requestId: crypto.randomUUID(),
|
|
126
|
+
agentId: target || null
|
|
93
127
|
},
|
|
94
128
|
BACKGROUND_CONTEXT
|
|
95
129
|
);
|
|
@@ -99,26 +133,28 @@ class AgentAdapter extends Emitter {
|
|
|
99
133
|
const rewound = await this.#connection.control.rewind(messageId, BACKGROUND_CONTEXT);
|
|
100
134
|
return rewound.done ? { text: rewound.text } : null;
|
|
101
135
|
}
|
|
102
|
-
stop() {
|
|
103
|
-
void this.#connection.control.
|
|
136
|
+
stop(agentId = this.active()) {
|
|
137
|
+
void this.#connection.control.stopAgent(agentId, BACKGROUND_CONTEXT);
|
|
104
138
|
}
|
|
105
|
-
busy() {
|
|
106
|
-
return isBusy(this.#view());
|
|
139
|
+
busy(agentId = this.active()) {
|
|
140
|
+
return isBusy(this.#view(agentId));
|
|
107
141
|
}
|
|
108
|
-
queue() {
|
|
109
|
-
return queued(this.#view());
|
|
142
|
+
queue(agentId = this.active()) {
|
|
143
|
+
return queued(this.#view(agentId));
|
|
110
144
|
}
|
|
111
|
-
unqueue(
|
|
112
|
-
void this.#connection.control.
|
|
145
|
+
unqueue(agentId, messageId) {
|
|
146
|
+
void this.#connection.control.unqueue(agentId, messageId.replace(/^q/, ""), BACKGROUND_CONTEXT);
|
|
113
147
|
}
|
|
114
148
|
suggestion() {
|
|
115
149
|
return null;
|
|
116
150
|
}
|
|
117
151
|
answer() {
|
|
118
152
|
}
|
|
119
|
-
resolveLick(
|
|
120
|
-
const message = this
|
|
121
|
-
const entry = this.#view()?.entries.find(
|
|
153
|
+
resolveLick(agentId, messageId, state) {
|
|
154
|
+
const message = this.messages(agentId).find((candidate) => candidate.id === messageId);
|
|
155
|
+
const entry = this.#view(agentId)?.entries.find(
|
|
156
|
+
(candidate) => `e${candidate.id}` === messageId
|
|
157
|
+
);
|
|
122
158
|
const lick = message?.role === "lick" && entry ? entryLick(entry) : void 0;
|
|
123
159
|
if (lick) void this.#connection.control.resolveLick(lick.id, state, BACKGROUND_CONTEXT);
|
|
124
160
|
}
|
|
@@ -128,19 +164,25 @@ class AgentAdapter extends Emitter {
|
|
|
128
164
|
clear() {
|
|
129
165
|
void this.#connection.control.reset(null, BACKGROUND_CONTEXT);
|
|
130
166
|
}
|
|
131
|
-
#configure(change) {
|
|
167
|
+
#configure(agentId, change) {
|
|
132
168
|
void this.#connection.control.configure(
|
|
133
|
-
{
|
|
169
|
+
{
|
|
170
|
+
model: change.model ?? null,
|
|
171
|
+
thinkingLevel: change.thinkingLevel ?? null,
|
|
172
|
+
agentId
|
|
173
|
+
},
|
|
134
174
|
BACKGROUND_CONTEXT
|
|
135
175
|
);
|
|
136
176
|
}
|
|
137
|
-
setModel(
|
|
177
|
+
setModel(agentId, model) {
|
|
138
178
|
const at = model.indexOf("/");
|
|
139
179
|
if (at <= 0) return;
|
|
140
|
-
this.#configure(
|
|
180
|
+
this.#configure(agentId, {
|
|
181
|
+
model: { provider: model.slice(0, at), modelId: model.slice(at + 1) }
|
|
182
|
+
});
|
|
141
183
|
}
|
|
142
|
-
setThinking(
|
|
143
|
-
this.#configure({ thinkingLevel: level });
|
|
184
|
+
setThinking(agentId, level) {
|
|
185
|
+
this.#configure(agentId, { thinkingLevel: level });
|
|
144
186
|
}
|
|
145
187
|
async older() {
|
|
146
188
|
return [];
|
|
@@ -148,8 +190,49 @@ class AgentAdapter extends Emitter {
|
|
|
148
190
|
commands() {
|
|
149
191
|
return [];
|
|
150
192
|
}
|
|
151
|
-
createScoop() {
|
|
152
|
-
|
|
193
|
+
createScoop(parentId, name) {
|
|
194
|
+
const provisional = `scoop:creating-${crypto.randomUUID()}`;
|
|
195
|
+
const created = this.#connection.control.createScoop(parentId, name, BACKGROUND_CONTEXT).then(
|
|
196
|
+
(result) => result.id,
|
|
197
|
+
() => null
|
|
198
|
+
);
|
|
199
|
+
this.#creating.set(provisional, created);
|
|
200
|
+
void created.then((id) => {
|
|
201
|
+
if (this.#active !== provisional) return;
|
|
202
|
+
this.#active = id ?? parentId;
|
|
203
|
+
this.emit("active", this.#active);
|
|
204
|
+
this.emit("messages", this.#active);
|
|
205
|
+
});
|
|
206
|
+
return {
|
|
207
|
+
id: provisional,
|
|
208
|
+
name,
|
|
209
|
+
kind: "scoop",
|
|
210
|
+
parentId,
|
|
211
|
+
status: "idle",
|
|
212
|
+
model: "",
|
|
213
|
+
contextFill: 0,
|
|
214
|
+
unread: 0
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async drop(agentId) {
|
|
218
|
+
const parent = this.#summary().find((summary) => summary.id === agentId)?.parentId ?? CONE;
|
|
219
|
+
const dropped = await this.#connection.control.drop(agentId, BACKGROUND_CONTEXT);
|
|
220
|
+
if (dropped.error !== null) throw new Error(dropped.error);
|
|
221
|
+
if (this.#active === agentId) this.select(parent);
|
|
222
|
+
}
|
|
223
|
+
async createCone(name) {
|
|
224
|
+
const created = await this.#connection.control.createCone(name, BACKGROUND_CONTEXT);
|
|
225
|
+
if (created.error !== null) throw new Error(created.error);
|
|
226
|
+
return {
|
|
227
|
+
id: created.id,
|
|
228
|
+
name: name.trim(),
|
|
229
|
+
kind: "cone",
|
|
230
|
+
parentId: null,
|
|
231
|
+
status: "idle",
|
|
232
|
+
model: "",
|
|
233
|
+
contextFill: 0,
|
|
234
|
+
unread: 0
|
|
235
|
+
};
|
|
153
236
|
}
|
|
154
237
|
frozen() {
|
|
155
238
|
return [];
|