@agentxm/extension-authoring 0.30.1 → 0.30.2

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.
@@ -8,6 +8,12 @@
8
8
  * was never declared becomes enabled — adopting content you cannot see would
9
9
  * leave the workspace holding authored work it never materializes.
10
10
  *
11
+ * A package already at its authoring location that nothing declares is
12
+ * adopted in place: the workspace declares it, enabled, and nothing moves.
13
+ * In-place adoption refuses when an installed copy of the same identity also
14
+ * exists, when the workspace already declares the name, and when the authored
15
+ * manifest is invalid or names a different identity.
16
+ *
11
17
  * `prepare` settles the identity, the destination, and the activation, and
12
18
  * writes nothing. `previewOrApply` resolves that same candidate inside the
13
19
  * workspace transaction, so a failed move restores both directories.
@@ -17,14 +23,15 @@
17
23
  import * as Effect from "effect/Effect";
18
24
  import * as FileSystem from "effect/FileSystem";
19
25
  import type * as HttpClient from "effect/unstable/http/HttpClient";
26
+ import * as Option from "effect/Option";
20
27
  import * as Path from "effect/Path";
21
28
  import { ExtensionManagers, McpSecretStore, type ManagerRequirements } from "@agentxm/extension-materialization";
22
29
  import { type RecipeRequirements } from "@agentxm/workspace-reconciliation";
23
30
  import { type ExtensionType, type FqnInvalidError, type Handle } from "@agentxm/extension-model/unstable/extensions";
24
31
  import { type CandidateFingerprintFailed, type ExecutionCandidate, type PlanExecution } from "@agentxm/workspace-operations";
25
32
  import type { CodingAgentRepository } from "@agentxm/workspace-projection";
26
- import { WorkspaceMutations, type ConfiguredAgentOutcomesProvider, type LockfileValidationError, type WorkspaceLockfileReadFailure, type WorkspaceSettingsReadFailure } from "@agentxm/workspace-state";
27
- import type { AuthoredPackageError } from "../authored-package-errors.js";
33
+ import { LockfileReader, WorkspaceMutations, type ConfiguredAgentOutcomesProvider, type LockfileValidationError, type WorkspaceLockfileReadFailure, type WorkspaceSettingsReadFailure, type WorkspaceStateReadFailure } from "@agentxm/workspace-state";
34
+ import { type AuthoredPackageError } from "../authored-package-errors.js";
28
35
  import { AuthoringFailed } from "../errors.js";
29
36
  import { AuthoringScopeUnsupported, type AuthoringOwnerMismatch, type AuthoringOwnerRequired } from "../create/errors.js";
