@ai-ecoverse/slicc-agent 6.0.10 → 6.0.12

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 CHANGED
@@ -751,8 +751,8 @@ Every same-repo pull request gets one `cert-review` status from `.github/workflo
751
751
 
752
752
  - **What is behaviour, by output:**
753
753
  - **Plan:** packs the base and the pull request merged onto it with `npm pack --dry-run --ignore-scripts`. A change touches behaviour when it changes a file either side ships (`dist`, `bin`, `packages/vfs-root` and whatever `files` lists, so a shipped prompt or agent definition counts), a build input (`src/`, `bin/`, `build.mjs`, `tsconfig*.json`, `patches/`, `package.json` and its lock, `.releaserc*`, `scripts/publish.mjs`), `release.yml` or `ci.yml`, or the reviewer itself.
754
- - **Not behaviour:** anything else, such as `evals/`, `test/`, `README.md`, `AGENTS.md`, `renovate.json`, `lefthook.yml`, `scripts/check-*.mjs` and `review.yml`. It gets a green `not behaviour: <files>`.
755
- - **Evidence comes by id from the job that made it:** the jobs that run the pull request's code (`pack candidate` builds it; both `suite` legs run its tests) share the run's artifact store with the jobs that don't, and code in them can reach the runner's artifact token. So each producer job hands on its artifact's `artifact-id` and `artifact-digest`, consumers download by `artifact-ids` with `digest-mismatch: error`, no upload uses `overwrite`, and the base build has its own job, `pack base`, which runs only main's code. `tools/cert-review/artifacts.mjs` lists the run's artifacts and fails on any in `agent-tgz-` or `cert-review-` that its producer didn't upload with that id, name and digest. `same` runs it before it compares the tarballs, and it uploads its evidence before it posts, so "not behaviour (identical dist)" can't be bought with a planted artifact. `record` runs it on everything, and a failure makes the record INCOMPLETE. **Protected:** the base build, the delta, the same-dist evidence and the review's verdict and evidence. **Not protected, by design:** the candidate tarball and both suites' results (the base leg runs the pull request's tests on main's tree), because code on a runner can always lie about its own results. They're taken by exact name, at most one each.
754
+ - **Not behaviour:** anything else, such as `evals/`, `test/`, `README.md`, `AGENTS.md`, `renovate.json`, `lefthook.yml` and `scripts/check-*.mjs`. It gets a green `not behaviour: <files>`.
755
+ - **Evidence comes by id from the job that made it:** the jobs that run the pull request's code (`pack candidate` builds it; both `suite` legs run its tests) share the run's artifact store with the jobs that don't, and code in them can reach the runner's artifact token. So each producer job hands on its artifact's `artifact-id` and `artifact-digest`, consumers download by `artifact-ids` with `digest-mismatch: error`, no upload uses `overwrite`, and the base build has its own job, `pack base`, which runs only main's code and restores no Actions cache, because the candidate job shares the run's cache scope. `tools/cert-review/artifacts.mjs` lists the run's artifacts and fails on any in `agent-tgz-` or `cert-review-` that its producer didn't upload with that id, name and digest. `same` runs it before it compares the tarballs, and it uploads its evidence before it posts, so "not behaviour (identical dist)" can't be bought with a planted artifact. `record` runs it on everything, and a failure makes the record INCOMPLETE. **Protected:** the base build, the delta, the same-dist evidence and the review's verdict and evidence. **Not protected, by design:** the candidate tarball and both suites' results (the base leg runs the pull request's tests on main's tree), because code on a runner can always lie about its own results. They're taken by exact name, at most one each.
756
756
  - **Same dist:** a behaviour change is packed for real on both sides. If the tarballs carry the same files, entry types, modes and bytes once each side's exact version and `gitHead` are normalised (source maps without their `mappings`), and `package.json` differs only in `devDependencies`, `version` and `gitHead`, the status is `not behaviour (identical dist)` with both sha256. A change to the reviewer, `release.yml`, `ci.yml`, `.releaserc*` or `scripts/publish.mjs` always gets the full review.
757
757
  - **When it runs:** on `merge-ok`, and again on every push while the label is on. Before that a behaviour change is red with `waits for merge-ok`. A same-repo pull request by `renovate[bot]` is reviewed without the label, on every push, so a devDependency bump settles as `not behaviour (identical dist)` without anyone labelling it, and a bump that changes what ships gets the full review.
758
758
  - **The full review:**
@@ -130,7 +130,7 @@ async function attachMemory(options, connect, context) {
130
130
  const files = new MemoryFiles(env);
131
131
  const memories = replicatedState([]);
132
132
  const scopes = replicatedState([]);
133
- const reload = async (using) => {
133
+ const load = async (using) => {
134
134
  const found = await listPlaces(options, using);
135
135
  const entries = [];
136
136
  for (const place of found) entries.push(...await files.entries(place, using));
@@ -144,6 +144,12 @@ async function attachMemory(options, connect, context) {
144
144
  );
145
145
  memories.replace(using, entries);
146
146
  };
147
+ let loading = Promise.resolve();
148
+ const reload = (using) => {
149
+ const next = loading.then(() => load(using));
150
+ loading = next.catch(() => void 0);
151
+ return next;
152
+ };
147
153
  const parts = { options, files, memories, scopes, reload };
148
154
  connect({
149
155
  harness: options.harness,
@@ -160,7 +166,14 @@ async function attachMemory(options, connect, context) {
160
166
  clearTimeout(timer);
161
167
  timer = setTimeout(() => void reload(context).catch(() => void 0), options.reloadMs);
162
168
  };
163
- const offCone = agents.onCone(schedule);
169
+ const named = () => JSON.stringify(Object.entries(agents.state().cones).map(([id, cone]) => [id, cone.name]));
170
+ let cones = named();
171
+ const offCones = agents.onChange(() => {
172
+ const next = named();
173
+ if (next === cones) return;
174
+ cones = next;
175
+ schedule();
176
+ });
164
177
  const watched = await env.watch(
165
178
  [
166
179
  { path: memoryRoot(home), recursive: true },
@@ -188,7 +201,7 @@ async function attachMemory(options, connect, context) {
188
201
  command: (argv, caller, using) => command(parts, argv, caller, using),
189
202
  reload,
190
203
  async close(using) {
191
- offCone();
204
+ offCones();
192
205
  clearTimeout(timer);
193
206
  await watcher?.close(using);
194
207
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/memory/runtime.ts"],
4
- "sourcesContent": ["import {\n type Context,\n type MutableReplicatedState,\n type ReplicatedState,\n replicatedState,\n} from '@earendil-works/chord';\nimport type { Harness } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport type { Agents } from '../agents.ts';\nimport type { Role } from '../roles/roles.ts';\nimport { workspace } from '../scoops/service.ts';\nimport { MEMORY_FILE, type MemoryEntry, type MemoryScope, type MemoryTag } from './format.ts';\nimport type { Host } from './prompt.ts';\nimport { MEMORY_WRITE } from './prompt.ts';\nimport {\n GLOBAL,\n MemoryFiles,\n memoryRoot,\n type Place,\n placeOf,\n ROLE_PREFIX,\n rolePlace,\n roleRoot,\n} from './store.ts';\n\nexport type Answer = { code: number; out: string };\n\nexport interface MemoryDraft {\n id?: string;\n scope: string;\n section: string;\n title: string;\n body: string;\n tag: MemoryTag | null;\n}\n\nexport interface MemoryRuntime {\n readonly memories: ReplicatedState<MemoryEntry[]>;\n readonly scopes: ReplicatedState<MemoryScope[]>;\n save(draft: MemoryDraft, context: Context): Promise<MemoryEntry>;\n remove(id: string, context: Context): Promise<boolean>;\n command(argv: readonly string[], caller: string | null, context: Context): Promise<Answer>;\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport interface MemoryAttach {\n harness: Harness;\n agents: Agents;\n env: ExecutionEnv;\n home: string;\n agentDir?: string;\n reloadMs: number;\n idleCheckMs?: number;\n roles?: (context: Context) => Promise<{ roles: readonly Role[] } | undefined>;\n}\n\nexport const USAGE = `usage:\n memory show [<scope>] print a memory file (yours by default)\n memory scopes every memory scope, its entries and its file\n\nScopes: global (everyone), a cone's id, or role:<path> for a role's memory.\nAgents write memory with the ${MEMORY_WRITE} tool; the files are plain markdown you can edit.\n`;\n\ntype Listed = Place & { group?: 'cones' | 'roles'; name: string };\n\nasync function roleDirs(\n env: ExecutionEnv,\n dir: string,\n prefix: string,\n context: Context,\n depth = 0\n): Promise<string[]> {\n const listed = await env.listDir(dir, context);\n if (!listed.ok || depth > 4) return [];\n const out: string[] = [];\n for (const info of listed.value) {\n if (info.kind !== 'directory') continue;\n const path = prefix ? `${prefix}/${info.name}` : info.name;\n const found = await env.exists(`${dir}/${info.name}/${MEMORY_FILE}`, context);\n if (found.ok && found.value) out.push(path);\n out.push(...(await roleDirs(env, `${dir}/${info.name}`, path, context, depth + 1)));\n }\n return out;\n}\n\nexport async function listPlaces(options: MemoryAttach, context: Context): Promise<Listed[]> {\n const { agents, env, home } = options;\n const state = agents.state();\n const out: Listed[] = [{ ...(placeOf(home, GLOBAL) as Place), name: 'Shared' }];\n const cones = new Set(Object.keys(state.cones));\n const stored = await env.listDir(memoryRoot(home), context);\n if (stored.ok)\n for (const info of stored.value) if (info.kind === 'directory') cones.add(info.name);\n for (const id of cones) {\n const place = placeOf(home, id);\n if (place) out.push({ ...place, group: 'cones', name: state.cones[id]?.name ?? id });\n }\n const roles = new Set(await roleDirs(env, roleRoot(home), '', context));\n const known = await options.roles?.(context).catch(() => undefined);\n for (const role of known?.roles ?? [])\n if (role.memory?.scope === 'user') roles.add(role.memory.path);\n for (const path of [...roles].sort()) {\n const place = placeOf(home, `${ROLE_PREFIX}${path}`);\n if (place) out.push({ ...place, group: 'roles', name: path.split('/').at(-1) ?? path });\n }\n return out;\n}\n\nasync function callerPlace(\n options: MemoryAttach,\n caller: string | null,\n context: Context\n): Promise<Place | { problem: string }> {\n const { agents, env, home } = options;\n const record = caller ? agents.state().scoops[caller] : undefined;\n if (record?.memory)\n return rolePlace(env, home, record.memory, record.origin ?? workspace(record.folder), context);\n const cone = caller?.startsWith('cone:') ? caller.slice('cone:'.length) : agents.activeCone();\n return placeOf(home, cone) ?? { problem: `there is no memory scope ${cone}` };\n}\n\ntype Parts = {\n options: MemoryAttach;\n files: MemoryFiles;\n memories: MutableReplicatedState<MemoryEntry[]>;\n scopes: MutableReplicatedState<MemoryScope[]>;\n reload: (context: Context) => Promise<void>;\n};\n\nfunction placeFor(home: string, scope: string): Place {\n const place = placeOf(home, scope);\n if (!place) throw new Error(`there is no memory scope ${scope}`);\n return place;\n}\n\nasync function save(parts: Parts, draft: MemoryDraft, context: Context): Promise<MemoryEntry> {\n const { files, memories, options } = parts;\n const place = placeFor(options.home, draft.scope);\n const previous = draft.id ? memories.value.find((item) => item.id === draft.id) : undefined;\n const moved = previous && previous.scope !== draft.scope;\n try {\n const written = await files.change(\n place,\n {\n kind: 'save',\n ...(previous && !moved ? { id: previous.id } : {}),\n section: draft.section,\n title: draft.title,\n body: draft.body,\n tag: draft.tag,\n },\n context\n );\n if (previous && moved)\n await files.change(\n placeFor(options.home, previous.scope),\n { kind: 'remove', id: previous.id },\n context\n );\n return written.entry as MemoryEntry;\n } finally {\n await parts.reload(context).catch(() => undefined);\n }\n}\n\nasync function command(\n parts: Parts,\n argv: readonly string[],\n caller: string | null,\n context: Context\n): Promise<Answer> {\n const { files, memories, scopes, options } = parts;\n const verb = argv[0] ?? 'help';\n if (verb === 'scopes') {\n await parts.reload(context);\n const rows = scopes.value.map((scope) => {\n const count = memories.value.filter((item) => item.scope === scope.id).length;\n const file = placeFor(options.home, scope.id).file;\n return `${scope.id}\\t${scope.label}\\t${count} entr${count === 1 ? 'y' : 'ies'}\\t${file}`;\n });\n return { code: 0, out: `${rows.join('\\n')}\\n` };\n }\n if (verb !== 'show') return { code: verb === 'help' || verb === '--help' ? 0 : 2, out: USAGE };\n const place = argv[1]\n ? (placeOf(options.home, argv[1]) ?? { problem: `there is no memory scope ${argv[1]}` })\n : await callerPlace(options, caller, context);\n if ('problem' in place) return { code: 1, out: `memory: ${place.problem}\\n` };\n const read = await files.read(place.file, context);\n return {\n code: 0,\n out: read?.text.trim()\n ? `# ${place.file}\\n\\n${read.text.trimEnd()}\\n`\n : `${place.file} is empty\\n`,\n };\n}\n\nexport async function attachMemory(\n options: MemoryAttach,\n connect: (host: Host) => void,\n context: Context\n): Promise<MemoryRuntime> {\n const { agents, env, home } = options;\n const files = new MemoryFiles(env);\n const memories = replicatedState<MemoryEntry[]>([]);\n const scopes = replicatedState<MemoryScope[]>([]);\n const reload = async (using: Context) => {\n const found = await listPlaces(options, using);\n const entries: MemoryEntry[] = [];\n for (const place of found) entries.push(...(await files.entries(place, using)));\n scopes.replace(\n using,\n found.map((place) => ({\n id: place.scope,\n label: place.name,\n ...(place.group ? { group: place.group } : {}),\n }))\n );\n memories.replace(using, entries);\n };\n const parts: Parts = { options, files, memories, scopes, reload };\n connect({\n harness: options.harness,\n agents,\n env,\n home,\n ...(options.agentDir ? { agentDir: options.agentDir } : {}),\n files,\n changed: reload,\n });\n await reload(context).catch(() => undefined);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n };\n const offCone = agents.onCone(schedule);\n const watched = await env.watch(\n [\n { path: memoryRoot(home), recursive: true },\n { path: roleRoot(home), recursive: true },\n ],\n schedule,\n context\n );\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n return {\n memories,\n scopes,\n save: (draft, using) => save(parts, draft, using),\n async remove(id, using) {\n const entry = memories.value.find((item) => item.id === id);\n if (!entry) return false;\n const written = await files.change(\n placeFor(home, entry.scope),\n { kind: 'remove', id },\n using\n );\n await reload(using);\n return written.found;\n },\n command: (argv, caller, using) => command(parts, argv, caller, using),\n reload,\n async close(using) {\n offCone();\n clearTimeout(timer);\n await watcher?.close(using);\n },\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n type Context,\n type MutableReplicatedState,\n type ReplicatedState,\n replicatedState,\n} from '@earendil-works/chord';\nimport type { Harness } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport type { Agents } from '../agents.ts';\nimport type { Role } from '../roles/roles.ts';\nimport { workspace } from '../scoops/service.ts';\nimport { MEMORY_FILE, type MemoryEntry, type MemoryScope, type MemoryTag } from './format.ts';\nimport type { Host } from './prompt.ts';\nimport { MEMORY_WRITE } from './prompt.ts';\nimport {\n GLOBAL,\n MemoryFiles,\n memoryRoot,\n type Place,\n placeOf,\n ROLE_PREFIX,\n rolePlace,\n roleRoot,\n} from './store.ts';\n\nexport type Answer = { code: number; out: string };\n\nexport interface MemoryDraft {\n id?: string;\n scope: string;\n section: string;\n title: string;\n body: string;\n tag: MemoryTag | null;\n}\n\nexport interface MemoryRuntime {\n readonly memories: ReplicatedState<MemoryEntry[]>;\n readonly scopes: ReplicatedState<MemoryScope[]>;\n save(draft: MemoryDraft, context: Context): Promise<MemoryEntry>;\n remove(id: string, context: Context): Promise<boolean>;\n command(argv: readonly string[], caller: string | null, context: Context): Promise<Answer>;\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport interface MemoryAttach {\n harness: Harness;\n agents: Agents;\n env: ExecutionEnv;\n home: string;\n agentDir?: string;\n reloadMs: number;\n idleCheckMs?: number;\n roles?: (context: Context) => Promise<{ roles: readonly Role[] } | undefined>;\n}\n\nexport const USAGE = `usage:\n memory show [<scope>] print a memory file (yours by default)\n memory scopes every memory scope, its entries and its file\n\nScopes: global (everyone), a cone's id, or role:<path> for a role's memory.\nAgents write memory with the ${MEMORY_WRITE} tool; the files are plain markdown you can edit.\n`;\n\ntype Listed = Place & { group?: 'cones' | 'roles'; name: string };\n\nasync function roleDirs(\n env: ExecutionEnv,\n dir: string,\n prefix: string,\n context: Context,\n depth = 0\n): Promise<string[]> {\n const listed = await env.listDir(dir, context);\n if (!listed.ok || depth > 4) return [];\n const out: string[] = [];\n for (const info of listed.value) {\n if (info.kind !== 'directory') continue;\n const path = prefix ? `${prefix}/${info.name}` : info.name;\n const found = await env.exists(`${dir}/${info.name}/${MEMORY_FILE}`, context);\n if (found.ok && found.value) out.push(path);\n out.push(...(await roleDirs(env, `${dir}/${info.name}`, path, context, depth + 1)));\n }\n return out;\n}\n\nexport async function listPlaces(options: MemoryAttach, context: Context): Promise<Listed[]> {\n const { agents, env, home } = options;\n const state = agents.state();\n const out: Listed[] = [{ ...(placeOf(home, GLOBAL) as Place), name: 'Shared' }];\n const cones = new Set(Object.keys(state.cones));\n const stored = await env.listDir(memoryRoot(home), context);\n if (stored.ok)\n for (const info of stored.value) if (info.kind === 'directory') cones.add(info.name);\n for (const id of cones) {\n const place = placeOf(home, id);\n if (place) out.push({ ...place, group: 'cones', name: state.cones[id]?.name ?? id });\n }\n const roles = new Set(await roleDirs(env, roleRoot(home), '', context));\n const known = await options.roles?.(context).catch(() => undefined);\n for (const role of known?.roles ?? [])\n if (role.memory?.scope === 'user') roles.add(role.memory.path);\n for (const path of [...roles].sort()) {\n const place = placeOf(home, `${ROLE_PREFIX}${path}`);\n if (place) out.push({ ...place, group: 'roles', name: path.split('/').at(-1) ?? path });\n }\n return out;\n}\n\nasync function callerPlace(\n options: MemoryAttach,\n caller: string | null,\n context: Context\n): Promise<Place | { problem: string }> {\n const { agents, env, home } = options;\n const record = caller ? agents.state().scoops[caller] : undefined;\n if (record?.memory)\n return rolePlace(env, home, record.memory, record.origin ?? workspace(record.folder), context);\n const cone = caller?.startsWith('cone:') ? caller.slice('cone:'.length) : agents.activeCone();\n return placeOf(home, cone) ?? { problem: `there is no memory scope ${cone}` };\n}\n\ntype Parts = {\n options: MemoryAttach;\n files: MemoryFiles;\n memories: MutableReplicatedState<MemoryEntry[]>;\n scopes: MutableReplicatedState<MemoryScope[]>;\n reload: (context: Context) => Promise<void>;\n};\n\nfunction placeFor(home: string, scope: string): Place {\n const place = placeOf(home, scope);\n if (!place) throw new Error(`there is no memory scope ${scope}`);\n return place;\n}\n\nasync function save(parts: Parts, draft: MemoryDraft, context: Context): Promise<MemoryEntry> {\n const { files, memories, options } = parts;\n const place = placeFor(options.home, draft.scope);\n const previous = draft.id ? memories.value.find((item) => item.id === draft.id) : undefined;\n const moved = previous && previous.scope !== draft.scope;\n try {\n const written = await files.change(\n place,\n {\n kind: 'save',\n ...(previous && !moved ? { id: previous.id } : {}),\n section: draft.section,\n title: draft.title,\n body: draft.body,\n tag: draft.tag,\n },\n context\n );\n if (previous && moved)\n await files.change(\n placeFor(options.home, previous.scope),\n { kind: 'remove', id: previous.id },\n context\n );\n return written.entry as MemoryEntry;\n } finally {\n await parts.reload(context).catch(() => undefined);\n }\n}\n\nasync function command(\n parts: Parts,\n argv: readonly string[],\n caller: string | null,\n context: Context\n): Promise<Answer> {\n const { files, memories, scopes, options } = parts;\n const verb = argv[0] ?? 'help';\n if (verb === 'scopes') {\n await parts.reload(context);\n const rows = scopes.value.map((scope) => {\n const count = memories.value.filter((item) => item.scope === scope.id).length;\n const file = placeFor(options.home, scope.id).file;\n return `${scope.id}\\t${scope.label}\\t${count} entr${count === 1 ? 'y' : 'ies'}\\t${file}`;\n });\n return { code: 0, out: `${rows.join('\\n')}\\n` };\n }\n if (verb !== 'show') return { code: verb === 'help' || verb === '--help' ? 0 : 2, out: USAGE };\n const place = argv[1]\n ? (placeOf(options.home, argv[1]) ?? { problem: `there is no memory scope ${argv[1]}` })\n : await callerPlace(options, caller, context);\n if ('problem' in place) return { code: 1, out: `memory: ${place.problem}\\n` };\n const read = await files.read(place.file, context);\n return {\n code: 0,\n out: read?.text.trim()\n ? `# ${place.file}\\n\\n${read.text.trimEnd()}\\n`\n : `${place.file} is empty\\n`,\n };\n}\n\nexport async function attachMemory(\n options: MemoryAttach,\n connect: (host: Host) => void,\n context: Context\n): Promise<MemoryRuntime> {\n const { agents, env, home } = options;\n const files = new MemoryFiles(env);\n const memories = replicatedState<MemoryEntry[]>([]);\n const scopes = replicatedState<MemoryScope[]>([]);\n const load = async (using: Context) => {\n const found = await listPlaces(options, using);\n const entries: MemoryEntry[] = [];\n for (const place of found) entries.push(...(await files.entries(place, using)));\n scopes.replace(\n using,\n found.map((place) => ({\n id: place.scope,\n label: place.name,\n ...(place.group ? { group: place.group } : {}),\n }))\n );\n memories.replace(using, entries);\n };\n let loading: Promise<unknown> = Promise.resolve();\n const reload = (using: Context) => {\n const next = loading.then(() => load(using));\n loading = next.catch(() => undefined);\n return next;\n };\n const parts: Parts = { options, files, memories, scopes, reload };\n connect({\n harness: options.harness,\n agents,\n env,\n home,\n ...(options.agentDir ? { agentDir: options.agentDir } : {}),\n files,\n changed: reload,\n });\n await reload(context).catch(() => undefined);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n };\n const named = () =>\n JSON.stringify(Object.entries(agents.state().cones).map(([id, cone]) => [id, cone.name]));\n let cones = named();\n const offCones = agents.onChange(() => {\n const next = named();\n if (next === cones) return;\n cones = next;\n schedule();\n });\n const watched = await env.watch(\n [\n { path: memoryRoot(home), recursive: true },\n { path: roleRoot(home), recursive: true },\n ],\n schedule,\n context\n );\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n return {\n memories,\n scopes,\n save: (draft, using) => save(parts, draft, using),\n async remove(id, using) {\n const entry = memories.value.find((item) => item.id === id);\n if (!entry) return false;\n const written = await files.change(\n placeFor(home, entry.scope),\n { kind: 'remove', id },\n using\n );\n await reload(using);\n return written.found;\n },\n command: (argv, caller, using) => command(parts, argv, caller, using),\n reload,\n async close(using) {\n offCones();\n clearTimeout(timer);\n await watcher?.close(using);\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -67,7 +67,9 @@ function controlPlane(env, runner, options = {}) {
67
67
  await env.writeFile(`${responses}/${id}.out`, result.out, context);
