@agentxm/extension-authoring 0.28.4-bootstrap.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.
@@ -0,0 +1,164 @@
1
+ /**
2
+ * New hook operation — scaffolds a new hook directory with the manifest and a
3
+ * starter entrypoint.
4
+ *
5
+ * @experimental This API is unstable and may change without notice.
6
+ */
7
+ import * as FileSystem from "effect/FileSystem";
8
+ import * as Path from "effect/Path";
9
+ import * as Effect from "effect/Effect";
10
+ import { AuthoringFailed } from "../errors.js";
11
+ import { AuthoringFailureAdapter, withAdaptedStepFailures } from "../failure-adapter.js";
12
+ import { createCanonicalDirectory, recoverCanonicalDirectory } from "@agentxm/extension-workspace";
13
+ import { preflightCreateOnly } from "../create-preflight.js";
14
+ import { decodeExtensionNameSync } from "@agentxm/extension-model/unstable/extensions";
15
+ import { WorkspaceMutations } from "@agentxm/workspace-state";
16
+ import { decodeVersionSync } from "@agentxm/extension-model/unstable/version-constraints";
17
+ import { HOOK_EXTENSION_DIR, HOOK_MANIFEST_FILENAME, HOOK_MANIFEST_SCHEMA_URL, } from "@agentxm/extension-model/unstable/hooks/manifest-schema";
18
+ // -----------------------------------------------------------------------------
19
+ // Helpers
20
+ // -----------------------------------------------------------------------------
21
+ const INITIAL_HOOK_VERSION = decodeVersionSync("0.1.0");
22
+ const entrypointFilename = (runtime) => {
23
+ switch (runtime) {
24
+ case "bash":
25
+ return "hook.sh";
26
+ case "node":
27
+ return "hook.js";
28
+ case "python":
29
+ return "hook.py";
30
+ }
31
+ };
32
+ const matcherForBinding = (event, matcher) => event === "tool.pre" || event === "tool.post" ? matcher : undefined;
33
+ const makeEntrypoint = (runtime, fqn) => {
34
+ switch (runtime) {
35
+ case "bash":
36
+ return `#!/usr/bin/env bash
37
+ # ${fqn}
38
+ # Receives the agent hook event payload as JSON on stdin.
39
+ set -euo pipefail
40
+
41
+ payload="$(cat)"
42
+
43
+ # TODO: inspect "$payload" and implement the hook.
44
+ # Emit JSON on stdout or exit non-zero to influence the agent.
45
+ exit 0
46
+ `;
47
+ case "node":
48
+ return `#!/usr/bin/env node
49
+ // ${fqn}
50
+ // Receives the agent hook event payload as JSON on stdin.
51
+ let raw = "";
52
+ process.stdin.on("data", (chunk) => {
53
+ raw += chunk;
54
+ });
55
+ process.stdin.on("end", () => {
56
+ const payload = raw ? JSON.parse(raw) : {};
57
+
58
+ // TODO: inspect payload and implement the hook.
59
+ // Emit JSON on stdout or exit non-zero to influence the agent.
60
+ process.exit(0);
61
+ });
62
+ `;
63
+ case "python":
64
+ return `#!/usr/bin/env python3
65
+ """${fqn}
66
+
67
+ Receives the agent hook event payload as JSON on stdin.
68
+ """
69
+ import json
70
+ import sys
71
+
72
+ raw = sys.stdin.read()
73
+ payload = json.loads(raw) if raw else {}
74
+
75
+ # TODO: inspect payload and implement the hook.
76
+ # Emit JSON on stdout or exit non-zero to influence the agent.
77
+ sys.exit(0)
78
+ `;
79
+ }
80
+ };
81
+ // -----------------------------------------------------------------------------
82
+ // Public API
83
+ // -----------------------------------------------------------------------------
84
+ /**
85
+ * New-hook operation handler.
86
+ *
87
+ * 1. Compute managed extension directory path
88
+ * 2. Check if the hook already exists (directory or settings entry)
89
+ * 3. Create the managed extension + src directories
90
+ * 4. Write hook.json manifest
91
+ * 5. Write starter entrypoint in src/
92
+ */
93
+ export const newHook = (op) => Effect.gen(function* () {
94
+ const fs = yield* FileSystem.FileSystem;
95
+ const path = yield* Path.Path;
96
+ const ws = yield* WorkspaceMutations;
97
+ const base = ws.baseDir;
98
+ if (ws.layout.scope !== "project") {
99
+ return yield* new AuthoringFailed({
100
+ category: "validation",
101
+ detail: "New hooks can only be scaffolded in a project workspace",
102
+ });
103
+ }
104
+ const { name, owner, runtime, event, matcher } = op.args;
105
+ const fqn = `${owner}/${HOOK_EXTENSION_DIR}/${name}`;
106
+ const configuredHooks = yield* ws.getConfiguredHookEntries();
107
+ const canonicalPath = path.join(ws.layout.authoredRoot("hook"), name);
108
+ yield* recoverCanonicalDirectory({ baseDir: base, canonicalPath });
109
+ yield* preflightCreateOnly({
110
+ subject: "Hook",
111
+ name,
112
+ configured: Object.hasOwn(configuredHooks, name),
113
+ destinations: [canonicalPath],
114
+ });
115
+ const entrypointFile = entrypointFilename(runtime);
116
+ const bindingMatcher = matcherForBinding(event, matcher);
117
+ const manifest = {
118
+ $schema: HOOK_MANIFEST_SCHEMA_URL,
119
+ owner,
120
+ type: "hook",
121
+ name: decodeExtensionNameSync(name),
122
+ version: INITIAL_HOOK_VERSION,
123
+ runtime,
124
+ entrypoint: `src/${entrypointFile}`,
125
+ bindings: [
126
+ bindingMatcher === undefined ? { on: event } : { on: event, matcherRaw: bindingMatcher },
127
+ ],
128
+ };
129
+ yield* createCanonicalDirectory({
130
+ baseDir: base,
131
+ canonicalPath,
132
+ subject: "Hook",
133
+ requiredFiles: [HOOK_MANIFEST_FILENAME, `src/${entrypointFile}`],
134
+ populate: (stagingPath) => {
135
+ const srcDir = path.join(stagingPath, "src");
136
+ return Effect.gen(function* () {
137
+ yield* fs.makeDirectory(srcDir, { recursive: true }).pipe(Effect.mapError((e) => new AuthoringFailed({
138
+ category: "validation",
139
+ detail: `Failed to create hook directory: ${srcDir}`,
140
+ cause: e,
141
+ })));
142
+ yield* fs
143
+ .writeFileString(path.join(stagingPath, HOOK_MANIFEST_FILENAME), JSON.stringify(manifest, null, 2) + "\n")
144
+ .pipe(Effect.mapError((e) => new AuthoringFailed({
145
+ category: "validation",
146
+ detail: "Hook manifest could not be written",
147
+ cause: e,
148
+ })));
149
+ yield* fs
150
+ .writeFileString(path.join(srcDir, entrypointFile), makeEntrypoint(runtime, fqn))
151
+ .pipe(Effect.mapError((e) => new AuthoringFailed({
152
+ category: "validation",
153
+ detail: `Failed to write ${entrypointFile}`,
154
+ cause: e,
155
+ })));
156
+ });
157
+ },
158
+ });
159
+ return {
160
+ result: "success",
161
+ message: `Created hook ${fqn}`,
162
+ };
163
+ }).pipe(withAdaptedStepFailures);
164
+ //# sourceMappingURL=new-hook.js.map
@@ -0,0 +1,14 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as FileSystem from "effect/FileSystem";
3
+ import * as Path from "effect/Path";
4
+ import type { ExtensionFqnParts } from "@agentxm/extension-model/unstable/extensions/common";
5
+ import { type FrontmatterParseFailure } from "@agentxm/registry-protocol/unstable/content/frontmatter";
6
+ import { NativeImportConflict, NativeImportFailed, NativeImportInvalid, NativeImportUnsupported } from "@agentxm/extension-workspace";
7
+ export interface ImportNativeExtensionPackageArgs {
8
+ readonly sourcePath: string;
9
+ readonly targetDir: string;
10
+ readonly target: ExtensionFqnParts;
11
+ }
12
+ export type NativeImportError = NativeImportConflict | NativeImportFailed | NativeImportInvalid | NativeImportUnsupported | FrontmatterParseFailure;
13
+ export declare const importNativeExtensionPackage: (args: ImportNativeExtensionPackageArgs) => Effect.Effect<void, NativeImportError, FileSystem.FileSystem | Path.Path>;
14
+ //# sourceMappingURL=import-native-package.d.ts.map
@@ -0,0 +1,135 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as FileSystem from "effect/FileSystem";
3
+ import * as Path from "effect/Path";
4
+ import * as Schema from "effect/Schema";
5
+ import YAML from "yaml";
6
+ import { manifestFilenameForType, manifestSchemaForType, } from "@agentxm/registry-protocol/unstable/publish/manifest-policy";
7
+ import { parseFrontmatterEffect, } from "@agentxm/registry-protocol/unstable/content/frontmatter";
8
+ import { copyExtensionDirectory } from "@agentxm/extension-workspace";
9
+ import { NativeImportConflict, NativeImportFailed, NativeImportInvalid, NativeImportUnsupported, } from "@agentxm/extension-workspace";
10
+ const NATIVE_IMPORT_VERSION = "0.1.0";
11
+ const MANIFEST_FILENAMES = new Set([
12
+ "skill.json",
13
+ "mcp.json",
14
+ "subagent.json",
15
+ "rule.json",
16
+ "hook.json",
17
+ "knowledge.json",
18
+ "pack.json",
19
+ ]);
20
+ const mapWriteError = (detail) => (cause) => new NativeImportFailed({ detail, cause });
21
+ const rewriteFrontmatterName = (filePath, name) => Effect.gen(function* () {
22
+ const fs = yield* FileSystem.FileSystem;
23
+ const content = yield* fs
24
+ .readFileString(filePath)
25
+ .pipe(Effect.mapError(mapWriteError(`Native content could not be read: ${filePath}`)));
26
+ const parsed = yield* parseFrontmatterEffect(content);
27
+ if (typeof parsed.frontmatter !== "object" || parsed.frontmatter === null) {
28
+ return yield* new NativeImportInvalid({
29
+ detail: `Native content must contain YAML frontmatter: ${filePath}`,
30
+ });
31
+ }
32
+ const frontmatter = { ...parsed.frontmatter, name };
33
+ const yaml = YAML.stringify(frontmatter, { lineWidth: 0 }).trim();
34
+ const body = parsed.body.startsWith("\n") ? parsed.body : `\n${parsed.body}`;
35
+ yield* fs
36
+ .writeFileString(filePath, `---\n${yaml}\n---${body}`)
37
+ .pipe(Effect.mapError(mapWriteError(`Native content could not be normalized: ${filePath}`)));
38
+ });
39
+ const selectMarkdownFile = (sourcePath, preferredName) => Effect.gen(function* () {
40
+ const fs = yield* FileSystem.FileSystem;
41
+ const path = yield* Path.Path;
42
+ const inspectionFailed = mapWriteError(`Native source could not be inspected: ${sourcePath}`);
43
+ const stat = yield* fs.stat(sourcePath).pipe(Effect.mapError(inspectionFailed));
44
+ if (stat.type === "File")
45
+ return sourcePath;
46
+ if (stat.type !== "Directory") {
47
+ return yield* new NativeImportInvalid({
48
+ detail: `Native source must be a Markdown file or directory: ${sourcePath}`,
49
+ });
50
+ }
51
+ const entries = yield* fs.readDirectory(sourcePath).pipe(Effect.mapError(inspectionFailed));
52
+ const preferred = [`${preferredName}.md`, preferredName, "SKILL.md", "RULE.md"]
53
+ .map((name) => entries.find((entry) => entry === name))
54
+ .find((entry) => entry !== undefined);
55
+ if (preferred !== undefined)
56
+ return path.join(sourcePath, preferred);
57
+ const markdown = entries.filter((entry) => entry.toLowerCase().endsWith(".md") && entry.toLowerCase() !== "readme.md");
58
+ if (markdown.length !== 1 || markdown[0] === undefined) {
59
+ return yield* new NativeImportInvalid({
60
+ detail: `Native source must contain exactly one unambiguous Markdown document: ${sourcePath}`,
61
+ });
62
+ }
63
+ return path.join(sourcePath, markdown[0]);
64
+ });
65
+ const rejectManagedPackage = (sourcePath) => Effect.gen(function* () {
66
+ const fs = yield* FileSystem.FileSystem;
67
+ const path = yield* Path.Path;
68
+ const inspectionFailed = mapWriteError(`Native source could not be inspected: ${sourcePath}`);
69
+ const stat = yield* fs.stat(sourcePath).pipe(Effect.mapError(inspectionFailed));
70
+ const directory = stat.type === "Directory" ? sourcePath : path.dirname(sourcePath);
71
+ const entries = yield* fs.readDirectory(directory).pipe(Effect.mapError(inspectionFailed));
72
+ const manifest = entries.find((entry) => MANIFEST_FILENAMES.has(entry));
73
+ if (manifest !== undefined) {
74
+ return yield* new NativeImportInvalid({
75
+ detail: `Source is already a managed AXM package (${manifest}); use fork instead of import`,
76
+ });
77
+ }
78
+ });
79
+ export const importNativeExtensionPackage = (args) => Effect.gen(function* () {
80
+ const fs = yield* FileSystem.FileSystem;
81
+ const path = yield* Path.Path;
82
+ const importFailed = mapWriteError(`Native import failed for ${args.sourcePath}`);
83
+ if (args.target.type !== "skill" && args.target.type !== "subagent") {
84
+ return yield* new NativeImportUnsupported({ type: args.target.type });
85
+ }
86
+ yield* rejectManagedPackage(args.sourcePath);
87
+ if (yield* fs.exists(args.targetDir).pipe(Effect.mapError(importFailed))) {
88
+ return yield* new NativeImportConflict({ targetDir: args.targetDir });
89
+ }
90
+ yield* fs
91
+ .makeDirectory(args.targetDir, { recursive: true })
92
+ .pipe(Effect.mapError(mapWriteError(`Import target could not be created: ${args.targetDir}`)));
93
+ switch (args.target.type) {
94
+ case "skill": {
95
+ const stat = yield* fs.stat(args.sourcePath).pipe(Effect.mapError(importFailed));
96
+ if (stat.type === "Directory") {
97
+ yield* copyExtensionDirectory(args.sourcePath, path.join(args.targetDir, "src")).pipe(Effect.mapError(mapWriteError("Native skill content could not be copied")));
98
+ }
99
+ else {
100
+ yield* fs
101
+ .makeDirectory(path.join(args.targetDir, "src"), { recursive: true })
102
+ .pipe(Effect.mapError(importFailed));
103
+ yield* fs
104
+ .copyFile(args.sourcePath, path.join(args.targetDir, "src", "SKILL.md"))
105
+ .pipe(Effect.mapError(mapWriteError("Native skill document could not be copied")));
106
+ }
107
+ yield* rewriteFrontmatterName(path.join(args.targetDir, "src", "SKILL.md"), args.target.name);
108
+ break;
109
+ }
110
+ case "subagent": {
111
+ yield* fs
112
+ .makeDirectory(path.join(args.targetDir, "src"), { recursive: true })
113
+ .pipe(Effect.mapError(importFailed));
114
+ const sourceFile = yield* selectMarkdownFile(args.sourcePath, args.target.name);
115
+ const targetFile = path.join(args.targetDir, "src", `${args.target.name}.md`);
116
+ yield* fs
117
+ .copyFile(sourceFile, targetFile)
118
+ .pipe(Effect.mapError(mapWriteError("Native subagent document could not be copied")));
119
+ yield* rewriteFrontmatterName(targetFile, args.target.name);
120
+ break;
121
+ }
122
+ }
123
+ const manifest = {
124
+ $schema: `https://axm.sh/schemas/${manifestFilenameForType(args.target.type).replace(".json", ".schema.json")}`,
125
+ owner: args.target.owner,
126
+ type: args.target.type,
127
+ name: args.target.name,
128
+ version: NATIVE_IMPORT_VERSION,
129
+ };
130
+ yield* Schema.decodeUnknownEffect(manifestSchemaForType(args.target.type))(manifest).pipe(Effect.mapError((cause) => new NativeImportInvalid({ detail: "Imported package manifest is invalid", cause })));
131
+ yield* fs
132
+ .writeFileString(path.join(args.targetDir, manifestFilenameForType(args.target.type)), `${JSON.stringify(manifest, null, 2)}\n`)
133
+ .pipe(Effect.mapError(mapWriteError("Imported package manifest could not be written")));
134
+ });
135
+ //# sourceMappingURL=import-native-package.js.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Extension-authoring feature: new-extension scaffolding, fork, native
3
+ * import, authored identity decoding, and authored pack membership. The
4
+ * application supplies the failure adapter that serializes authoring
5
+ * failures into the plan-step vocabulary.
6
+ *
7
+ * @experimental All exports from this module are unstable and may change without notice.
8
+ * @packageDocumentation
9
+ */
10
+ export { AuthoringFailed } from "./errors.js";
11
+ export { AuthoringFailureAdapter, withAdaptedStepFailures, type AuthoringFailureAdapterService, } from "./failure-adapter.js";
12
+ export { decodeDesiredExtensionIdentity, type DecodedDesiredExtensionIdentity, type DesiredPackageAuthority, } from "./desired-identity.js";
13
+ export { forkExtensionPackage, type ForkExtensionPackageArgs } from "./fork-package.js";
14
+ export { importNativeExtensionPackage, type ImportNativeExtensionPackageArgs, type NativeImportError, } from "./import-native-package.js";
15
+ export { preflightCreateOnly, type CreateOnlyPreflightArgs } from "./create-preflight.js";
16
+ export { markerFqnForRef, type MarkerFqnRef } from "./marker-fqn.js";
17
+ export { newSkill, type NewSkillOperation, type NewSkillOperationArgs, } from "./skills/new-skill.js";
18
+ export { newHook, type NewHookOperation, type NewHookOperationArgs } from "./hooks/new-hook.js";
19
+ export { newPack, type NewPackOperation, type NewPackOperationArgs } from "./packs/new-pack.js";
20
+ export { addToPack, type AddToPackOperation, type AddToPackOperationArgs, } from "./packs/add-to-pack.js";
21
+ export { removeFromPack, type RemoveFromPackOperation, type RemoveFromPackOperationArgs, } from "./packs/remove-from-pack.js";
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Extension-authoring feature: new-extension scaffolding, fork, native
3
+ * import, authored identity decoding, and authored pack membership. The
4
+ * application supplies the failure adapter that serializes authoring
5
+ * failures into the plan-step vocabulary.
6
+ *
7
+ * @experimental All exports from this module are unstable and may change without notice.
8
+ * @packageDocumentation
9
+ */
10
+ export { AuthoringFailed } from "./errors.js";
11
+ export { AuthoringFailureAdapter, withAdaptedStepFailures, } from "./failure-adapter.js";
12
+ export { decodeDesiredExtensionIdentity, } from "./desired-identity.js";
13
+ export { forkExtensionPackage } from "./fork-package.js";
14
+ export { importNativeExtensionPackage, } from "./import-native-package.js";
15
+ export { preflightCreateOnly } from "./create-preflight.js";
16
+ export { markerFqnForRef } from "./marker-fqn.js";
17
+ // Scaffolding operations
18
+ export { newSkill, } from "./skills/new-skill.js";
19
+ export { newHook } from "./hooks/new-hook.js";
20
+ export { newPack } from "./packs/new-pack.js";
21
+ // Authored pack membership operations
22
+ export { addToPack, } from "./packs/add-to-pack.js";
23
+ export { removeFromPack, } from "./packs/remove-from-pack.js";
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Helpers for managed-region marker extension identifiers.
3
+ *
4
+ * @experimental This API is unstable and may change without notice.
5
+ */
6
+ import type { ExtensionName, ExtensionType } from "@agentxm/extension-model/unstable/extensions/common";
7
+ import type { Handle } from "@agentxm/extension-model/unstable/extensions/handle";
8
+ export type MarkerFqnRef = {
9
+ readonly refType: "registry" | "workspace";
10
+ readonly owner: Handle;
11
+ } | {
12
+ readonly refType: "git-hosted" | "local";
13
+ };
14
+ export declare const markerFqnForRef: (args: {
15
+ readonly ref: MarkerFqnRef;
16
+ readonly manifest: {
17
+ readonly owner: Handle;
18
+ readonly name: ExtensionName;
19
+ };
20
+ readonly type: ExtensionType;
21
+ readonly name: ExtensionName;
22
+ }) => string;
23
+ //# sourceMappingURL=marker-fqn.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Helpers for managed-region marker extension identifiers.
3
+ *
4
+ * @experimental This API is unstable and may change without notice.
5
+ */
6
+ import { formatFqn } from "@agentxm/extension-model/unstable/extensions/fqn";
7
+ export const markerFqnForRef = (args) => args.ref.refType === "registry" || args.ref.refType === "workspace"
8
+ ? formatFqn({ owner: args.ref.owner, type: args.type, name: args.name })
9
+ : formatFqn({ owner: args.manifest.owner, type: args.type, name: args.manifest.name });
10
+ //# sourceMappingURL=marker-fqn.js.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Add-to-pack operation — applies a precomputed manifest-add delta to a pack manifest.
3
+ *
4
+ * Validates manifest precondition (stale check) before writing.
5
+ *
6
+ * @experimental This API is unstable and may change without notice.
7
+ */
8
+ import * as FileSystem from "effect/FileSystem";
9
+ import * as Path from "effect/Path";
10
+ import { AuthoringFailureAdapter } from "../failure-adapter.js";
11
+ import type { Handle } from "@agentxm/extension-model/unstable/extensions";
12
+ import type { OperationHandler } from "@agentxm/workspace-operations";
13
+ import type { Operation } from "@agentxm/workspace-operations";
14
+ import { WorkspaceMutations } from "@agentxm/workspace-state";
15
+ /**
16
+ * Args for the add-to-pack operation.
17
+ */
18
+ export interface AddToPackOperationArgs {
19
+ /** Pack name (without owner). */
20
+ readonly packName: string;
21
+ /** Pack owner (e.g., "@myorg"). */
22
+ readonly packOwner: Handle;
23
+ /** Precomputed manifest delta: FQN -> version range entries to add. */
24
+ readonly additions: Readonly<Record<string, string>>;
25
+ /** Manifest content hash at plan time for stale-check. */
26
+ readonly manifestHash: string;
27
+ }
28
+ /**
29
+ * Add extensions to a pack manifest.
30
+ *
31
+ * @experimental This API is unstable and may change without notice.
32
+ */
33
+ export type AddToPackOperation = Operation<"add-to-pack", AddToPackOperationArgs>;
34
+ /**
35
+ * Add-to-pack operation handler.
36
+ *
37
+ * 1. Short-circuit if additions map is empty (no-op)
38
+ * 2. Read current manifest and compute hash
39
+ * 3. Compare hash with args.manifestHash (stale check)
40
+ * 4. Apply additions to manifest
41
+ * 5. Write updated manifest
42
+ */
43
+ export declare const addToPack: OperationHandler<AddToPackOperation, FileSystem.FileSystem | Path.Path | WorkspaceMutations | AuthoringFailureAdapter>;
44
+ //# sourceMappingURL=add-to-pack.d.ts.map
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Add-to-pack operation — applies a precomputed manifest-add delta to a pack manifest.
3
+ *
4
+ * Validates manifest precondition (stale check) before writing.
5
+ *
6
+ * @experimental This API is unstable and may change without notice.
7
+ */
8
+ import * as FileSystem from "effect/FileSystem";
9
+ import * as Path from "effect/Path";
10
+ import * as Effect from "effect/Effect";
11
+ import * as Schema from "effect/Schema";
12
+ import { AuthoringFailed } from "../errors.js";
13
+ import { AuthoringFailureAdapter, withAdaptedStepFailures } from "../failure-adapter.js";
14
+ import { WorkspaceMutations } from "@agentxm/workspace-state";
15
+ import { isWorkspaceSourceLocator } from "@agentxm/extension-model/unstable/sources/workspace";
16
+ import { PACK_MANIFEST_FILENAME, PackManifestSchema, } from "@agentxm/extension-model/unstable/packs/manifest-schema";
17
+ import { packManifestArtifact } from "./artifact.js";
18
+ import { hashContent } from "./hash-content.js";
19
+ // -----------------------------------------------------------------------------
20
+ // Public API
21
+ // -----------------------------------------------------------------------------
22
+ /**
23
+ * Add-to-pack operation handler.
24
+ *
25
+ * 1. Short-circuit if additions map is empty (no-op)
26
+ * 2. Read current manifest and compute hash
27
+ * 3. Compare hash with args.manifestHash (stale check)
28
+ * 4. Apply additions to manifest
29
+ * 5. Write updated manifest
30
+ */
31
+ export const addToPack = (op) => Effect.gen(function* () {
32
+ const fs = yield* FileSystem.FileSystem;
33
+ const path = yield* Path.Path;
34
+ const ws = yield* WorkspaceMutations;
35
+ if (ws.layout.scope !== "project") {
36
+ return yield* new AuthoringFailed({
37
+ category: "validation",
38
+ detail: "Authored packs can only be edited in a project workspace",
39
+ });
40
+ }
41
+ const { packName, packOwner, additions, manifestHash } = op.args;
42
+ const configured = (yield* ws.getConfiguredPackEntries())[packName];
43
+ if (configured === undefined || !isWorkspaceSourceLocator(configured.source)) {
44
+ return yield* new AuthoringFailed({
45
+ category: "conflict",
46
+ detail: `Pack "${packName}" is not an authored workspace pack`,
47
+ recover: "Only workspace-authored packs can be edited in place.",
48
+ });
49
+ }
50
+ // 1. Short-circuit if nothing to add
51
+ if (Object.keys(additions).length === 0) {
52
+ return { result: "success", message: "No pack entries added" };
53
+ }
54
+ const manifestPath = path.join(ws.layout.authoredRoot("pack"), packName, PACK_MANIFEST_FILENAME);
55
+ yield* ws.runTransaction({
56
+ targets: [manifestPath],
57
+ transition: Effect.gen(function* () {
58
+ // Read and stale-check under the workspace lock.
59
+ const manifestContent = yield* fs.readFileString(manifestPath).pipe(Effect.mapError((e) => new AuthoringFailed({
60
+ category: "not_found",
61
+ detail: `Pack manifest not found at ${manifestPath}`,
62
+ suggestions: [{ description: "Ensure the pack exists on disk" }],
63
+ cause: e,
64
+ })));
65
+ // 3. Stale-check: compare content hash
66
+ const currentHash = hashContent(manifestContent);
67
+ if (currentHash !== manifestHash) {
68
+ return yield* new AuthoringFailed({
69
+ category: "conflict",
70
+ detail: `Pack manifest is stale — it was modified since the plan was created`,
71
+ suggestions: [{ description: "Re-run the command to create a fresh plan" }],
72
+ });
73
+ }
74
+ // 4. Parse and apply additions
75
+ const json = yield* Effect.try({
76
+ try: () => {
77
+ const parsed = JSON.parse(manifestContent);
78
+ return parsed;
79
+ },
80
+ catch: (e) => new AuthoringFailed({
81
+ category: "validation",
82
+ detail: `Failed to parse pack manifest: ${manifestPath}`,
83
+ cause: e,
84
+ }),
85
+ });
86
+ const manifest = yield* Schema.decodeUnknownEffect(PackManifestSchema)(json).pipe(Effect.mapError((e) => new AuthoringFailed({
87
+ category: "validation",
88
+ detail: `Invalid pack manifest: ${manifestPath}`,
89
+ cause: e,
90
+ })));
91
+ const dependencies = { ...manifest.dependencies };
92
+ for (const [fqn, version] of Object.entries(additions)) {
93
+ dependencies[fqn] = version;
94
+ }
95
+ const updatedManifest = {
96
+ ...manifest,
97
+ owner: manifest.owner,
98
+ type: manifest.type,
99
+ name: manifest.name,
100
+ version: manifest.version,
101
+ dependencies,
102
+ };
103
+ const validatedUpdatedManifest = yield* Schema.decodeUnknownEffect(PackManifestSchema)(updatedManifest).pipe(Effect.mapError((cause) => new AuthoringFailed({
104
+ category: "validation",
105
+ detail: `Updated pack manifest is invalid: ${manifestPath}`,
106
+ cause,
107
+ })));
108
+ // 5. Write updated manifest
109
+ yield* fs
110
+ .writeFileString(manifestPath, JSON.stringify(validatedUpdatedManifest, null, 2) + "\n")
111
+ .pipe(Effect.mapError((e) => new AuthoringFailed({
112
+ category: "internal",
113
+ detail: `Failed to write pack manifest: ${manifestPath}`,
114
+ cause: e,
115
+ })));
116
+ }),
117
+ validate: () => fs.readFileString(manifestPath).pipe(Effect.flatMap((content) => Effect.try({
118
+ try: () => JSON.parse(content),
119
+ catch: (cause) => new AuthoringFailed({
120
+ category: "validation",
121
+ detail: `Updated pack manifest could not be parsed: ${manifestPath}`,
122
+ cause,
123
+ }),
124
+ })), Effect.flatMap(Schema.decodeUnknownEffect(PackManifestSchema)), Effect.flatMap((manifest) => Object.entries(additions).every(([fqn, constraint]) => manifest.dependencies[fqn] === constraint)
125
+ ? Effect.void
126
+ : new AuthoringFailed({
127
+ category: "internal",
128
+ detail: `Updated pack manifest did not retain the requested dependencies`,
129
+ })), Effect.mapError((cause) => new AuthoringFailed({
130
+ category: "validation",
131
+ detail: `Updated pack manifest failed postcondition validation`,
132
+ cause,
133
+ }))),
134
+ });
135
+ return {
136
+ result: "success",
137
+ message: `Added ${Object.keys(additions).length} extension${Object.keys(additions).length === 1 ? "" : "s"} to pack`,
138
+ artifact: packManifestArtifact({
139
+ owner: packOwner,
140
+ name: packName,
141
+ scope: ws.scope,
142
+ change: "updated",
143
+ fileCount: 1,
144
+ }),
145
+ };
146
+ }).pipe(withAdaptedStepFailures);
147
+ //# sourceMappingURL=add-to-pack.js.map
@@ -0,0 +1,13 @@
1
+ import type { Handle } from "@agentxm/extension-model/unstable/extensions";
2
+ import type { JobStepArtifact, JobStepArtifactTarget } from "@agentxm/workspace-operations";
3
+ export declare const packManifestPath: (scope: JobStepArtifact["scope"], owner: Handle, name: string) => string;
4
+ export declare const packManifestTarget: (scope: JobStepArtifact["scope"], owner: Handle, name: string, change: JobStepArtifactTarget["change"]) => JobStepArtifactTarget;
5
+ export declare const packManifestArtifact: (args: {
6
+ readonly owner: Handle;
7
+ readonly name: string;
8
+ readonly scope: JobStepArtifact["scope"];
9
+ readonly change: JobStepArtifact["change"];
10
+ readonly version?: string;
11
+ readonly fileCount?: number;
12
+ }) => JobStepArtifact;
13
+ //# sourceMappingURL=artifact.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { PACK_MANIFEST_FILENAME } from "@agentxm/extension-model/unstable/packs/manifest-schema";
2
+ export const packManifestPath = (scope, owner, name) => scope === "project"
3
+ ? `packs/${name}/${PACK_MANIFEST_FILENAME}`
4
+ : `.axm/workspace/agent_extensions/${owner}/packs/${name}/${PACK_MANIFEST_FILENAME}`;
5
+ export const packManifestTarget = (scope, owner, name, change) => ({
6
+ path: packManifestPath(scope, owner, name),
7
+ change,
8
+ });
9
+ export const packManifestArtifact = (args) => ({
10
+ path: packManifestPath(args.scope, args.owner, args.name),
11
+ scope: args.scope,
12
+ change: args.change,
13
+ ...(args.version === undefined ? {} : { version: args.version }),
14
+ ...(args.fileCount === undefined ? {} : { fileCount: args.fileCount }),
15
+ targets: [packManifestTarget(args.scope, args.owner, args.name, args.change)],
16
+ });
17
+ //# sourceMappingURL=artifact.js.map
@@ -0,0 +1,2 @@
1
+ export declare const hashContent: (content: string) => string;
2
+ //# sourceMappingURL=hash-content.d.ts.map
@@ -0,0 +1,3 @@
1
+ import * as crypto from "node:crypto";
2
+ export const hashContent = (content) => crypto.createHash("sha256").update(content).digest("hex");
3
+ //# sourceMappingURL=hash-content.js.map