@ai-ecoverse/slicc-agent 3.48.1 → 3.49.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.
Files changed (86) hide show
  1. package/README.md +27 -1
  2. package/bin/agent +1 -0
  3. package/dist/control.js +1 -1
  4. package/dist/control.js.map +2 -2
  5. package/dist/freezer/index.js +29 -8
  6. package/dist/freezer/index.js.map +2 -2
  7. package/dist/freezer/title.js +2 -2
  8. package/dist/freezer/title.js.map +2 -2
  9. package/dist/gelatiere/catalog.js +1 -1
  10. package/dist/gelatiere/catalog.js.map +2 -2
  11. package/dist/gelatiere/index.js +44 -21
  12. package/dist/gelatiere/index.js.map +2 -2
  13. package/dist/gelatiere/store.js +9 -5
  14. package/dist/gelatiere/store.js.map +2 -2
  15. package/dist/index.js.map +2 -2
  16. package/dist/kernel/accounts.js +141 -0
  17. package/dist/kernel/accounts.js.map +7 -0
  18. package/dist/kernel/agent-users.js +180 -0
  19. package/dist/kernel/agent-users.js.map +7 -0
  20. package/dist/kernel/env.js +9 -3
  21. package/dist/kernel/env.js.map +2 -2
  22. package/dist/kernel/users.js +16 -1
  23. package/dist/kernel/users.js.map +2 -2
  24. package/dist/layout.js +62 -0
  25. package/dist/layout.js.map +7 -0
  26. package/dist/licks/index.js.map +2 -2
  27. package/dist/licks/sources.js +69 -20
  28. package/dist/licks/sources.js.map +2 -2
  29. package/dist/main.js +70 -36
  30. package/dist/main.js.map +2 -2
  31. package/dist/mcp/index.js +5 -3
  32. package/dist/mcp/index.js.map +2 -2
  33. package/dist/mcp/paths.js +15 -0
  34. package/dist/mcp/paths.js.map +7 -0
  35. package/dist/mcp/runtime.js +16 -14
  36. package/dist/mcp/runtime.js.map +3 -3
  37. package/dist/memory/extract.js +2 -1
  38. package/dist/memory/extract.js.map +2 -2
  39. package/dist/memory/prompt.js +5 -1
  40. package/dist/memory/prompt.js.map +2 -2
  41. package/dist/memory/runtime.js +9 -1
  42. package/dist/memory/runtime.js.map +2 -2
  43. package/dist/scoops/cli.js +40 -19
  44. package/dist/scoops/cli.js.map +2 -2
  45. package/dist/scoops/extension.js +19 -15
  46. package/dist/scoops/extension.js.map +2 -2
  47. package/dist/scoops/index.js +75 -9
  48. package/dist/scoops/index.js.map +2 -2
  49. package/dist/scoops/requests.js +2 -1
  50. package/dist/scoops/requests.js.map +2 -2
  51. package/dist/scoops/service.js +45 -38
  52. package/dist/scoops/service.js.map +2 -2
  53. package/dist/scoops/sync.js +63 -13
  54. package/dist/scoops/sync.js.map +2 -2
  55. package/dist/skills/index.js +67 -22
  56. package/dist/skills/index.js.map +3 -3
  57. package/dist/sprinkles/index.js +114 -25
  58. package/dist/sprinkles/index.js.map +2 -2
  59. package/dist/sprinkles/sprinkles.js +13 -4
  60. package/dist/sprinkles/sprinkles.js.map +2 -2
  61. package/dist/types/freezer/index.d.ts +3 -1
  62. package/dist/types/freezer/title.d.ts +1 -1
  63. package/dist/types/gelatiere/index.d.ts +8 -0
  64. package/dist/types/gelatiere/store.d.ts +3 -2
  65. package/dist/types/index.d.ts +1 -1
  66. package/dist/types/kernel/accounts.d.ts +29 -0
  67. package/dist/types/kernel/agent-users.d.ts +37 -0
  68. package/dist/types/kernel/env.d.ts +6 -0
  69. package/dist/types/kernel/users.d.ts +15 -0
  70. package/dist/types/layout.d.ts +26 -0
  71. package/dist/types/licks/index.d.ts +2 -1
  72. package/dist/types/licks/sources.d.ts +11 -1
  73. package/dist/types/mcp/paths.d.ts +3 -0
  74. package/dist/types/mcp/runtime.d.ts +3 -3
  75. package/dist/types/memory/prompt.d.ts +1 -0
  76. package/dist/types/memory/runtime.d.ts +1 -0
  77. package/dist/types/scoops/cli.d.ts +5 -0
  78. package/dist/types/scoops/extension.d.ts +6 -3
  79. package/dist/types/scoops/index.d.ts +9 -0
  80. package/dist/types/scoops/requests.d.ts +2 -0
  81. package/dist/types/scoops/service.d.ts +5 -3
  82. package/dist/types/scoops/sync.d.ts +9 -6
  83. package/dist/types/skills/index.d.ts +10 -2
  84. package/dist/types/sprinkles/index.d.ts +15 -0
  85. package/dist/types/sprinkles/sprinkles.d.ts +7 -1
  86. package/package.json +2 -2
@@ -1,8 +1,13 @@
1
1
  import { live } from "../agents.js";
