spawnfile 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compiler/containerArtifactsPlans.js +1 -0
- package/dist/compiler/containerArtifactsTypes.d.ts +8 -0
- package/dist/compiler/containerDaimonGrokWorkerProvisioning.js +21 -1
- package/dist/compiler/containerDaimonGrokWorkerRender.d.ts +9 -0
- package/dist/compiler/containerDaimonGrokWorkerRender.js +7 -2
- package/dist/compiler/training/broker/declaration.d.ts +7 -0
- package/dist/compiler/training/broker/declaration.js +13 -1
- package/dist/compiler/training/broker/entrypoint.js +7 -10
- package/dist/compiler/training/broker/index.d.ts +2 -1
- package/dist/compiler/training/broker/index.js +2 -1
- package/dist/compiler/training/broker/projection.d.ts +9 -0
- package/dist/compiler/training/broker/projection.js +20 -1
- package/dist/compiler/training/broker/recycleClient.d.ts +33 -0
- package/dist/compiler/training/broker/recycleClient.js +112 -0
- package/dist/compiler/training/broker/registration.d.ts +3 -0
- package/dist/compiler/training/broker/registration.js +3 -0
- package/dist/compiler/training/broker/rootScript.d.ts +32 -0
- package/dist/compiler/training/broker/rootScript.js +33 -0
- package/dist/compiler/training/broker/runtime.js +4 -3
- package/dist/compiler/training/index.d.ts +3 -0
- package/dist/compiler/training/index.js +3 -0
- package/dist/compiler/training/preparation/assets/package-lock.json +2 -2
- package/dist/compiler/training/preparation/broker.d.ts +10 -1
- package/dist/compiler/training/preparation/broker.js +25 -3
- package/dist/compiler/training/preparation/prepare.js +5 -4
- package/dist/compiler/training/subjectDeclaration.d.ts +25 -0
- package/dist/compiler/training/subjectDeclaration.js +52 -0
- package/dist/compiler/training/subjectLease.d.ts +114 -0
- package/dist/compiler/training/subjectLease.js +175 -0
- package/dist/runtime/daimon/adapter.d.ts +7 -9
- package/dist/runtime/daimon/adapter.js +14 -19
- package/dist/runtime/daimon/config.js +13 -1
- package/dist/runtime/daimon/grokSkillsSupport.d.ts +20 -0
- package/dist/runtime/daimon/grokSkillsSupport.js +61 -0
- package/dist/runtime/daimon/grokWorkerConfigBytes.d.ts +126 -0
- package/dist/runtime/daimon/grokWorkerConfigBytes.js +157 -0
- package/dist/runtime/daimon/grokWorkerSkills.d.ts +61 -0
- package/dist/runtime/daimon/grokWorkerSkills.js +107 -0
- package/dist/runtime/types.d.ts +8 -0
- package/package.json +1 -1
- 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) };
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -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
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:
|
|
16
|
+
ref: 00e8f1dcf2b31d32e8048dd292df840a749b7869
|
|
17
17
|
default_branch: main
|
|
18
18
|
install:
|
|
19
19
|
kind: container_image
|
|
20
20
|
image: noopolis/spawnfile-runtime-daimon
|
|
21
|
-
tag: 0.2.
|
|
22
|
-
digest: sha256:
|
|
21
|
+
tag: 0.2.2-00e8f1d
|
|
22
|
+
digest: sha256:8ea499317850b9cd72039d1108b8b58cd0c9735256fbd7ac71d5dbe32969dea2
|
|
23
23
|
capability_receipts:
|
|
24
|
-
amd64: sha256:
|
|
25
|
-
arm64: sha256:
|
|
24
|
+
amd64: sha256:b68d2b27f8ec7f6237f2e2a5253fa3fc16f5555968ce82a07350ba258e659a93
|
|
25
|
+
arm64: sha256:e3bab10df4fb65089895974e23048ebe2142f47b42ef0e939c0dd1595f3d8b94
|
|
26
26
|
contract_manifest_sha256: sha256:e39551277c6727c8807b1247e759f57addcee3f17f1405ebc927179647d64de6
|
|
27
27
|
status: active
|
|
28
28
|
|