@ai-ecoverse/slicc-agent 3.48.1 → 3.50.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 (89) hide show
  1. package/README.md +38 -6
  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/roles/roles.js +54 -15
  44. package/dist/roles/roles.js.map +2 -2
  45. package/dist/scoops/cli.js +40 -19
  46. package/dist/scoops/cli.js.map +2 -2
  47. package/dist/scoops/extension.js +19 -15
  48. package/dist/scoops/extension.js.map +2 -2
  49. package/dist/scoops/index.js +82 -11
  50. package/dist/scoops/index.js.map +2 -2
  51. package/dist/scoops/requests.js +2 -1
  52. package/dist/scoops/requests.js.map +2 -2
  53. package/dist/scoops/service.js +45 -38
  54. package/dist/scoops/service.js.map +2 -2
  55. package/dist/scoops/sync.js +63 -13
  56. package/dist/scoops/sync.js.map +2 -2
  57. package/dist/skills/index.js +76 -34
  58. package/dist/skills/index.js.map +3 -3
  59. package/dist/sprinkles/index.js +114 -25
  60. package/dist/sprinkles/index.js.map +2 -2
  61. package/dist/sprinkles/sprinkles.js +13 -4
  62. package/dist/sprinkles/sprinkles.js.map +2 -2
  63. package/dist/types/freezer/index.d.ts +3 -1
  64. package/dist/types/freezer/title.d.ts +1 -1
  65. package/dist/types/gelatiere/index.d.ts +8 -0
  66. package/dist/types/gelatiere/store.d.ts +3 -2
  67. package/dist/types/index.d.ts +1 -1
  68. package/dist/types/kernel/accounts.d.ts +29 -0
  69. package/dist/types/kernel/agent-users.d.ts +37 -0
  70. package/dist/types/kernel/env.d.ts +6 -0
  71. package/dist/types/kernel/users.d.ts +15 -0
  72. package/dist/types/layout.d.ts +26 -0
  73. package/dist/types/licks/index.d.ts +2 -1
  74. package/dist/types/licks/sources.d.ts +11 -1
  75. package/dist/types/mcp/paths.d.ts +3 -0
  76. package/dist/types/mcp/runtime.d.ts +3 -3
  77. package/dist/types/memory/prompt.d.ts +1 -0
  78. package/dist/types/memory/runtime.d.ts +1 -0
  79. package/dist/types/roles/roles.d.ts +14 -1
  80. package/dist/types/scoops/cli.d.ts +5 -0
  81. package/dist/types/scoops/extension.d.ts +6 -3
  82. package/dist/types/scoops/index.d.ts +9 -0
  83. package/dist/types/scoops/requests.d.ts +2 -0
  84. package/dist/types/scoops/service.d.ts +5 -3
  85. package/dist/types/scoops/sync.d.ts +9 -6
  86. package/dist/types/skills/index.d.ts +10 -2
  87. package/dist/types/sprinkles/index.d.ts +15 -0
  88. package/dist/types/sprinkles/sprinkles.d.ts +7 -1
  89. package/package.json +4 -4