68
68
  if (result.err !== void 0)
69
69
  await env.writeFile(`${responses}/${id}.err`, result.err, context);
70
- await env.writeFile(`${responses}/${id}.code`, String(result.code), context);
70
+ const staged = `${responses}/.tmp-${id}.code`;
71
+ await env.writeFile(staged, String(result.code), context);
72
+ await env.renameFile(staged, `${responses}/${id}.code`, context);
71
73
  await env.remove(`${requests}/${id}`, { force: true }, context);
72
74
  await env.remove(`${responses}/${id}.ack`, { force: true }, context);
73
75
  await options.mirror?.(context).catch(() => void 0);
@@ -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 { CronRun } from '../licks/crontab.ts';\nimport type { Answer } from './service.ts';\n\nexport const CONTROL_DIR = '/var/lib/slicc/agent/requests';\nexport const PROTOCOL = 'slicc-agent/2';\nexport const PROTOCOL_MARK = 'slicc-agent/';\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 cron?: CronRun;\n}\n\nconst HEADERS: Record<string, number> = { 'slicc-agent/1': 6, [PROTOCOL]: 7 };\n\nexport function cronOf(field: string): CronRun | undefined {\n const [job, due, missed, extra] = field.split(' ');\n const count = Number(missed);\n if (!job || !due || extra !== undefined || !Number.isInteger(count) || count < 0)\n return undefined;\n return { job, due, missed: count };\n}\n\nexport function parseRequest(id: string, bytes: Uint8Array): Request | undefined {\n const decoder = new TextDecoder();\n const fields: string[] = [];\n let at = 0;\n let header = 1;\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 header = HEADERS[fields[0] as string] ?? 0;\n }\n if (!header) return undefined;\n const [, as, caller, cwd, pid] = fields;\n const cron = header === 7 ? cronOf(fields[5] as string) : undefined;\n const argc = Number(fields.at(-1));\n if (!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 ...(cron ? { cron } : {}),\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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",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport type { CronRun } from '../licks/crontab.ts';\nimport type { Answer } from './service.ts';\n\nexport const CONTROL_DIR = '/var/lib/slicc/agent/requests';\nexport const PROTOCOL = 'slicc-agent/2';\nexport const PROTOCOL_MARK = 'slicc-agent/';\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 cron?: CronRun;\n}\n\nconst HEADERS: Record<string, number> = { 'slicc-agent/1': 6, [PROTOCOL]: 7 };\n\nexport function cronOf(field: string): CronRun | undefined {\n const [job, due, missed, extra] = field.split(' ');\n const count = Number(missed);\n if (!job || !due || extra !== undefined || !Number.isInteger(count) || count < 0)\n return undefined;\n return { job, due, missed: count };\n}\n\nexport function parseRequest(id: string, bytes: Uint8Array): Request | undefined {\n const decoder = new TextDecoder();\n const fields: string[] = [];\n let at = 0;\n let header = 1;\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 header = HEADERS[fields[0] as string] ?? 0;\n }\n if (!header) return undefined;\n const [, as, caller, cwd, pid] = fields;\n const cron = header === 7 ? cronOf(fields[5] as string) : undefined;\n const argc = Number(fields.at(-1));\n if (!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 ...(cron ? { cron } : {}),\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 const staged = `${responses}/.tmp-${id}.code`;\n await env.writeFile(staged, String(result.code), context);\n await env.renameFile(staged, `${responses}/${id}.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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-agent",
3
- "version": "6.0.10",
3
+ "version": "6.0.12",
4
4
  "description": "SLICC's agent core on pi durable: an agent worker, session storage, remoting and pi extensions",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -63,9 +63,9 @@
63
63
  "release": "semantic-release"
64
64
  },
65
65
  "devDependencies": {
66
- "@ai-ecoverse/slicc-kernel": "1.124.3",
66
+ "@ai-ecoverse/slicc-kernel": "1.124.8",
67
67
  "@ai-ecoverse/slicc-scrub": "1.0.0",
68
- "@ai-ecoverse/slicc-shared-web": "1.23.2",
68
+ "@ai-ecoverse/slicc-shared-web": "1.23.3",
69
69
  "@ai-ecoverse/slicc-spectrum": "1.86.4",
70
70
  "@ai-ecoverse/wasm-bash": "5.3.0-19",
71
71
  "@ai-ecoverse/wasm-coreutils": "9.12.0-9",