@ai-ecoverse/slicc-agent 3.6.0 → 3.8.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 +20 -3
- 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 +30 -3
- 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/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 +330 -0
- package/dist/scoops/extension.js.map +7 -0
- package/dist/scoops/from.js +5 -0
- package/dist/scoops/from.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 +419 -0
- package/dist/scoops/service.js.map +7 -0
- package/dist/scoops/sync.js +253 -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 +39 -16
- 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 +4 -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/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 +79 -0
- package/dist/types/scoops/from.d.ts +5 -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 +119 -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/spectrum/messages.d.ts +2 -1
- package/dist/types/views.d.ts +12 -0
- package/dist/views.js +89 -0
- package/dist/views.js.map +7 -0
- package/package.json +5 -3
- 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,136 @@
|
|
|
1
|
+
const CONTROL_DIR = "/var/lib/slicc/agent/requests";
|
|
2
|
+
const PROTOCOL = "slicc-agent/1";
|
|
3
|
+
const STALE_MS = 10 * 6e4;
|
|
4
|
+
const HEADER = 6;
|
|
5
|
+
function parseRequest(id, bytes) {
|
|
6
|
+
const decoder = new TextDecoder();
|
|
7
|
+
const fields = [];
|
|
8
|
+
let at = 0;
|
|
9
|
+
while (fields.length < HEADER) {
|
|
10
|
+
const end = bytes.indexOf(0, at);
|
|
11
|
+
if (end < 0) return void 0;
|
|
12
|
+
fields.push(decoder.decode(bytes.subarray(at, end)));
|
|
13
|
+
at = end + 1;
|
|
14
|
+
}
|
|
15
|
+
const [version, as, caller, cwd, pid, count] = fields;
|
|
16
|
+
const argc = Number(count);
|
|
17
|
+
if (version !== PROTOCOL || !Number.isInteger(argc) || argc < 0) return void 0;
|
|
18
|
+
const argv = [];
|
|
19
|
+
while (argv.length < argc) {
|
|
20
|
+
const end = bytes.indexOf(0, at);
|
|
21
|
+
if (end < 0) return void 0;
|
|
22
|
+
argv.push(decoder.decode(bytes.subarray(at, end)));
|
|
23
|
+
at = end + 1;
|
|
24
|
+
}
|
|
25
|
+
const process = Number(pid);
|
|
26
|
+
return {
|
|
27
|
+
id,
|
|
28
|
+
as: as === "subagent" ? "subagent" : "agent",
|
|
29
|
+
caller,
|
|
30
|
+
cwd: cwd || "/",
|
|
31
|
+
pid: Number.isInteger(process) && process > 0 ? process : null,
|
|
32
|
+
argv,
|
|
33
|
+
stdin: decoder.decode(bytes.subarray(at))
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function controlPlane(env, runner, options = {}) {
|
|
37
|
+
const dir = options.dir ?? CONTROL_DIR;
|
|
38
|
+
const requests = `${dir}/in`;
|
|
39
|
+
const responses = `${dir}/out`;
|
|
40
|
+
const now = options.now ?? Date.now;
|
|
41
|
+
const handled = /* @__PURE__ */ new Set();
|
|
42
|
+
let watcher;
|
|
43
|
+
let timer;
|
|
44
|
+
let lock = Promise.resolve();
|
|
45
|
+
const serial = (operation) => {
|
|
46
|
+
const next = lock.then(operation, operation);
|
|
47
|
+
lock = next.catch(() => void 0);
|
|
48
|
+
return next;
|
|
49
|
+
};
|
|
50
|
+
async function answer(id, result, context) {
|
|
51
|
+
await env.writeFile(`${responses}/${id}.out`, result.out, context);
|
|
52
|
+
await env.writeFile(`${responses}/${id}.code`, String(result.code), context);
|
|
53
|
+
await env.remove(`${requests}/${id}`, { force: true }, context);
|
|
54
|
+
await env.remove(`${responses}/${id}.ack`, { force: true }, context);
|
|
55
|
+
await options.mirror?.(context).catch(() => void 0);
|
|
56
|
+
}
|
|
57
|
+
async function handle(id, context) {
|
|
58
|
+
if (handled.has(id)) return;
|
|
59
|
+
handled.add(id);
|
|
60
|
+
try {
|
|
61
|
+
const bytes = await env.readBinaryFile(`${requests}/${id}`, context);
|
|
62
|
+
if (!bytes.ok) return;
|
|
63
|
+
await env.writeFile(`${responses}/${id}.ack`, "", context);
|
|
64
|
+
const request = parseRequest(id, bytes.value);
|
|
65
|
+
if (!request) {
|
|
66
|
+
await answer(
|
|
67
|
+
id,
|
|
68
|
+
{ code: 2, out: "agent: this request is not understood; update the agent command\n" },
|
|
69
|
+
context
|
|
70
|
+
);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const result = runner.blocking(request) ? await runner.run(request, context) : await serial(() => runner.run(request, context));
|
|
74
|
+
await answer(id, result, context);
|
|
75
|
+
} finally {
|
|
76
|
+
handled.delete(id);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function scan(context) {
|
|
80
|
+
const listed = await env.listDir(requests, context);
|
|
81
|
+
if (!listed.ok) return;
|
|
82
|
+
for (const entry of listed.value.sort((a, b) => a.name.localeCompare(b.name)))
|
|
83
|
+
if (entry.kind === "file" && !entry.name.startsWith("."))
|
|
84
|
+
void handle(entry.name, context).catch(() => void 0);
|
|
85
|
+
}
|
|
86
|
+
async function sweep(context) {
|
|
87
|
+
for (const folder of [requests, responses]) {
|
|
88
|
+
const listed = await env.listDir(folder, context);
|
|
89
|
+
if (!listed.ok) continue;
|
|
90
|
+
for (const entry of listed.value) {
|
|
91
|
+
const stale = now() - entry.mtimeMs > STALE_MS;
|
|
92
|
+
const removable = folder === responses || entry.name.startsWith(".tmp-");
|
|
93
|
+
if (stale && removable)
|
|
94
|
+
await env.remove(`${folder}/${entry.name}`, { force: true }, context);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
async start(context) {
|
|
100
|
+
await env.createDir(requests, { recursive: true }, context);
|
|
101
|
+
await env.createDir(responses, { recursive: true }, context);
|
|
102
|
+
const leftovers = await env.listDir(requests, context);
|
|
103
|
+
if (leftovers.ok) {
|
|
104
|
+
for (const entry of leftovers.value)
|
|
105
|
+
if (entry.name.startsWith(".tmp-"))
|
|
106
|
+
await env.remove(`${requests}/${entry.name}`, { force: true }, context);
|
|
107
|
+
}
|
|
108
|
+
await sweep(context);
|
|
109
|
+
const watched = await env.watch(
|
|
110
|
+
[{ path: requests }],
|
|
111
|
+
() => void scan(context).catch(() => void 0),
|
|
112
|
+
context
|
|
113
|
+
);
|
|
114
|
+
if (watched.ok) watcher = watched.value;
|
|
115
|
+
await scan(context);
|
|
116
|
+
timer = setInterval(
|
|
117
|
+
() => void sweep(context).catch(() => void 0),
|
|
118
|
+
options.sweepEvery ?? 6e4
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
sweep,
|
|
122
|
+
async close(context) {
|
|
123
|
+
clearInterval(timer);
|
|
124
|
+
await watcher?.close(context);
|
|
125
|
+
await lock;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
CONTROL_DIR,
|
|
131
|
+
PROTOCOL,
|
|
132
|
+
STALE_MS,
|
|
133
|
+
controlPlane,
|
|
134
|
+
parseRequest
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=requests.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/scoops/requests.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport type { Answer } from './service.ts';\n\nexport const CONTROL_DIR = '/var/lib/slicc/agent/requests';\nexport const PROTOCOL = 'slicc-agent/1';\nexport const STALE_MS = 10 * 60_000;\n\nexport interface Request {\n id: string;\n as: 'agent' | 'subagent';\n caller: string;\n cwd: string;\n pid: number | null;\n argv: string[];\n stdin: string;\n}\n\nconst HEADER = 6;\n\nexport function parseRequest(id: string, bytes: Uint8Array): Request | undefined {\n const decoder = new TextDecoder();\n const fields: string[] = [];\n let at = 0;\n while (fields.length < HEADER) {\n const end = bytes.indexOf(0, at);\n if (end < 0) return undefined;\n fields.push(decoder.decode(bytes.subarray(at, end)));\n at = end + 1;\n }\n const [version, as, caller, cwd, pid, count] = fields;\n const argc = Number(count);\n if (version !== PROTOCOL || !Number.isInteger(argc) || argc < 0) return undefined;\n const argv: string[] = [];\n while (argv.length < argc) {\n const end = bytes.indexOf(0, at);\n if (end < 0) return undefined;\n argv.push(decoder.decode(bytes.subarray(at, end)));\n at = end + 1;\n }\n const process = Number(pid);\n return {\n id,\n as: as === 'subagent' ? 'subagent' : 'agent',\n caller: caller as string,\n cwd: cwd || '/',\n pid: Number.isInteger(process) && process > 0 ? process : null,\n argv,\n stdin: decoder.decode(bytes.subarray(at)),\n };\n}\n\nexport interface Runner {\n run(request: Request, context: Context): Promise<Answer>;\n blocking(request: Request): boolean;\n}\n\nexport interface ControlPlane {\n start(context: Context): Promise<void>;\n sweep(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport interface PlaneOptions {\n dir?: string;\n now?: () => number;\n sweepEvery?: number;\n mirror?: (context: Context) => Promise<void>;\n}\n\nexport function controlPlane(\n env: ExecutionEnv,\n runner: Runner,\n options: PlaneOptions = {}\n): ControlPlane {\n const dir = options.dir ?? CONTROL_DIR;\n const requests = `${dir}/in`;\n const responses = `${dir}/out`;\n const now = options.now ?? Date.now;\n const handled = new Set<string>();\n let watcher: FileWatcher | undefined;\n let timer: ReturnType<typeof setInterval> | undefined;\n let lock: Promise<unknown> = Promise.resolve();\n const serial = <T>(operation: () => Promise<T>): Promise<T> => {\n const next = lock.then(operation, operation);\n lock = next.catch(() => undefined);\n return next;\n };\n\n async function answer(id: string, result: Answer, context: Context) {\n await env.writeFile(`${responses}/${id}.out`, result.out, context);\n await env.writeFile(`${responses}/${id}.code`, String(result.code), context);\n await env.remove(`${requests}/${id}`, { force: true }, context);\n await env.remove(`${responses}/${id}.ack`, { force: true }, context);\n await options.mirror?.(context).catch(() => undefined);\n }\n\n async function handle(id: string, context: Context) {\n if (handled.has(id)) return;\n handled.add(id);\n try {\n const bytes = await env.readBinaryFile(`${requests}/${id}`, context);\n if (!bytes.ok) return;\n await env.writeFile(`${responses}/${id}.ack`, '', context);\n const request = parseRequest(id, bytes.value);\n if (!request) {\n await answer(\n id,\n { code: 2, out: 'agent: this request is not understood; update the agent command\\n' },\n context\n );\n return;\n }\n const result = runner.blocking(request)\n ? await runner.run(request, context)\n : await serial(() => runner.run(request, context));\n await answer(id, result, context);\n } finally {\n handled.delete(id);\n }\n }\n\n async function scan(context: Context) {\n const listed = await env.listDir(requests, context);\n if (!listed.ok) return;\n for (const entry of listed.value.sort((a, b) => a.name.localeCompare(b.name)))\n if (entry.kind === 'file' && !entry.name.startsWith('.'))\n void handle(entry.name, context).catch(() => undefined);\n }\n\n async function sweep(context: Context) {\n for (const folder of [requests, responses]) {\n const listed = await env.listDir(folder, context);\n if (!listed.ok) continue;\n for (const entry of listed.value) {\n const stale = now() - entry.mtimeMs > STALE_MS;\n const removable = folder === responses || entry.name.startsWith('.tmp-');\n if (stale && removable)\n await env.remove(`${folder}/${entry.name}`, { force: true }, context);\n }\n }\n }\n\n return {\n async start(context) {\n await env.createDir(requests, { recursive: true }, context);\n await env.createDir(responses, { recursive: true }, context);\n const leftovers = await env.listDir(requests, context);\n if (leftovers.ok)\n for (const entry of leftovers.value)\n if (entry.name.startsWith('.tmp-'))\n await env.remove(`${requests}/${entry.name}`, { force: true }, context);\n await sweep(context);\n const watched = await env.watch(\n [{ path: requests }],\n () => void scan(context).catch(() => undefined),\n context\n );\n if (watched.ok) watcher = watched.value;\n await scan(context);\n timer = setInterval(\n () => void sweep(context).catch(() => undefined),\n options.sweepEvery ?? 60_000\n );\n },\n sweep,\n async close(context) {\n clearInterval(timer);\n await watcher?.close(context);\n await lock;\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import {
|
|
2
|
+
configure,
|
|
3
|
+
defineDoc
|
|
4
|
+
} from "@earendil-works/pi-durable";
|
|
5
|
+
import {
|
|
6
|
+
freeFolder,
|
|
7
|
+
live,
|
|
8
|
+
scoopId
|
|
9
|
+
} from "../agents.js";
|
|
10
|
+
import { visible } from "./visible.js";
|
|
11
|
+
const SCOOPS_ROOT = "/scoops";
|
|
12
|
+
const LEDGER_SIZE = 512;
|
|
13
|
+
const ScoopWorkDoc = defineDoc({
|
|
14
|
+
kind: "slicc.scoops.work",
|
|
15
|
+
version: 1,
|
|
16
|
+
scope: "session",
|
|
17
|
+
initial: () => ({ feeds: {}, waits: {}, ledger: {}, turns: {} })
|
|
18
|
+
});
|
|
19
|
+
function workspace(folder) {
|
|
20
|
+
return `${SCOOPS_ROOT}/${folder}/workspace`;
|
|
21
|
+
}
|
|
22
|
+
async function tail(conversation, context) {
|
|
23
|
+
const { entries } = await conversation.context(context);
|
|
24
|
+
return { in: conversation.id, at: Number(entries.at(-1)?.id ?? 0) };
|
|
25
|
+
}
|
|
26
|
+
async function turnStart(conversation, context) {
|
|
27
|
+
const { entries } = await conversation.context(context);
|
|
28
|
+
return Number(entries.findLast((entry) => entry.kind === "pi.user")?.id ?? 0);
|
|
29
|
+
}
|
|
30
|
+
function coneOf(state, conversation) {
|
|
31
|
+
return Object.entries(state.cones).find(([, cone]) => cone.conversation === conversation)?.[0];
|
|
32
|
+
}
|
|
33
|
+
function findScoop(state, handle, cone) {
|
|
34
|
+
const id = handle.startsWith("scoop:") ? handle : scoopId(handle);
|
|
35
|
+
const reachable = (record2) => !!record2 && live(record2) && (cone === null || record2.cone === cone);
|
|
36
|
+
const record = state.scoops[id];
|
|
37
|
+
if (reachable(record)) return [id, record];
|
|
38
|
+
return Object.entries(state.scoops).find(
|
|
39
|
+
([, candidate]) => reachable(candidate) && candidate.name === handle
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function remember(work, request, answer) {
|
|
43
|
+
if (!request) return;
|
|
44
|
+
work.ledger[request] = answer;
|
|
45
|
+
const keys = Object.keys(work.ledger);
|
|
46
|
+
for (const key of keys.slice(0, Math.max(0, keys.length - LEDGER_SIZE))) delete work.ledger[key];
|
|
47
|
+
}
|
|
48
|
+
async function coneOfId(core, coneId, context) {
|
|
49
|
+
const { agents } = await core.host;
|
|
50
|
+
const found = await agents.conversation(coneId, context);
|
|
51
|
+
if (!found) throw new Error(`There is no cone ${coneId}.`);
|
|
52
|
+
return found;
|
|
53
|
+
}
|
|
54
|
+
async function isBusy(core, conversation, context) {
|
|
55
|
+
const { harness } = await core.host;
|
|
56
|
+
const handle = await harness.conversation(conversation, context);
|
|
57
|
+
const view = await handle?.viewState(context);
|
|
58
|
+
const run = view?.value.docs["pi.live"]?.run;
|
|
59
|
+
view?.dispose();
|
|
60
|
+
return Boolean(run);
|
|
61
|
+
}
|
|
62
|
+
async function answered(core, request, context) {
|
|
63
|
+
if (!request) return void 0;
|
|
64
|
+
const { harness } = await core.host;
|
|
65
|
+
return (await harness.snapshot(ScoopWorkDoc, context))?.ledger[request];
|
|
66
|
+
}
|
|
67
|
+
async function addFeed(core, tx, parent, feed2) {
|
|
68
|
+
const task = await tx.createTask(
|
|
69
|
+
core.tasks().reporter,
|
|
70
|
+
{
|
|
71
|
+
scoop: feed2.scoop,
|
|
72
|
+
prompt: feed2.prompt,
|
|
73
|
+
...feed2.followUp ? { followUp: true } : {},
|
|
74
|
+
...feed2.request ? { request: feed2.request } : {}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
ownership: { kind: "conversation" },
|
|
78
|
+
conversationId: parent,
|
|
79
|
+
background: true
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
(await tx.doc(ScoopWorkDoc)).feeds[String(task)] = {
|
|
83
|
+
scoop: feed2.scoop,
|
|
84
|
+
created: feed2.created,
|
|
85
|
+
report: feed2.report,
|
|
86
|
+
target: feed2.target,
|
|
87
|
+
channel: "scoop-notify",
|
|
88
|
+
from: feed2.from
|
|
89
|
+
};
|
|
90
|
+
return Number(task);
|
|
91
|
+
}
|
|
92
|
+
function selection(tools, names) {
|
|
93
|
+
if (!names || !tools) return void 0;
|
|
94
|
+
return tools.filter((tool) => names.includes(tool.name));
|
|
95
|
+
}
|
|
96
|
+
function liveCount(state, cone) {
|
|
97
|
+
return Object.values(state.scoops).filter((scoop) => scoop.cone === cone && live(scoop)).length;
|
|
98
|
+
}
|
|
99
|
+
function claimTurn(work, request, turn) {
|
|
100
|
+
if (turn === null) return void 0;
|
|
101
|
+
const counted = work.turns[request.cone];
|
|
102
|
+
const count = counted && counted.at === turn ? counted.count : 0;
|
|
103
|
+
if (count >= request.limits.maxPerTurn)
|
|
104
|
+
return {
|
|
105
|
+
code: 1,
|
|
106
|
+
out: `subagent: this turn already started ${count} scoops (the limit is ${request.limits.maxPerTurn})
|
|
107
|
+
`
|
|
108
|
+
};
|
|
109
|
+
work.turns[request.cone] = { at: turn, count: count + 1 };
|
|
110
|
+
return void 0;
|
|
111
|
+
}
|
|
112
|
+
function agentChange(request, folder, tools) {
|
|
113
|
+
const model = request.model ?? modelOf(request.role?.model);
|
|
114
|
+
const thinking = request.thinking ?? request.role?.thinking;
|
|
115
|
+
const chosen = selection(tools, request.tools ?? request.role?.tools);
|
|
116
|
+
const instructions = [request.role?.prompt, request.instructions].filter(Boolean).join("\n\n");
|
|
117
|
+
return {
|
|
118
|
+
cwd: request.cwd ?? workspace(folder),
|
|
119
|
+
...instructions ? { instructions } : {},
|
|
120
|
+
...model ? { model } : {},
|
|
121
|
+
...thinking ? { thinkingLevel: thinking } : {},
|
|
122
|
+
...chosen ? { tools: chosen } : {}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function spawn(core, request, context) {
|
|
126
|
+
const done = await answered(core, request.request, context);
|
|
127
|
+
if (done) return done;
|
|
128
|
+
const { agents, tools } = await core.host;
|
|
129
|
+
const parent = await coneOfId(core, request.cone, context);
|
|
130
|
+
const count = liveCount(agents.state(), request.cone);
|
|
131
|
+
if (count >= request.limits.maxLiveScoops)
|
|
132
|
+
return {
|
|
133
|
+
code: 1,
|
|
134
|
+
out: `subagent: this cone already has ${count} live scoops (the limit is ${request.limits.maxLiveScoops}); stop one first
|
|
135
|
+
`
|
|
136
|
+
};
|
|
137
|
+
const created = request.fromAgent ? await tail(parent, context) : null;
|
|
138
|
+
const turn = request.fromAgent ? await turnStart(parent, context) : null;
|
|
139
|
+
return agents.update(async (tx, doc) => {
|
|
140
|
+
const work = await tx.doc(ScoopWorkDoc);
|
|
141
|
+
const refused = claimTurn(work, request, turn);
|
|
142
|
+
if (refused) return refused;
|
|
143
|
+
const folder = freeFolder(doc, request.name);
|
|
144
|
+
const id = scoopId(folder);
|
|
145
|
+
const anchor = await tx.createTask(core.tasks().anchor, null, {
|
|
146
|
+
ownership: { kind: "conversation" },
|
|
147
|
+
conversationId: parent.id,
|
|
148
|
+
background: true
|
|
149
|
+
});
|
|
150
|
+
const child = await tx.createConversation({ ownership: { kind: "task", taskId: anchor } });
|
|
151
|
+
await configure(tx, child.id, agentChange(request, folder, tools));
|
|
152
|
+
doc.scoops[id] = {
|
|
153
|
+
name: request.name.trim() || folder,
|
|
154
|
+
folder,
|
|
155
|
+
cone: request.cone,
|
|
156
|
+
conversation: child.id,
|
|
157
|
+
anchor,
|
|
158
|
+
role: request.role?.name ?? null,
|
|
159
|
+
kind: request.kind ?? "async",
|
|
160
|
+
parent: request.parent ?? request.cone,
|
|
161
|
+
depth: request.depth ?? 1,
|
|
162
|
+
roots: request.roots ?? null,
|
|
163
|
+
created,
|
|
164
|
+
dropped: null,
|
|
165
|
+
gone: false
|
|
166
|
+
};
|
|
167
|
+
const target = request.target === void 0 ? request.fromAgent ? `cone:${request.cone}` : null : request.target;
|
|
168
|
+
const made = [];
|
|
169
|
+
const from = askerName(doc, request.asker === void 0 ? target : request.asker);
|
|
170
|
+
for (const prompt of request.prompts)
|
|
171
|
+
made.push(
|
|
172
|
+
await addFeed(core, tx, parent.id, {
|
|
173
|
+
scoop: id,
|
|
174
|
+
created,
|
|
175
|
+
report: target !== null,
|
|
176
|
+
target,
|
|
177
|
+
from,
|
|
178
|
+
prompt
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
const answer = { code: 0, out: `${folder}
|
|
182
|
+
`, tasks: made };
|
|
183
|
+
remember(work, request.request, answer);
|
|
184
|
+
return answer;
|
|
185
|
+
}, context);
|
|
186
|
+
}
|
|
187
|
+
function askerName(state, asker) {
|
|
188
|
+
if (asker === null) return "terminal";
|
|
189
|
+
if (asker.startsWith("cone:")) return state.cones[asker.slice("cone:".length)]?.name ?? asker;
|
|
190
|
+
return state.scoops[asker]?.name ?? asker;
|
|
191
|
+
}
|
|
192
|
+
const missing = (id) => ({
|
|
193
|
+
code: 1,
|
|
194
|
+
out: `subagent: there is no scoop ${id.replace("scoop:", "")}
|
|
195
|
+
`
|
|
196
|
+
});
|
|
197
|
+
async function feed(core, id, prompt, options, context) {
|
|
198
|
+
const done = await answered(core, options.request, context);
|
|
199
|
+
if (done) return done;
|
|
200
|
+
const { harness, agents } = await core.host;
|
|
201
|
+
const record = agents.state().scoops[id];
|
|
202
|
+
if (!record || !live(record)) return missing(id);
|
|
203
|
+
const parent = await coneOfId(core, record.cone, context);
|
|
204
|
+
const created = options.fromAgent ? await tail(parent, context) : null;
|
|
205
|
+
const { target } = options;
|
|
206
|
+
return harness.commit(async (tx) => {
|
|
207
|
+
await addFeed(core, tx, parent.id, {
|
|
208
|
+
scoop: id,
|
|
209
|
+
created,
|
|
210
|
+
report: target !== null,
|
|
211
|
+
target,
|
|
212
|
+
from: askerName(agents.state(), target),
|
|
213
|
+
prompt,
|
|
214
|
+
followUp: options.followUp,
|
|
215
|
+
request: options.request
|
|
216
|
+
});
|
|
217
|
+
const answer = { code: 0, out: `sent to ${record.folder}
|
|
218
|
+
` };
|
|
219
|
+
remember(await tx.doc(ScoopWorkDoc), options.request, answer);
|
|
220
|
+
return answer;
|
|
221
|
+
}, context);
|
|
222
|
+
}
|
|
223
|
+
async function signal(core, record, context) {
|
|
224
|
+
const { harness, groups } = await core.host;
|
|
225
|
+
const conversation = await harness.conversation(record.conversation, context);
|
|
226
|
+
await conversation?.abort(context, { background: true });
|
|
227
|
+
await groups?.signal(record.conversation);
|
|
228
|
+
}
|
|
229
|
+
async function stopFeeds(core, scoop, keep, context) {
|
|
230
|
+
const { harness } = await core.host;
|
|
231
|
+
const work = await harness.snapshot(ScoopWorkDoc, context) ?? { feeds: {} };
|
|
232
|
+
const dropped = [];
|
|
233
|
+
for (const [task, entry] of Object.entries(work.feeds)) {
|
|
234
|
+
if (entry.scoop !== scoop || await keep(entry)) continue;
|
|
235
|
+
const record = await harness.getTask(Number(task), context);
|
|
236
|
+
if (record && record.state.status !== "terminal")
|
|
237
|
+
await harness.abortTask(Number(task), context);
|
|
238
|
+
dropped.push(task);
|
|
239
|
+
}
|
|
240
|
+
if (dropped.length)
|
|
241
|
+
await harness.commit(async (tx) => {
|
|
242
|
+
const doc = await tx.doc(ScoopWorkDoc);
|
|
243
|
+
for (const task of dropped) delete doc.feeds[task];
|
|
244
|
+
}, context);
|
|
245
|
+
return dropped.length;
|
|
246
|
+
}
|
|
247
|
+
async function stop(core, id, fromAgent, context) {
|
|
248
|
+
const { agents } = await core.host;
|
|
249
|
+
const record = agents.state().scoops[id];
|
|
250
|
+
if (!record || !live(record)) return missing(id);
|
|
251
|
+
await stopFeeds(core, id, async () => false, context);
|
|
252
|
+
await signal(core, record, context);
|
|
253
|
+
const mark = fromAgent ? await tail(await coneOfId(core, record.cone, context), context) : { in: 0, at: 0 };
|
|
254
|
+
await agents.update((_tx, state) => {
|
|
255
|
+
state.scoops[id].dropped = mark;
|
|
256
|
+
}, context);
|
|
257
|
+
return {
|
|
258
|
+
code: 0,
|
|
259
|
+
out: `stopped ${record.folder}; its transcript is kept, and its files stay in ${SCOOPS_ROOT}/${record.folder}
|
|
260
|
+
`
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function rename(core, id, name, context) {
|
|
264
|
+
const { agents } = await core.host;
|
|
265
|
+
const record = agents.state().scoops[id];
|
|
266
|
+
if (!record || !live(record)) return missing(id);
|
|
267
|
+
const wanted = name.trim();
|
|
268
|
+
if (!wanted) return { code: 2, out: "subagent: rename needs a non-empty name\n" };
|
|
269
|
+
await agents.update((_tx, state) => {
|
|
270
|
+
state.scoops[id].name = wanted;
|
|
271
|
+
}, context);
|
|
272
|
+
return { code: 0, out: `${record.folder} is now called ${wanted}
|
|
273
|
+
` };
|
|
274
|
+
}
|
|
275
|
+
async function rewindOne(core, coneId, id, record, seen, result, context) {
|
|
276
|
+
const { harness, licks } = await core.host;
|
|
277
|
+
if (!await seen(record.created)) {
|
|
278
|
+
if (live(record)) {
|
|
279
|
+
await stopFeeds(core, id, async () => false, context);
|
|
280
|
+
await signal(core, record, context);
|
|
281
|
+
result.stopped.push(record.name);
|
|
282
|
+
}
|
|
283
|
+
return { gone: true };
|
|
284
|
+
}
|
|
285
|
+
if (record.dropped !== null) {
|
|
286
|
+
if (await seen(record.dropped)) return void 0;
|
|
287
|
+
result.restored.push(record.name);
|
|
288
|
+
return { dropped: null };
|
|
289
|
+
}
|
|
290
|
+
const withdrawn = await stopFeeds(core, id, (entry) => seen(entry.created), context);
|
|
291
|
+
if (!withdrawn) return void 0;
|
|
292
|
+
const conversation = await harness.conversation(record.conversation, context);
|
|
293
|
+
await conversation?.abort(context);
|
|
294
|
+
await licks.forget(`cone:${coneId}`, "scoop-notify", id, context);
|
|
295
|
+
return void 0;
|
|
296
|
+
}
|
|
297
|
+
async function rewound(core, coneId, fork, context) {
|
|
298
|
+
const { harness, agents } = await core.host;
|
|
299
|
+
const read = (id) => harness.commit((tx) => tx.conversation(id), context);
|
|
300
|
+
const seen = async (mark) => mark === null || mark.in === 0 || await visible(read, fork.id, mark);
|
|
301
|
+
const result = { stopped: [], restored: [] };
|
|
302
|
+
const changes = [];
|
|
303
|
+
for (const [id, record] of Object.entries(agents.state().scoops)) {
|
|
304
|
+
if (record.cone !== coneId || record.gone) continue;
|
|
305
|
+
const change = await rewindOne(core, coneId, id, record, seen, result, context);
|
|
306
|
+
if (change) changes.push([id, change]);
|
|
307
|
+
}
|
|
308
|
+
if (changes.length)
|
|
309
|
+
await agents.update((_tx, state) => {
|
|
310
|
+
for (const [id, change] of changes) Object.assign(state.scoops[id], change);
|
|
311
|
+
}, context);
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
314
|
+
async function list(core, coneId, context) {
|
|
315
|
+
const { agents } = await core.host;
|
|
316
|
+
const out = [];
|
|
317
|
+
for (const [id, record] of Object.entries(agents.state().scoops)) {
|
|
318
|
+
if (coneId !== null && record.cone !== coneId || !live(record)) continue;
|
|
319
|
+
out.push({
|
|
320
|
+
id,
|
|
321
|
+
folder: record.folder,
|
|
322
|
+
name: record.name,
|
|
323
|
+
role: record.role,
|
|
324
|
+
busy: await isBusy(core, record.conversation, context)
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
return out;
|
|
328
|
+
}
|
|
329
|
+
async function working(core, ids, context) {
|
|
330
|
+
const { harness, agents } = await core.host;
|
|
331
|
+
const feeds = Object.values((await harness.snapshot(ScoopWorkDoc, context))?.feeds ?? {});
|
|
332
|
+
const out = [];
|
|
333
|
+
for (const id of ids) {
|
|
334
|
+
const record = agents.state().scoops[id];
|
|
335
|
+
if (!record) continue;
|
|
336
|
+
if (feeds.some((entry) => entry.scoop === id) || await isBusy(core, record.conversation, context))
|
|
337
|
+
out.push(id);
|
|
338
|
+
}
|
|
339
|
+
return out;
|
|
340
|
+
}
|
|
341
|
+
async function notifyWhenDone(core, coneId, ids, deadline, context) {
|
|
342
|
+
const { harness } = await core.host;
|
|
343
|
+
const parent = await coneOfId(core, coneId, context);
|
|
344
|
+
await harness.commit(
|
|
345
|
+
(tx) => tx.createTask(
|
|
346
|
+
core.tasks().wait,
|
|
347
|
+
{ scoops: ids, cone: coneId, deadline },
|
|
348
|
+
{ ownership: { kind: "conversation" }, conversationId: parent.id, background: true }
|
|
349
|
+
),
|
|
350
|
+
context
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
async function detach(core, tasks, target, context) {
|
|
354
|
+
const { harness } = await core.host;
|
|
355
|
+
await harness.commit(async (tx) => {
|
|
356
|
+
const work = await tx.doc(ScoopWorkDoc);
|
|
357
|
+
for (const task of tasks) {
|
|
358
|
+
const entry = work.feeds[String(task)];
|
|
359
|
+
if (!entry) continue;
|
|
360
|
+
entry.report = true;
|
|
361
|
+
entry.target = target;
|
|
362
|
+
entry.channel = "bash";
|
|
363
|
+
}
|
|
364
|
+
}, context);
|
|
365
|
+
}
|
|
366
|
+
async function note(core, id, text, context) {
|
|
367
|
+
const { agents, licks } = await core.host;
|
|
368
|
+
const record = agents.state().scoops[id];
|
|
369
|
+
if (!record || !live(record)) return missing(id);
|
|
370
|
+
const label = `${record.name} (${record.role ?? "scoop"})`;
|
|
371
|
+
await licks.deliver(
|
|
372
|
+
{
|
|
373
|
+
channel: "scoop-notify",
|
|
374
|
+
source: `${id}#note`,
|
|
375
|
+
title: label,
|
|
376
|
+
text: `[scoop ${label} progress]`,
|
|
377
|
+
body: text,
|
|
378
|
+
target: `cone:${record.cone}`
|
|
379
|
+
},
|
|
380
|
+
context
|
|
381
|
+
);
|
|
382
|
+
return { code: 0, out: "note sent to the cone\n" };
|
|
383
|
+
}
|
|
384
|
+
function createScoops(host, tasks) {
|
|
385
|
+
const core = { host, tasks };
|
|
386
|
+
return {
|
|
387
|
+
detach: (made, target, context) => detach(core, made, target, context),
|
|
388
|
+
note: (id, text, context) => note(core, id, text, context),
|
|
389
|
+
spawn: (request, context) => spawn(core, request, context),
|
|
390
|
+
feed: (id, prompt, options, context) => feed(core, id, prompt, options, context),
|
|
391
|
+
stop: (id, fromAgent, context) => stop(core, id, fromAgent, context),
|
|
392
|
+
rename: (id, name, context) => rename(core, id, name, context),
|
|
393
|
+
rewound: (coneId, fork, context) => rewound(core, coneId, fork, context),
|
|
394
|
+
list: (coneId, context) => list(core, coneId, context),
|
|
395
|
+
busy: (conversation, context) => isBusy(core, conversation, context),
|
|
396
|
+
working: (ids, context) => working(core, ids, context),
|
|
397
|
+
notifyWhenDone: (coneId, ids, deadline, context) => notifyWhenDone(core, coneId, ids, deadline, context)
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
function modelOf(value) {
|
|
401
|
+
if (!value) return void 0;
|
|
402
|
+
const at = value.indexOf("/");
|
|
403
|
+
if (at <= 0) return void 0;
|
|
404
|
+
return { provider: value.slice(0, at), modelId: value.slice(at + 1) };
|
|
405
|
+
}
|
|
406
|
+
export {
|
|
407
|
+
LEDGER_SIZE,
|
|
408
|
+
SCOOPS_ROOT,
|
|
409
|
+
ScoopWorkDoc,
|
|
410
|
+
askerName,
|
|
411
|
+
coneOf,
|
|
412
|
+
createScoops,
|
|
413
|
+
findScoop,
|
|
414
|
+
modelOf,
|
|
415
|
+
tail,
|
|
416
|
+
turnStart,
|
|
417
|
+
workspace
|
|
418
|
+
};
|
|
419
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/scoops/service.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type AgentChange,\n type AnyTask,\n type Conversation,\n type ConversationId,\n configure,\n defineDoc,\n type Harness,\n type TaskId,\n type ToolRegistration,\n type Tx,\n} from '@earendil-works/pi-durable';\nimport {\n type Agents,\n type AgentsState,\n freeFolder,\n live,\n type Mark,\n type ScoopRecord,\n scoopId,\n} from '../agents.ts';\nimport type { ProcessGroups } from '../kernel/groups.ts';\nimport type { Licks } from '../licks/licks.ts';\nimport type { Role } from '../roles/roles.ts';\nimport { visible } from './visible.ts';\n\nexport const SCOOPS_ROOT = '/scoops';\nexport const LEDGER_SIZE = 512;\n\nexport type Feed = {\n scoop: string;\n created: Mark | null;\n report: boolean;\n target: string | null;\n channel: 'scoop-notify' | 'bash';\n from?: string;\n};\ntype Wait = { scoops: string[]; done: Record<string, string>; cone: string };\nexport type Answer = { code: number; out: string; tasks?: number[] };\ntype WorkState = {\n feeds: Record<string, Feed>;\n waits: Record<string, Wait>;\n ledger: Record<string, Answer>;\n turns: Record<string, { at: number; count: number }>;\n};\n\nexport const ScoopWorkDoc = defineDoc<WorkState>({\n kind: 'slicc.scoops.work',\n version: 1,\n scope: 'session',\n initial: () => ({ feeds: {}, waits: {}, ledger: {}, turns: {} }),\n});\n\nexport interface ScoopTasks {\n anchor: AnyTask;\n reporter: AnyTask;\n wait: AnyTask;\n}\n\nexport interface ScoopsHost {\n harness: Harness;\n agents: Agents;\n licks: Licks;\n groups?: ProcessGroups;\n tools?: readonly ToolRegistration[];\n}\n\nexport interface Rewound {\n stopped: string[];\n restored: string[];\n}\n\nexport interface ScoopSummary {\n id: string;\n folder: string;\n name: string;\n role: string | null;\n busy: boolean;\n}\n\nexport interface Limits {\n maxLiveScoops: number;\n maxPerTurn: number;\n}\n\nexport interface SpawnRequest {\n cone: string;\n kind?: 'async' | 'sync';\n parent?: string;\n depth?: number;\n roots?: { write: string[]; read: string[] };\n cwd?: string;\n instructions?: string;\n target?: string | null;\n asker?: string | null;\n name: string;\n role?: Role;\n model?: { provider: string; modelId: string };\n thinking?: AgentChange['thinkingLevel'];\n tools?: string[];\n prompts: string[];\n fromAgent: boolean;\n request?: string;\n limits: Limits;\n}\n\nexport function workspace(folder: string): string {\n return `${SCOOPS_ROOT}/${folder}/workspace`;\n}\n\nexport async function tail(conversation: Conversation, context: Context): Promise<Mark> {\n const { entries } = await conversation.context(context);\n return { in: conversation.id, at: Number(entries.at(-1)?.id ?? 0) };\n}\n\nexport async function turnStart(conversation: Conversation, context: Context): Promise<number> {\n const { entries } = await conversation.context(context);\n return Number(entries.findLast((entry) => entry.kind === 'pi.user')?.id ?? 0);\n}\n\nexport function coneOf(state: Readonly<AgentsState>, conversation: number): string | undefined {\n return Object.entries(state.cones).find(([, cone]) => cone.conversation === conversation)?.[0];\n}\n\nexport function findScoop(\n state: Readonly<AgentsState>,\n handle: string,\n cone: string | null\n): [string, ScoopRecord] | undefined {\n const id = handle.startsWith('scoop:') ? handle : scoopId(handle);\n const reachable = (record: ScoopRecord | undefined): record is ScoopRecord =>\n !!record && live(record) && (cone === null || record.cone === cone);\n const record = state.scoops[id];\n if (reachable(record)) return [id, record];\n return Object.entries(state.scoops).find(\n ([, candidate]) => reachable(candidate) && candidate.name === handle\n );\n}\n\nfunction remember(work: WorkState, request: string | undefined, answer: Answer): void {\n if (!request) return;\n work.ledger[request] = answer;\n const keys = Object.keys(work.ledger);\n for (const key of keys.slice(0, Math.max(0, keys.length - LEDGER_SIZE))) delete work.ledger[key];\n}\n\ntype Core = { host: Promise<ScoopsHost>; tasks: () => ScoopTasks };\n\nasync function coneOfId(core: Core, coneId: string, context: Context): Promise<Conversation> {\n const { agents } = await core.host;\n const found = await agents.conversation(coneId, context);\n if (!found) throw new Error(`There is no cone ${coneId}.`);\n return found;\n}\n\nasync function isBusy(core: Core, conversation: number, context: Context): Promise<boolean> {\n const { harness } = await core.host;\n const handle = await harness.conversation(conversation as ConversationId, context);\n const view = await handle?.viewState(context);\n const run = (view?.value.docs['pi.live'] as { run?: unknown } | undefined)?.run;\n view?.dispose();\n return Boolean(run);\n}\n\nasync function answered(\n core: Core,\n request: string | undefined,\n context: Context\n): Promise<Answer | undefined> {\n if (!request) return undefined;\n const { harness } = await core.host;\n return (await harness.snapshot(ScoopWorkDoc, context))?.ledger[request];\n}\n\ntype NewFeed = Omit<Feed, 'channel' | 'from'> & {\n from: string;\n prompt: string;\n request?: string;\n followUp?: boolean;\n};\n\nasync function addFeed(core: Core, tx: Tx, parent: number, feed: NewFeed): Promise<number> {\n const task = await tx.createTask(\n core.tasks().reporter as never,\n {\n scoop: feed.scoop,\n prompt: feed.prompt,\n ...(feed.followUp ? { followUp: true } : {}),\n ...(feed.request ? { request: feed.request } : {}),\n },\n {\n ownership: { kind: 'conversation' },\n conversationId: parent as ConversationId,\n background: true,\n }\n );\n (await tx.doc(ScoopWorkDoc)).feeds[String(task)] = {\n scoop: feed.scoop,\n created: feed.created,\n report: feed.report,\n target: feed.target,\n channel: 'scoop-notify',\n from: feed.from,\n };\n return Number(task);\n}\n\nfunction selection(tools: readonly ToolRegistration[] | undefined, names: string[] | undefined) {\n if (!names || !tools) return undefined;\n return tools.filter((tool) => names.includes(tool.name));\n}\n\nfunction liveCount(state: Readonly<AgentsState>, cone: string): number {\n return Object.values(state.scoops).filter((scoop) => scoop.cone === cone && live(scoop)).length;\n}\n\nfunction claimTurn(\n work: WorkState,\n request: SpawnRequest,\n turn: number | null\n): Answer | undefined {\n if (turn === null) return undefined;\n const counted = work.turns[request.cone];\n const count = counted && counted.at === turn ? counted.count : 0;\n if (count >= request.limits.maxPerTurn)\n return {\n code: 1,\n out: `subagent: this turn already started ${count} scoops (the limit is ${request.limits.maxPerTurn})\\n`,\n };\n work.turns[request.cone] = { at: turn, count: count + 1 };\n return undefined;\n}\n\nfunction agentChange(\n request: SpawnRequest,\n folder: string,\n tools: readonly ToolRegistration[] | undefined\n): AgentChange {\n const model = request.model ?? modelOf(request.role?.model);\n const thinking = request.thinking ?? request.role?.thinking;\n const chosen = selection(tools, request.tools ?? request.role?.tools);\n const instructions = [request.role?.prompt, request.instructions].filter(Boolean).join('\\n\\n');\n return {\n cwd: request.cwd ?? workspace(folder),\n ...(instructions ? { instructions } : {}),\n ...(model ? { model } : {}),\n ...(thinking ? { thinkingLevel: thinking as AgentChange['thinkingLevel'] } : {}),\n ...(chosen ? { tools: chosen } : {}),\n };\n}\n\nasync function spawn(core: Core, request: SpawnRequest, context: Context): Promise<Answer> {\n const done = await answered(core, request.request, context);\n if (done) return done;\n const { agents, tools } = await core.host;\n const parent = await coneOfId(core, request.cone, context);\n const count = liveCount(agents.state(), request.cone);\n if (count >= request.limits.maxLiveScoops)\n return {\n code: 1,\n out: `subagent: this cone already has ${count} live scoops (the limit is ${request.limits.maxLiveScoops}); stop one first\\n`,\n };\n const created = request.fromAgent ? await tail(parent, context) : null;\n const turn = request.fromAgent ? await turnStart(parent, context) : null;\n return agents.update(async (tx, doc) => {\n const work = await tx.doc(ScoopWorkDoc);\n const refused = claimTurn(work as WorkState, request, turn);\n if (refused) return refused;\n const folder = freeFolder(doc, request.name);\n const id = scoopId(folder);\n const anchor = await tx.createTask(core.tasks().anchor as never, null, {\n ownership: { kind: 'conversation' },\n conversationId: parent.id,\n background: true,\n });\n const child = await tx.createConversation({ ownership: { kind: 'task', taskId: anchor } });\n await configure(tx, child.id, agentChange(request, folder, tools));\n doc.scoops[id] = {\n name: request.name.trim() || folder,\n folder,\n cone: request.cone,\n conversation: child.id,\n anchor,\n role: request.role?.name ?? null,\n kind: request.kind ?? 'async',\n parent: request.parent ?? request.cone,\n depth: request.depth ?? 1,\n roots: request.roots ?? null,\n created,\n dropped: null,\n gone: false,\n };\n const target =\n request.target === undefined\n ? request.fromAgent\n ? `cone:${request.cone}`\n : null\n : request.target;\n const made: number[] = [];\n const from = askerName(doc, request.asker === undefined ? target : request.asker);\n for (const prompt of request.prompts)\n made.push(\n await addFeed(core, tx, parent.id, {\n scoop: id,\n created,\n report: target !== null,\n target,\n from,\n prompt,\n })\n );\n const answer = { code: 0, out: `${folder}\\n`, tasks: made };\n remember(work as WorkState, request.request, answer);\n return answer;\n }, context);\n}\n\nexport function askerName(state: Readonly<AgentsState>, asker: string | null): string {\n if (asker === null) return 'terminal';\n if (asker.startsWith('cone:')) return state.cones[asker.slice('cone:'.length)]?.name ?? asker;\n return state.scoops[asker]?.name ?? asker;\n}\n\nconst missing = (id: string): Answer => ({\n code: 1,\n out: `subagent: there is no scoop ${id.replace('scoop:', '')}\\n`,\n});\n\nasync function feed(\n core: Core,\n id: string,\n prompt: string,\n options: { fromAgent: boolean; request: string; followUp: boolean; target: string | null },\n context: Context\n): Promise<Answer> {\n const done = await answered(core, options.request, context);\n if (done) return done;\n const { harness, agents } = await core.host;\n const record = agents.state().scoops[id];\n if (!record || !live(record)) return missing(id);\n const parent = await coneOfId(core, record.cone, context);\n const created = options.fromAgent ? await tail(parent, context) : null;\n const { target } = options;\n return harness.commit(async (tx) => {\n await addFeed(core, tx, parent.id, {\n scoop: id,\n created,\n report: target !== null,\n target,\n from: askerName(agents.state(), target),\n prompt,\n followUp: options.followUp,\n request: options.request,\n });\n const answer = { code: 0, out: `sent to ${record.folder}\\n` };\n remember((await tx.doc(ScoopWorkDoc)) as WorkState, options.request, answer);\n return answer;\n }, context);\n}\n\nasync function signal(core: Core, record: ScoopRecord, context: Context): Promise<void> {\n const { harness, groups } = await core.host;\n const conversation = await harness.conversation(record.conversation as ConversationId, context);\n await conversation?.abort(context, { background: true });\n await groups?.signal(record.conversation);\n}\n\nasync function stopFeeds(\n core: Core,\n scoop: string,\n keep: (feed: Feed) => Promise<boolean>,\n context: Context\n) {\n const { harness } = await core.host;\n const work = (await harness.snapshot(ScoopWorkDoc, context)) ?? { feeds: {} };\n const dropped: string[] = [];\n for (const [task, entry] of Object.entries(work.feeds)) {\n if (entry.scoop !== scoop || (await keep(entry))) continue;\n const record = await harness.getTask(Number(task) as TaskId, context);\n if (record && record.state.status !== 'terminal')\n await harness.abortTask(Number(task) as TaskId, context);\n dropped.push(task);\n }\n if (dropped.length)\n await harness.commit(async (tx) => {\n const doc = await tx.doc(ScoopWorkDoc);\n for (const task of dropped) delete doc.feeds[task];\n }, context);\n return dropped.length;\n}\n\nasync function stop(core: Core, id: string, fromAgent: boolean, context: Context): Promise<Answer> {\n const { agents } = await core.host;\n const record = agents.state().scoops[id];\n if (!record || !live(record)) return missing(id);\n await stopFeeds(core, id, async () => false, context);\n await signal(core, record, context);\n const mark = fromAgent\n ? await tail(await coneOfId(core, record.cone, context), context)\n : { in: 0, at: 0 };\n await agents.update((_tx, state) => {\n (state.scoops[id] as ScoopRecord).dropped = mark;\n }, context);\n return {\n code: 0,\n out: `stopped ${record.folder}; its transcript is kept, and its files stay in ${SCOOPS_ROOT}/${record.folder}\\n`,\n };\n}\n\nasync function rename(core: Core, id: string, name: string, context: Context): Promise<Answer> {\n const { agents } = await core.host;\n const record = agents.state().scoops[id];\n if (!record || !live(record)) return missing(id);\n const wanted = name.trim();\n if (!wanted) return { code: 2, out: 'subagent: rename needs a non-empty name\\n' };\n await agents.update((_tx, state) => {\n (state.scoops[id] as ScoopRecord).name = wanted;\n }, context);\n return { code: 0, out: `${record.folder} is now called ${wanted}\\n` };\n}\n\ntype Seen = (mark: Mark | null) => Promise<boolean>;\n\nasync function rewindOne(\n core: Core,\n coneId: string,\n id: string,\n record: ScoopRecord,\n seen: Seen,\n result: Rewound,\n context: Context\n): Promise<Partial<ScoopRecord> | undefined> {\n const { harness, licks } = await core.host;\n if (!(await seen(record.created))) {\n if (live(record)) {\n await stopFeeds(core, id, async () => false, context);\n await signal(core, record, context);\n result.stopped.push(record.name);\n }\n return { gone: true };\n }\n if (record.dropped !== null) {\n if (await seen(record.dropped)) return undefined;\n result.restored.push(record.name);\n return { dropped: null };\n }\n const withdrawn = await stopFeeds(core, id, (entry) => seen(entry.created), context);\n if (!withdrawn) return undefined;\n const conversation = await harness.conversation(record.conversation as ConversationId, context);\n await conversation?.abort(context);\n await licks.forget(`cone:${coneId}`, 'scoop-notify', id, context);\n return undefined;\n}\n\nasync function rewound(\n core: Core,\n coneId: string,\n fork: Conversation,\n context: Context\n): Promise<Rewound> {\n const { harness, agents } = await core.host;\n const read = (id: number) =>\n harness.commit((tx) => tx.conversation(id as ConversationId), context);\n const seen: Seen = async (mark) =>\n mark === null || mark.in === 0 || (await visible(read, fork.id, mark));\n const result: Rewound = { stopped: [], restored: [] };\n const changes: [string, Partial<ScoopRecord>][] = [];\n for (const [id, record] of Object.entries(agents.state().scoops)) {\n if (record.cone !== coneId || record.gone) continue;\n const change = await rewindOne(core, coneId, id, record, seen, result, context);\n if (change) changes.push([id, change]);\n }\n if (changes.length)\n await agents.update((_tx, state) => {\n for (const [id, change] of changes) Object.assign(state.scoops[id] as ScoopRecord, change);\n }, context);\n return result;\n}\n\nasync function list(core: Core, coneId: string | null, context: Context): Promise<ScoopSummary[]> {\n const { agents } = await core.host;\n const out: ScoopSummary[] = [];\n for (const [id, record] of Object.entries(agents.state().scoops)) {\n if ((coneId !== null && record.cone !== coneId) || !live(record)) continue;\n out.push({\n id,\n folder: record.folder,\n name: record.name,\n role: record.role,\n busy: await isBusy(core, record.conversation, context),\n });\n }\n return out;\n}\n\nasync function working(core: Core, ids: readonly string[], context: Context): Promise<string[]> {\n const { harness, agents } = await core.host;\n const feeds = Object.values((await harness.snapshot(ScoopWorkDoc, context))?.feeds ?? {});\n const out: string[] = [];\n for (const id of ids) {\n const record = agents.state().scoops[id];\n if (!record) continue;\n if (\n feeds.some((entry) => entry.scoop === id) ||\n (await isBusy(core, record.conversation, context))\n )\n out.push(id);\n }\n return out;\n}\n\nasync function notifyWhenDone(\n core: Core,\n coneId: string,\n ids: string[],\n deadline: number,\n context: Context\n) {\n const { harness } = await core.host;\n const parent = await coneOfId(core, coneId, context);\n await harness.commit(\n (tx) =>\n tx.createTask(\n core.tasks().wait as never,\n { scoops: ids, cone: coneId, deadline },\n { ownership: { kind: 'conversation' }, conversationId: parent.id, background: true }\n ),\n context\n );\n}\n\nasync function detach(core: Core, tasks: readonly number[], target: string, context: Context) {\n const { harness } = await core.host;\n await harness.commit(async (tx) => {\n const work = await tx.doc(ScoopWorkDoc);\n for (const task of tasks) {\n const entry = work.feeds[String(task)];\n if (!entry) continue;\n entry.report = true;\n entry.target = target;\n entry.channel = 'bash';\n }\n }, context);\n}\n\nasync function note(core: Core, id: string, text: string, context: Context): Promise<Answer> {\n const { agents, licks } = await core.host;\n const record = agents.state().scoops[id];\n if (!record || !live(record)) return missing(id);\n const label = `${record.name} (${record.role ?? 'scoop'})`;\n await licks.deliver(\n {\n channel: 'scoop-notify',\n source: `${id}#note`,\n title: label,\n text: `[scoop ${label} progress]`,\n body: text,\n target: `cone:${record.cone}`,\n },\n context\n );\n return { code: 0, out: 'note sent to the cone\\n' };\n}\n\nexport function createScoops(host: Promise<ScoopsHost>, tasks: () => ScoopTasks) {\n const core: Core = { host, tasks };\n return {\n detach: (made: readonly number[], target: string, context: Context) =>\n detach(core, made, target, context),\n note: (id: string, text: string, context: Context) => note(core, id, text, context),\n spawn: (request: SpawnRequest, context: Context) => spawn(core, request, context),\n feed: (\n id: string,\n prompt: string,\n options: { fromAgent: boolean; request: string; followUp: boolean; target: string | null },\n context: Context\n ) => feed(core, id, prompt, options, context),\n stop: (id: string, fromAgent: boolean, context: Context) => stop(core, id, fromAgent, context),\n rename: (id: string, name: string, context: Context) => rename(core, id, name, context),\n rewound: (coneId: string, fork: Conversation, context: Context) =>\n rewound(core, coneId, fork, context),\n list: (coneId: string | null, context: Context) => list(core, coneId, context),\n busy: (conversation: number, context: Context) => isBusy(core, conversation, context),\n working: (ids: readonly string[], context: Context) => working(core, ids, context),\n notifyWhenDone: (coneId: string, ids: string[], deadline: number, context: Context) =>\n notifyWhenDone(core, coneId, ids, deadline, context),\n };\n}\n\nexport function modelOf(\n value: string | undefined\n): { provider: string; modelId: string } | undefined {\n if (!value) return undefined;\n const at = value.indexOf('/');\n if (at <= 0) return undefined;\n return { provider: value.slice(0, at), modelId: value.slice(at + 1) };\n}\n\nexport type Scoops = ReturnType<typeof createScoops>;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["record", "feed"]
|
|
7
|
+
}
|