@@ -0,0 +1,141 @@
1
+ import { callerOf } from "../scoops/identity.js";
2
+ const SCOOP_UMASK = 23;
3
+ const CONE_UMASK = 63;
4
+ const CONE_HOME_MODE = 456;
5
+ const SCOOP_HOME_MODE = 1512;
6
+ const SUDO_GROUP = "sudo";
7
+ const CONE_PREFIX = "cone:";
8
+ const SCOOP_USER = "scoop-";
9
+ function scoopUser(folder) {
10
+ return `${SCOOP_USER}${folder}`;
11
+ }
12
+ function homeOf(user) {
13
+ return `/home/${user}`;
14
+ }
15
+ function userOf(state, agentId) {
16
+ if (agentId.startsWith(CONE_PREFIX)) return agentId.slice(CONE_PREFIX.length);
17
+ const record = state.scoops[agentId];
18
+ return record ? scoopUser(record.folder) : void 0;
19
+ }
20
+ function wanted(state) {
21
+ const cones = /* @__PURE__ */ new Set([
22
+ ...Object.keys(state.cones),
23
+ ...Object.values(state.frozen ?? {}).map((frozen) => frozen.cone)
24
+ ]);
25
+ const out = [...cones].map((cone) => ({
26
+ agentId: `${CONE_PREFIX}${cone}`,
27
+ cone: null,
28
+ spec: { name: cone, groups: [SUDO_GROUP], homeMode: CONE_HOME_MODE, umask: CONE_UMASK }
29
+ }));
30
+ for (const [id, record] of Object.entries(state.scoops)) {
31
+ if (record.gone || record.removed || record.dropped !== null) continue;
32
+ out.push({
33
+ agentId: id,
34
+ cone: `${CONE_PREFIX}${record.cone}`,
35
+ spec: {
36
+ name: scoopUser(record.folder),
37
+ groups: [record.cone],
38
+ homeGroup: record.cone,
39
+ homeMode: SCOOP_HOME_MODE,
40
+ umask: SCOOP_UMASK
41
+ }
42
+ });
43
+ }
44
+ return out;
45
+ }
46
+ function createAccounts(pool, agents, mode = "off", groups, warn = (message) => console.warn(message)) {
47
+ const added = /* @__PURE__ */ new Map();
48
+ let chain = Promise.resolve();
49
+ const state = () => agents()?.state() ?? { cones: {}, active: "cone", next: 2, scoops: {} };
50
+ const groupOf = (want) => want.cone ? want.cone.slice(CONE_PREFIX.length) : null;
51
+ const moved = async (want, from) => {
52
+ await groups?.removeMember(from, want.spec.name).catch(() => void 0);
53
+ await pool.drop(want.agentId);
54
+ };
55
+ const ensure = (want) => {
56
+ const known = added.get(want.agentId);
57
+ const group = groupOf(want);
58
+ if (known?.name === want.spec.name && known.group === group) return known.ready;
59
+ const parent = want.cone ? wanted(state()).find((cone) => cone.agentId === want.cone) : void 0;
60
+ if (want.cone && !parent)
61
+ return Promise.reject(
62
+ Object.assign(new Error(`${want.spec.name} has no cone user: there is no cone ${group}`), {
63
+ code: "ENOENT"
64
+ })
65
+ );
66
+ const before = known?.group && known.group !== group ? moved(want, known.group) : void 0;
67
+ const ready = Promise.all([parent ? ensure(parent) : void 0, before]).then(() => pool.users.add(want.spec)).then(() => void 0);
68
+ added.set(want.agentId, { name: want.spec.name, group, ready });
69
+ ready.catch(() => {
70
+ if (added.get(want.agentId)?.ready === ready) added.delete(want.agentId);
71
+ });
72
+ return ready;
73
+ };
74
+ const retire = async (agentId, name) => {
75
+ added.delete(agentId);
76
+ await pool.drop(agentId);
77
+ await pool.users.remove(name).catch(() => false);
78
+ };
79
+ const reconcile = async (stale = []) => {
80
+ const want = wanted(state());
81
+ const names = new Set(want.map((entry) => entry.agentId));
82
+ for (const entry of want)
83
+ await ensure(entry).catch(
84
+ (error) => warn(`slicc-agent: couldn't add the kernel user ${entry.spec.name}: ${error.message}`)
85
+ );
86
+ const gone = [...added].filter(([agentId]) => !names.has(agentId));
87
+ const scoopsFirst = gone.sort(
88
+ ([a], [b]) => Number(a.startsWith(CONE_PREFIX)) - Number(b.startsWith(CONE_PREFIX))
89
+ );
90
+ for (const [agentId, entry] of scoopsFirst) await retire(agentId, entry.name);
91
+ for (const name of stale) await pool.users.remove(name).catch(() => false);
92
+ };
93
+ const queue = (run) => {
94
+ chain = chain.then(run, run);
95
+ return chain;
96
+ };
97
+ const home = (agentId) => {
98
+ const user = userOf(state(), agentId);
99
+ return user === void 0 ? void 0 : homeOf(user);
100
+ };
101
+ return {
102
+ mode,
103
+ agentOf(conversation) {
104
+ const current = agents();
105
+ return callerOf(current, conversation) ?? (current ? `${CONE_PREFIX}${current.activeCone()}` : void 0);
106
+ },
107
+ home,
108
+ async client(agentId) {
109
+ const want = wanted(state()).find((entry) => entry.agentId === agentId);
110
+ if (!want)
111
+ throw Object.assign(new Error(`${agentId} has no kernel user`), { code: "ENOENT" });
112
+ await ensure(want);
113
+ return pool.client(agentId, want.spec.name);
114
+ },
115
+ reconcile: () => queue(() => reconcile()),
116
+ boot: () => queue(async () => {
117
+ const known = new Set(
118
+ Object.values(state().scoops).map((record) => scoopUser(record.folder))
119
+ );
120
+ const keep = new Set(wanted(state()).map((entry) => entry.spec.name));
121
+ const listed = await pool.users.list().catch(() => []);
122
+ const stale = listed.map((user) => user.name).filter((name) => known.has(name) && !keep.has(name));
123
+ await reconcile(stale);
124
+ })
125
+ };
126
+ }
127
+ export {
128
+ CONE_HOME_MODE,
129
+ CONE_PREFIX,
130
+ CONE_UMASK,
131
+ SCOOP_HOME_MODE,
132
+ SCOOP_UMASK,
133
+ SCOOP_USER,
134
+ SUDO_GROUP,
135
+ createAccounts,
136
+ homeOf,
137
+ scoopUser,
138
+ userOf,
139
+ wanted
140
+ };
141
+ //# sourceMappingURL=accounts.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/kernel/accounts.ts"],
4
+ "sourcesContent": ["import type { Agents, AgentsState } from '../agents.ts';\nimport { callerOf } from '../scoops/identity.ts';\nimport type { KernelClient } from './client.ts';\nimport type { KernelGroups, UserClients, UserSpec } from './users.ts';\n\nexport type UsersMode = 'off' | 'report' | 'enforce';\n\nexport const SCOOP_UMASK = 0o027;\nexport const CONE_UMASK = 0o077;\nexport const CONE_HOME_MODE = 0o710;\nexport const SCOOP_HOME_MODE = 0o2750;\nexport const SUDO_GROUP = 'sudo';\nexport const CONE_PREFIX = 'cone:';\nexport const SCOOP_USER = 'scoop-';\n\nexport function scoopUser(folder: string): string {\n return `${SCOOP_USER}${folder}`;\n}\n\nexport function homeOf(user: string): string {\n return `/home/${user}`;\n}\n\nexport function userOf(state: Readonly<AgentsState>, agentId: string): string | undefined {\n if (agentId.startsWith(CONE_PREFIX)) return agentId.slice(CONE_PREFIX.length);\n const record = state.scoops[agentId];\n return record ? scoopUser(record.folder) : undefined;\n}\n\nexport type Wanted = { agentId: string; cone: string | null; spec: UserSpec };\n\nexport function wanted(state: Readonly<AgentsState>): Wanted[] {\n const cones = new Set([\n ...Object.keys(state.cones),\n ...Object.values(state.frozen ?? {}).map((frozen) => frozen.cone),\n ]);\n const out: Wanted[] = [...cones].map((cone) => ({\n agentId: `${CONE_PREFIX}${cone}`,\n cone: null,\n spec: { name: cone, groups: [SUDO_GROUP], homeMode: CONE_HOME_MODE, umask: CONE_UMASK },\n }));\n for (const [id, record] of Object.entries(state.scoops)) {\n if (record.gone || record.removed || record.dropped !== null) continue;\n out.push({\n agentId: id,\n cone: `${CONE_PREFIX}${record.cone}`,\n spec: {\n name: scoopUser(record.folder),\n groups: [record.cone],\n homeGroup: record.cone,\n homeMode: SCOOP_HOME_MODE,\n umask: SCOOP_UMASK,\n },\n });\n }\n return out;\n}\n\nexport interface Accounts {\n readonly mode: UsersMode;\n agentOf(conversation: number | undefined): string | undefined;\n home(agentId: string): string | undefined;\n client(agentId: string): Promise<KernelClient>;\n reconcile(): Promise<void>;\n boot(): Promise<void>;\n}\n\nexport function createAccounts(\n pool: UserClients,\n agents: () => Agents | undefined,\n mode: UsersMode = 'off',\n groups?: KernelGroups,\n warn: (message: string) => void = (message) => console.warn(message)\n): Accounts {\n const added = new Map<string, { name: string; group: string | null; ready: Promise<void> }>();\n let chain: Promise<void> = Promise.resolve();\n const state = (): Readonly<AgentsState> =>\n agents()?.state() ?? { cones: {}, active: 'cone', next: 2, scoops: {} };\n const groupOf = (want: Wanted) => (want.cone ? want.cone.slice(CONE_PREFIX.length) : null);\n const moved = async (want: Wanted, from: string) => {\n await groups?.removeMember(from, want.spec.name).catch(() => undefined);\n await pool.drop(want.agentId);\n };\n const ensure = (want: Wanted): Promise<void> => {\n const known = added.get(want.agentId);\n const group = groupOf(want);\n if (known?.name === want.spec.name && known.group === group) return known.ready;\n const parent = want.cone\n ? wanted(state()).find((cone) => cone.agentId === want.cone)\n : undefined;\n if (want.cone && !parent)\n return Promise.reject(\n Object.assign(new Error(`${want.spec.name} has no cone user: there is no cone ${group}`), {\n code: 'ENOENT',\n })\n );\n const before = known?.group && known.group !== group ? moved(want, known.group) : undefined;\n const ready = Promise.all([parent ? ensure(parent) : undefined, before])\n .then(() => pool.users.add(want.spec))\n .then(() => undefined);\n added.set(want.agentId, { name: want.spec.name, group, ready });\n ready.catch(() => {\n if (added.get(want.agentId)?.ready === ready) added.delete(want.agentId);\n });\n return ready;\n };\n const retire = async (agentId: string, name: string) => {\n added.delete(agentId);\n await pool.drop(agentId);\n await pool.users.remove(name).catch(() => false);\n };\n const reconcile = async (stale: readonly string[] = []) => {\n const want = wanted(state());\n const names = new Set(want.map((entry) => entry.agentId));\n for (const entry of want)\n await ensure(entry).catch((error: Error) =>\n warn(`slicc-agent: couldn't add the kernel user ${entry.spec.name}: ${error.message}`)\n );\n const gone = [...added].filter(([agentId]) => !names.has(agentId));\n const scoopsFirst = gone.sort(\n ([a], [b]) => Number(a.startsWith(CONE_PREFIX)) - Number(b.startsWith(CONE_PREFIX))\n );\n for (const [agentId, entry] of scoopsFirst) await retire(agentId, entry.name);\n for (const name of stale) await pool.users.remove(name).catch(() => false);\n };\n const queue = (run: () => Promise<void>) => {\n chain = chain.then(run, run);\n return chain;\n };\n const home = (agentId: string) => {\n const user = userOf(state(), agentId);\n return user === undefined ? undefined : homeOf(user);\n };\n return {\n mode,\n agentOf(conversation) {\n const current = agents();\n return (\n callerOf(current, conversation) ??\n (current ? `${CONE_PREFIX}${current.activeCone()}` : undefined)\n );\n },\n home,\n async client(agentId) {\n const want = wanted(state()).find((entry) => entry.agentId === agentId);\n if (!want)\n throw Object.assign(new Error(`${agentId} has no kernel user`), { code: 'ENOENT' });\n await ensure(want);\n return pool.client(agentId, want.spec.name);\n },\n reconcile: () => queue(() => reconcile()),\n boot: () =>\n queue(async () => {\n const known = new Set(\n Object.values(state().scoops).map((record) => scoopUser(record.folder))\n );\n const keep = new Set(wanted(state()).map((entry) => entry.spec.name));\n const listed = await pool.users.list().catch(() => []);\n const stale = listed\n .map((user) => user.name)\n .filter((name) => known.has(name) && !keep.has(name));\n await reconcile(stale);\n }),\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,180 @@
1
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
2
+ import { FIRST_CONE } from "../agents.js";
3
+ import {
4
+ AGENT_DIR,
5
+ LEGACY,
6
+ ROOT_HOME,
7
+ ROOT_USER,
8
+ SESSIONS,
9
+ SHARED_DIR,
10
+ usersLayout
11
+ } from "../layout.js";
12
+ import {
13
+ CONE_PREFIX,
14
+ createAccounts,
15
+ homeOf,
16
+ wanted
17
+ } from "./accounts.js";
18
+ import { HOME, quote, SliccKernelEnv } from "./env.js";
19
+ function modeOf(client) {
20
+ const mode = client.users;
21
+ return mode === "report" || mode === "enforce" ? mode : "off";
22
+ }
23
+ async function missingSupport(pool, client, requested) {
24
+ if (!pool) return requested === true ? ["admin (the page passed no KernelSource.users)"] : [];
25
+ const missing = [];
26
+ if (!await pool.users.list().then(
27
+ () => true,
28
+ () => false
29
+ ))
30
+ missing.push("users");
31
+ if (client.users === void 0)
32
+ missing.push("the users mode (kernel 1.57)");
33
+ if (!await pool.groups.list().then(
34
+ () => true,
35
+ () => false
36
+ ))
37
+ missing.push("groups and homeMode (kernel 1.60 and KernelSource.groups)");
38
+ return missing;
39
+ }
40
+ async function warnSupport(pool, client, requested, report = (message) => console.error(message)) {
41
+ const missing = await missingSupport(pool, client, requested);
42
+ if (missing.length)
43
+ report(
44
+ `slicc-agent: kernel users were requested, but this kernel or page lacks ${missing.join(", ")}`
45
+ );
46
+ }
47
+ function legacyCwd(state, agentId, cwd) {
48
+ if (cwd === HOME) return true;
49
+ const record = state.scoops[agentId];
50
+ return record !== void 0 && cwd === LEGACY.workspace(record.folder);
51
+ }
52
+ function spoolScript(user, dir) {
53
+ const at = quote(dir);
54
+ const made = `mkdir -p ${at}/in ${at}/out`;
55
+ if (user === ROOT_USER) return made;
56
+ return `${made} && chown -R ${quote(`${user}:${user}`)} ${at} && chmod 700 ${at} ${at}/in ${at}/out`;
57
+ }
58
+ function agentUsers(pool, client, root, agents) {
59
+ const mode = modeOf(client);
60
+ const accounts = createAccounts(pool, agents, mode, pool.groups);
61
+ const state = () => agents()?.state() ?? { cones: {}, active: "cone", next: 2, scoops: {} };
62
+ const envOf = async (agentId, home) => new SliccKernelEnv(await accounts.client(agentId), { cwd: home });
63
+ const layout = usersLayout(state, envOf, root);
64
+ let ready = () => void 0;
65
+ const started = new Promise((resolve) => {
66
+ ready = resolve;
67
+ });
68
+ let off = () => void 0;
69
+ const homes = () => wanted(state()).map((entry) => homeOf(entry.spec.name));
70
+ const listeners = /* @__PURE__ */ new Set();
71
+ let known = "";
72
+ const changed = () => {
73
+ const key = homes().join("\n");
74
+ if (key === known) return;
75
+ known = key;
76
+ for (const listener of listeners) listener();
77
+ };
78
+ return {
79
+ accounts,
80
+ layout,
81
+ enforced: mode === "enforce",
82
+ people: {
83
+ homes,
84
+ homeOf(conversationId) {
85
+ const agentId = accounts.agentOf(conversationId);
86
+ return agentId ? accounts.home(agentId) : void 0;
87
+ },
88
+ async envOf(at) {
89
+ const user = wanted(state()).find((entry) => homeOf(entry.spec.name) === at);
90
+ return user ? envOf(user.agentId, at).catch(() => void 0) : void 0;
91
+ }
92
+ },
93
+ sprinklePlaces() {
94
+ return wanted(state()).filter((entry) => !entry.cone).map((entry) => ({ dir: `${homeOf(entry.spec.name)}/sprinkles`, cone: entry.spec.name }));
95
+ },
96
+ sprinkles() {
97
+ return {
98
+ places: () => this.sprinklePlaces(),
99
+ envOf: (agentId) => this.envOfAgent(agentId),
100
+ homeOf: (agentId) => accounts.home(agentId),
101
+ shared: SHARED_DIR
102
+ };
103
+ },
104
+ async envOfAgent(agentId) {
105
+ const home = accounts.home(agentId);
106
+ return home ? envOf(agentId, home).catch(() => void 0) : void 0;
107
+ },
108
+ async gelatiere(root2) {
109
+ const first = `${CONE_PREFIX}${FIRST_CONE}`;
110
+ const files = await this.envOfAgent(first);
111
+ return {
112
+ home: homeOf(FIRST_CONE),
113
+ profile: SHARED_DIR,
114
+ shared: SHARED_DIR,
115
+ ...files ? { files } : {},
116
+ as: async (caller) => {
117
+ const env = caller ? await this.envOfAgent(caller) : void 0;
118
+ const home = caller ? accounts.home(caller) : void 0;
119
+ return env && home ? { env, home } : { env: root2, home: ROOT_HOME };
120
+ }
121
+ };
122
+ },
123
+ async lickDirs() {
124
+ const cones = wanted(state()).filter((entry) => !entry.cone);
125
+ const out = [{ dir: `${ROOT_HOME}/.slicc`, home: ROOT_HOME, cone: null }];
126
+ for (const entry of cones) {
127
+ const home = homeOf(entry.spec.name);
128
+ const env = await envOf(entry.agentId, home).catch(() => void 0);
129
+ out.push({ dir: `${home}/.slicc`, home, cone: entry.spec.name, ...env ? { env } : {} });
130
+ }
131
+ return out;
132
+ },
133
+ follow(runtimes) {
134
+ listeners.add(() => {
135
+ for (const runtime of runtimes)
136
+ void (runtime.reconcile ?? runtime.reload)?.call(runtime, BACKGROUND_CONTEXT).catch(() => void 0);
137
+ });
138
+ },
139
+ spools: {
140
+ users: () => [ROOT_USER, ...wanted(state()).map((entry) => entry.spec.name)],
141
+ async prepare(user, dir, context) {
142
+ await root.exec(spoolScript(user, dir), void 0, context);
143
+ }
144
+ },
145
+ async route(target) {
146
+ await started;
147
+ const agentId = accounts.agentOf(target.conversationId);
148
+ const home = agentId ? accounts.home(agentId) : void 0;
149
+ if (!agentId || !home) return void 0;
150
+ const legacy = target.cwd !== void 0 && legacyCwd(state(), agentId, target.cwd);
151
+ return { client: await accounts.client(agentId), home, ...legacy ? { cwd: home } : {} };
152
+ },
153
+ async start(opened) {
154
+ await root.exec(
155
+ `mkdir -p ${SESSIONS.tmp} ${AGENT_DIR} ${SHARED_DIR} && chmod 1777 ${SESSIONS.tmp} && chmod 755 ${AGENT_DIR} && chmod 700 ${SHARED_DIR}`,
156
+ void 0,
157
+ BACKGROUND_CONTEXT
158
+ );
159
+ await accounts.boot();
160
+ known = homes().join("\n");
161
+ off = opened.onChange(() => {
162
+ void accounts.reconcile();
163
+ changed();
164
+ });
165
+ ready();
166
+ },
167
+ close() {
168
+ off();
169
+ }
170
+ };
171
+ }
172
+ export {
173
+ agentUsers,
174
+ legacyCwd,
175
+ missingSupport,
176
+ modeOf,
177
+ spoolScript,
178
+ warnSupport
179
+ };
180
+ //# sourceMappingURL=agent-users.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/kernel/agent-users.ts"],
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Agents, AgentsState } from '../agents.ts';\nimport { FIRST_CONE } from '../agents.ts';\nimport type { GelatiereAttach } from '../gelatiere/index.ts';\nimport {\n AGENT_DIR,\n type Layout,\n LEGACY,\n ROOT_HOME,\n ROOT_USER,\n SESSIONS,\n SHARED_DIR,\n usersLayout,\n} from '../layout.ts';\nimport type { LickDir } from '../licks/sources.ts';\nimport type { Spools } from '../scoops/index.ts';\nimport type { SkillPeople } from '../skills/index.ts';\nimport type { SprinklePlace, SprinklesAttach } from '../sprinkles/index.ts';\nimport {\n type Accounts,\n CONE_PREFIX,\n createAccounts,\n homeOf,\n type UsersMode,\n wanted,\n} from './accounts.ts';\nimport type { KernelClient } from './client.ts';\nimport { type EnvRoute, type EnvTargetLike, HOME, quote, SliccKernelEnv } from './env.ts';\nimport type { UserClients } from './users.ts';\n\nexport interface AgentUsers {\n readonly accounts: Accounts;\n readonly layout: Layout;\n readonly spools: Spools;\n readonly enforced: boolean;\n readonly people: SkillPeople;\n follow(\n runtimes: readonly {\n reload?(context: Context): Promise<void>;\n reconcile?(context: Context): Promise<void>;\n }[]\n ): void;\n lickDirs(): Promise<LickDir[]>;\n sprinklePlaces(): SprinklePlace[];\n sprinkles(): Partial<SprinklesAttach>;\n envOfAgent(agentId: string): Promise<SliccKernelEnv | undefined>;\n gelatiere(root: SliccKernelEnv): Promise<Partial<GelatiereAttach>>;\n route(target: EnvTargetLike): Promise<EnvRoute | undefined>;\n start(agents: Agents): Promise<void>;\n close(): void;\n}\n\nexport function modeOf(client: KernelClient): UsersMode {\n const mode = (client as { users?: unknown }).users;\n return mode === 'report' || mode === 'enforce' ? mode : 'off';\n}\n\nexport async function missingSupport(\n pool: UserClients | undefined,\n client: KernelClient,\n requested: boolean | undefined\n): Promise<string[]> {\n if (!pool) return requested === true ? ['admin (the page passed no KernelSource.users)'] : [];\n const missing: string[] = [];\n if (\n !(await pool.users.list().then(\n () => true,\n () => false\n ))\n )\n missing.push('users');\n if ((client as { users?: unknown }).users === undefined)\n missing.push('the users mode (kernel 1.57)');\n if (\n !(await pool.groups.list().then(\n () => true,\n () => false\n ))\n )\n missing.push('groups and homeMode (kernel 1.60 and KernelSource.groups)');\n return missing;\n}\n\nexport async function warnSupport(\n pool: UserClients | undefined,\n client: KernelClient,\n requested: boolean | undefined,\n report: (message: string) => void = (message) => console.error(message)\n): Promise<void> {\n const missing = await missingSupport(pool, client, requested);\n if (missing.length)\n report(\n `slicc-agent: kernel users were requested, but this kernel or page lacks ${missing.join(', ')}`\n );\n}\n\nexport function legacyCwd(state: Readonly<AgentsState>, agentId: string, cwd: string): boolean {\n if (cwd === HOME) return true;\n const record = state.scoops[agentId];\n return record !== undefined && cwd === LEGACY.workspace(record.folder);\n}\n\nexport function spoolScript(user: string, dir: string): string {\n const at = quote(dir);\n const made = `mkdir -p ${at}/in ${at}/out`;\n if (user === ROOT_USER) return made;\n return `${made} && chown -R ${quote(`${user}:${user}`)} ${at} && chmod 700 ${at} ${at}/in ${at}/out`;\n}\n\nexport function agentUsers(\n pool: UserClients,\n client: KernelClient,\n root: SliccKernelEnv,\n agents: () => Agents | undefined\n): AgentUsers {\n const mode = modeOf(client);\n const accounts = createAccounts(pool, agents, mode, pool.groups);\n const state = () => agents()?.state() ?? { cones: {}, active: 'cone', next: 2, scoops: {} };\n const envOf = async (agentId: string, home: string) =>\n new SliccKernelEnv(await accounts.client(agentId), { cwd: home });\n const layout = usersLayout(state, envOf, root);\n let ready: () => void = () => undefined;\n const started = new Promise<void>((resolve) => {\n ready = resolve;\n });\n let off: () => void = () => undefined;\n const homes = () => wanted(state()).map((entry) => homeOf(entry.spec.name));\n const listeners = new Set<() => void>();\n let known = '';\n const changed = () => {\n const key = homes().join('\\n');\n if (key === known) return;\n known = key;\n for (const listener of listeners) listener();\n };\n return {\n accounts,\n layout,\n enforced: mode === 'enforce',\n people: {\n homes,\n homeOf(conversationId) {\n const agentId = accounts.agentOf(conversationId);\n return agentId ? accounts.home(agentId) : undefined;\n },\n async envOf(at) {\n const user = wanted(state()).find((entry) => homeOf(entry.spec.name) === at);\n return user ? envOf(user.agentId, at).catch(() => undefined) : undefined;\n },\n },\n sprinklePlaces() {\n return wanted(state())\n .filter((entry) => !entry.cone)\n .map((entry) => ({ dir: `${homeOf(entry.spec.name)}/sprinkles`, cone: entry.spec.name }));\n },\n sprinkles() {\n return {\n places: () => this.sprinklePlaces(),\n envOf: (agentId: string) => this.envOfAgent(agentId),\n homeOf: (agentId: string) => accounts.home(agentId),\n shared: SHARED_DIR,\n };\n },\n async envOfAgent(agentId) {\n const home = accounts.home(agentId);\n return home ? envOf(agentId, home).catch(() => undefined) : undefined;\n },\n async gelatiere(root) {\n const first = `${CONE_PREFIX}${FIRST_CONE}`;\n const files = await this.envOfAgent(first);\n return {\n home: homeOf(FIRST_CONE),\n profile: SHARED_DIR,\n shared: SHARED_DIR,\n ...(files ? { files } : {}),\n as: async (caller) => {\n const env = caller ? await this.envOfAgent(caller) : undefined;\n const home = caller ? accounts.home(caller) : undefined;\n return env && home ? { env, home } : { env: root, home: ROOT_HOME };\n },\n };\n },\n async lickDirs() {\n const cones = wanted(state()).filter((entry) => !entry.cone);\n const out: LickDir[] = [{ dir: `${ROOT_HOME}/.slicc`, home: ROOT_HOME, cone: null }];\n for (const entry of cones) {\n const home = homeOf(entry.spec.name);\n const env = await envOf(entry.agentId, home).catch(() => undefined);\n out.push({ dir: `${home}/.slicc`, home, cone: entry.spec.name, ...(env ? { env } : {}) });\n }\n return out;\n },\n follow(runtimes) {\n listeners.add(() => {\n for (const runtime of runtimes)\n void (runtime.reconcile ?? runtime.reload)\n ?.call(runtime, BACKGROUND_CONTEXT)\n .catch(() => undefined);\n });\n },\n spools: {\n users: () => [ROOT_USER, ...wanted(state()).map((entry) => entry.spec.name)],\n async prepare(user, dir, context) {\n await root.exec(spoolScript(user, dir), undefined, context);\n },\n },\n async route(target) {\n await started;\n const agentId = accounts.agentOf(target.conversationId);\n const home = agentId ? accounts.home(agentId) : undefined;\n if (!agentId || !home) return undefined;\n const legacy = target.cwd !== undefined && legacyCwd(state(), agentId, target.cwd);\n return { client: await accounts.client(agentId), home, ...(legacy ? { cwd: home } : {}) };\n },\n async start(opened) {\n await root.exec(\n `mkdir -p ${SESSIONS.tmp} ${AGENT_DIR} ${SHARED_DIR} && chmod 1777 ${SESSIONS.tmp} && chmod 755 ${AGENT_DIR} && chmod 700 ${SHARED_DIR}`,\n undefined,\n BACKGROUND_CONTEXT\n );\n await accounts.boot();\n known = homes().join('\\n');\n off = opened.onChange(() => {\n void accounts.reconcile();\n changed();\n });\n ready();\n },\n close() {\n off();\n },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["root"]
7
+ }
@@ -300,13 +300,19 @@ class SliccKernelEnv {
300
300
  }
301
301
  const HOME = "/home";
302
302
  function kernelEnvironment(client, options = {}) {
303
- const { identify, ...rest } = options;
304
- const make = (target, exports) => new SliccKernelEnv(client, {
303
+ const { identify, route, ...rest } = options;
304
+ const make = (target, exports, routed) => new SliccKernelEnv(routed?.client ?? client, {
305
305
  ...rest,
306
- cwd: target.cwd ?? HOME,
306
+ cwd: routed?.cwd ?? target.cwd ?? routed?.home ?? HOME,
307
307
  ...target.conversationId === void 0 ? {} : { owner: target.conversationId },
308
308
  ...exports ? { exports } : {}
309
309
  });
310
+ if (route)
311
+ return async (target, context) => {
312
+ const routed = await route(target);
313
+ const exports = identify && context ? await identify(target, context) : void 0;
314
+ return make(target, exports, routed);
315
+ };
310
316
  return (target, context) => identify && context ? identify(target, context).then((exports) => make(target, exports)) : make(target);
311
317
  }
312
318
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/kernel/env.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type BinaryReader,\n type DirReader,\n type ExecutionEnv,\n type ExecutionError,\n err,\n FileError,\n type FileInfo,\n type FileWatcher,\n ok,\n type Result,\n type ShellExecOptions,\n type ShellExecResult,\n type TextLineReader,\n type WatchChange,\n type WatchTarget,\n} from '@earendil-works/pi-durable/env';\nimport { mimeOf } from '../images/index.ts';\nimport type { Scrub } from '../secrets/scrub.ts';\nimport type { Activity } from './activity.ts';\nimport type { KernelClient, KernelProcess } from './client.ts';\nimport { type ExecHooks, execute } from './exec.ts';\nimport { attempt, info, KernelDirReader, SnapshotLines, SnapshotReader } from './files.ts';\nimport type { ProcessGroups } from './groups.ts';\nimport { dirname, normalize, resolve } from './paths.ts';\nimport { nativeWatch, type PollOptions, pollWatch, RESCAN_MS } from './watch.ts';\n\nexport interface KernelEnvOptions {\n cwd: string;\n id?: string;\n watch?: PollOptions;\n activity?: Activity;\n groups?: ProcessGroups;\n owner?: number;\n exports?: Readonly<Record<string, string>>;\n scrub?: Scrub;\n spawn?: (argv: readonly string[]) => readonly string[];\n}\n\nexport const STREAMED_READ = 8 * 1024 * 1024;\nexport const READ_CHUNK = 1024 * 1024;\n\nexport async function scrubbedFile(scrub: Scrub, bytes: Uint8Array): Promise<Uint8Array> {\n if (mimeOf(bytes)) return bytes;\n if (bytes.length <= STREAMED_READ) return (await scrub.texts([bytes]))[0] as Uint8Array;\n const stream = scrub.stream();\n const parts: Uint8Array[] = [];\n for (let at = 0; at < bytes.length; at += READ_CHUNK)\n parts.push(await stream.push(bytes.subarray(at, at + READ_CHUNK)));\n parts.push(await stream.end());\n const all = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0));\n let offset = 0;\n for (const part of parts) {\n all.set(part, offset);\n offset += part.length;\n }\n return all;\n}\n\nexport function quote(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nexport function prefixed(\n command: string,\n exports: Readonly<Record<string, string>> | undefined\n): string {\n const names = Object.keys(exports ?? {}).filter((name) => /^[A-Z_][A-Z0-9_]*$/.test(name));\n if (!names.length) return command;\n return `export ${names.map((name) => `${name}=${quote((exports as Record<string, string>)[name] as string)}`).join(' ')}; ${command}`;\n}\n\ntype FileResult<T> = Promise<Result<T, FileError>>;\n\nexport class SliccKernelEnv implements ExecutionEnv {\n readonly id: string;\n cwd: string;\n readonly #client: KernelClient;\n readonly #watchers = new Set<FileWatcher>();\n readonly #poll: PollOptions;\n readonly #activity: Activity | undefined;\n readonly #groups: ProcessGroups | undefined;\n readonly #owner: number | undefined;\n readonly #exports: Readonly<Record<string, string>> | undefined;\n readonly #hooks: ExecHooks;\n readonly #scrub: Scrub | undefined;\n\n constructor(client: KernelClient, options: KernelEnvOptions) {\n this.#client = client;\n this.cwd = options.cwd;\n this.id = options.id ?? 'slicc-kernel';\n this.#poll = options.watch ?? {};\n this.#activity = options.activity;\n this.#groups = options.groups;\n this.#owner = options.owner;\n this.#exports = options.exports;\n this.#scrub = options.scrub;\n const scrub = options.scrub;\n this.#hooks = {\n ...(scrub ? { scrub: () => scrub.stream() } : {}),\n ...(options.spawn ? { spawn: options.spawn } : {}),\n };\n }\n\n #path(path: string): string {\n return resolve(this.cwd, path);\n }\n\n #written(path: string): string {\n const resolved = this.#path(path);\n this.#activity?.wrote(resolved);\n return resolved;\n }\n\n get #fs() {\n return this.#client.fs;\n }\n\n async absolutePath(path: string): FileResult<string> {\n return ok(this.#path(path));\n }\n\n async joinPath(parts: string[]): FileResult<string> {\n return ok(normalize(parts.join('/')));\n }\n\n readTextFile(path: string, context: Context): FileResult<string> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.readText(resolved));\n }\n\n readBinaryFile(path: string, context: Context): FileResult<Uint8Array> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.readFile(resolved));\n }\n\n async openTextLineReader(path: string, context: Context): FileResult<TextLineReader> {\n const text = await this.readTextFile(path, context);\n return text.ok ? ok(new SnapshotLines(text.value)) : text;\n }\n\n async readTextLines(\n path: string,\n options: { maxLines?: number } | undefined,\n context: Context\n ): FileResult<string[]> {\n if (options?.maxLines !== undefined && options.maxLines <= 0) return ok([]);\n const text = await this.readTextFile(path, context);\n if (!text.ok) return text;\n const reader = new SnapshotLines(text.value);\n const lines: string[] = [];\n for (\n let line = await reader.readLine();\n line.ok && line.value;\n line = await reader.readLine()\n ) {\n if (options?.maxLines !== undefined && lines.length >= options.maxLines) break;\n lines.push(line.value.text);\n }\n return ok(lines);\n }\n\n openBinaryReader(\n path: string,\n options: { noFollow?: boolean } | undefined,\n context: Context\n ): FileResult<BinaryReader> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if (options?.noFollow && (await this.#fs.lstat(resolved)).isSymbolicLink) {\n throw new FileError('invalid', 'Refusing to follow a symbolic link', resolved);\n }\n const stat = await this.#fs.stat(resolved);\n if (stat.isDirectory) throw new FileError('is_directory', 'Is a directory', resolved);\n if (!stat.isFile) throw new FileError('invalid', 'Not a regular file', resolved);\n const bytes = await this.#fs.readFile(resolved);\n if (!this.#scrub) return new SnapshotReader(bytes, info(resolved, stat));\n const shown = await scrubbedFile(this.#scrub, bytes);\n return new SnapshotReader(shown, { ...info(resolved, stat), size: shown.length });\n });\n }\n\n writeFile(path: string, content: string | Uint8Array, context: Context): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, async () => {\n await this.#fs.mkdir(dirname(resolved));\n await this.#fs.writeFile(resolved, content);\n });\n }\n\n appendFile(path: string, content: string | Uint8Array, context: Context): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, async () => {\n const before = (await this.#fs.exists(resolved))\n ? await this.#fs.readFile(resolved)\n : new Uint8Array();\n const added = typeof content === 'string' ? new TextEncoder().encode(content) : content;\n const all = new Uint8Array(before.length + added.length);\n all.set(before);\n all.set(added, before.length);\n await this.#fs.mkdir(dirname(resolved));\n await this.#fs.writeFile(resolved, all);\n });\n }\n\n truncateFile(path: string, size: number, context: Context): FileResult<void> {\n const resolved = this.#written(path);\n if (!Number.isSafeInteger(size) || size < 0) {\n return Promise.resolve(\n err(new FileError('invalid', 'File size must be a non-negative integer', resolved))\n );\n }\n return attempt(context, resolved, async () => {\n const before = await this.#fs.readFile(resolved);\n const after = new Uint8Array(size);\n after.set(before.subarray(0, size));\n await this.#fs.writeFile(resolved, after);\n });\n }\n\n flushFile(path: string, context: Context): FileResult<void> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if ((await this.#fs.stat(resolved)).isDirectory)\n throw Object.assign(new Error('Is a directory'), { code: 'EISDIR' });\n });\n }\n\n renameFile(sourcePath: string, destinationPath: string, context: Context): FileResult<void> {\n const source = this.#written(sourcePath);\n return attempt(context, source, () => this.#fs.rename(source, this.#written(destinationPath)));\n }\n\n fileInfo(path: string, context: Context): FileResult<FileInfo> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => info(resolved, await this.#fs.lstat(resolved)));\n }\n\n async listDir(path: string, context: Context): FileResult<FileInfo[]> {\n const reader = await this.openDirReader(path, context);\n if (!reader.ok) return reader;\n return ok(await (reader.value as KernelDirReader).rest());\n }\n\n openDirReader(path: string, context: Context): FileResult<DirReader> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if (!(await this.#fs.stat(resolved)).isDirectory) {\n throw Object.assign(new Error('Not a directory'), { code: 'ENOTDIR' });\n }\n return new KernelDirReader(this.#fs, resolved, await this.#fs.readdir(resolved));\n });\n }\n\n watch(\n targets: readonly WatchTarget[],\n onChange: (change: WatchChange) => void,\n context: Context\n ): FileResult<FileWatcher> {\n return attempt(context, this.cwd, async () => {\n let absorb: (paths: readonly string[]) => void = () => undefined;\n const reported = (change: WatchChange) => {\n if ('paths' in change) absorb(change.paths);\n onChange(change);\n };\n const native =\n this.#poll.mode === 'polling'\n ? undefined\n : await nativeWatch(this.#fs, this.cwd, targets, reported);\n const intervalMs = native ? (this.#poll.rescanMs ?? RESCAN_MS) : this.#poll.intervalMs;\n const polled = await pollWatch(this.#fs, this.cwd, targets, onChange, {\n ...(intervalMs === undefined ? {} : { intervalMs }),\n });\n absorb = (paths) => polled.absorb(paths);\n const watcher: FileWatcher = native\n ? {\n mode: 'native',\n async close(closing: Context) {\n await native.close(closing);\n await polled.close(closing);\n },\n }\n : polled;\n this.#watchers.add(watcher);\n return {\n mode: watcher.mode,\n close: async (closing: Context) => {\n this.#watchers.delete(watcher);\n await watcher.close(closing);\n },\n };\n });\n }\n\n canonicalPath(path: string, context: Context): FileResult<string> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.realpath(resolved));\n }\n\n async exists(path: string, context: Context): FileResult<boolean> {\n const found = await this.fileInfo(path, context);\n if (found.ok) return ok(true);\n return found.error.code === 'not_found' ? ok(false) : found;\n }\n\n createDir(\n path: string,\n _options: { recursive?: boolean } | undefined,\n context: Context\n ): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, () => this.#fs.mkdir(resolved));\n }\n\n remove(\n path: string,\n options: { recursive?: boolean; force?: boolean } | undefined,\n context: Context\n ): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, async () => {\n if (!options?.recursive) {\n const stat = await this.#fs.lstat(resolved).catch((error: unknown) => {\n if (options?.force) return undefined;\n throw error;\n });\n if (stat?.isDirectory) throw Object.assign(new Error('Is a directory'), { code: 'EISDIR' });\n }\n await this.#fs.rm(resolved, { force: options?.force ?? false });\n });\n }\n\n async createTempDir(prefix: string | undefined, context: Context): FileResult<string> {\n const path = `/tmp/${prefix ?? 'tmp-'}${crypto.randomUUID()}`;\n return attempt(context, path, async () => {\n await this.#fs.mkdir(path);\n return path;\n });\n }\n\n async createTempFile(\n options: { prefix?: string; suffix?: string } | undefined,\n context: Context\n ): FileResult<string> {\n const dir = await this.createTempDir('tmp-', context);\n if (!dir.ok) return dir;\n const path = `${dir.value}/${options?.prefix ?? ''}${crypto.randomUUID()}${options?.suffix ?? ''}`;\n return attempt(context, path, async () => {\n await this.#fs.writeFile(path, '');\n return path;\n });\n }\n\n exec(\n command: string | readonly string[],\n options: ShellExecOptions | undefined,\n context: Context\n ): Promise<Result<ShellExecResult, ExecutionError>> {\n const ended = this.#activity?.began();\n const line = typeof command === 'string' ? prefixed(command, this.#exports) : command;\n const groups = this.#groups;\n const owner = this.#owner;\n if (!groups || owner === undefined)\n return execute(\n this.#client,\n this.cwd,\n line,\n options,\n context,\n undefined,\n undefined,\n this.#hooks\n ).finally(ended);\n return this.#grouped(groups, owner, line, options, context).finally(ended);\n }\n\n async #grouped(\n groups: ProcessGroups,\n owner: number,\n line: string | readonly string[],\n options: ShellExecOptions | undefined,\n context: Context\n ): Promise<Result<ShellExecResult, ExecutionError>> {\n for (let wait = groups.pending(owner); wait; wait = groups.pending(owner)) await wait;\n const pgid = groups.shared(owner);\n const release = pgid === undefined && groups.joins() ? groups.reserve(owner) : () => undefined;\n const spawned = (process: KernelProcess) => {\n groups.track(owner, process);\n release();\n };\n const join = groups.joins()\n ? {\n ...(pgid === undefined ? {} : { pgid }),\n refused: (code: string) => groups.refused(owner, code),\n }\n : undefined;\n return execute(\n this.#client,\n this.cwd,\n line,\n options,\n context,\n spawned,\n join,\n this.#hooks\n ).finally(release);\n }\n\n async cleanup(context: Context): Promise<void> {\n for (const watcher of [...this.#watchers]) await watcher.close(context);\n this.#watchers.clear();\n }\n}\n\nexport const HOME = '/home';\n\nexport type EnvTargetLike = { cwd?: string; conversationId?: number; read?: unknown };\n\nexport function kernelEnvironment(\n client: KernelClient,\n options: Omit<KernelEnvOptions, 'cwd'> & {\n identify?: (target: EnvTargetLike, context: Context) => Promise<Record<string, string>>;\n } = {}\n): (target: EnvTargetLike, context?: Context) => SliccKernelEnv | Promise<SliccKernelEnv> {\n const { identify, ...rest } = options;\n const make = (target: EnvTargetLike, exports?: Record<string, string>) =>\n new SliccKernelEnv(client, {\n ...rest,\n cwd: target.cwd ?? HOME,\n ...(target.conversationId === undefined ? {} : { owner: target.conversationId }),\n ...(exports ? { exports } : {}),\n });\n return (target, context) =>\n identify && context\n ? identify(target, context).then((exports) => make(target, exports))\n : make(target);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type BinaryReader,\n type DirReader,\n type ExecutionEnv,\n type ExecutionError,\n err,\n FileError,\n type FileInfo,\n type FileWatcher,\n ok,\n type Result,\n type ShellExecOptions,\n type ShellExecResult,\n type TextLineReader,\n type WatchChange,\n type WatchTarget,\n} from '@earendil-works/pi-durable/env';\nimport { mimeOf } from '../images/index.ts';\nimport type { Scrub } from '../secrets/scrub.ts';\nimport type { Activity } from './activity.ts';\nimport type { KernelClient, KernelProcess } from './client.ts';\nimport { type ExecHooks, execute } from './exec.ts';\nimport { attempt, info, KernelDirReader, SnapshotLines, SnapshotReader } from './files.ts';\nimport type { ProcessGroups } from './groups.ts';\nimport { dirname, normalize, resolve } from './paths.ts';\nimport { nativeWatch, type PollOptions, pollWatch, RESCAN_MS } from './watch.ts';\n\nexport interface KernelEnvOptions {\n cwd: string;\n id?: string;\n watch?: PollOptions;\n activity?: Activity;\n groups?: ProcessGroups;\n owner?: number;\n exports?: Readonly<Record<string, string>>;\n scrub?: Scrub;\n spawn?: (argv: readonly string[]) => readonly string[];\n}\n\nexport const STREAMED_READ = 8 * 1024 * 1024;\nexport const READ_CHUNK = 1024 * 1024;\n\nexport async function scrubbedFile(scrub: Scrub, bytes: Uint8Array): Promise<Uint8Array> {\n if (mimeOf(bytes)) return bytes;\n if (bytes.length <= STREAMED_READ) return (await scrub.texts([bytes]))[0] as Uint8Array;\n const stream = scrub.stream();\n const parts: Uint8Array[] = [];\n for (let at = 0; at < bytes.length; at += READ_CHUNK)\n parts.push(await stream.push(bytes.subarray(at, at + READ_CHUNK)));\n parts.push(await stream.end());\n const all = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0));\n let offset = 0;\n for (const part of parts) {\n all.set(part, offset);\n offset += part.length;\n }\n return all;\n}\n\nexport function quote(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nexport function prefixed(\n command: string,\n exports: Readonly<Record<string, string>> | undefined\n): string {\n const names = Object.keys(exports ?? {}).filter((name) => /^[A-Z_][A-Z0-9_]*$/.test(name));\n if (!names.length) return command;\n return `export ${names.map((name) => `${name}=${quote((exports as Record<string, string>)[name] as string)}`).join(' ')}; ${command}`;\n}\n\ntype FileResult<T> = Promise<Result<T, FileError>>;\n\nexport class SliccKernelEnv implements ExecutionEnv {\n readonly id: string;\n cwd: string;\n readonly #client: KernelClient;\n readonly #watchers = new Set<FileWatcher>();\n readonly #poll: PollOptions;\n readonly #activity: Activity | undefined;\n readonly #groups: ProcessGroups | undefined;\n readonly #owner: number | undefined;\n readonly #exports: Readonly<Record<string, string>> | undefined;\n readonly #hooks: ExecHooks;\n readonly #scrub: Scrub | undefined;\n\n constructor(client: KernelClient, options: KernelEnvOptions) {\n this.#client = client;\n this.cwd = options.cwd;\n this.id = options.id ?? 'slicc-kernel';\n this.#poll = options.watch ?? {};\n this.#activity = options.activity;\n this.#groups = options.groups;\n this.#owner = options.owner;\n this.#exports = options.exports;\n this.#scrub = options.scrub;\n const scrub = options.scrub;\n this.#hooks = {\n ...(scrub ? { scrub: () => scrub.stream() } : {}),\n ...(options.spawn ? { spawn: options.spawn } : {}),\n };\n }\n\n #path(path: string): string {\n return resolve(this.cwd, path);\n }\n\n #written(path: string): string {\n const resolved = this.#path(path);\n this.#activity?.wrote(resolved);\n return resolved;\n }\n\n get #fs() {\n return this.#client.fs;\n }\n\n async absolutePath(path: string): FileResult<string> {\n return ok(this.#path(path));\n }\n\n async joinPath(parts: string[]): FileResult<string> {\n return ok(normalize(parts.join('/')));\n }\n\n readTextFile(path: string, context: Context): FileResult<string> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.readText(resolved));\n }\n\n readBinaryFile(path: string, context: Context): FileResult<Uint8Array> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.readFile(resolved));\n }\n\n async openTextLineReader(path: string, context: Context): FileResult<TextLineReader> {\n const text = await this.readTextFile(path, context);\n return text.ok ? ok(new SnapshotLines(text.value)) : text;\n }\n\n async readTextLines(\n path: string,\n options: { maxLines?: number } | undefined,\n context: Context\n ): FileResult<string[]> {\n if (options?.maxLines !== undefined && options.maxLines <= 0) return ok([]);\n const text = await this.readTextFile(path, context);\n if (!text.ok) return text;\n const reader = new SnapshotLines(text.value);\n const lines: string[] = [];\n for (\n let line = await reader.readLine();\n line.ok && line.value;\n line = await reader.readLine()\n ) {\n if (options?.maxLines !== undefined && lines.length >= options.maxLines) break;\n lines.push(line.value.text);\n }\n return ok(lines);\n }\n\n openBinaryReader(\n path: string,\n options: { noFollow?: boolean } | undefined,\n context: Context\n ): FileResult<BinaryReader> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if (options?.noFollow && (await this.#fs.lstat(resolved)).isSymbolicLink) {\n throw new FileError('invalid', 'Refusing to follow a symbolic link', resolved);\n }\n const stat = await this.#fs.stat(resolved);\n if (stat.isDirectory) throw new FileError('is_directory', 'Is a directory', resolved);\n if (!stat.isFile) throw new FileError('invalid', 'Not a regular file', resolved);\n const bytes = await this.#fs.readFile(resolved);\n if (!this.#scrub) return new SnapshotReader(bytes, info(resolved, stat));\n const shown = await scrubbedFile(this.#scrub, bytes);\n return new SnapshotReader(shown, { ...info(resolved, stat), size: shown.length });\n });\n }\n\n writeFile(path: string, content: string | Uint8Array, context: Context): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, async () => {\n await this.#fs.mkdir(dirname(resolved));\n await this.#fs.writeFile(resolved, content);\n });\n }\n\n appendFile(path: string, content: string | Uint8Array, context: Context): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, async () => {\n const before = (await this.#fs.exists(resolved))\n ? await this.#fs.readFile(resolved)\n : new Uint8Array();\n const added = typeof content === 'string' ? new TextEncoder().encode(content) : content;\n const all = new Uint8Array(before.length + added.length);\n all.set(before);\n all.set(added, before.length);\n await this.#fs.mkdir(dirname(resolved));\n await this.#fs.writeFile(resolved, all);\n });\n }\n\n truncateFile(path: string, size: number, context: Context): FileResult<void> {\n const resolved = this.#written(path);\n if (!Number.isSafeInteger(size) || size < 0) {\n return Promise.resolve(\n err(new FileError('invalid', 'File size must be a non-negative integer', resolved))\n );\n }\n return attempt(context, resolved, async () => {\n const before = await this.#fs.readFile(resolved);\n const after = new Uint8Array(size);\n after.set(before.subarray(0, size));\n await this.#fs.writeFile(resolved, after);\n });\n }\n\n flushFile(path: string, context: Context): FileResult<void> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if ((await this.#fs.stat(resolved)).isDirectory)\n throw Object.assign(new Error('Is a directory'), { code: 'EISDIR' });\n });\n }\n\n renameFile(sourcePath: string, destinationPath: string, context: Context): FileResult<void> {\n const source = this.#written(sourcePath);\n return attempt(context, source, () => this.#fs.rename(source, this.#written(destinationPath)));\n }\n\n fileInfo(path: string, context: Context): FileResult<FileInfo> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => info(resolved, await this.#fs.lstat(resolved)));\n }\n\n async listDir(path: string, context: Context): FileResult<FileInfo[]> {\n const reader = await this.openDirReader(path, context);\n if (!reader.ok) return reader;\n return ok(await (reader.value as KernelDirReader).rest());\n }\n\n openDirReader(path: string, context: Context): FileResult<DirReader> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if (!(await this.#fs.stat(resolved)).isDirectory) {\n throw Object.assign(new Error('Not a directory'), { code: 'ENOTDIR' });\n }\n return new KernelDirReader(this.#fs, resolved, await this.#fs.readdir(resolved));\n });\n }\n\n watch(\n targets: readonly WatchTarget[],\n onChange: (change: WatchChange) => void,\n context: Context\n ): FileResult<FileWatcher> {\n return attempt(context, this.cwd, async () => {\n let absorb: (paths: readonly string[]) => void = () => undefined;\n const reported = (change: WatchChange) => {\n if ('paths' in change) absorb(change.paths);\n onChange(change);\n };\n const native =\n this.#poll.mode === 'polling'\n ? undefined\n : await nativeWatch(this.#fs, this.cwd, targets, reported);\n const intervalMs = native ? (this.#poll.rescanMs ?? RESCAN_MS) : this.#poll.intervalMs;\n const polled = await pollWatch(this.#fs, this.cwd, targets, onChange, {\n ...(intervalMs === undefined ? {} : { intervalMs }),\n });\n absorb = (paths) => polled.absorb(paths);\n const watcher: FileWatcher = native\n ? {\n mode: 'native',\n async close(closing: Context) {\n await native.close(closing);\n await polled.close(closing);\n },\n }\n : polled;\n this.#watchers.add(watcher);\n return {\n mode: watcher.mode,\n close: async (closing: Context) => {\n this.#watchers.delete(watcher);\n await watcher.close(closing);\n },\n };\n });\n }\n\n canonicalPath(path: string, context: Context): FileResult<string> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.realpath(resolved));\n }\n\n async exists(path: string, context: Context): FileResult<boolean> {\n const found = await this.fileInfo(path, context);\n if (found.ok) return ok(true);\n return found.error.code === 'not_found' ? ok(false) : found;\n }\n\n createDir(\n path: string,\n _options: { recursive?: boolean } | undefined,\n context: Context\n ): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, () => this.#fs.mkdir(resolved));\n }\n\n remove(\n path: string,\n options: { recursive?: boolean; force?: boolean } | undefined,\n context: Context\n ): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, async () => {\n if (!options?.recursive) {\n const stat = await this.#fs.lstat(resolved).catch((error: unknown) => {\n if (options?.force) return undefined;\n throw error;\n });\n if (stat?.isDirectory) throw Object.assign(new Error('Is a directory'), { code: 'EISDIR' });\n }\n await this.#fs.rm(resolved, { force: options?.force ?? false });\n });\n }\n\n async createTempDir(prefix: string | undefined, context: Context): FileResult<string> {\n const path = `/tmp/${prefix ?? 'tmp-'}${crypto.randomUUID()}`;\n return attempt(context, path, async () => {\n await this.#fs.mkdir(path);\n return path;\n });\n }\n\n async createTempFile(\n options: { prefix?: string; suffix?: string } | undefined,\n context: Context\n ): FileResult<string> {\n const dir = await this.createTempDir('tmp-', context);\n if (!dir.ok) return dir;\n const path = `${dir.value}/${options?.prefix ?? ''}${crypto.randomUUID()}${options?.suffix ?? ''}`;\n return attempt(context, path, async () => {\n await this.#fs.writeFile(path, '');\n return path;\n });\n }\n\n exec(\n command: string | readonly string[],\n options: ShellExecOptions | undefined,\n context: Context\n ): Promise<Result<ShellExecResult, ExecutionError>> {\n const ended = this.#activity?.began();\n const line = typeof command === 'string' ? prefixed(command, this.#exports) : command;\n const groups = this.#groups;\n const owner = this.#owner;\n if (!groups || owner === undefined)\n return execute(\n this.#client,\n this.cwd,\n line,\n options,\n context,\n undefined,\n undefined,\n this.#hooks\n ).finally(ended);\n return this.#grouped(groups, owner, line, options, context).finally(ended);\n }\n\n async #grouped(\n groups: ProcessGroups,\n owner: number,\n line: string | readonly string[],\n options: ShellExecOptions | undefined,\n context: Context\n ): Promise<Result<ShellExecResult, ExecutionError>> {\n for (let wait = groups.pending(owner); wait; wait = groups.pending(owner)) await wait;\n const pgid = groups.shared(owner);\n const release = pgid === undefined && groups.joins() ? groups.reserve(owner) : () => undefined;\n const spawned = (process: KernelProcess) => {\n groups.track(owner, process);\n release();\n };\n const join = groups.joins()\n ? {\n ...(pgid === undefined ? {} : { pgid }),\n refused: (code: string) => groups.refused(owner, code),\n }\n : undefined;\n return execute(\n this.#client,\n this.cwd,\n line,\n options,\n context,\n spawned,\n join,\n this.#hooks\n ).finally(release);\n }\n\n async cleanup(context: Context): Promise<void> {\n for (const watcher of [...this.#watchers]) await watcher.close(context);\n this.#watchers.clear();\n }\n}\n\nexport const HOME = '/home';\n\nexport type EnvTargetLike = { cwd?: string; conversationId?: number; read?: unknown };\n\nexport type EnvRoute = { client: KernelClient; home: string; cwd?: string };\n\nexport function kernelEnvironment(\n client: KernelClient,\n options: Omit<KernelEnvOptions, 'cwd'> & {\n identify?: (target: EnvTargetLike, context: Context) => Promise<Record<string, string>>;\n route?: (target: EnvTargetLike) => Promise<EnvRoute | undefined>;\n } = {}\n): (target: EnvTargetLike, context?: Context) => SliccKernelEnv | Promise<SliccKernelEnv> {\n const { identify, route, ...rest } = options;\n const make = (target: EnvTargetLike, exports?: Record<string, string>, routed?: EnvRoute) =>\n new SliccKernelEnv(routed?.client ?? client, {\n ...rest,\n cwd: routed?.cwd ?? target.cwd ?? routed?.home ?? HOME,\n ...(target.conversationId === undefined ? {} : { owner: target.conversationId }),\n ...(exports ? { exports } : {}),\n });\n if (route)\n return async (target, context) => {\n const routed = await route(target);\n const exports = identify && context ? await identify(target, context) : undefined;\n return make(target, exports, routed);\n };\n return (target, context) =>\n identify && context\n ? identify(target, context).then((exports) => make(target, exports))\n : make(target);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,10 +3,19 @@ function failure(error) {
3
3
  const code = error?.code;
4
4
  return typeof code === "string" ? { message, code } : { message };
5
5
  }
6
+ const missing = (what) => Object.assign(new Error(`this kernel has no ${what}`), { code: "ENOSYS" });
7
+ function grouped(groups, call) {
8
+ if (!groups) throw missing("groups");
9
+ const [group, user] = call.args;
10
+ if (call.op === "groups.addMember") return groups.addMember(group, user);
11
+ if (call.op === "groups.removeMember") return groups.removeMember(group, user);
12
+ return groups.list();
13
+ }
6
14
  async function perform(source, call) {
7
15
  const users = source.users;
8
16
  if (call.op === "connect") return source.connect(call.args[0]);
9
- if (!users) throw Object.assign(new Error("this kernel has no users"), { code: "ENOSYS" });
17
+ if (call.op.startsWith("groups.")) return grouped(source.groups, call);
18
+ if (!users) throw missing("users");
10
19
  if (call.op === "users.add") return users.add(call.args[0]);
11
20
  if (call.op === "users.remove")
12
21
  return users.remove(call.args[0], call.args[1]);
@@ -54,6 +63,11 @@ function adminOver(port) {
54
63
  remove: (name, options) => call("users.remove", name, options),
55
64
  list: () => call("users.list")
56
65
  },
66
+ groups: {
67
+ list: () => call("groups.list"),
68
+ addMember: (group, user) => call("groups.addMember", group, user),
69
+ removeMember: (group, user) => call("groups.removeMember", group, user)
70
+ },
57
71
  close() {
58
72
  shut = true;
59
73
  for (const waiting of pending.values()) waiting.reject(new Error("the kernel admin closed"));
@@ -73,6 +87,7 @@ function userClients(admin, attach) {
73
87
  };
74
88
  return {
75
89
  users: admin.users,
90
+ groups: admin.groups,
76
91
  client(agentId, user) {
77
92
  const known = held.get(agentId);
78
93
  if (known?.user === user) return known.client;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/kernel/users.ts"],
4
- "sourcesContent": ["import type { PortEndpoint } from '../wire.ts';\nimport type { KernelClient } from './client.ts';\n\nexport interface KernelUser {\n name: string;\n uid: number;\n gid: number;\n home: string;\n shell: string;\n groups: number[];\n umask?: number;\n}\n\nexport interface UserSpec {\n name: string;\n home?: string;\n groups?: string[];\n umask?: number;\n homeGroup?: string;\n}\n\nexport interface KernelUsers {\n add(spec: UserSpec): Promise<KernelUser>;\n remove(name: string, options?: { keepHome?: boolean }): Promise<boolean>;\n list(): Promise<KernelUser[]>;\n}\n\nexport interface UserConnect {\n user?: string;\n agentId?: string;\n}\n\nexport interface KernelSource {\n connect(options?: UserConnect): Promise<Transferable>;\n users?: KernelUsers;\n}\n\nexport interface KernelAdmin {\n connect(options: UserConnect): Promise<MessagePort>;\n users: KernelUsers;\n close(): void;\n}\n\ntype Op = 'connect' | 'users.add' | 'users.remove' | 'users.list';\ntype Call = { id: number; op: Op; args: unknown[] };\ntype Reply = { id: number; value?: unknown; error?: { message: string; code?: string } };\n\nfunction failure(error: unknown): { message: string; code?: string } {\n const message = error instanceof Error ? error.message : String(error);\n const code = (error as { code?: unknown } | null)?.code;\n return typeof code === 'string' ? { message, code } : { message };\n}\n\nasync function perform(source: KernelSource, call: Call): Promise<unknown> {\n const users = source.users;\n if (call.op === 'connect') return source.connect(call.args[0] as UserConnect);\n if (!users) throw Object.assign(new Error('this kernel has no users'), { code: 'ENOSYS' });\n if (call.op === 'users.add') return users.add(call.args[0] as UserSpec);\n if (call.op === 'users.remove')\n return users.remove(call.args[0] as string, call.args[1] as { keepHome?: boolean });\n return users.list();\n}\n\nexport function serveAdmin(port: PortEndpoint, source: KernelSource): void {\n port.addEventListener('message', (event: MessageEvent) => {\n const call = event.data as Call;\n perform(source, call).then(\n (value) => {\n const transfer = call.op === 'connect' ? [value as Transferable] : [];\n port.postMessage({ id: call.id, value } satisfies Reply, transfer);\n },\n (error: unknown) => port.postMessage({ id: call.id, error: failure(error) } satisfies Reply)\n );\n });\n port.start?.();\n}\n\nexport function adminOver(port: PortEndpoint): KernelAdmin {\n const pending = new Map<number, { resolve(value: unknown): void; reject(error: Error): void }>();\n let next = 0;\n let shut = false;\n port.addEventListener('message', (event: MessageEvent) => {\n const reply = event.data as Reply;\n const waiting = pending.get(reply.id);\n if (!waiting) return;\n pending.delete(reply.id);\n if (reply.error) waiting.reject(Object.assign(new Error(reply.error.message), reply.error));\n else waiting.resolve(reply.value);\n });\n port.start?.();\n const call = <T>(op: Op, ...args: unknown[]) =>\n new Promise<T>((resolve, reject) => {\n if (shut) {\n reject(new Error('the kernel admin closed'));\n return;\n }\n const id = ++next;\n pending.set(id, { resolve: resolve as (value: unknown) => void, reject });\n port.postMessage({ id, op, args } satisfies Call);\n });\n return {\n connect: (options) => call<MessagePort>('connect', options),\n users: {\n add: (spec) => call<KernelUser>('users.add', spec),\n remove: (name, options) => call<boolean>('users.remove', name, options),\n list: () => call<KernelUser[]>('users.list'),\n },\n close() {\n shut = true;\n for (const waiting of pending.values()) waiting.reject(new Error('the kernel admin closed'));\n pending.clear();\n port.close?.();\n },\n };\n}\n\nexport type UserClient = KernelClient & {\n close?(options?: { kill?: boolean }): unknown;\n closed?: Promise<unknown>;\n};\n\nexport interface UserClients {\n readonly users: KernelUsers;\n client(agentId: string, user: string): Promise<UserClient>;\n drop(agentId: string): Promise<void>;\n close(): Promise<void>;\n}\n\ntype Held = { user: string; client: Promise<UserClient> };\n\nexport function userClients(\n admin: KernelAdmin,\n attach: (port: MessagePort) => Promise<UserClient>\n): UserClients {\n const held = new Map<string, Held>();\n const forget = (agentId: string, entry: Held) => {\n if (held.get(agentId) === entry) held.delete(agentId);\n };\n const end = async (entry: Held, kill: boolean) => {\n const client = await entry.client.catch(() => undefined);\n await client?.close?.({ kill });\n };\n return {\n users: admin.users,\n client(agentId, user) {\n const known = held.get(agentId);\n if (known?.user === user) return known.client;\n if (known) void end(known, true);\n const entry: Held = {\n user,\n client: admin.connect({ user, agentId }).then(attach),\n };\n held.set(agentId, entry);\n entry.client.then(\n (client) =>\n client.closed?.then(\n () => forget(agentId, entry),\n () => forget(agentId, entry)\n ),\n () => forget(agentId, entry)\n );\n return entry.client;\n },\n async drop(agentId) {\n const entry = held.get(agentId);\n if (!entry) return;\n held.delete(agentId);\n await end(entry, true);\n },\n async close() {\n const entries = [...held.values()];\n held.clear();\n await Promise.all(entries.map((entry) => end(entry, false)));\n admin.close();\n },\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { PortEndpoint } from '../wire.ts';\nimport type { KernelClient } from './client.ts';\n\nexport interface KernelUser {\n name: string;\n uid: number;\n gid: number;\n home: string;\n shell: string;\n groups: number[];\n umask?: number;\n}\n\nexport interface UserSpec {\n name: string;\n home?: string;\n groups?: string[];\n umask?: number;\n homeGroup?: string;\n homeMode?: number;\n}\n\nexport interface KernelUsers {\n add(spec: UserSpec): Promise<KernelUser>;\n remove(name: string, options?: { keepHome?: boolean }): Promise<boolean>;\n list(): Promise<KernelUser[]>;\n mode?: string;\n}\n\nexport interface KernelGroup {\n name: string;\n gid: number;\n members: string[];\n}\n\nexport interface KernelGroups {\n list(): Promise<KernelGroup[]>;\n addMember(group: string, user: string): Promise<unknown>;\n removeMember(group: string, user: string): Promise<unknown>;\n}\n\nexport interface UserConnect {\n user?: string;\n agentId?: string;\n}\n\nexport interface KernelSource {\n connect(options?: UserConnect): Promise<Transferable>;\n users?: KernelUsers;\n groups?: KernelGroups;\n}\n\nexport interface KernelAdmin {\n connect(options: UserConnect): Promise<MessagePort>;\n users: KernelUsers;\n groups: KernelGroups;\n close(): void;\n}\n\ntype Op =\n | 'connect'\n | 'users.add'\n | 'users.remove'\n | 'users.list'\n | 'groups.list'\n | 'groups.addMember'\n | 'groups.removeMember';\ntype Call = { id: number; op: Op; args: unknown[] };\ntype Reply = { id: number; value?: unknown; error?: { message: string; code?: string } };\n\nfunction failure(error: unknown): { message: string; code?: string } {\n const message = error instanceof Error ? error.message : String(error);\n const code = (error as { code?: unknown } | null)?.code;\n return typeof code === 'string' ? { message, code } : { message };\n}\n\nconst missing = (what: string) =>\n Object.assign(new Error(`this kernel has no ${what}`), { code: 'ENOSYS' });\n\nfunction grouped(groups: KernelGroups | undefined, call: Call): Promise<unknown> {\n if (!groups) throw missing('groups');\n const [group, user] = call.args as [string, string];\n if (call.op === 'groups.addMember') return groups.addMember(group, user);\n if (call.op === 'groups.removeMember') return groups.removeMember(group, user);\n return groups.list();\n}\n\nasync function perform(source: KernelSource, call: Call): Promise<unknown> {\n const users = source.users;\n if (call.op === 'connect') return source.connect(call.args[0] as UserConnect);\n if (call.op.startsWith('groups.')) return grouped(source.groups, call);\n if (!users) throw missing('users');\n if (call.op === 'users.add') return users.add(call.args[0] as UserSpec);\n if (call.op === 'users.remove')\n return users.remove(call.args[0] as string, call.args[1] as { keepHome?: boolean });\n return users.list();\n}\n\nexport function serveAdmin(port: PortEndpoint, source: KernelSource): void {\n port.addEventListener('message', (event: MessageEvent) => {\n const call = event.data as Call;\n perform(source, call).then(\n (value) => {\n const transfer = call.op === 'connect' ? [value as Transferable] : [];\n port.postMessage({ id: call.id, value } satisfies Reply, transfer);\n },\n (error: unknown) => port.postMessage({ id: call.id, error: failure(error) } satisfies Reply)\n );\n });\n port.start?.();\n}\n\nexport function adminOver(port: PortEndpoint): KernelAdmin {\n const pending = new Map<number, { resolve(value: unknown): void; reject(error: Error): void }>();\n let next = 0;\n let shut = false;\n port.addEventListener('message', (event: MessageEvent) => {\n const reply = event.data as Reply;\n const waiting = pending.get(reply.id);\n if (!waiting) return;\n pending.delete(reply.id);\n if (reply.error) waiting.reject(Object.assign(new Error(reply.error.message), reply.error));\n else waiting.resolve(reply.value);\n });\n port.start?.();\n const call = <T>(op: Op, ...args: unknown[]) =>\n new Promise<T>((resolve, reject) => {\n if (shut) {\n reject(new Error('the kernel admin closed'));\n return;\n }\n const id = ++next;\n pending.set(id, { resolve: resolve as (value: unknown) => void, reject });\n port.postMessage({ id, op, args } satisfies Call);\n });\n return {\n connect: (options) => call<MessagePort>('connect', options),\n users: {\n add: (spec) => call<KernelUser>('users.add', spec),\n remove: (name, options) => call<boolean>('users.remove', name, options),\n list: () => call<KernelUser[]>('users.list'),\n },\n groups: {\n list: () => call<KernelGroup[]>('groups.list'),\n addMember: (group, user) => call('groups.addMember', group, user),\n removeMember: (group, user) => call('groups.removeMember', group, user),\n },\n close() {\n shut = true;\n for (const waiting of pending.values()) waiting.reject(new Error('the kernel admin closed'));\n pending.clear();\n port.close?.();\n },\n };\n}\n\nexport type UserClient = KernelClient & {\n close?(options?: { kill?: boolean }): unknown;\n closed?: Promise<unknown>;\n};\n\nexport interface UserClients {\n readonly users: KernelUsers;\n readonly groups: KernelGroups;\n client(agentId: string, user: string): Promise<UserClient>;\n drop(agentId: string): Promise<void>;\n close(): Promise<void>;\n}\n\ntype Held = { user: string; client: Promise<UserClient> };\n\nexport function userClients(\n admin: KernelAdmin,\n attach: (port: MessagePort) => Promise<UserClient>\n): UserClients {\n const held = new Map<string, Held>();\n const forget = (agentId: string, entry: Held) => {\n if (held.get(agentId) === entry) held.delete(agentId);\n };\n const end = async (entry: Held, kill: boolean) => {\n const client = await entry.client.catch(() => undefined);\n await client?.close?.({ kill });\n };\n return {\n users: admin.users,\n groups: admin.groups,\n client(agentId, user) {\n const known = held.get(agentId);\n if (known?.user === user) return known.client;\n if (known) void end(known, true);\n const entry: Held = {\n user,\n client: admin.connect({ user, agentId }).then(attach),\n };\n held.set(agentId, entry);\n entry.client.then(\n (client) =>\n client.closed?.then(\n () => forget(agentId, entry),\n () => forget(agentId, entry)\n ),\n () => forget(agentId, entry)\n );\n return entry.client;\n },\n async drop(agentId) {\n const entry = held.get(agentId);\n if (!entry) return;\n held.delete(agentId);\n await end(entry, true);\n },\n async close() {\n const entries = [...held.values()];\n held.clear();\n await Promise.all(entries.map((entry) => end(entry, false)));\n admin.close();\n },\n };\n}\n"],
5
+ "mappings": "AAsEA,SAAS,QAAQ,OAAoD;AACnE,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,QAAM,OAAQ,OAAqC;AACnD,SAAO,OAAO,SAAS,WAAW,EAAE,SAAS,KAAK,IAAI,EAAE,QAAQ;AAClE;AAEA,MAAM,UAAU,CAAC,SACf,OAAO,OAAO,IAAI,MAAM,sBAAsB,IAAI,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AAE3E,SAAS,QAAQ,QAAkC,MAA8B;AAC/E,MAAI,CAAC,OAAQ,OAAM,QAAQ,QAAQ;AACnC,QAAM,CAAC,OAAO,IAAI,IAAI,KAAK;AAC3B,MAAI,KAAK,OAAO,mBAAoB,QAAO,OAAO,UAAU,OAAO,IAAI;AACvE,MAAI,KAAK,OAAO,sBAAuB,QAAO,OAAO,aAAa,OAAO,IAAI;AAC7E,SAAO,OAAO,KAAK;AACrB;AAEA,eAAe,QAAQ,QAAsB,MAA8B;AACzE,QAAM,QAAQ,OAAO;AACrB,MAAI,KAAK,OAAO,UAAW,QAAO,OAAO,QAAQ,KAAK,KAAK,CAAC,CAAgB;AAC5E,MAAI,KAAK,GAAG,WAAW,SAAS,EAAG,QAAO,QAAQ,OAAO,QAAQ,IAAI;AACrE,MAAI,CAAC,MAAO,OAAM,QAAQ,OAAO;AACjC,MAAI,KAAK,OAAO,YAAa,QAAO,MAAM,IAAI,KAAK,KAAK,CAAC,CAAa;AACtE,MAAI,KAAK,OAAO;AACd,WAAO,MAAM,OAAO,KAAK,KAAK,CAAC,GAAa,KAAK,KAAK,CAAC,CAA2B;AACpF,SAAO,MAAM,KAAK;AACpB;AAEO,SAAS,WAAW,MAAoB,QAA4B;AACzE,OAAK,iBAAiB,WAAW,CAAC,UAAwB;AACxD,UAAM,OAAO,MAAM;AACnB,YAAQ,QAAQ,IAAI,EAAE;AAAA,MACpB,CAAC,UAAU;AACT,cAAM,WAAW,KAAK,OAAO,YAAY,CAAC,KAAqB,IAAI,CAAC;AACpE,aAAK,YAAY,EAAE,IAAI,KAAK,IAAI,MAAM,GAAmB,QAAQ;AAAA,MACnE;AAAA,MACA,CAAC,UAAmB,KAAK,YAAY,EAAE,IAAI,KAAK,IAAI,OAAO,QAAQ,KAAK,EAAE,CAAiB;AAAA,IAC7F;AAAA,EACF,CAAC;AACD,OAAK,QAAQ;AACf;AAEO,SAAS,UAAU,MAAiC;AACzD,QAAM,UAAU,oBAAI,IAA2E;AAC/F,MAAI,OAAO;AACX,MAAI,OAAO;AACX,OAAK,iBAAiB,WAAW,CAAC,UAAwB;AACxD,UAAM,QAAQ,MAAM;AACpB,UAAM,UAAU,QAAQ,IAAI,MAAM,EAAE;AACpC,QAAI,CAAC,QAAS;AACd,YAAQ,OAAO,MAAM,EAAE;AACvB,QAAI,MAAM,MAAO,SAAQ,OAAO,OAAO,OAAO,IAAI,MAAM,MAAM,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC;AAAA,QACrF,SAAQ,QAAQ,MAAM,KAAK;AAAA,EAClC,CAAC;AACD,OAAK,QAAQ;AACb,QAAM,OAAO,CAAI,OAAW,SAC1B,IAAI,QAAW,CAAC,SAAS,WAAW;AAClC,QAAI,MAAM;AACR,aAAO,IAAI,MAAM,yBAAyB,CAAC;AAC3C;AAAA,IACF;AACA,UAAM,KAAK,EAAE;AACb,YAAQ,IAAI,IAAI,EAAE,SAA8C,OAAO,CAAC;AACxE,SAAK,YAAY,EAAE,IAAI,IAAI,KAAK,CAAgB;AAAA,EAClD,CAAC;AACH,SAAO;AAAA,IACL,SAAS,CAAC,YAAY,KAAkB,WAAW,OAAO;AAAA,IAC1D,OAAO;AAAA,MACL,KAAK,CAAC,SAAS,KAAiB,aAAa,IAAI;AAAA,MACjD,QAAQ,CAAC,MAAM,YAAY,KAAc,gBAAgB,MAAM,OAAO;AAAA,MACtE,MAAM,MAAM,KAAmB,YAAY;AAAA,IAC7C;AAAA,IACA,QAAQ;AAAA,MACN,MAAM,MAAM,KAAoB,aAAa;AAAA,MAC7C,WAAW,CAAC,OAAO,SAAS,KAAK,oBAAoB,OAAO,IAAI;AAAA,MAChE,cAAc,CAAC,OAAO,SAAS,KAAK,uBAAuB,OAAO,IAAI;AAAA,IACxE;AAAA,IACA,QAAQ;AACN,aAAO;AACP,iBAAW,WAAW,QAAQ,OAAO,EAAG,SAAQ,OAAO,IAAI,MAAM,yBAAyB,CAAC;AAC3F,cAAQ,MAAM;AACd,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AACF;AAiBO,SAAS,YACd,OACA,QACa;AACb,QAAM,OAAO,oBAAI,IAAkB;AACnC,QAAM,SAAS,CAAC,SAAiB,UAAgB;AAC/C,QAAI,KAAK,IAAI,OAAO,MAAM,MAAO,MAAK,OAAO,OAAO;AAAA,EACtD;AACA,QAAM,MAAM,OAAO,OAAa,SAAkB;AAChD,UAAM,SAAS,MAAM,MAAM,OAAO,MAAM,MAAM,MAAS;AACvD,UAAM,QAAQ,QAAQ,EAAE,KAAK,CAAC;AAAA,EAChC;AACA,SAAO;AAAA,IACL,OAAO,MAAM;AAAA,IACb,QAAQ,MAAM;AAAA,IACd,OAAO,SAAS,MAAM;AACpB,YAAM,QAAQ,KAAK,IAAI,OAAO;AAC9B,UAAI,OAAO,SAAS,KAAM,QAAO,MAAM;AACvC,UAAI,MAAO,MAAK,IAAI,OAAO,IAAI;AAC/B,YAAM,QAAc;AAAA,QAClB;AAAA,QACA,QAAQ,MAAM,QAAQ,EAAE,MAAM,QAAQ,CAAC,EAAE,KAAK,MAAM;AAAA,MACtD;AACA,WAAK,IAAI,SAAS,KAAK;AACvB,YAAM,OAAO;AAAA,QACX,CAAC,WACC,OAAO,QAAQ;AAAA,UACb,MAAM,OAAO,SAAS,KAAK;AAAA,UAC3B,MAAM,OAAO,SAAS,KAAK;AAAA,QAC7B;AAAA,QACF,MAAM,OAAO,SAAS,KAAK;AAAA,MAC7B;AACA,aAAO,MAAM;AAAA,IACf;AAAA,IACA,MAAM,KAAK,SAAS;AAClB,YAAM,QAAQ,KAAK,IAAI,OAAO;AAC9B,UAAI,CAAC,MAAO;AACZ,WAAK,OAAO,OAAO;AACnB,YAAM,IAAI,OAAO,IAAI;AAAA,IACvB;AAAA,IACA,MAAM,QAAQ;AACZ,YAAM,UAAU,CAAC,GAAG,KAAK,OAAO,CAAC;AACjC,WAAK,MAAM;AACX,YAAM,QAAQ,IAAI,QAAQ,IAAI,CAAC,UAAU,IAAI,OAAO,KAAK,CAAC,CAAC;AAC3D,YAAM,MAAM;AAAA,IACd;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }