spawnfile 0.1.19 → 0.1.21

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 (41) hide show
  1. package/dist/compiler/containerArtifactsPlans.js +1 -0
  2. package/dist/compiler/containerArtifactsTypes.d.ts +8 -0
  3. package/dist/compiler/containerDaimonGrokWorkerProvisioning.js +21 -1
  4. package/dist/compiler/containerDaimonGrokWorkerRender.d.ts +9 -0
  5. package/dist/compiler/containerDaimonGrokWorkerRender.js +7 -2
  6. package/dist/compiler/training/broker/declaration.d.ts +7 -0
  7. package/dist/compiler/training/broker/declaration.js +13 -1
  8. package/dist/compiler/training/broker/entrypoint.js +7 -10
  9. package/dist/compiler/training/broker/index.d.ts +2 -1
  10. package/dist/compiler/training/broker/index.js +2 -1
  11. package/dist/compiler/training/broker/projection.d.ts +9 -0
  12. package/dist/compiler/training/broker/projection.js +20 -1
  13. package/dist/compiler/training/broker/recycleClient.d.ts +33 -0
  14. package/dist/compiler/training/broker/recycleClient.js +112 -0
  15. package/dist/compiler/training/broker/registration.d.ts +3 -0
  16. package/dist/compiler/training/broker/registration.js +3 -0
  17. package/dist/compiler/training/broker/rootScript.d.ts +32 -0
  18. package/dist/compiler/training/broker/rootScript.js +33 -0
  19. package/dist/compiler/training/broker/runtime.js +4 -3
  20. package/dist/compiler/training/index.d.ts +3 -0
  21. package/dist/compiler/training/index.js +3 -0
  22. package/dist/compiler/training/preparation/assets/package-lock.json +2 -2
  23. package/dist/compiler/training/preparation/broker.d.ts +10 -1
  24. package/dist/compiler/training/preparation/broker.js +25 -3
  25. package/dist/compiler/training/preparation/prepare.js +5 -4
  26. package/dist/compiler/training/subjectDeclaration.d.ts +25 -0
  27. package/dist/compiler/training/subjectDeclaration.js +52 -0
  28. package/dist/compiler/training/subjectLease.d.ts +114 -0
  29. package/dist/compiler/training/subjectLease.js +175 -0
  30. package/dist/runtime/daimon/adapter.d.ts +7 -9
  31. package/dist/runtime/daimon/adapter.js +14 -19
  32. package/dist/runtime/daimon/config.js +13 -1
  33. package/dist/runtime/daimon/grokSkillsSupport.d.ts +20 -0
  34. package/dist/runtime/daimon/grokSkillsSupport.js +61 -0
  35. package/dist/runtime/daimon/grokWorkerConfigBytes.d.ts +126 -0
  36. package/dist/runtime/daimon/grokWorkerConfigBytes.js +157 -0
  37. package/dist/runtime/daimon/grokWorkerSkills.d.ts +61 -0
  38. package/dist/runtime/daimon/grokWorkerSkills.js +107 -0
  39. package/dist/runtime/types.d.ts +8 -0
  40. package/package.json +1 -1
  41. package/runtimes.yaml +5 -5
