@madebywild/agent-harness-framework 1.1.0 → 1.3.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.
- package/README.md +32 -32
- package/dist/cli/adapters/commander.d.ts.map +1 -1
- package/dist/cli/adapters/commander.js +76 -1
- package/dist/cli/adapters/commander.js.map +1 -1
- package/dist/cli/adapters/interactive.d.ts.map +1 -1
- package/dist/cli/adapters/interactive.js +111 -11
- package/dist/cli/adapters/interactive.js.map +1 -1
- package/dist/cli/command-registry.d.ts.map +1 -1
- package/dist/cli/command-registry.js +111 -4
- package/dist/cli/command-registry.js.map +1 -1
- package/dist/cli/contracts.d.ts +17 -3
- package/dist/cli/contracts.d.ts.map +1 -1
- package/dist/cli/handlers/entities.d.ts +4 -0
- package/dist/cli/handlers/entities.d.ts.map +1 -1
- package/dist/cli/handlers/entities.js +22 -0
- package/dist/cli/handlers/entities.js.map +1 -1
- package/dist/cli/handlers/init.d.ts +7 -1
- package/dist/cli/handlers/init.d.ts.map +1 -1
- package/dist/cli/handlers/init.js +41 -2
- package/dist/cli/handlers/init.js.map +1 -1
- package/dist/cli/handlers/preset.d.ts +13 -0
- package/dist/cli/handlers/preset.d.ts.map +1 -0
- package/dist/cli/handlers/preset.js +51 -0
- package/dist/cli/handlers/preset.js.map +1 -0
- package/dist/cli/renderers/text.d.ts.map +1 -1
- package/dist/cli/renderers/text.js +55 -0
- package/dist/cli/renderers/text.js.map +1 -1
- package/dist/delegated-init.d.ts +20 -0
- package/dist/delegated-init.d.ts.map +1 -0
- package/dist/delegated-init.js +71 -0
- package/dist/delegated-init.js.map +1 -0
- package/dist/engine/entities.d.ts +14 -1
- package/dist/engine/entities.d.ts.map +1 -1
- package/dist/engine/entities.js +263 -107
- package/dist/engine/entities.js.map +1 -1
- package/dist/engine/presets.d.ts +3 -0
- package/dist/engine/presets.d.ts.map +1 -0
- package/dist/engine/presets.js +237 -0
- package/dist/engine/presets.js.map +1 -0
- package/dist/engine/utils.d.ts +1 -0
- package/dist/engine/utils.d.ts.map +1 -1
- package/dist/engine/utils.js +12 -6
- package/dist/engine/utils.js.map +1 -1
- package/dist/engine.d.ts +13 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +43 -1
- package/dist/engine.js.map +1 -1
- package/dist/entity-registries.d.ts +28 -2
- package/dist/entity-registries.d.ts.map +1 -1
- package/dist/entity-registries.js +209 -135
- package/dist/entity-registries.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +123 -2
- package/dist/loader.js.map +1 -1
- package/dist/paths.d.ts +3 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +8 -0
- package/dist/paths.js.map +1 -1
- package/dist/planner.d.ts.map +1 -1
- package/dist/planner.js +24 -0
- package/dist/planner.js.map +1 -1
- package/dist/preset-builtin.d.ts +3 -0
- package/dist/preset-builtin.d.ts.map +1 -0
- package/dist/preset-builtin.js +139 -0
- package/dist/preset-builtin.js.map +1 -0
- package/dist/preset-packages.d.ts +9 -0
- package/dist/preset-packages.d.ts.map +1 -0
- package/dist/preset-packages.js +109 -0
- package/dist/preset-packages.js.map +1 -0
- package/dist/presets.d.ts +12 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +79 -0
- package/dist/presets.js.map +1 -0
- package/dist/provider-adapters/claude.d.ts.map +1 -1
- package/dist/provider-adapters/claude.js +44 -13
- package/dist/provider-adapters/claude.js.map +1 -1
- package/dist/provider-adapters/codex.d.ts.map +1 -1
- package/dist/provider-adapters/codex.js +10 -6
- package/dist/provider-adapters/codex.js.map +1 -1
- package/dist/provider-adapters/copilot.d.ts.map +1 -1
- package/dist/provider-adapters/copilot.js +16 -2
- package/dist/provider-adapters/copilot.js.map +1 -1
- package/dist/registry-validator.d.ts.map +1 -1
- package/dist/registry-validator.js +150 -7
- package/dist/registry-validator.js.map +1 -1
- package/dist/repository.d.ts.map +1 -1
- package/dist/repository.js +4 -0
- package/dist/repository.js.map +1 -1
- package/dist/types.d.ts +55 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +10 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +56 -0
- package/dist/utils.js.map +1 -1
- package/package.json +16 -2
package/dist/engine/entities.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import * as TOML from "@iarna/toml";
|
|
3
4
|
import { DEFAULT_REGISTRY_ID, providerIdSchema } from "@madebywild/agent-harness-manifest";
|
|
4
5
|
import { fetchEntityFromRegistry } from "../entity-registries.js";
|
|
5
|
-
import { DEFAULT_PROMPT_SOURCE_PATH, defaultCommandOverridePath, defaultCommandSourcePath, defaultHookOverridePath, defaultHookSourcePath, defaultMcpOverridePath, defaultMcpSourcePath, defaultPromptOverridePath, defaultSkillOverridePath, defaultSkillSourcePath, defaultSubagentOverridePath, defaultSubagentSourcePath, resolveHarnessPaths, } from "../paths.js";
|
|
6
|
+
import { DEFAULT_PROMPT_SOURCE_PATH, defaultCommandOverridePath, defaultCommandSourcePath, defaultHookOverridePath, defaultHookSourcePath, defaultMcpOverridePath, defaultMcpSourcePath, defaultPromptOverridePath, defaultSettingsSourcePath, defaultSkillOverridePath, defaultSkillSourcePath, defaultSubagentOverridePath, defaultSubagentSourcePath, resolveHarnessPaths, } from "../paths.js";
|
|
6
7
|
import { listFilesRecursively, removeIfExists, writeLock, writeManifest } from "../repository.js";
|
|
7
8
|
import { CLI_ENTITY_TO_MANIFEST_ENTITY, CLI_ENTITY_TYPES } from "../types.js";
|
|
8
|
-
import { ensureParentDir, exists, normalizeRelativePath, nowIso, sha256, stableStringify } from "../utils.js";
|
|
9
|
+
import { ensureParentDir, exists, normalizeRelativePath, nowIso, parseJsonAsRecord, parseTomlAsRecord, sha256, stableStringify, withSingleTrailingNewline, } from "../utils.js";
|
|
9
10
|
import { readLockOrDefault, readManifestOrThrow, removeLockEntityRecord, setLockEntityRecord, upsertLockEntityRecord, writeManagedSourceIndex, } from "./state.js";
|
|
10
11
|
import { computeSkillSourceSha, isSkillOverrideFile, manifestEntityTypeToCliEntityType, registryIdFromInput, resolveEntityRegistrySelection, resolveRemoveTargetId, sortEntities, validateEntityId, } from "./utils.js";
|
|
11
12
|
export async function ensureOverrideFiles(cwd, entityType, entityId, existing) {
|
|
@@ -58,6 +59,12 @@ export async function readCurrentSourceSha(cwd, entity) {
|
|
|
58
59
|
}
|
|
59
60
|
return sha256(stableStringify(parsed));
|
|
60
61
|
}
|
|
62
|
+
if (entity.type === "settings") {
|
|
63
|
+
const text = await fs.readFile(sourceAbs, "utf8");
|
|
64
|
+
const provider = resolveSettingsProviderOrThrow(entity.id);
|
|
65
|
+
const parsed = parseSettingsPayloadFromText(provider, text, entity.sourcePath);
|
|
66
|
+
return sha256(stableStringify(parsed));
|
|
67
|
+
}
|
|
61
68
|
if (entity.type === "command") {
|
|
62
69
|
const text = await fs.readFile(sourceAbs, "utf8");
|
|
63
70
|
return sha256(text);
|
|
@@ -80,6 +87,29 @@ export async function loadSkillSourceHashes(skillRootAbs) {
|
|
|
80
87
|
output.sort((left, right) => left.path.localeCompare(right.path));
|
|
81
88
|
return output;
|
|
82
89
|
}
|
|
90
|
+
function resolveSettingsProviderOrThrow(id) {
|
|
91
|
+
try {
|
|
92
|
+
return providerIdSchema.parse(id);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw new Error(`Settings id must be one of: ${providerIdSchema.options.join(", ")}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function parseSettingsPayloadFromText(provider, text, sourcePath) {
|
|
99
|
+
try {
|
|
100
|
+
return provider === "codex" ? parseTomlAsRecord(text, TOML) : parseJsonAsRecord(text);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
const format = provider === "codex" ? "TOML" : "JSON";
|
|
104
|
+
throw new Error(`Settings source '${sourcePath}' is invalid ${format}: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function serializeSettingsPayload(provider, payload) {
|
|
108
|
+
if (provider === "codex") {
|
|
109
|
+
return withSingleTrailingNewline(TOML.stringify(payload));
|
|
110
|
+
}
|
|
111
|
+
return stableStringify(payload);
|
|
112
|
+
}
|
|
83
113
|
export async function materializeFetchedEntity(cwd, entity, fetched) {
|
|
84
114
|
if (entity.type === "prompt" && fetched.type === "prompt") {
|
|
85
115
|
const sourceAbs = path.join(cwd, entity.sourcePath);
|
|
@@ -105,6 +135,12 @@ export async function materializeFetchedEntity(cwd, entity, fetched) {
|
|
|
105
135
|
await fs.writeFile(sourceAbs, fetched.sourceText, "utf8");
|
|
106
136
|
return;
|
|
107
137
|
}
|
|
138
|
+
if (entity.type === "settings" && fetched.type === "settings") {
|
|
139
|
+
const sourceAbs = path.join(cwd, entity.sourcePath);
|
|
140
|
+
await ensureParentDir(sourceAbs);
|
|
141
|
+
await fs.writeFile(sourceAbs, serializeSettingsPayload(fetched.provider, fetched.sourcePayload), "utf8");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
108
144
|
if (entity.type === "command" && fetched.type === "command") {
|
|
109
145
|
const sourceAbs = path.join(cwd, entity.sourcePath);
|
|
110
146
|
await ensureParentDir(sourceAbs);
|
|
@@ -146,18 +182,29 @@ export async function addPromptEntity(cwd, options) {
|
|
|
146
182
|
if (await exists(sourceAbs)) {
|
|
147
183
|
throw new Error(`Cannot add prompt because '${sourcePath}' already exists`);
|
|
148
184
|
}
|
|
149
|
-
|
|
150
|
-
let
|
|
185
|
+
let sourceText;
|
|
186
|
+
let registryId;
|
|
151
187
|
let importedSourceSha256;
|
|
152
188
|
let registryRevision;
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
189
|
+
if (options?.sourceText) {
|
|
190
|
+
sourceText = options.sourceText;
|
|
191
|
+
registryId = DEFAULT_REGISTRY_ID;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
const registry = resolveEntityRegistrySelection(manifest, options?.registry);
|
|
195
|
+
registryId = registry.id;
|
|
196
|
+
if (registry.definition.type === "git") {
|
|
197
|
+
const fetched = await fetchEntityFromRegistry(registry.id, registry.definition, "prompt", "system");
|
|
198
|
+
if (fetched.type !== "prompt") {
|
|
199
|
+
throw new Error(`REGISTRY_FETCH_FAILED: expected prompt from registry '${registry.id}'`);
|
|
200
|
+
}
|
|
201
|
+
sourceText = fetched.sourceText;
|
|
202
|
+
importedSourceSha256 = fetched.importedSourceSha256;
|
|
203
|
+
registryRevision = fetched.registryRevision;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
sourceText = "# System Prompt\n\nDescribe the core behavior for the assistant.\n";
|
|
157
207
|
}
|
|
158
|
-
sourceText = fetched.sourceText;
|
|
159
|
-
importedSourceSha256 = fetched.importedSourceSha256;
|
|
160
|
-
registryRevision = fetched.registryRevision;
|
|
161
208
|
}
|
|
162
209
|
await ensureParentDir(sourceAbs);
|
|
163
210
|
await fs.writeFile(sourceAbs, sourceText, "utf8");
|
|
@@ -165,7 +212,7 @@ export async function addPromptEntity(cwd, options) {
|
|
|
165
212
|
manifest.entities.push({
|
|
166
213
|
id: "system",
|
|
167
214
|
type: "prompt",
|
|
168
|
-
registry:
|
|
215
|
+
registry: registryId,
|
|
169
216
|
sourcePath,
|
|
170
217
|
overrides,
|
|
171
218
|
enabled: true,
|
|
@@ -176,7 +223,7 @@ export async function addPromptEntity(cwd, options) {
|
|
|
176
223
|
await upsertLockEntityRecord(paths, manifest, {
|
|
177
224
|
id: "system",
|
|
178
225
|
type: "prompt",
|
|
179
|
-
registry:
|
|
226
|
+
registry: registryId,
|
|
180
227
|
sourceSha256: sha256(sourceText),
|
|
181
228
|
overrideSha256ByProvider: overrideShaByProvider,
|
|
182
229
|
importedSourceSha256,
|
|
@@ -196,35 +243,48 @@ export async function addSkillEntity(cwd, skillId, options) {
|
|
|
196
243
|
if (await exists(skillRootAbs)) {
|
|
197
244
|
throw new Error(`Cannot add skill because '${skillRootRel}' already exists`);
|
|
198
245
|
}
|
|
199
|
-
const registry = resolveEntityRegistrySelection(manifest, options?.registry);
|
|
200
246
|
let sourceSha256;
|
|
247
|
+
let registryId;
|
|
201
248
|
let importedSourceSha256;
|
|
202
249
|
let registryRevision;
|
|
203
250
|
let skillFiles;
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
throw new Error(`REGISTRY_FETCH_FAILED: expected skill '${skillId}' from registry '${registry.id}'`);
|
|
208
|
-
}
|
|
209
|
-
skillFiles = fetched.files.map((file) => ({
|
|
210
|
-
path: file.path,
|
|
251
|
+
if (options?.files) {
|
|
252
|
+
skillFiles = options.files.map((file) => ({
|
|
253
|
+
path: normalizeRelativePath(file.path),
|
|
211
254
|
content: file.content,
|
|
212
|
-
sha256: file.
|
|
255
|
+
sha256: sha256(file.content),
|
|
213
256
|
}));
|
|
214
|
-
sourceSha256 =
|
|
215
|
-
|
|
216
|
-
registryRevision = fetched.registryRevision;
|
|
257
|
+
sourceSha256 = computeSkillSourceSha(skillFiles.map((file) => ({ path: file.path, sha256: file.sha256 })));
|
|
258
|
+
registryId = DEFAULT_REGISTRY_ID;
|
|
217
259
|
}
|
|
218
260
|
else {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
261
|
+
const registry = resolveEntityRegistrySelection(manifest, options?.registry);
|
|
262
|
+
registryId = registry.id;
|
|
263
|
+
if (registry.definition.type === "git") {
|
|
264
|
+
const fetched = await fetchEntityFromRegistry(registry.id, registry.definition, "skill", skillId);
|
|
265
|
+
if (fetched.type !== "skill") {
|
|
266
|
+
throw new Error(`REGISTRY_FETCH_FAILED: expected skill '${skillId}' from registry '${registry.id}'`);
|
|
267
|
+
}
|
|
268
|
+
skillFiles = fetched.files.map((file) => ({
|
|
269
|
+
path: file.path,
|
|
270
|
+
content: file.content,
|
|
271
|
+
sha256: file.sha256,
|
|
272
|
+
}));
|
|
273
|
+
sourceSha256 = fetched.importedSourceSha256;
|
|
274
|
+
importedSourceSha256 = fetched.importedSourceSha256;
|
|
275
|
+
registryRevision = fetched.registryRevision;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
const content = `---\nname: ${skillId}\ndescription: Describe what this skill does.\n---\n\n# ${skillId}\n\nAdd usage guidance here.\n`;
|
|
279
|
+
skillFiles = [
|
|
280
|
+
{
|
|
281
|
+
path: "SKILL.md",
|
|
282
|
+
content,
|
|
283
|
+
sha256: sha256(content),
|
|
284
|
+
},
|
|
285
|
+
];
|
|
286
|
+
sourceSha256 = computeSkillSourceSha(skillFiles.map((file) => ({ path: file.path, sha256: file.sha256 })));
|
|
287
|
+
}
|
|
228
288
|
}
|
|
229
289
|
for (const file of skillFiles) {
|
|
230
290
|
const absolute = path.join(skillRootAbs, file.path);
|
|
@@ -235,7 +295,7 @@ export async function addSkillEntity(cwd, skillId, options) {
|
|
|
235
295
|
manifest.entities.push({
|
|
236
296
|
id: skillId,
|
|
237
297
|
type: "skill",
|
|
238
|
-
registry:
|
|
298
|
+
registry: registryId,
|
|
239
299
|
sourcePath,
|
|
240
300
|
overrides,
|
|
241
301
|
enabled: true,
|
|
@@ -246,7 +306,7 @@ export async function addSkillEntity(cwd, skillId, options) {
|
|
|
246
306
|
await upsertLockEntityRecord(paths, manifest, {
|
|
247
307
|
id: skillId,
|
|
248
308
|
type: "skill",
|
|
249
|
-
registry:
|
|
309
|
+
registry: registryId,
|
|
250
310
|
sourceSha256,
|
|
251
311
|
overrideSha256ByProvider: overrideShaByProvider,
|
|
252
312
|
importedSourceSha256,
|
|
@@ -265,28 +325,36 @@ export async function addMcpEntity(cwd, configId, options) {
|
|
|
265
325
|
if (await exists(sourceAbs)) {
|
|
266
326
|
throw new Error(`Cannot add MCP config because '${sourcePath}' already exists`);
|
|
267
327
|
}
|
|
268
|
-
const registry = resolveEntityRegistrySelection(manifest, options?.registry);
|
|
269
328
|
let sourceJson;
|
|
329
|
+
let registryId;
|
|
270
330
|
let importedSourceSha256;
|
|
271
331
|
let registryRevision;
|
|
272
|
-
if (
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
throw new Error(`REGISTRY_FETCH_FAILED: expected mcp config '${configId}' from registry '${registry.id}'`);
|
|
276
|
-
}
|
|
277
|
-
sourceJson = fetched.sourceJson;
|
|
278
|
-
importedSourceSha256 = fetched.importedSourceSha256;
|
|
279
|
-
registryRevision = fetched.registryRevision;
|
|
332
|
+
if (options?.sourceJson) {
|
|
333
|
+
sourceJson = options.sourceJson;
|
|
334
|
+
registryId = DEFAULT_REGISTRY_ID;
|
|
280
335
|
}
|
|
281
336
|
else {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
337
|
+
const registry = resolveEntityRegistrySelection(manifest, options?.registry);
|
|
338
|
+
registryId = registry.id;
|
|
339
|
+
if (registry.definition.type === "git") {
|
|
340
|
+
const fetched = await fetchEntityFromRegistry(registry.id, registry.definition, "mcp_config", configId);
|
|
341
|
+
if (fetched.type !== "mcp_config") {
|
|
342
|
+
throw new Error(`REGISTRY_FETCH_FAILED: expected mcp config '${configId}' from registry '${registry.id}'`);
|
|
343
|
+
}
|
|
344
|
+
sourceJson = fetched.sourceJson;
|
|
345
|
+
importedSourceSha256 = fetched.importedSourceSha256;
|
|
346
|
+
registryRevision = fetched.registryRevision;
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
sourceJson = {
|
|
350
|
+
servers: {
|
|
351
|
+
[configId]: {
|
|
352
|
+
command: "echo",
|
|
353
|
+
args: ["configure-this-mcp-server"],
|
|
354
|
+
},
|
|
287
355
|
},
|
|
288
|
-
}
|
|
289
|
-
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
290
358
|
}
|
|
291
359
|
const sourceContent = stableStringify(sourceJson);
|
|
292
360
|
await ensureParentDir(sourceAbs);
|
|
@@ -295,7 +363,7 @@ export async function addMcpEntity(cwd, configId, options) {
|
|
|
295
363
|
manifest.entities.push({
|
|
296
364
|
id: configId,
|
|
297
365
|
type: "mcp_config",
|
|
298
|
-
registry:
|
|
366
|
+
registry: registryId,
|
|
299
367
|
sourcePath,
|
|
300
368
|
overrides,
|
|
301
369
|
enabled: true,
|
|
@@ -306,7 +374,7 @@ export async function addMcpEntity(cwd, configId, options) {
|
|
|
306
374
|
await upsertLockEntityRecord(paths, manifest, {
|
|
307
375
|
id: configId,
|
|
308
376
|
type: "mcp_config",
|
|
309
|
-
registry:
|
|
377
|
+
registry: registryId,
|
|
310
378
|
sourceSha256: sha256(stableStringify(sourceJson)),
|
|
311
379
|
overrideSha256ByProvider: overrideShaByProvider,
|
|
312
380
|
importedSourceSha256,
|
|
@@ -325,23 +393,31 @@ export async function addSubagentEntity(cwd, subagentId, options) {
|
|
|
325
393
|
if (await exists(sourceAbs)) {
|
|
326
394
|
throw new Error(`Cannot add subagent because '${sourcePath}' already exists`);
|
|
327
395
|
}
|
|
328
|
-
const registry = resolveEntityRegistrySelection(manifest, options?.registry);
|
|
329
396
|
let sourceText;
|
|
397
|
+
let registryId;
|
|
330
398
|
let importedSourceSha256;
|
|
331
399
|
let registryRevision;
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
throw new Error(`REGISTRY_FETCH_FAILED: expected subagent '${subagentId}' from registry '${registry.id}'`);
|
|
336
|
-
}
|
|
337
|
-
sourceText = fetched.sourceText;
|
|
338
|
-
importedSourceSha256 = fetched.importedSourceSha256;
|
|
339
|
-
registryRevision = fetched.registryRevision;
|
|
400
|
+
if (options?.sourceText) {
|
|
401
|
+
sourceText = options.sourceText;
|
|
402
|
+
registryId = DEFAULT_REGISTRY_ID;
|
|
340
403
|
}
|
|
341
404
|
else {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
405
|
+
const registry = resolveEntityRegistrySelection(manifest, options?.registry);
|
|
406
|
+
registryId = registry.id;
|
|
407
|
+
if (registry.definition.type === "git") {
|
|
408
|
+
const fetched = await fetchEntityFromRegistry(registry.id, registry.definition, "subagent", subagentId);
|
|
409
|
+
if (fetched.type !== "subagent") {
|
|
410
|
+
throw new Error(`REGISTRY_FETCH_FAILED: expected subagent '${subagentId}' from registry '${registry.id}'`);
|
|
411
|
+
}
|
|
412
|
+
sourceText = fetched.sourceText;
|
|
413
|
+
importedSourceSha256 = fetched.importedSourceSha256;
|
|
414
|
+
registryRevision = fetched.registryRevision;
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
sourceText =
|
|
418
|
+
`---\nname: ${subagentId}\ndescription: Describe what this subagent does.\n---\n\n` +
|
|
419
|
+
`You are the ${subagentId} subagent.\n\nAdd instructions here.\n`;
|
|
420
|
+
}
|
|
345
421
|
}
|
|
346
422
|
await ensureParentDir(sourceAbs);
|
|
347
423
|
await fs.writeFile(sourceAbs, sourceText, "utf8");
|
|
@@ -349,7 +425,7 @@ export async function addSubagentEntity(cwd, subagentId, options) {
|
|
|
349
425
|
manifest.entities.push({
|
|
350
426
|
id: subagentId,
|
|
351
427
|
type: "subagent",
|
|
352
|
-
registry:
|
|
428
|
+
registry: registryId,
|
|
353
429
|
sourcePath,
|
|
354
430
|
overrides,
|
|
355
431
|
enabled: true,
|
|
@@ -360,7 +436,7 @@ export async function addSubagentEntity(cwd, subagentId, options) {
|
|
|
360
436
|
await upsertLockEntityRecord(paths, manifest, {
|
|
361
437
|
id: subagentId,
|
|
362
438
|
type: "subagent",
|
|
363
|
-
registry:
|
|
439
|
+
registry: registryId,
|
|
364
440
|
sourceSha256: sha256(sourceText),
|
|
365
441
|
overrideSha256ByProvider: overrideShaByProvider,
|
|
366
442
|
importedSourceSha256,
|
|
@@ -379,31 +455,39 @@ export async function addHookEntity(cwd, hookId, options) {
|
|
|
379
455
|
if (await exists(sourceAbs)) {
|
|
380
456
|
throw new Error(`Cannot add hook because '${sourcePath}' already exists`);
|
|
381
457
|
}
|
|
382
|
-
const registry = resolveEntityRegistrySelection(manifest, options?.registry);
|
|
383
458
|
let sourceJson;
|
|
459
|
+
let registryId;
|
|
384
460
|
let importedSourceSha256;
|
|
385
461
|
let registryRevision;
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
throw new Error(`REGISTRY_FETCH_FAILED: expected hook '${hookId}' from registry '${registry.id}'`);
|
|
390
|
-
}
|
|
391
|
-
sourceJson = fetched.sourceJson;
|
|
392
|
-
importedSourceSha256 = fetched.importedSourceSha256;
|
|
393
|
-
registryRevision = fetched.registryRevision;
|
|
462
|
+
if (options?.sourceJson) {
|
|
463
|
+
sourceJson = options.sourceJson;
|
|
464
|
+
registryId = DEFAULT_REGISTRY_ID;
|
|
394
465
|
}
|
|
395
466
|
else {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
}
|
|
467
|
+
const registry = resolveEntityRegistrySelection(manifest, options?.registry);
|
|
468
|
+
registryId = registry.id;
|
|
469
|
+
if (registry.definition.type === "git") {
|
|
470
|
+
const fetched = await fetchEntityFromRegistry(registry.id, registry.definition, "hook", hookId);
|
|
471
|
+
if (fetched.type !== "hook") {
|
|
472
|
+
throw new Error(`REGISTRY_FETCH_FAILED: expected hook '${hookId}' from registry '${registry.id}'`);
|
|
473
|
+
}
|
|
474
|
+
sourceJson = fetched.sourceJson;
|
|
475
|
+
importedSourceSha256 = fetched.importedSourceSha256;
|
|
476
|
+
registryRevision = fetched.registryRevision;
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
sourceJson = {
|
|
480
|
+
mode: "best_effort",
|
|
481
|
+
events: {
|
|
482
|
+
pre_tool_use: [
|
|
483
|
+
{
|
|
484
|
+
type: "command",
|
|
485
|
+
command: "echo 'replace-with-hook-command'",
|
|
486
|
+
},
|
|
487
|
+
],
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
}
|
|
407
491
|
}
|
|
408
492
|
const sourceContent = stableStringify(sourceJson);
|
|
409
493
|
await ensureParentDir(sourceAbs);
|
|
@@ -412,7 +496,7 @@ export async function addHookEntity(cwd, hookId, options) {
|
|
|
412
496
|
manifest.entities.push({
|
|
413
497
|
id: hookId,
|
|
414
498
|
type: "hook",
|
|
415
|
-
registry:
|
|
499
|
+
registry: registryId,
|
|
416
500
|
sourcePath,
|
|
417
501
|
overrides,
|
|
418
502
|
enabled: true,
|
|
@@ -423,13 +507,70 @@ export async function addHookEntity(cwd, hookId, options) {
|
|
|
423
507
|
await upsertLockEntityRecord(paths, manifest, {
|
|
424
508
|
id: hookId,
|
|
425
509
|
type: "hook",
|
|
426
|
-
registry:
|
|
510
|
+
registry: registryId,
|
|
427
511
|
sourceSha256: sha256(stableStringify(sourceJson)),
|
|
428
512
|
overrideSha256ByProvider: overrideShaByProvider,
|
|
429
513
|
importedSourceSha256,
|
|
430
514
|
registryRevision,
|
|
431
515
|
});
|
|
432
516
|
}
|
|
517
|
+
export async function addSettingsEntity(cwd, provider, options) {
|
|
518
|
+
const settingsProvider = providerIdSchema.parse(provider);
|
|
519
|
+
const paths = resolveHarnessPaths(cwd);
|
|
520
|
+
const manifest = await readManifestOrThrow(paths);
|
|
521
|
+
if (manifest.entities.some((entity) => entity.type === "settings" && entity.id === settingsProvider)) {
|
|
522
|
+
throw new Error(`Settings '${settingsProvider}' already exists`);
|
|
523
|
+
}
|
|
524
|
+
const sourcePath = defaultSettingsSourcePath(settingsProvider);
|
|
525
|
+
const sourceAbs = path.join(cwd, sourcePath);
|
|
526
|
+
if (await exists(sourceAbs)) {
|
|
527
|
+
throw new Error(`Cannot add settings because '${sourcePath}' already exists`);
|
|
528
|
+
}
|
|
529
|
+
let sourcePayload;
|
|
530
|
+
let registryId;
|
|
531
|
+
let importedSourceSha256;
|
|
532
|
+
let registryRevision;
|
|
533
|
+
if (options?.sourcePayload) {
|
|
534
|
+
sourcePayload = options.sourcePayload;
|
|
535
|
+
registryId = DEFAULT_REGISTRY_ID;
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
const registry = resolveEntityRegistrySelection(manifest, options?.registry);
|
|
539
|
+
registryId = registry.id;
|
|
540
|
+
sourcePayload = {};
|
|
541
|
+
if (registry.definition.type === "git") {
|
|
542
|
+
const fetched = await fetchEntityFromRegistry(registry.id, registry.definition, "settings", settingsProvider);
|
|
543
|
+
if (fetched.type !== "settings") {
|
|
544
|
+
throw new Error(`REGISTRY_FETCH_FAILED: expected settings '${settingsProvider}' from registry '${registry.id}'`);
|
|
545
|
+
}
|
|
546
|
+
sourcePayload = fetched.sourcePayload;
|
|
547
|
+
importedSourceSha256 = fetched.importedSourceSha256;
|
|
548
|
+
registryRevision = fetched.registryRevision;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const sourceContent = serializeSettingsPayload(settingsProvider, sourcePayload);
|
|
552
|
+
await ensureParentDir(sourceAbs);
|
|
553
|
+
await fs.writeFile(sourceAbs, sourceContent, "utf8");
|
|
554
|
+
manifest.entities.push({
|
|
555
|
+
id: settingsProvider,
|
|
556
|
+
type: "settings",
|
|
557
|
+
registry: registryId,
|
|
558
|
+
sourcePath,
|
|
559
|
+
enabled: true,
|
|
560
|
+
});
|
|
561
|
+
manifest.entities = sortEntities(manifest.entities);
|
|
562
|
+
await writeManifest(paths, manifest);
|
|
563
|
+
await writeManagedSourceIndex(paths, manifest);
|
|
564
|
+
await upsertLockEntityRecord(paths, manifest, {
|
|
565
|
+
id: settingsProvider,
|
|
566
|
+
type: "settings",
|
|
567
|
+
registry: registryId,
|
|
568
|
+
sourceSha256: sha256(sourceContent),
|
|
569
|
+
overrideSha256ByProvider: {},
|
|
570
|
+
importedSourceSha256,
|
|
571
|
+
registryRevision,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
433
574
|
export async function addCommandEntity(cwd, commandId, options) {
|
|
434
575
|
validateEntityId(commandId, "command");
|
|
435
576
|
const paths = resolveHarnessPaths(cwd);
|
|
@@ -442,21 +583,29 @@ export async function addCommandEntity(cwd, commandId, options) {
|
|
|
442
583
|
if (await exists(sourceAbs)) {
|
|
443
584
|
throw new Error(`Cannot add command because '${sourcePath}' already exists`);
|
|
444
585
|
}
|
|
445
|
-
const registry = resolveEntityRegistrySelection(manifest, options?.registry);
|
|
446
586
|
let sourceText;
|
|
587
|
+
let registryId;
|
|
447
588
|
let importedSourceSha256;
|
|
448
589
|
let registryRevision;
|
|
449
|
-
if (
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
throw new Error(`REGISTRY_FETCH_FAILED: expected command '${commandId}' from registry '${registry.id}'`);
|
|
453
|
-
}
|
|
454
|
-
sourceText = fetched.sourceText;
|
|
455
|
-
importedSourceSha256 = fetched.importedSourceSha256;
|
|
456
|
-
registryRevision = fetched.registryRevision;
|
|
590
|
+
if (options?.sourceText) {
|
|
591
|
+
sourceText = options.sourceText;
|
|
592
|
+
registryId = DEFAULT_REGISTRY_ID;
|
|
457
593
|
}
|
|
458
594
|
else {
|
|
459
|
-
|
|
595
|
+
const registry = resolveEntityRegistrySelection(manifest, options?.registry);
|
|
596
|
+
registryId = registry.id;
|
|
597
|
+
if (registry.definition.type === "git") {
|
|
598
|
+
const fetched = await fetchEntityFromRegistry(registry.id, registry.definition, "command", commandId);
|
|
599
|
+
if (fetched.type !== "command") {
|
|
600
|
+
throw new Error(`REGISTRY_FETCH_FAILED: expected command '${commandId}' from registry '${registry.id}'`);
|
|
601
|
+
}
|
|
602
|
+
sourceText = fetched.sourceText;
|
|
603
|
+
importedSourceSha256 = fetched.importedSourceSha256;
|
|
604
|
+
registryRevision = fetched.registryRevision;
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
sourceText = `---\ndescription: "Describe what this command does"\n---\n\n# ${commandId}\n\nDescribe the task here. Use $ARGUMENTS to reference arguments passed to this command.\n`;
|
|
608
|
+
}
|
|
460
609
|
}
|
|
461
610
|
await ensureParentDir(sourceAbs);
|
|
462
611
|
await fs.writeFile(sourceAbs, sourceText, "utf8");
|
|
@@ -464,7 +613,7 @@ export async function addCommandEntity(cwd, commandId, options) {
|
|
|
464
613
|
manifest.entities.push({
|
|
465
614
|
id: commandId,
|
|
466
615
|
type: "command",
|
|
467
|
-
registry:
|
|
616
|
+
registry: registryId,
|
|
468
617
|
sourcePath,
|
|
469
618
|
overrides,
|
|
470
619
|
enabled: true,
|
|
@@ -475,7 +624,7 @@ export async function addCommandEntity(cwd, commandId, options) {
|
|
|
475
624
|
await upsertLockEntityRecord(paths, manifest, {
|
|
476
625
|
id: commandId,
|
|
477
626
|
type: "command",
|
|
478
|
-
registry:
|
|
627
|
+
registry: registryId,
|
|
479
628
|
sourceSha256: sha256(sourceText),
|
|
480
629
|
overrideSha256ByProvider: overrideShaByProvider,
|
|
481
630
|
importedSourceSha256,
|
|
@@ -532,15 +681,22 @@ export async function pullRegistryEntities(cwd, options) {
|
|
|
532
681
|
for (const planned of plannedUpdates) {
|
|
533
682
|
const { entity, fetched } = planned;
|
|
534
683
|
await materializeFetchedEntity(cwd, entity, fetched);
|
|
535
|
-
|
|
536
|
-
entity.
|
|
537
|
-
|
|
684
|
+
let overrideShaByProvider = {};
|
|
685
|
+
if (entity.type !== "settings") {
|
|
686
|
+
const ensuredOverrides = await ensureOverrideFiles(cwd, entity.type, entity.id, entity.overrides);
|
|
687
|
+
entity.overrides = ensuredOverrides.overrides;
|
|
688
|
+
overrideShaByProvider = ensuredOverrides.overrideShaByProvider;
|
|
689
|
+
manifestMutated = true;
|
|
690
|
+
}
|
|
691
|
+
const sourceSha256ForLock = entity.type === "settings" && fetched.type === "settings"
|
|
692
|
+
? sha256(serializeSettingsPayload(fetched.provider, fetched.sourcePayload))
|
|
693
|
+
: fetched.importedSourceSha256;
|
|
538
694
|
setLockEntityRecord(lock, {
|
|
539
695
|
id: entity.id,
|
|
540
696
|
type: entity.type,
|
|
541
697
|
registry: entity.registry,
|
|
542
|
-
sourceSha256:
|
|
543
|
-
overrideSha256ByProvider:
|
|
698
|
+
sourceSha256: sourceSha256ForLock,
|
|
699
|
+
overrideSha256ByProvider: overrideShaByProvider,
|
|
544
700
|
importedSourceSha256: fetched.importedSourceSha256,
|
|
545
701
|
registryRevision: fetched.registryRevision,
|
|
546
702
|
});
|