30
37
  export interface AdoptExtensionRequest {
@@ -44,9 +51,14 @@ export interface AdoptExtensionCandidate {
44
51
  readonly fqn: string;
45
52
  readonly owner: Handle;
46
53
  readonly name: string;
47
- /** Workspace-relative directory the package is moved out of. */
48
- readonly acquiredPath: string;
49
- /** Workspace-relative directory the package is moved into. */
54
+ /**
55
+ * `move` relocates an installed copy into authorship; `in-place` declares a
56
+ * package already at its authoring location and moves nothing.
57
+ */
58
+ readonly mode: "move" | "in-place";
59
+ /** Workspace-relative directory the package is moved out of, when it moves. */
60
+ readonly acquiredPath: Option.Option<string>;
61
+ /** Workspace-relative authoring directory the adopted package occupies. */
50
62
  readonly authoredPath: string;
51
63
  /** Workspace-relative settings file the declaration is rewritten in. */
52
64
  readonly settingsPath: string;
@@ -55,9 +67,9 @@ export interface AdoptExtensionCandidate {
55
67
  readonly execution: ExecutionCandidate<AdoptExtensionRequirements>;
56
68
  }
57
69
  /** Every failure settling an adoption can surface before anything is written. */
58
- export type AdoptExtensionFailure = AuthoringFailed | AuthoredPackageError | AuthoringOwnerRequired | AuthoringOwnerMismatch | AuthoringScopeUnsupported | LockfileValidationError | WorkspaceLockfileReadFailure | WorkspaceSettingsReadFailure | CandidateFingerprintFailed | FqnInvalidError;
70
+ export type AdoptExtensionFailure = AuthoringFailed | AuthoredPackageError | AuthoringOwnerRequired | AuthoringOwnerMismatch | AuthoringScopeUnsupported | LockfileValidationError | WorkspaceLockfileReadFailure | WorkspaceSettingsReadFailure | WorkspaceStateReadFailure | CandidateFingerprintFailed | FqnInvalidError;
59
71
  /** Everything settling an adoption reads before it freezes a candidate. */
60
- export type PrepareAdoptExtensionRequirements = FileSystem.FileSystem | Path.Path | HttpClient.HttpClient | WorkspaceMutations | ExtensionManagers | ConfiguredAgentOutcomesProvider;
72
+ export type PrepareAdoptExtensionRequirements = FileSystem.FileSystem | Path.Path | HttpClient.HttpClient | WorkspaceMutations | LockfileReader | ExtensionManagers | ConfiguredAgentOutcomesProvider;
61
73
  /** The plan an adoption resolves. */
62
74
  export declare const adoptExtensionPlanName = "Adopt workspace extension";
63
75
  /** Settle an adoption without writing anything. */
@@ -8,6 +8,12 @@
8
8
  * was never declared becomes enabled — adopting content you cannot see would
9
9
  * leave the workspace holding authored work it never materializes.
10
10
  *
11
+ * A package already at its authoring location that nothing declares is
12
+ * adopted in place: the workspace declares it, enabled, and nothing moves.
13
+ * In-place adoption refuses when an installed copy of the same identity also
14
+ * exists, when the workspace already declares the name, and when the authored
15
+ * manifest is invalid or names a different identity.
16
+ *
11
17
  * `prepare` settles the identity, the destination, and the activation, and
12
18
  * writes nothing. `previewOrApply` resolves that same candidate inside the
13
19
  * workspace transaction, so a failed move restores both directories.
@@ -23,9 +29,11 @@ import { materializeAuthoredMcpServer } from "@agentxm/workspace-reconciliation"
23
29
  import { buildAuthoredExtensionStep, } from "@agentxm/workspace-reconciliation";
24
30
  import { extensionTypeToPlural, formatFqn, parseFqn, } from "@agentxm/extension-model/unstable/extensions";
25
31
  import { operationPresentation, prepareExecutionCandidate, resolveExecutionCandidate, } from "@agentxm/workspace-operations";
26
- import { WorkspaceMutations, resolveWorkspaceExtensionRef, } from "@agentxm/workspace-state";
32
+ import { LockfileReader, WorkspaceMutations, observeInstallRoot, resolveWorkspaceExtensionRef, } from "@agentxm/workspace-state";
27
33
  import { protectCreatedAncestors } from "@agentxm/workspace-transactions";
34
+ import { CreateDestinationExists } from "@agentxm/extension-materialization";
28
35
  import { authoredDeclaration } from "../authored-declaration.js";
36
+ import { CreateDestinationInspectionFailed, } from "../authored-package-errors.js";
29
37
  import { preflightCreateOnly } from "../create-preflight.js";
30
38
  import { AuthoringFailed } from "../errors.js";
31
39
  import { requireAuthoredOwner, settingsRelativePath } from "../create/authoring-owner.js";
@@ -46,6 +54,7 @@ export const prepareAdoptExtension = Effect.fn("AdoptExtension.prepare")(functio
46
54
  const fs = yield* FileSystem.FileSystem;
47
55
  const path = yield* Path.Path;
48
56
  const managers = yield* ExtensionManagers;
57
+ const locks = yield* LockfileReader;
49
58
  const parsed = yield* Effect.fromResult(parseFqn(request.fqn));
50
59
  if (ws.layout.scope !== "project") {
51
60
  return yield* new AuthoringScopeUnsupported({ subject: "adoption", scope: ws.layout.scope });
@@ -58,6 +67,8 @@ export const prepareAdoptExtension = Effect.fn("AdoptExtension.prepare")(functio
58
67
  const acquiredPath = path.relative(ws.baseDir, sourceDir);
59
68
  const authoredPath = path.relative(ws.baseDir, targetDir);
60
69
  const settingsPath = settingsRelativePath(path, ws);
70
+ const declaration = authoredDeclaration(ws, parsed.type, name);
71
+ const current = yield* declaration.read;
61
72
  // Refuse an occupied authoring destination here, so a preview refuses it
62
73
  // too; the closure repeats the check under the transaction lock.
63
74
  const createOnly = preflightCreateOnly({
@@ -66,13 +77,50 @@ export const prepareAdoptExtension = Effect.fn("AdoptExtension.prepare")(functio
66
77
  configured: false,
67
78
  destinations: [targetDir],
68
79
  });
69
- yield* createOnly;
70
- const declaration = authoredDeclaration(ws, parsed.type, name);
71
- const current = yield* declaration.read;
80
+ // In-place adoption is decided from what is on disk now and repeated under
81
+ // the transaction lock, so preview and apply refuse the same states.
82
+ const inPlaceRefusals = Effect.gen(function* () {
83
+ const inventory = yield* observeInstallRoot({
84
+ layout: ws.layout,
85
+ graph: yield* ws.getDesiredStateGraph(),
86
+ locks,
87
+ });
88
+ const installedCopy = inventory.packages.find((entry) => entry.type === parsed.type &&
89
+ entry.name === name &&
90
+ (entry.owner === undefined || entry.owner === parsed.owner));
91
+ // Two copies of one identity leave adoption no single content to author.
92
+ if (installedCopy !== undefined) {
93
+ return yield* new CreateDestinationExists({ subject: "Adopt target", path: targetDir });
94
+ }
95
+ const declared = yield* declaration.read;
96
+ if (declared.configured) {
97
+ return yield* new AuthoringFailed({
98
+ category: "conflict",
99
+ detail: `${fqn} is already declared in ${settingsPath}`,
100
+ });
101
+ }
102
+ yield* resolveWorkspaceExtensionRef({
103
+ settingsName: name,
104
+ source: "workspace",
105
+ expectedType: parsed.type,
106
+ layout: ws.layout,
107
+ scope: ws.scope,
108
+ staticPackage: { owner: parsed.owner, name, root: targetDir },
109
+ }).pipe(Effect.mapError((cause) => new AuthoringFailed({
110
+ category: "validation",
111
+ detail: `The authored package at ${authoredPath} is not a valid ${fqn} package`,
112
+ cause,
113
+ })));
114
+ });
115
+ const authoredExists = yield* fs
116
+ .exists(targetDir)
117
+ .pipe(Effect.mapError((cause) => new CreateDestinationInspectionFailed({ path: targetDir, cause })));
118
+ const mode = authoredExists ? "in-place" : "move";
119
+ yield* mode === "in-place" ? inPlaceRefusals : createOnly;
72
120
  // A package the workspace never declared becomes enabled: adopting content
73
121
  // that stays invisible is not what adoption was asked for.
74
- const enabled = Option.getOrElse(current.enabled, () => true);
75
- const artifact = {
122
+ const enabled = mode === "in-place" ? true : Option.getOrElse(current.enabled, () => true);
123
+ const moveArtifact = {
76
124
  path: authoredPath,
77
125
  scope: ws.scope,
78
126
  change: "created",
@@ -82,43 +130,63 @@ export const prepareAdoptExtension = Effect.fn("AdoptExtension.prepare")(functio
82
130
  { path: settingsPath, change: "updated" },
83
131
  ],
84
132
  };
85
- const common = {
133
+ // In-place adoption writes only the declaration; the authored package keeps
134
+ // every byte, so it is not reported as a changed path.
135
+ const inPlaceArtifact = {
136
+ path: settingsPath,
137
+ scope: ws.scope,
138
+ change: "updated",
139
+ targets: [{ path: settingsPath, change: "updated" }],
140
+ };
141
+ const shared = {
86
142
  toStepFailure: authoringStepFailure,
87
143
  location: targetDir,
88
- transactionTargets: [sourceDir],
89
144
  versionRange: Option.none(),
90
145
  label: `Adopt ${fqn}`,
91
- message: `Adopted ${fqn}`,
92
146
  enabled,
93
- allowConfiguredSourceTransition: true,
94
147
  markAuthored: Effect.andThen(declaration.retireExternalResolution, declaration.declare({ enabled: true, env: current.env })),
95
148
  finalizeAuthored: declaration.declare({ enabled, env: current.env }),
96
- plannedArtifact: artifact,
97
- buildArtifact: () => Effect.succeed(artifact),
98
- preflight: Effect.gen(function* () {
99
- yield* createOnly;
100
- // The acquired directory must already hold a resolvable package: a move
101
- // that lands unreadable content would leave the workspace authoring
102
- // something it cannot materialize.
103
- yield* resolveWorkspaceExtensionRef({
104
- settingsName: name,
105
- source: "workspace",
106
- expectedType: parsed.type,
107
- layout: ws.layout,
108
- scope: ws.scope,
109
- staticPackage: { owner: parsed.owner, name, root: sourceDir },
110
- });
111
- }).pipe(Effect.asVoid),
112
- scaffold: Effect.gen(function* () {
113
- yield* protectCreatedAncestors(fs, path, path.dirname(targetDir));
114
- yield* fs.makeDirectory(path.dirname(targetDir), { recursive: true });
115
- yield* fs.rename(sourceDir, targetDir);
116
- }).pipe(Effect.mapError((cause) => new AuthoringFailed({
117
- category: "internal",
118
- detail: `Could not move ${fqn} into authored package storage`,
119
- cause,
120
- }))),
121
149
  };
150
+ const common = mode === "in-place"
151
+ ? {
152
+ ...shared,
153
+ message: `Adopted ${fqn} in place`,
154
+ plannedArtifact: inPlaceArtifact,
155
+ buildArtifact: () => Effect.succeed(inPlaceArtifact),
156
+ preflight: inPlaceRefusals,
157
+ scaffold: Effect.void,
158
+ }
159
+ : {
160
+ ...shared,
161
+ message: `Adopted ${fqn}`,
162
+ transactionTargets: [sourceDir],
163
+ allowConfiguredSourceTransition: true,
164
+ plannedArtifact: moveArtifact,
165
+ buildArtifact: () => Effect.succeed(moveArtifact),
166
+ preflight: Effect.gen(function* () {
167
+ yield* createOnly;
168
+ // The acquired directory must already hold a resolvable package: a move
169
+ // that lands unreadable content would leave the workspace authoring
170
+ // something it cannot materialize.
171
+ yield* resolveWorkspaceExtensionRef({
172
+ settingsName: name,
173
+ source: "workspace",
174
+ expectedType: parsed.type,
175
+ layout: ws.layout,
176
+ scope: ws.scope,
177
+ staticPackage: { owner: parsed.owner, name, root: sourceDir },
178
+ });
179
+ }).pipe(Effect.asVoid),
180
+ scaffold: Effect.gen(function* () {
181
+ yield* protectCreatedAncestors(fs, path, path.dirname(targetDir));
182
+ yield* fs.makeDirectory(path.dirname(targetDir), { recursive: true });
183
+ yield* fs.rename(sourceDir, targetDir);
184
+ }).pipe(Effect.mapError((cause) => new AuthoringFailed({
185
+ category: "internal",
186
+ detail: `Could not move ${fqn} into authored package storage`,
187
+ cause,
188
+ }))),
189
+ };
122
190
  const step = (() => {
123
191
  switch (parsed.type) {
124
192
  case "skill":
@@ -156,7 +224,8 @@ export const prepareAdoptExtension = Effect.fn("AdoptExtension.prepare")(functio
156
224
  fqn,
157
225
  owner: parsed.owner,
158
226
  name,
159
- acquiredPath,
227
+ mode,
228
+ acquiredPath: mode === "move" ? Option.some(acquiredPath) : Option.none(),
160
229
  authoredPath,
161
230
  settingsPath,
162
231
  enabled,
package/package.json CHANGED
@@ -4,26 +4,26 @@
4
4
  "url": "https://github.com/agentxm/axm/issues"
5
5
  },
6
6
  "dependencies": {
7
- "@agentxm/extension-content": "^0.30.1",
8
- "@agentxm/extension-materialization": "^0.30.1",
9
- "@agentxm/extension-model": "^0.30.1",
10
- "@agentxm/extension-sources": "^0.30.1",
11
- "@agentxm/registry-auth": "^0.30.1",
12
- "@agentxm/registry-client": "^0.30.1",
13
- "@agentxm/workspace-operations": "^0.30.1",
14
- "@agentxm/workspace-projection": "^0.30.1",
15
- "@agentxm/workspace-reconciliation": "^0.30.1",
16
- "@agentxm/workspace-state": "^0.30.1",
17
- "@agentxm/workspace-transactions": "^0.30.1",
7
+ "@agentxm/extension-content": "^0.30.2",
8
+ "@agentxm/extension-materialization": "^0.30.2",
9
+ "@agentxm/extension-model": "^0.30.2",
10
+ "@agentxm/extension-sources": "^0.30.2",
11
+ "@agentxm/registry-auth": "^0.30.2",
12
+ "@agentxm/registry-client": "^0.30.2",
13
+ "@agentxm/workspace-operations": "^0.30.2",
14
+ "@agentxm/workspace-projection": "^0.30.2",
15
+ "@agentxm/workspace-reconciliation": "^0.30.2",
16
+ "@agentxm/workspace-state": "^0.30.2",
17
+ "@agentxm/workspace-transactions": "^0.30.2",
18
18
  "effect": "4.0.0-rc.115",
19
19
  "semver": "^7.8.5",
20
20
  "yaml": "^2.9.0"
21
21
  },
22
22
  "description": "AXM extension-authoring feature: new-extension scaffolding, fork, native import, adopt/demote identity policy, and authored pack membership for the axm CLI. Unstable and unsupported — use the axm.sh CLI.",
23
23
  "devDependencies": {
24
- "@agentxm/agent-integration": "^0.30.1",
25
- "@agentxm/extension-resolution": "^0.30.1",
26
- "@agentxm/specification-metadata": "^0.30.1",
24
+ "@agentxm/agent-integration": "^0.30.2",
25
+ "@agentxm/extension-resolution": "^0.30.2",
26
+ "@agentxm/specification-metadata": "^0.30.2",
27
27
  "@effect/platform-node": "4.0.0-rc.115",
28
28
  "@effect/vitest": "4.0.0-rc.115",
29
29
  "@types/bun": "^1.3.14",
@@ -65,5 +65,5 @@
65
65
  },
66
66
  "sideEffects": false,
67
67
  "type": "module",
68
- "version": "0.30.1"
68
+ "version": "0.30.2"
69
69
  }