@@ -0,0 +1,61 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { parse as parseYaml } from "yaml";
3
+ import { SpawnfileError } from "../../shared/index.js";
4
+ import { daimonAgentSkillsFromResolved, parseDaimonAgentSkills } from "./grokWorkerSkills.js";
5
+ /** The first Daimon whose broker accepts `skillsSha256` and renders `$GROK_HOME/skills` (noopolis/daimon-harness#55). */
6
+ export const DAIMON_GROK_SKILLS_MIN_VERSION = "0.2.2";
7
+ const runtimesYamlUrl = new URL("../../../runtimes.yaml", import.meta.url);
8
+ let pinned;
9
+ /**
10
+ * The Daimon package version of the runtime image `runtimes.yaml` pins, from
11
+ * its never-moved `<package version>-<short commit>` tag. Read synchronously
12
+ * because `preflightAgent` (`spawnfile validate`) is synchronous.
13
+ */
14
+ export const pinnedDaimonRuntimeVersion = (source) => {
15
+ if (source === undefined && pinned !== undefined)
16
+ return pinned;
17
+ const document = parseYaml(source ?? readFileSync(runtimesYamlUrl, "utf8"));
18
+ const tag = document.runtimes?.daimon?.install?.tag;
19
+ const match = typeof tag === "string" ? /^(\d+\.\d+\.\d+)-[0-9a-f]{7,}$/u.exec(tag) : null;
20
+ if (!match)
21
+ throw new SpawnfileError("runtime_error", `runtimes.yaml pins no versioned Daimon runtime image tag: ${JSON.stringify(tag)}`);
22
+ if (source === undefined)
23
+ pinned = match[1];
24
+ return match[1];
25
+ };
26
+ const below = (version, minimum) => {
27
+ const left = version.split(".").map(Number), right = minimum.split(".").map(Number);
28
+ for (let index = 0; index < 3; index += 1)
29
+ if (left[index] !== right[index])
30
+ return left[index] < right[index];
31
+ return false;
32
+ };
33
+ /**
34
+ * Why a brokered Grok agent's declared skills cannot compile, or `undefined`.
35
+ *
36
+ * Two causes, both refused before anything is emitted: a skill Daimon's own
37
+ * parser would refuse (a name outside its component rule, a missing
38
+ * `SKILL.md`, a size past `AGENT_SKILLS_BOUNDS`), and a pinned runtime image
39
+ * older than 0.2.2, whose strict service parser rejects the `skillsSha256`
40
+ * key — so the broker, and every Grok agent of the organization, would fail
41
+ * to start.
42
+ */
43
+ export const daimonGrokSkillsError = (node, engine, version = () => pinnedDaimonRuntimeVersion()) => {
44
+ if (engine !== "grok" || node.skills.length === 0)
45
+ return undefined;
46
+ try {
47
+ parseDaimonAgentSkills(daimonAgentSkillsFromResolved(node.skills));
48
+ }
49
+ catch (error) {
50
+ if (error instanceof SpawnfileError)
51
+ return `Daimon Grok agent ${node.name}: ${error.message}`;
52
+ throw error;
53
+ }
54
+ const pinnedVersion = version();
55
+ if (below(pinnedVersion, DAIMON_GROK_SKILLS_MIN_VERSION)) {
56
+ return `Daimon Grok agent ${node.name} declares skills (${node.skills.map((skill) => skill.name).sort().join(", ")}), but runtimes.yaml pins `
57
+ + `Daimon ${pinnedVersion}, whose engine broker rejects service.json skillsSha256 and would refuse to start every Grok agent of the `
58
+ + `organization. Re-pin the Daimon runtime image to ${DAIMON_GROK_SKILLS_MIN_VERSION} or later, or drop the skills from this agent.`;
59
+ }
60
+ return undefined;
61
+ };
@@ -41,3 +41,129 @@ export declare const DAIMON_GROK_WORKER_PROFILE_SAMPLES: readonly [{
41
41
  readonly bytes: "[profiles.daimon-strict]\nextends = \"strict\"\nrestrict_network = true\ndeny = [\"/run/daimon-engine-broker\", \"/var/lib/daimon-workers/2201\", \"/var/lib/spawnfile/daimon/usage\"]\n";
42
42
  readonly denyPaths: readonly ["/var/lib/spawnfile/daimon/usage", "/run/daimon-engine-broker", "/run/daimon-engine-broker", "/var/lib/daimon-workers/2201"];
43
43
  }];