2
+ import { LEGACY } from "../layout.js";
2
3
  import { DEFAULT_LIMITS, loadRoles, packageDirs } from "../roles/roles.js";
3
4
  import { createCli, RUN_DIR } from "./cli.js";
4
5
  import { guardExtension, scoopsExtension, scoopTasks } from "./extension.js";
5
- import { controlPlane, PROTOCOL } from "./requests.js";
6
+ import {
7
+ CONTROL_DIR,
8
+ controlPlane,
9
+ PROTOCOL
10
+ } from "./requests.js";
6
11
  import { createScoops } from "./service.js";
7
12
  const BUILTIN_ROLES = ["scout", "worker", "reviewer", "oracle", "delegate", "gelatiere"];
8
13
  const PNPM_HOME = "/home/.local/share/pnpm";
@@ -98,6 +103,66 @@ function mirror(agents, env) {
98
103
  }
99
104
  };
100
105
  }
106
+ function planeOf(env, cli, options, context) {
107
+ const common = {
108
+ ...options.sweepEvery ? { sweepEvery: options.sweepEvery } : {},
109
+ mirror: mirror(options.agents, env),
110
+ dir: options.controlDir ?? CONTROL_DIR
111
+ };
112
+ if (!options.spools) return controlPlane(env, cli, common);
113
+ return spooled(env, cli, options.spools, options.agents, common, context);
114
+ }
115
+ function spooled(env, cli, spools, agents, options, context) {
116
+ const planes = /* @__PURE__ */ new Map();
117
+ let off = () => void 0;
118
+ let chain = Promise.resolve();
119
+ const open = async (user) => {
120
+ const dir = `${options.dir}/${user}`;
121
+ try {
122
+ await spools.prepare(user, dir, context);
123
+ const plane = controlPlane(env, cli, { ...options, dir, user });
124
+ await plane.start(context);
125
+ return plane;
126
+ } catch {
127
+ planes.delete(user);
128
+ return void 0;
129
+ }
130
+ };
131
+ const sync = () => {
132
+ const step = async () => {
133
+ const wanted = new Set(spools.users());
134
+ for (const user of wanted)
135
+ if (!planes.has(user)) {
136
+ const opened = open(user);
137
+ planes.set(user, opened);
138
+ await opened;
139
+ }
140
+ for (const [user, plane] of [...planes])
141
+ if (!wanted.has(user)) {
142
+ planes.delete(user);
143
+ await (await plane)?.close(context);
144
+ }
145
+ };
146
+ chain = chain.then(step, step);
147
+ return chain;
148
+ };
149
+ const all = async () => (await Promise.all(planes.values())).filter((plane) => !!plane);
150
+ return {
151
+ async start() {
152
+ off = agents.onChange(() => void sync());
153
+ await sync();
154
+ },
155
+ async sweep(using) {
156
+ for (const plane of await all()) await plane.sweep(using);
157
+ },
158
+ async close(using) {
159
+ off();
160
+ await chain;
161
+ for (const plane of await all()) await plane.close(using);
162
+ planes.clear();
163
+ }
164
+ };
165
+ }
101
166
  function setupScoops(registry, licks, fileTools = []) {
102
167
  let bind = () => void 0;
103
168
  const host = new Promise((resolve) => {
@@ -107,13 +172,15 @@ function setupScoops(registry, licks, fileTools = []) {
107
172
  const tasks = scoopTasks(lookup);
108
173
  const scoops = createScoops(host, () => tasks);
109
174
  let cached = { roles: [], warnings: [], limits: { ...DEFAULT_LIMITS } };
110
- registry.install(scoopsExtension(tasks));
175
+ let layout = LEGACY;
176
+ registry.install(scoopsExtension(tasks, () => layout));
111
177
  registry.install(guardExtension(lookup, fileTools));
112
178
  return {
113
179
  scoops,
114
180
  async attach(options, context) {
115
181
  const { harness, agents, groups, env, home } = options;
116
182
  const assets = options.assets ?? packageAssets();
183
+ layout = options.layout ?? LEGACY;
117
184
  bind({
118
185
  harness,
119
186
  agents,
@@ -122,6 +189,8 @@ function setupScoops(registry, licks, fileTools = []) {
122
189
  groups,
123
190
  reads: options.reads,
124
191
  files: env,
192
+ layout,
193
+ ...options.enforced ? { enforced: true } : {},
125
194
  ...options.mcp ? { mcp: options.mcp } : {}
126
195
  });
127
196
  const builtin = async () => {
@@ -142,9 +211,9 @@ function setupScoops(registry, licks, fileTools = []) {
142
211
  source: "package",
143
212
  dir
144
213
  })),
145
- { source: "user", dir: `${home}/.pi/agent/agents` }
214
+ { source: "user", dir: `${options.agentDir ?? `${home}/.pi/agent`}/agents` }
146
215
  ],
147
- settingsPath: `${home}/.pi/agent/settings.json`
216
+ settingsPath: `${options.agentDir ?? `${home}/.pi/agent`}/settings.json`
148
217
  },
149
218
  using
150
219
  );
@@ -167,13 +236,10 @@ function setupScoops(registry, licks, fileTools = []) {
167
236
  ...options.gelatiere ? { gelatiere: options.gelatiere } : {},
168
237
  ...options.freezer ? { freezer: options.freezer } : {},
169
238
  ...options.models ? { models: options.models } : {},
239
+ layout,
170
240
  ...options.panel ? { panel: options.panel } : {}
171
241
  });
172
- const plane = controlPlane(env, cli, {
173
- ...options.controlDir ? { dir: options.controlDir } : {},
174
- ...options.sweepEvery ? { sweepEvery: options.sweepEvery } : {},
175
- mirror: mirror(agents, env)
176
- });
242
+ const plane = planeOf(env, cli, options, context);
177
243
  await plane.start(context);
178
244
  return { roles, plane, close: (using) => plane.close(using) };
179
245
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/scoops/index.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { Harness, Registry, ToolRegistration } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport { type Agents, live } from '../agents.ts';\nimport type { ProcessGroups } from '../kernel/groups.ts';\nimport type { Licks } from '../licks/licks.ts';\nimport { DEFAULT_LIMITS, loadRoles, packageDirs, type Roles } from '../roles/roles.ts';\nimport { type CliOptions, createCli, RUN_DIR } from './cli.ts';\nimport { guardExtension, scoopsExtension, scoopTasks } from './extension.ts';\nimport { type ControlPlane, controlPlane, PROTOCOL } from './requests.ts';\nimport { createScoops, type Scoops, type ScoopsHost } from './service.ts';\n\nexport const BUILTIN_ROLES = ['scout', 'worker', 'reviewer', 'oracle', 'delegate', 'gelatiere'];\nexport const PNPM_HOME = '/home/.local/share/pnpm';\nexport const BIN_DIR = '/usr/local/bin';\n\nexport type Assets = (path: string) => Promise<string>;\n\nexport function packageAssets(base: URL = new URL('../../', import.meta.url)): Assets {\n return async (path) => {\n const response = await fetch(new URL(path, base));\n if (!response.ok) throw new Error(`${path}: ${response.status}`);\n return response.text();\n };\n}\n\nexport interface AttachOptions {\n harness: Harness;\n agents: Agents;\n groups: ProcessGroups;\n env: ExecutionEnv;\n home: string;\n assets?: Assets;\n binDir?: string;\n controlDir?: string;\n sweepEvery?: number;\n alive?: (pid: number) => Promise<boolean>;\n reads: () => readonly string[];\n memory?: CliOptions['memory'];\n gelatiere?: CliOptions['gelatiere'];\n freezer?: CliOptions['freezer'];\n models?: CliOptions['models'];\n panel?: CliOptions['panel'];\n mcp?: (names: readonly string[]) => ToolRegistration[];\n}\n\nexport interface ScoopsRuntime {\n roles(context: Context): Promise<Roles>;\n plane: ControlPlane;\n close(context: Context): Promise<void>;\n}\n\nexport interface ScoopsSetup {\n scoops: Scoops;\n attach(options: AttachOptions, context: Context): Promise<ScoopsRuntime>;\n}\n\nexport const COMMANDS = ['agent', 'subagent', 'memory', 'gelatiere', 'freezer', 'models', 'panel'];\n\nexport const RETIRED_COMMANDS = ['sprinkle'];\n\nexport async function pnpmHomeOf(env: ExecutionEnv, context: Context): Promise<string> {\n let out = '';\n const ran = await env\n .exec(\n 'printf %s \"$PNPM_HOME\"',\n {\n onOutput: (text) => {\n out += text;\n },\n },\n context\n )\n .catch(() => undefined);\n return ran?.ok && ran.value.exitCode === 0 && /^\\/\\S*$/.test(out) ? out : PNPM_HOME;\n}\n\nexport interface Installed {\n done: boolean;\n skipped: string[];\n}\n\nasync function theirs(env: ExecutionEnv, path: string, context: Context): Promise<boolean> {\n const found = await env.fileInfo(path, context);\n if (!found.ok) return found.error.code !== 'not_found';\n if (found.value.kind !== 'file') return true;\n const read = await env.readTextFile(path, context);\n return !read.ok || !read.value.includes(PROTOCOL);\n}\n\nasync function install(\n env: ExecutionEnv,\n script: string,\n binDir: string,\n context: Context\n): Promise<Installed> {\n const skipped: string[] = [];\n if (!(await env.createDir(binDir, { recursive: true }, context)).ok)\n return { done: false, skipped };\n for (const name of COMMANDS) {\n const path = `${binDir}/${name}`;\n if (await theirs(env, path, context)) {\n skipped.push(name);\n continue;\n }\n if (!(await env.writeFile(path, script, context)).ok) return { done: false, skipped };\n const mode = await env.exec(['chmod', '755', path], undefined, context);\n if (!mode.ok || mode.value.exitCode !== 0) return { done: false, skipped };\n }\n for (const name of RETIRED_COMMANDS) {\n const text = await env.readTextFile(`${binDir}/${name}`, context);\n if (text.ok && text.value.includes(PROTOCOL))\n await env.remove(`${binDir}/${name}`, { force: true }, context);\n }\n return { done: true, skipped };\n}\n\nexport async function installCommands(\n env: ExecutionEnv,\n script: string,\n binDir: string,\n context: Context,\n warn: (message: string) => void = (message) => console.warn(message)\n): Promise<Installed> {\n const installed = await install(env, script, binDir, context).catch(\n (): Installed => ({ done: false, skipped: [] })\n );\n if (installed.skipped.length) {\n const paths = installed.skipped.map((name) => `${binDir}/${name}`).join(', ');\n warn(`slicc-agent: left ${paths} alone, because it isn't slicc-agent's command`);\n }\n if (installed.done) await retire(env, binDir, context).catch(() => undefined);\n return installed;\n}\n\nexport async function retire(env: ExecutionEnv, binDir: string, context: Context) {\n const old = `${await pnpmHomeOf(env, context)}/bin`;\n if (old === binDir) return;\n for (const name of [...COMMANDS, ...RETIRED_COMMANDS]) {\n const path = `${old}/${name}`;\n const text = await env.readTextFile(path, context);\n if (text.ok && text.value.includes(PROTOCOL)) await env.remove(path, { force: true }, context);\n }\n}\n\nfunction mirror(agents: Agents, env: ExecutionEnv) {\n return async (context: Context) => {\n for (const [id, record] of Object.entries(agents.state().scoops)) {\n const state = record.gone ? 'gone' : live(record) ? 'live' : 'stopped';\n const metadata = {\n handle: record.folder,\n id,\n name: record.name,\n role: record.role,\n cone: record.cone,\n state,\n };\n await env.writeFile(\n `${RUN_DIR}/${record.folder}/metadata.json`,\n `${JSON.stringify(metadata, null, 2)}\\n`,\n context\n );\n }\n };\n}\n\nexport function setupScoops(\n registry: Registry,\n licks: Licks,\n fileTools: readonly ToolRegistration[] = []\n): ScoopsSetup {\n let bind: (host: ScoopsHost) => void = () => undefined;\n const host = new Promise<ScoopsHost>((resolve) => {\n bind = resolve;\n });\n const lookup = () => host;\n const tasks = scoopTasks(lookup);\n const scoops = createScoops(host, () => tasks);\n let cached: Roles = { roles: [], warnings: [], limits: { ...DEFAULT_LIMITS } };\n registry.install(scoopsExtension(tasks));\n registry.install(guardExtension(lookup, fileTools));\n return {\n scoops,\n async attach(options, context) {\n const { harness, agents, groups, env, home } = options;\n const assets = options.assets ?? packageAssets();\n bind({\n harness,\n agents,\n licks,\n tools: fileTools,\n groups,\n reads: options.reads,\n files: env,\n ...(options.mcp ? { mcp: options.mcp } : {}),\n });\n const builtin = async () => {\n const out: { path: string; text: string }[] = [];\n for (const name of BUILTIN_ROLES) {\n const path = `packages/vfs-root/agents/${name}.md`;\n out.push({ path: `builtin:${name}`, text: await assets(path) });\n }\n return out;\n };\n const roles = async (using: Context) => {\n cached = await loadRoles(\n {\n files: env,\n builtin,\n dirs: [\n ...(await packageDirs(env, '/node_modules', using)).map((dir) => ({\n source: 'package' as const,\n dir,\n })),\n { source: 'user', dir: `${home}/.pi/agent/agents` },\n ],\n settingsPath: `${home}/.pi/agent/settings.json`,\n },\n using\n );\n return cached;\n };\n await roles(context).catch(() => cached);\n const script = await assets('bin/agent').catch(() => undefined);\n if (script) {\n const binDir = options.binDir ?? BIN_DIR;\n await installCommands(env, script, binDir, context);\n }\n const cli = createCli({\n env,\n harness,\n agents,\n scoops,\n roles: () => roles(context),\n ...(options.alive ? { alive: options.alive } : {}),\n ...(options.memory ? { memory: options.memory } : {}),\n ...(options.gelatiere ? { gelatiere: options.gelatiere } : {}),\n ...(options.freezer ? { freezer: options.freezer } : {}),\n ...(options.models ? { models: options.models } : {}),\n ...(options.panel ? { panel: options.panel } : {}),\n });\n const plane = controlPlane(env, cli, {\n ...(options.controlDir ? { dir: options.controlDir } : {}),\n ...(options.sweepEvery ? { sweepEvery: options.sweepEvery } : {}),\n mirror: mirror(agents, env),\n });\n await plane.start(context);\n return { roles, plane, close: (using) => plane.close(using) };\n },\n };\n}\n\nexport type { Rewound, Scoops } from './service.ts';\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { Harness, Registry, ToolRegistration } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport { type Agents, live } from '../agents.ts';\nimport type { ProcessGroups } from '../kernel/groups.ts';\nimport { type Layout, LEGACY } from '../layout.ts';\nimport type { Licks } from '../licks/licks.ts';\nimport { DEFAULT_LIMITS, loadRoles, packageDirs, type Roles } from '../roles/roles.ts';\nimport { type CliOptions, createCli, RUN_DIR } from './cli.ts';\nimport { guardExtension, scoopsExtension, scoopTasks } from './extension.ts';\nimport {\n CONTROL_DIR,\n type ControlPlane,\n controlPlane,\n type PlaneOptions,\n PROTOCOL,\n} from './requests.ts';\nimport { createScoops, type Scoops, type ScoopsHost } from './service.ts';\n\nexport const BUILTIN_ROLES = ['scout', 'worker', 'reviewer', 'oracle', 'delegate', 'gelatiere'];\nexport const PNPM_HOME = '/home/.local/share/pnpm';\nexport const BIN_DIR = '/usr/local/bin';\n\nexport type Assets = (path: string) => Promise<string>;\n\nexport function packageAssets(base: URL = new URL('../../', import.meta.url)): Assets {\n return async (path) => {\n const response = await fetch(new URL(path, base));\n if (!response.ok) throw new Error(`${path}: ${response.status}`);\n return response.text();\n };\n}\n\nexport interface AttachOptions {\n harness: Harness;\n agents: Agents;\n groups: ProcessGroups;\n env: ExecutionEnv;\n home: string;\n assets?: Assets;\n binDir?: string;\n controlDir?: string;\n sweepEvery?: number;\n alive?: (pid: number) => Promise<boolean>;\n reads: () => readonly string[];\n memory?: CliOptions['memory'];\n gelatiere?: CliOptions['gelatiere'];\n freezer?: CliOptions['freezer'];\n models?: CliOptions['models'];\n panel?: CliOptions['panel'];\n mcp?: (names: readonly string[]) => ToolRegistration[];\n layout?: Layout;\n enforced?: boolean;\n spools?: Spools;\n agentDir?: string;\n}\n\nexport interface Spools {\n users(): readonly string[];\n prepare(user: string, dir: string, context: Context): Promise<void>;\n}\n\nexport interface ScoopsRuntime {\n roles(context: Context): Promise<Roles>;\n plane: ControlPlane;\n close(context: Context): Promise<void>;\n}\n\nexport interface ScoopsSetup {\n scoops: Scoops;\n attach(options: AttachOptions, context: Context): Promise<ScoopsRuntime>;\n}\n\nexport const COMMANDS = ['agent', 'subagent', 'memory', 'gelatiere', 'freezer', 'models', 'panel'];\n\nexport const RETIRED_COMMANDS = ['sprinkle'];\n\nexport async function pnpmHomeOf(env: ExecutionEnv, context: Context): Promise<string> {\n let out = '';\n const ran = await env\n .exec(\n 'printf %s \"$PNPM_HOME\"',\n {\n onOutput: (text) => {\n out += text;\n },\n },\n context\n )\n .catch(() => undefined);\n return ran?.ok && ran.value.exitCode === 0 && /^\\/\\S*$/.test(out) ? out : PNPM_HOME;\n}\n\nexport interface Installed {\n done: boolean;\n skipped: string[];\n}\n\nasync function theirs(env: ExecutionEnv, path: string, context: Context): Promise<boolean> {\n const found = await env.fileInfo(path, context);\n if (!found.ok) return found.error.code !== 'not_found';\n if (found.value.kind !== 'file') return true;\n const read = await env.readTextFile(path, context);\n return !read.ok || !read.value.includes(PROTOCOL);\n}\n\nasync function install(\n env: ExecutionEnv,\n script: string,\n binDir: string,\n context: Context\n): Promise<Installed> {\n const skipped: string[] = [];\n if (!(await env.createDir(binDir, { recursive: true }, context)).ok)\n return { done: false, skipped };\n for (const name of COMMANDS) {\n const path = `${binDir}/${name}`;\n if (await theirs(env, path, context)) {\n skipped.push(name);\n continue;\n }\n if (!(await env.writeFile(path, script, context)).ok) return { done: false, skipped };\n const mode = await env.exec(['chmod', '755', path], undefined, context);\n if (!mode.ok || mode.value.exitCode !== 0) return { done: false, skipped };\n }\n for (const name of RETIRED_COMMANDS) {\n const text = await env.readTextFile(`${binDir}/${name}`, context);\n if (text.ok && text.value.includes(PROTOCOL))\n await env.remove(`${binDir}/${name}`, { force: true }, context);\n }\n return { done: true, skipped };\n}\n\nexport async function installCommands(\n env: ExecutionEnv,\n script: string,\n binDir: string,\n context: Context,\n warn: (message: string) => void = (message) => console.warn(message)\n): Promise<Installed> {\n const installed = await install(env, script, binDir, context).catch(\n (): Installed => ({ done: false, skipped: [] })\n );\n if (installed.skipped.length) {\n const paths = installed.skipped.map((name) => `${binDir}/${name}`).join(', ');\n warn(`slicc-agent: left ${paths} alone, because it isn't slicc-agent's command`);\n }\n if (installed.done) await retire(env, binDir, context).catch(() => undefined);\n return installed;\n}\n\nexport async function retire(env: ExecutionEnv, binDir: string, context: Context) {\n const old = `${await pnpmHomeOf(env, context)}/bin`;\n if (old === binDir) return;\n for (const name of [...COMMANDS, ...RETIRED_COMMANDS]) {\n const path = `${old}/${name}`;\n const text = await env.readTextFile(path, context);\n if (text.ok && text.value.includes(PROTOCOL)) await env.remove(path, { force: true }, context);\n }\n}\n\nfunction mirror(agents: Agents, env: ExecutionEnv) {\n return async (context: Context) => {\n for (const [id, record] of Object.entries(agents.state().scoops)) {\n const state = record.gone ? 'gone' : live(record) ? 'live' : 'stopped';\n const metadata = {\n handle: record.folder,\n id,\n name: record.name,\n role: record.role,\n cone: record.cone,\n state,\n };\n await env.writeFile(\n `${RUN_DIR}/${record.folder}/metadata.json`,\n `${JSON.stringify(metadata, null, 2)}\\n`,\n context\n );\n }\n };\n}\n\nfunction planeOf(\n env: ExecutionEnv,\n cli: ReturnType<typeof createCli>,\n options: AttachOptions,\n context: Context\n): ControlPlane {\n const common: PlaneOptions & { dir: string } = {\n ...(options.sweepEvery ? { sweepEvery: options.sweepEvery } : {}),\n mirror: mirror(options.agents, env),\n dir: options.controlDir ?? CONTROL_DIR,\n };\n if (!options.spools) return controlPlane(env, cli, common);\n return spooled(env, cli, options.spools, options.agents, common, context);\n}\n\nfunction spooled(\n env: ExecutionEnv,\n cli: ReturnType<typeof createCli>,\n spools: Spools,\n agents: Agents,\n options: PlaneOptions & { dir: string },\n context: Context\n): ControlPlane {\n const planes = new Map<string, Promise<ControlPlane | undefined>>();\n let off: () => void = () => undefined;\n let chain: Promise<unknown> = Promise.resolve();\n const open = async (user: string): Promise<ControlPlane | undefined> => {\n const dir = `${options.dir}/${user}`;\n try {\n await spools.prepare(user, dir, context);\n const plane = controlPlane(env, cli, { ...options, dir, user });\n await plane.start(context);\n return plane;\n } catch {\n planes.delete(user);\n return undefined;\n }\n };\n const sync = () => {\n const step = async () => {\n const wanted = new Set(spools.users());\n for (const user of wanted)\n if (!planes.has(user)) {\n const opened = open(user);\n planes.set(user, opened);\n await opened;\n }\n for (const [user, plane] of [...planes])\n if (!wanted.has(user)) {\n planes.delete(user);\n await (await plane)?.close(context);\n }\n };\n chain = chain.then(step, step);\n return chain;\n };\n const all = async () =>\n (await Promise.all(planes.values())).filter((plane): plane is ControlPlane => !!plane);\n return {\n async start() {\n off = agents.onChange(() => void sync());\n await sync();\n },\n async sweep(using) {\n for (const plane of await all()) await plane.sweep(using);\n },\n async close(using) {\n off();\n await chain;\n for (const plane of await all()) await plane.close(using);\n planes.clear();\n },\n };\n}\n\nexport function setupScoops(\n registry: Registry,\n licks: Licks,\n fileTools: readonly ToolRegistration[] = []\n): ScoopsSetup {\n let bind: (host: ScoopsHost) => void = () => undefined;\n const host = new Promise<ScoopsHost>((resolve) => {\n bind = resolve;\n });\n const lookup = () => host;\n const tasks = scoopTasks(lookup);\n const scoops = createScoops(host, () => tasks);\n let cached: Roles = { roles: [], warnings: [], limits: { ...DEFAULT_LIMITS } };\n let layout = LEGACY;\n registry.install(scoopsExtension(tasks, () => layout));\n registry.install(guardExtension(lookup, fileTools));\n return {\n scoops,\n async attach(options, context) {\n const { harness, agents, groups, env, home } = options;\n const assets = options.assets ?? packageAssets();\n layout = options.layout ?? LEGACY;\n bind({\n harness,\n agents,\n licks,\n tools: fileTools,\n groups,\n reads: options.reads,\n files: env,\n layout,\n ...(options.enforced ? { enforced: true } : {}),\n ...(options.mcp ? { mcp: options.mcp } : {}),\n });\n const builtin = async () => {\n const out: { path: string; text: string }[] = [];\n for (const name of BUILTIN_ROLES) {\n const path = `packages/vfs-root/agents/${name}.md`;\n out.push({ path: `builtin:${name}`, text: await assets(path) });\n }\n return out;\n };\n const roles = async (using: Context) => {\n cached = await loadRoles(\n {\n files: env,\n builtin,\n dirs: [\n ...(await packageDirs(env, '/node_modules', using)).map((dir) => ({\n source: 'package' as const,\n dir,\n })),\n { source: 'user', dir: `${options.agentDir ?? `${home}/.pi/agent`}/agents` },\n ],\n settingsPath: `${options.agentDir ?? `${home}/.pi/agent`}/settings.json`,\n },\n using\n );\n return cached;\n };\n await roles(context).catch(() => cached);\n const script = await assets('bin/agent').catch(() => undefined);\n if (script) {\n const binDir = options.binDir ?? BIN_DIR;\n await installCommands(env, script, binDir, context);\n }\n const cli = createCli({\n env,\n harness,\n agents,\n scoops,\n roles: () => roles(context),\n ...(options.alive ? { alive: options.alive } : {}),\n ...(options.memory ? { memory: options.memory } : {}),\n ...(options.gelatiere ? { gelatiere: options.gelatiere } : {}),\n ...(options.freezer ? { freezer: options.freezer } : {}),\n ...(options.models ? { models: options.models } : {}),\n layout,\n ...(options.panel ? { panel: options.panel } : {}),\n });\n const plane = planeOf(env, cli, options, context);\n await plane.start(context);\n return { roles, plane, close: (using) => plane.close(using) };\n },\n };\n}\n\nexport type { Rewound, Scoops } from './service.ts';\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -66,7 +66,8 @@ function controlPlane(env, runner, options = {}) {
66
66
  const bytes = await env.readBinaryFile(`${requests}/${id}`, context);
67
67
  if (!bytes.ok) return;
68
68
  await env.writeFile(`${responses}/${id}.ack`, "", context);
69
- const request = parseRequest(id, bytes.value);
69
+ const parsed = parseRequest(id, bytes.value);
70
+ const request = parsed && options.user ? { ...parsed, user: options.user } : parsed;
70
71
  if (!request) {
71
72
  await answer(
72
73
  id,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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;\nconst COMMAND_NAMES = new Set(['subagent', 'memory', 'gelatiere', 'freezer', 'models', 'panel']);\n\nexport interface Request {\n id: string;\n as: 'agent' | 'subagent' | 'memory' | 'gelatiere' | 'freezer' | 'models' | 'panel';\n caller: string;\n tab?: string | null;\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 const tab = (caller as string).startsWith('tab:') ? (caller as string).slice(4) : null;\n return {\n id,\n as: COMMAND_NAMES.has(as as string) ? (as as Request['as']) : 'agent',\n caller: tab === null ? (caller as string) : '',\n ...(tab ? { tab } : {}),\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 if (result.err !== undefined)\n await env.writeFile(`${responses}/${id}.err`, result.err, 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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",
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;\nconst COMMAND_NAMES = new Set(['subagent', 'memory', 'gelatiere', 'freezer', 'models', 'panel']);\n\nexport interface Request {\n id: string;\n as: 'agent' | 'subagent' | 'memory' | 'gelatiere' | 'freezer' | 'models' | 'panel';\n caller: string;\n tab?: string | null;\n cwd: string;\n pid: number | null;\n argv: string[];\n stdin: string;\n user?: 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 const tab = (caller as string).startsWith('tab:') ? (caller as string).slice(4) : null;\n return {\n id,\n as: COMMAND_NAMES.has(as as string) ? (as as Request['as']) : 'agent',\n caller: tab === null ? (caller as string) : '',\n ...(tab ? { tab } : {}),\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 user?: string;\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 if (result.err !== undefined)\n await env.writeFile(`${responses}/${id}.err`, result.err, 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 parsed = parseRequest(id, bytes.value);\n const request = parsed && options.user ? { ...parsed, user: options.user } : parsed;\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
6
  "names": []
7
7
  }
@@ -8,9 +8,10 @@ import {
8
8
  scoopId
9
9
  } from "../agents.js";
10
10
  import { CODEMODE } from "../codemode/index.js";
11
+ import { LEGACY } from "../layout.js";
11
12
  import { McpAllowDoc, mcpEntries } from "../mcp/index.js";
12
13
  import { visible } from "./visible.js";
13
- const SCOOPS_ROOT = "/scoops";
14
+ import { SCOOPS_ROOT } from "../layout.js";
14
15
  const LEDGER_SIZE = 512;
15
16
  const ScoopWorkDoc = defineDoc({
16
17
  kind: "slicc.scoops.work",
@@ -18,8 +19,8 @@ const ScoopWorkDoc = defineDoc({
18
19
  scope: "session",
19
20
  initial: () => ({ feeds: {}, waits: {}, ledger: {}, turns: {} })
20
21
  });
21
- function workspace(folder) {
22
- return `${SCOOPS_ROOT}/${folder}/workspace`;
22
+ function workspace(folder, layout = LEGACY) {
23
+ return layout.workspace(folder);
23
24
  }
24
25
  async function tail(conversation, context) {
25
26
  const { entries } = await conversation.context(context);
@@ -117,13 +118,14 @@ function claimTurn(work, request, turn) {
117
118
  work.turns[request.cone] = { at: turn, count: count + 1 };
118
119
  return void 0;
119
120
  }
120
- function agentChange(request, folder, tools, mcp) {
121
+ function agentChange(request, folder, host) {
122
+ const { tools, mcp } = host;
121
123
  const model = request.model ?? modelOf(request.role?.model);
122
124
  const thinking = request.thinking ?? request.role?.thinking;
123
125
  const chosen = selection(tools, request.tools ?? request.role?.tools, mcp);
124
126
  const instructions = [request.role?.prompt, request.instructions].filter(Boolean).join("\n\n");
125
127
  return {
126
- cwd: request.cwd ?? workspace(folder),
128
+ cwd: request.cwd ?? workspace(folder, host.layout),
127
129
  ...instructions ? { instructions } : {},
128
130
  ...model ? { model } : {},
129
131
  ...thinking ? { thinkingLevel: thinking } : {},
@@ -137,10 +139,31 @@ async function narrowMcp(tx, state, cone, child, request) {
137
139
  if (listed !== null || inherited !== null)
138
140
  (await tx.doc(McpAllowDoc, child)).allow = listed ?? inherited;
139
141
  }
142
+ function scoopRecord(request, folder, conversation, anchor, created) {
143
+ return {
144
+ name: request.name.trim() || folder,
145
+ folder,
146
+ cone: request.cone,
147
+ conversation,
148
+ anchor,
149
+ role: request.role?.name ?? null,
150
+ ...request.role?.memory ? { memory: request.role.memory } : {},
151
+ ...request.role?.memory && request.origin ? { origin: request.origin } : {},
152
+ ...request.role?.context ? { context: request.role.context } : {},
153
+ kind: request.kind ?? "async",
154
+ parent: request.parent ?? request.cone,
155
+ depth: request.depth ?? 1,
156
+ roots: request.roots ?? null,
157
+ created,
158
+ dropped: null,
159
+ gone: false
160
+ };
161
+ }
140
162
  async function spawn(core, request, context) {
141
163
  const done = await answered(core, request.request, context);
142
164
  if (done) return done;
143
- const { agents, tools, mcp } = await core.host;
165
+ const host = await core.host;
166
+ const { agents } = host;
144
167
  const parent = await coneOfId(core, request.cone, context);
145
168
  const count = liveCount(agents.state(), request.cone);
146
169
  if (count >= request.limits.maxLiveScoops)
@@ -163,26 +186,9 @@ async function spawn(core, request, context) {
163
186
  background: true
164
187
  });
165
188
  const child = await tx.createConversation({ ownership: { kind: "task", taskId: anchor } });
166
- await configure(tx, child.id, agentChange(request, folder, tools, mcp));
189
+ await configure(tx, child.id, agentChange(request, folder, host));
167
190
  await narrowMcp(tx, doc, parent.id, child.id, request);
168
- doc.scoops[id] = {
169
- name: request.name.trim() || folder,
170
- folder,
171
- cone: request.cone,
172
- conversation: child.id,
173
- anchor,
174
- role: request.role?.name ?? null,
175
- ...request.role?.memory ? { memory: request.role.memory } : {},
176
- ...request.role?.memory && request.origin ? { origin: request.origin } : {},
177
- ...request.role?.context ? { context: request.role.context } : {},
178
- kind: request.kind ?? "async",
179
- parent: request.parent ?? request.cone,
180
- depth: request.depth ?? 1,
181
- roots: request.roots ?? null,
182
- created,
183
- dropped: null,
184
- gone: false
185
- };
191
+ doc.scoops[id] = scoopRecord(request, folder, child.id, Number(anchor), created);
186
192
  const target = request.target === void 0 ? request.fromAgent ? `cone:${request.cone}` : null : request.target;
187
193
  const made = [];
188
194
  const asker = request.asker === void 0 ? target : request.asker;
@@ -267,7 +273,7 @@ async function stopFeeds(core, scoop, keep, context) {
267
273
  return dropped.length;
268
274
  }
269
275
  async function stop(core, id, fromAgent, context) {
270
- const { agents, files } = await core.host;
276
+ const { agents, files, layout = LEGACY } = await core.host;
271
277
  const record = agents.state().scoops[id];
272
278
  if (!record || !live(record)) return missing(id);
273
279
  await stopFeeds(core, id, async () => false, context);
@@ -276,15 +282,12 @@ async function stop(core, id, fromAgent, context) {
276
282
  await agents.update((_tx, state) => {
277
283
  state.scoops[id].dropped = mark;
278
284
  }, context);
279
- const removed = await files?.remove(
280
- workspace(record.folder),
281
- { recursive: true, force: true },
282
- context
283
- );
284
- const folder = removed?.ok ? `removed ${workspace(record.folder)}` : `kept ${workspace(record.folder)}`;
285
+ const place = layout.workspace(record.folder);
286
+ const removed = await files?.remove(place, { recursive: true, force: true }, context);
287
+ const folder = removed?.ok ? `removed ${place}` : `kept ${place}`;
285
288
  return {
286
289
  code: 0,
287
- out: `stopped ${record.folder}; its transcript is kept, ${folder}, and its reports stay in ${SCOOPS_ROOT}/${record.folder}/reports
290
+ out: `stopped ${record.folder}; its transcript is kept, ${folder}, and its reports stay in ${layout.reports(record.folder, record.cone)}
288
291
  `
289
292
  };
290
293
  }
@@ -300,14 +303,18 @@ async function rename(core, id, name, context) {
300
303
  return { code: 0, out: `${record.folder} is now called ${wanted}
301
304
  ` };
302
305
  }
303
- async function resetWorkspace(files, folder, context) {
306
+ async function resetWorkspace(host, folder, context) {
307
+ const { files } = host;
304
308
  if (!files) return true;
305
- const cleared = await files.remove(workspace(folder), { recursive: true, force: true }, context);
309
+ const place = workspace(folder, host.layout);
310
+ const cleared = await files.remove(place, { recursive: true, force: true }, context);
306
311
  if (!cleared.ok) return false;
307
- return (await files.createDir(workspace(folder), { recursive: true }, context)).ok;
312
+ if (host.layout?.users) return true;
313
+ return (await files.createDir(place, { recursive: true }, context)).ok;
308
314
  }
309
315
  async function rewindOne(core, coneId, id, record, seen, result, context) {
310
- const { harness, licks, files } = await core.host;
316
+ const host = await core.host;
317
+ const { harness, licks } = host;
311
318
  if (!await seen(record.created)) {
312
319
  if (live(record)) {
313
320
  await stopFeeds(core, id, async () => false, context);
@@ -318,7 +325,7 @@ async function rewindOne(core, coneId, id, record, seen, result, context) {
318
325
  }
319
326
  if (record.dropped !== null) {
320
327
  if (await seen(record.dropped)) return void 0;
321
- const reset = await resetWorkspace(files, record.folder, context);
328
+ const reset = await resetWorkspace(host, record.folder, context);
322
329
  result.restored.push(
323
330
  reset ? record.name : `${record.name} (its working folder could not be reset)`
324
331
  );