44
+ /** Daimon `grokWorkerSkillsSha256` / `renderGrokWorkerSkillsTree` samples; the Spawnfile mirror must reproduce them exactly. */
45
+ export declare const DAIMON_GROK_WORKER_SKILLS_SAMPLES: readonly [{
46
+ readonly sha256: "74a407e0569e692f5f354dd8613d2b6411493440cadd9ad197fcad31a63b6b34";
47
+ readonly skills: readonly [];
48
+ readonly tree: readonly [];
49
+ }, {
50
+ readonly sha256: "48f444624ff818f29446cbbdb2a3057f93fbf760b6c8fefb5d0914d50fa80f62";
51
+ readonly skills: readonly [{
52
+ readonly name: "review";
53
+ readonly files: readonly [{
54
+ readonly path: "SKILL.md";
55
+ readonly content: "---\nname: review\ndescription: Review a diff.\n---\n\nRead the diff first.\n";
56
+ }];
57
+ }];
58
+ readonly tree: readonly [{
59
+ readonly path: "skills";
60
+ readonly kind: "directory";
61
+ readonly uid: 0;
62
+ readonly gid: 0;
63
+ readonly mode: 365;
64
+ }, {
65
+ readonly path: "skills/review";
66
+ readonly kind: "directory";
67
+ readonly uid: 0;
68
+ readonly gid: 0;
69
+ readonly mode: 365;
70
+ }, {
71
+ readonly path: "skills/review/SKILL.md";
72
+ readonly kind: "file";
73
+ readonly uid: 0;
74
+ readonly gid: 0;
75
+ readonly mode: 292;
76
+ readonly content: "---\nname: review\ndescription: Review a diff.\n---\n\nRead the diff first.\n";
77
+ }];
78
+ }, {
79
+ readonly sha256: "0fd754e7ce8db521c404752446fa224b0e1389c21dbbc0985835e1734480a978";
80
+ readonly skills: readonly [{
81
+ readonly name: "zeta_notes";
82
+ readonly files: readonly [{
83
+ readonly path: "SKILL.md";
84
+ readonly content: "# Zeta été 🚀\n";
85
+ }, {
86
+ readonly path: "references/deep/guide.md";
87
+ readonly content: "nested\n";
88
+ }, {
89
+ readonly path: "references/a.md";
90
+ readonly content: "";
91
+ }];
92
+ }, {
93
+ readonly name: "alpha-1";
94
+ readonly files: readonly [{
95
+ readonly path: "SKILL.md";
96
+ readonly content: "alpha\r\n";
97
+ }];
98
+ }];
99
+ readonly tree: readonly [{
100
+ readonly path: "skills";
101
+ readonly kind: "directory";
102
+ readonly uid: 0;
103
+ readonly gid: 0;
104
+ readonly mode: 365;
105
+ }, {
106
+ readonly path: "skills/alpha-1";
107
+ readonly kind: "directory";
108
+ readonly uid: 0;
109
+ readonly gid: 0;
110
+ readonly mode: 365;
111
+ }, {
112
+ readonly path: "skills/alpha-1/SKILL.md";
113
+ readonly kind: "file";
114
+ readonly uid: 0;
115
+ readonly gid: 0;
116
+ readonly mode: 292;
117
+ readonly content: "alpha\r\n";
118
+ }, {
119
+ readonly path: "skills/zeta_notes";
120
+ readonly kind: "directory";
121
+ readonly uid: 0;
122
+ readonly gid: 0;
123
+ readonly mode: 365;
124
+ }, {
125
+ readonly path: "skills/zeta_notes/SKILL.md";
126
+ readonly kind: "file";
127
+ readonly uid: 0;
128
+ readonly gid: 0;
129
+ readonly mode: 292;
130
+ readonly content: "# Zeta été 🚀\n";
131
+ }, {
132
+ readonly path: "skills/zeta_notes/references";
133
+ readonly kind: "directory";
134
+ readonly uid: 0;
135
+ readonly gid: 0;
136
+ readonly mode: 365;
137
+ }, {
138
+ readonly path: "skills/zeta_notes/references/a.md";
139
+ readonly kind: "file";
140
+ readonly uid: 0;
141
+ readonly gid: 0;
142
+ readonly mode: 292;
143
+ readonly content: "";
144
+ }, {
145
+ readonly path: "skills/zeta_notes/references/deep";
146
+ readonly kind: "directory";
147
+ readonly uid: 0;
148
+ readonly gid: 0;
149
+ readonly mode: 365;
150
+ }, {
151
+ readonly path: "skills/zeta_notes/references/deep/guide.md";
152
+ readonly kind: "file";
153
+ readonly uid: 0;
154
+ readonly gid: 0;
155
+ readonly mode: 292;
156
+ readonly content: "nested\n";
157
+ }];
158
+ }];
159
+ /** Daimon's skills parser limits and name/path component rule; the Spawnfile mirror must use exactly these. */
160
+ export declare const DAIMON_AGENT_SKILLS_RULES: {
161
+ readonly bounds: {
162
+ readonly skills: 64;
163
+ readonly filesPerSkill: 64;
164
+ readonly fileBytes: 262144;
165
+ readonly totalBytes: 4194304;
166
+ readonly depth: 8;
167
+ };
168
+ readonly componentPattern: "^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$";
169
+ };
@@ -51,3 +51,160 @@ export const DAIMON_GROK_WORKER_PROFILE_SAMPLES = [
51
51
  ]
52
52
  }
53
53
  ];
54
+ /** Daimon `grokWorkerSkillsSha256` / `renderGrokWorkerSkillsTree` samples; the Spawnfile mirror must reproduce them exactly. */
55
+ export const DAIMON_GROK_WORKER_SKILLS_SAMPLES = [
56
+ {
57
+ "sha256": "74a407e0569e692f5f354dd8613d2b6411493440cadd9ad197fcad31a63b6b34",
58
+ "skills": [],
59
+ "tree": []
60
+ },
61
+ {
62
+ "sha256": "48f444624ff818f29446cbbdb2a3057f93fbf760b6c8fefb5d0914d50fa80f62",
63
+ "skills": [
64
+ {
65
+ "name": "review",
66
+ "files": [
67
+ {
68
+ "path": "SKILL.md",
69
+ "content": "---\nname: review\ndescription: Review a diff.\n---\n\nRead the diff first.\n"
70
+ }
71
+ ]
72
+ }
73
+ ],
74
+ "tree": [
75
+ {
76
+ "path": "skills",
77
+ "kind": "directory",
78
+ "uid": 0,
79
+ "gid": 0,
80
+ "mode": 365
81
+ },
82
+ {
83
+ "path": "skills/review",
84
+ "kind": "directory",
85
+ "uid": 0,
86
+ "gid": 0,
87
+ "mode": 365
88
+ },
89
+ {
90
+ "path": "skills/review/SKILL.md",
91
+ "kind": "file",
92
+ "uid": 0,
93
+ "gid": 0,
94
+ "mode": 292,
95
+ "content": "---\nname: review\ndescription: Review a diff.\n---\n\nRead the diff first.\n"
96
+ }
97
+ ]
98
+ },
99
+ {
100
+ "sha256": "0fd754e7ce8db521c404752446fa224b0e1389c21dbbc0985835e1734480a978",
101
+ "skills": [
102
+ {
103
+ "name": "zeta_notes",
104
+ "files": [
105
+ {
106
+ "path": "SKILL.md",
107
+ "content": "# Zeta été 🚀\n"
108
+ },
109
+ {
110
+ "path": "references/deep/guide.md",
111
+ "content": "nested\n"
112
+ },
113
+ {
114
+ "path": "references/a.md",
115
+ "content": ""
116
+ }
117
+ ]
118
+ },
119
+ {
120
+ "name": "alpha-1",
121
+ "files": [
122
+ {
123
+ "path": "SKILL.md",
124
+ "content": "alpha\r\n"
125
+ }
126
+ ]
127
+ }
128
+ ],
129
+ "tree": [
130
+ {
131
+ "path": "skills",
132
+ "kind": "directory",
133
+ "uid": 0,
134
+ "gid": 0,
135
+ "mode": 365
136
+ },
137
+ {
138
+ "path": "skills/alpha-1",
139
+ "kind": "directory",
140
+ "uid": 0,
141
+ "gid": 0,
142
+ "mode": 365
143
+ },
144
+ {
145
+ "path": "skills/alpha-1/SKILL.md",
146
+ "kind": "file",
147
+ "uid": 0,
148
+ "gid": 0,
149
+ "mode": 292,
150
+ "content": "alpha\r\n"
151
+ },
152
+ {
153
+ "path": "skills/zeta_notes",
154
+ "kind": "directory",
155
+ "uid": 0,
156
+ "gid": 0,
157
+ "mode": 365
158
+ },
159
+ {
160
+ "path": "skills/zeta_notes/SKILL.md",
161
+ "kind": "file",
162
+ "uid": 0,
163
+ "gid": 0,
164
+ "mode": 292,
165
+ "content": "# Zeta été 🚀\n"
166
+ },
167
+ {
168
+ "path": "skills/zeta_notes/references",
169
+ "kind": "directory",
170
+ "uid": 0,
171
+ "gid": 0,
172
+ "mode": 365
173
+ },
174
+ {
175
+ "path": "skills/zeta_notes/references/a.md",
176
+ "kind": "file",
177
+ "uid": 0,
178
+ "gid": 0,
179
+ "mode": 292,
180
+ "content": ""
181
+ },
182
+ {
183
+ "path": "skills/zeta_notes/references/deep",
184
+ "kind": "directory",
185
+ "uid": 0,
186
+ "gid": 0,
187
+ "mode": 365
188
+ },
189
+ {
190
+ "path": "skills/zeta_notes/references/deep/guide.md",
191
+ "kind": "file",
192
+ "uid": 0,
193
+ "gid": 0,
194
+ "mode": 292,
195
+ "content": "nested\n"
196
+ }
197
+ ]
198
+ }
199
+ ];
200
+ /** Daimon's skills parser limits and name/path component rule; the Spawnfile mirror must use exactly these. */
201
+ export const DAIMON_AGENT_SKILLS_RULES = {
202
+ "bounds": {
203
+ "skills": 64,
204
+ "filesPerSkill": 64,
205
+ "fileBytes": 262144,
206
+ "totalBytes": 4194304,
207
+ "depth": 8
208
+ },
209
+ "componentPattern": "^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$"
210
+ };
@@ -0,0 +1,61 @@
1
+ import type { ResolvedSkill } from "../../compiler/types.js";
2
+ /**
3
+ * Mirror of Daimon's `grokWorkerSkills.ts` (`@noopolis/daimon/runtime` 0.2.2):
4
+ * the engine-neutral skills input, its `noopolis.daimon.grok-worker-skills.v1`
5
+ * digest and the `$GROK_HOME/skills` tree root provisioning writes. Pinned to
6
+ * Daimon's own output by `DAIMON_GROK_WORKER_SKILLS_SAMPLES`
7
+ * (`scripts/vendor-daimon-grok-contract.ts`), exactly as the sandbox profile
8
+ * mirror is: production renders `service.json` at compile time and never
9
+ * loads Daimon, while the broker re-hashes the provisioned tree before every
10
+ * turn, so one byte of drift here refuses every Grok turn rather than
11
+ * degrading quietly. Training additionally cross-checks this digest against
12
+ * Daimon's projection (`training/broker/projection.ts`).
13
+ */
14
+ export declare const DAIMON_GROK_WORKER_SKILLS_LAYOUT: {
15
+ readonly relativePath: "skills";
16
+ readonly uid: 0;
17
+ readonly gid: 0;
18
+ readonly directoryMode: 365;
19
+ readonly fileMode: 292;
20
+ readonly digestVersion: "noopolis.daimon.grok-worker-skills.v1";
21
+ };
22
+ export declare const DAIMON_AGENT_SKILLS_BOUNDS: {
23
+ readonly skills: 64;
24
+ readonly filesPerSkill: 64;
25
+ readonly fileBytes: 262144;
26
+ readonly totalBytes: 4194304;
27
+ readonly depth: 8;
28
+ };
29
+ export interface DaimonAgentSkillFile {
30
+ path: string;
31
+ content: string;
32
+ }
33
+ export interface DaimonAgentSkill {
34
+ name: string;
35
+ files: DaimonAgentSkillFile[];
36
+ }
37
+ export interface DaimonGrokWorkerSkillsEntry {
38
+ path: string;
39
+ kind: "directory" | "file";
40
+ uid: number;
41
+ gid: number;
42
+ mode: number;
43
+ content?: string;
44
+ }
45
+ /** What a registration carries when its agent declares skills; absent otherwise. */
46
+ export interface DaimonGrokWorkerSkills {
47
+ sha256: string;
48
+ tree: DaimonGrokWorkerSkillsEntry[];
49
+ }
50
+ /** Daimon's skill name and path component rule (vendored as `DAIMON_AGENT_SKILLS_RULES.componentPattern`). */
51
+ export declare const DAIMON_AGENT_SKILL_COMPONENT: RegExp;
52
+ /** Daimon's `parseAgentSkills`: the same refusals, sorted by name and path so equal sets digest equally. */
53
+ export declare const parseDaimonAgentSkills: (skills: readonly DaimonAgentSkill[]) => DaimonAgentSkill[];
54
+ /** Daimon's `grokWorkerSkillsSha256`: sha256 over the canonical (key-sorted) description of the tree. */
55
+ export declare const daimonGrokWorkerSkillsSha256: (skills: readonly DaimonAgentSkill[]) => string;
56
+ /** Daimon's `renderGrokWorkerSkillsTree`: every entry under `$GROK_HOME`, parents before children; none for no skills. */
57
+ export declare const renderDaimonGrokWorkerSkillsTree: (skills: readonly DaimonAgentSkill[]) => DaimonGrokWorkerSkillsEntry[];
58
+ /** Spawnfile's declared skills are one `SKILL.md` each, emitted verbatim into `<workspace>/.agents/skills/<name>/`. */
59
+ export declare const daimonAgentSkillsFromResolved: (skills: readonly Pick<ResolvedSkill, "name" | "content">[]) => DaimonAgentSkill[];
60
+ /** The registration's skills member: `undefined` for none, so a skill-less registration stays byte-identical. */
61
+ export declare const resolveDaimonGrokWorkerSkills: (skills: readonly DaimonAgentSkill[] | undefined) => DaimonGrokWorkerSkills | undefined;
@@ -0,0 +1,107 @@
1
+ import { createHash } from "node:crypto";
2
+ import { SpawnfileError } from "../../shared/index.js";
3
+ /**
4
+ * Mirror of Daimon's `grokWorkerSkills.ts` (`@noopolis/daimon/runtime` 0.2.2):
5
+ * the engine-neutral skills input, its `noopolis.daimon.grok-worker-skills.v1`
6
+ * digest and the `$GROK_HOME/skills` tree root provisioning writes. Pinned to
7
+ * Daimon's own output by `DAIMON_GROK_WORKER_SKILLS_SAMPLES`
8
+ * (`scripts/vendor-daimon-grok-contract.ts`), exactly as the sandbox profile
9
+ * mirror is: production renders `service.json` at compile time and never
10
+ * loads Daimon, while the broker re-hashes the provisioned tree before every
11
+ * turn, so one byte of drift here refuses every Grok turn rather than
12
+ * degrading quietly. Training additionally cross-checks this digest against
13
+ * Daimon's projection (`training/broker/projection.ts`).
14
+ */
15
+ export const DAIMON_GROK_WORKER_SKILLS_LAYOUT = {
16
+ relativePath: "skills", uid: 0, gid: 0, directoryMode: 0o555, fileMode: 0o444,
17
+ digestVersion: "noopolis.daimon.grok-worker-skills.v1"
18
+ };
19
+ export const DAIMON_AGENT_SKILLS_BOUNDS = { skills: 64, filesPerSkill: 64, fileBytes: 262_144, totalBytes: 4_194_304, depth: 8 };
20
+ /** Daimon's skill name and path component rule (vendored as `DAIMON_AGENT_SKILLS_RULES.componentPattern`). */
21
+ export const DAIMON_AGENT_SKILL_COMPONENT = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$/u;
22
+ const COMPONENT = DAIMON_AGENT_SKILL_COMPONENT;
23
+ const LONE_SURROGATE = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/u;
24
+ const invalid = (detail) => { throw new SpawnfileError("compile_error", `Invalid Daimon agent skills: ${detail}`); };
25
+ const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
26
+ const byteLength = (content) => Buffer.byteLength(content, "utf8");
27
+ /** Daimon's `parseAgentSkills`: the same refusals, sorted by name and path so equal sets digest equally. */
28
+ export const parseDaimonAgentSkills = (skills) => {
29
+ if (skills.length > DAIMON_AGENT_SKILLS_BOUNDS.skills)
30
+ invalid("too many skills");
31
+ let total = 0;
32
+ const names = new Set();
33
+ const parsed = skills.map((skill) => {
34
+ if (!COMPONENT.test(skill.name))
35
+ invalid(`skill name ${JSON.stringify(skill.name)}`);
36
+ if (names.has(skill.name))
37
+ invalid(`duplicate skill ${skill.name}`);
38
+ names.add(skill.name);
39
+ if (skill.files.length === 0 || skill.files.length > DAIMON_AGENT_SKILLS_BOUNDS.filesPerSkill)
40
+ invalid(`files of ${skill.name}`);
41
+ const paths = new Set();
42
+ for (const file of skill.files) {
43
+ const parts = file.path.split("/");
44
+ if (parts.length > DAIMON_AGENT_SKILLS_BOUNDS.depth || parts.some((part) => !COMPONENT.test(part)))
45
+ invalid(`path ${JSON.stringify(`${skill.name}/${file.path}`)}`);
46
+ const bytes = byteLength(file.content);
47
+ if (bytes > DAIMON_AGENT_SKILLS_BOUNDS.fileBytes)
48
+ invalid(`size of ${skill.name}/${file.path}`);
49
+ total += bytes;
50
+ if (paths.has(file.path))
51
+ invalid(`duplicate path ${skill.name}/${file.path}`);
52
+ paths.add(file.path);
53
+ if (LONE_SURROGATE.test(file.content))
54
+ invalid(`content of ${skill.name}/${file.path} is not Unicode text`);
55
+ }
56
+ for (const candidate of paths)
57
+ if ([...paths].some((other) => other.startsWith(`${candidate}/`)))
58
+ invalid(`path collision ${skill.name}/${candidate}`);
59
+ if (!paths.has("SKILL.md"))
60
+ invalid(`${skill.name} has no SKILL.md`);
61
+ return { name: skill.name, files: skill.files.map((file) => ({ path: file.path, content: file.content })).sort((a, b) => compare(a.path, b.path)) };
62
+ });
63
+ if (total > DAIMON_AGENT_SKILLS_BOUNDS.totalBytes)
64
+ invalid("total size");
65
+ return parsed.sort((a, b) => compare(a.name, b.name));
66
+ };
67
+ /** Daimon's `grokWorkerSkillsSha256`: sha256 over the canonical (key-sorted) description of the tree. */
68
+ export const daimonGrokWorkerSkillsSha256 = (skills) => {
69
+ // Keys are written in canonical (sorted) order, so plain JSON.stringify is Daimon's canonicalJson here.
70
+ const description = {
71
+ skills: parseDaimonAgentSkills(skills).map((skill) => ({
72
+ files: skill.files.map((file) => ({ bytes: byteLength(file.content), path: file.path, sha256: createHash("sha256").update(file.content, "utf8").digest("hex") })),
73
+ name: skill.name
74
+ })),
75
+ version: DAIMON_GROK_WORKER_SKILLS_LAYOUT.digestVersion
76
+ };
77
+ return createHash("sha256").update(JSON.stringify(description)).digest("hex");
78
+ };
79
+ /** Daimon's `renderGrokWorkerSkillsTree`: every entry under `$GROK_HOME`, parents before children; none for no skills. */
80
+ export const renderDaimonGrokWorkerSkillsTree = (skills) => {
81
+ const parsed = parseDaimonAgentSkills(skills);
82
+ if (parsed.length === 0)
83
+ return [];
84
+ const { relativePath, uid, gid, directoryMode, fileMode } = DAIMON_GROK_WORKER_SKILLS_LAYOUT;
85
+ const directory = (entry) => ({ path: entry, kind: "directory", uid, gid, mode: directoryMode });
86
+ const entries = [directory(relativePath)];
87
+ const seen = new Set([relativePath]);
88
+ for (const skill of parsed) {
89
+ for (const file of skill.files) {
90
+ const parts = [relativePath, skill.name, ...file.path.split("/")];
91
+ for (let depth = 2; depth < parts.length; depth += 1) {
92
+ const relative = parts.slice(0, depth).join("/");
93
+ if (!seen.has(relative)) {
94
+ seen.add(relative);
95
+ entries.push(directory(relative));
96
+ }
97
+ }
98
+ entries.push({ path: parts.join("/"), kind: "file", uid, gid, mode: fileMode, content: file.content });
99
+ }
100
+ }
101
+ return entries;
102
+ };
103
+ /** Spawnfile's declared skills are one `SKILL.md` each, emitted verbatim into `<workspace>/.agents/skills/<name>/`. */
104
+ export const daimonAgentSkillsFromResolved = (skills) => skills.map((skill) => ({ name: skill.name, files: [{ path: "SKILL.md", content: skill.content }] }));
105
+ /** The registration's skills member: `undefined` for none, so a skill-less registration stays byte-identical. */
106
+ export const resolveDaimonGrokWorkerSkills = (skills) => skills === undefined || skills.length === 0 ? undefined
107
+ : { sha256: daimonGrokWorkerSkillsSha256(skills), tree: renderDaimonGrokWorkerSkillsTree(skills) };
@@ -64,6 +64,14 @@ export interface ContainerTarget {
64
64
  maxTokens?: number;
65
65
  timeoutMs?: number;
66
66
  }>;
67
+ /** Daimon only: each brokered Grok agent's declared skills (absent for none), rendered into its worker `$GROK_HOME/skills`. */
68
+ grokSkillsByNodeId?: Record<string, {
69
+ name: string;
70
+ files: {
71
+ path: string;
72
+ content: string;
73
+ }[];
74
+ }[]>;
67
75
  files: EmittedFile[];
68
76
  id: string;
69
77
  opaqueMountTargets?: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spawnfile",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Canonical source compiler for autonomous agents and teams.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/runtimes.yaml CHANGED
@@ -13,16 +13,16 @@
13
13
  runtimes:
14
14
  daimon:
15
15
  remote: git@github.com:noopolis/daimon-harness.git
16
- ref: 14d0298c00861f949bb6993d1af9751cde53319f
16
+ ref: cbe75eb1fd5fed63782e54c4df72603e33e67d3e
17
17
  default_branch: main
18
18
  install:
19
19
  kind: container_image
20
20
  image: noopolis/spawnfile-runtime-daimon
21
- tag: 0.2.1-14d0298
22
- digest: sha256:2de3bd522ce88202b3295efa4f19c72c350e5e18c25ccba55e5acf3395e8569c
21
+ tag: 0.2.3-cbe75eb
22
+ digest: sha256:2b656c1c638b91b61deafc61786db6931501399da81fc88df7b93f12ed93eb55
23
23
  capability_receipts:
24
- amd64: sha256:26fe7ff328bb45fb6643b0792ab38b2658d5fcc3d2d8bd67d9848ea2987c04cc
25
- arm64: sha256:347da7c613ad3430e018de829587a6d686c498c6168ef4be6b8dd501fdb07ebb
24
+ amd64: sha256:c3c79482e692370900f9225e8d2b720b1619e4c40d36493ece2c331b0a3661ef
25
+ arm64: sha256:53ca61a1011b73ed2fb985c9c460cf2fc0e51f12016ba8add07969ac7b3412be
26
26
  contract_manifest_sha256: sha256:e39551277c6727c8807b1247e759f57addcee3f17f1405ebc927179647d64de6
27
27
  status: active
28
28