@agentxm/workspace-state 0.28.11 → 0.28.13
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/src/index.d.ts +22 -13
- package/dist/src/index.js +25 -16
- package/dist/src/live.d.ts +46 -0
- package/dist/src/live.js +60 -0
- package/dist/src/lockfile/lockfile.js +1 -2
- package/dist/src/settings/index.d.ts +1 -1
- package/dist/src/settings/index.js +1 -1
- package/dist/src/settings/schema.d.ts +12 -0
- package/dist/src/settings/schema.js +13 -1
- package/dist/src/settings/settings.d.ts +1 -1
- package/dist/src/settings/settings.js +1 -3
- package/dist/src/testing.d.ts +24 -0
- package/dist/src/testing.js +35 -0
- package/dist/src/utils/path-safety.d.ts +0 -5
- package/dist/src/utils/path-safety.js +1 -10
- package/dist/src/workspace/accepted-canonical-ref.d.ts +1 -1
- package/dist/src/workspace/accepted-canonical-ref.js +1 -1
- package/dist/src/workspace/accepted-resolution-writer.d.ts +42 -0
- package/dist/src/workspace/accepted-resolution-writer.js +67 -0
- package/dist/src/workspace/canonical-observation.js +1 -1
- package/dist/src/workspace/configured-entry.d.ts +11 -0
- package/dist/src/workspace/configured-entry.js +8 -0
- package/dist/src/workspace/create-symlink.d.ts +1 -1
- package/dist/src/workspace/create-symlink.js +1 -2
- package/dist/src/workspace/desired-identity.d.ts +19 -0
- package/dist/src/workspace/desired-identity.js +26 -0
- package/dist/src/workspace/desired-state-reader.d.ts +32 -0
- package/dist/src/workspace/desired-state-reader.js +56 -0
- package/dist/src/workspace/desired-state-writer.d.ts +46 -0
- package/dist/src/workspace/desired-state-writer.js +214 -0
- package/dist/src/workspace/entry-accessors.d.ts +50 -0
- package/dist/src/workspace/entry-accessors.js +41 -0
- package/dist/src/workspace/extension-paths-service.d.ts +33 -0
- package/dist/src/workspace/extension-paths-service.js +133 -0
- package/dist/src/workspace/location.d.ts +49 -0
- package/dist/src/workspace/location.js +85 -0
- package/dist/src/workspace/lockfile-reader.d.ts +41 -0
- package/dist/src/workspace/lockfile-reader.js +54 -0
- package/dist/src/workspace/materialized-file-target.d.ts +2 -2
- package/dist/src/workspace/materialized-file-target.js +2 -2
- package/dist/src/workspace/other-scope-reader.d.ts +34 -0
- package/dist/src/workspace/other-scope-reader.js +35 -0
- package/dist/src/workspace/paths.d.ts +2 -2
- package/dist/src/workspace/read-model/__fixtures__/test-layer.d.ts +1 -1
- package/dist/src/workspace/read-model/__fixtures__/test-layer.js +1 -1
- package/dist/src/workspace/read-model/discovery/skills.d.ts +1 -1
- package/dist/src/workspace/read-model/discovery/skills.js +1 -1
- package/dist/src/workspace/read-model/scanners/canonical-extensions.js +2 -2
- package/dist/src/workspace/read-model/scanners/mcp-config.js +1 -1
- package/dist/src/workspace/read-model/service.js +1 -1
- package/dist/src/workspace/read-model-record-readers.js +1 -1
- package/dist/src/workspace/remove-if-exists.d.ts +1 -1
- package/dist/src/workspace/remove-if-exists.js +1 -2
- package/dist/src/workspace/service-interface.d.ts +8 -60
- package/dist/src/workspace/service.d.ts +26 -1886
- package/dist/src/workspace/service.js +144 -1285
- package/dist/src/workspace/settings-reader.d.ts +59 -0
- package/dist/src/workspace/settings-reader.js +109 -0
- package/dist/src/workspace/settings-writer.d.ts +53 -0
- package/dist/src/workspace/settings-writer.js +90 -0
- package/dist/src/workspace/setup-scope-support.js +1 -1
- package/dist/src/workspace/shared.d.ts +24 -0
- package/dist/src/workspace/shared.js +20 -0
- package/dist/src/workspace/state-cells.d.ts +35 -0
- package/dist/src/workspace/state-cells.js +42 -0
- package/dist/src/workspace/subagent-paths.d.ts +49 -0
- package/dist/src/workspace/subagent-paths.js +26 -0
- package/dist/src/workspace/test-stubs.js +0 -11
- package/dist/src/workspace/workspace-records.d.ts +41 -0
- package/dist/src/workspace/workspace-records.js +43 -0
- package/package.json +14 -5
- package/dist/src/utils/atomic-write.d.ts +0 -67
- package/dist/src/utils/atomic-write.js +0 -71
- package/dist/src/workspace/configured-entry-resolution/types.d.ts +0 -32
- package/dist/src/workspace/configured-entry-resolution/types.js +0 -2
- package/dist/src/workspace/footprint-recorder.d.ts +0 -30
- package/dist/src/workspace/footprint-recorder.js +0 -32
- package/dist/src/workspace/mcp-entry-semantics.d.ts +0 -28
- package/dist/src/workspace/mcp-entry-semantics.js +0 -58
- package/dist/src/workspace/read-model/agent-presence.d.ts +0 -32
- package/dist/src/workspace/read-model/agent-presence.js +0 -21
- package/dist/src/workspace/scope-refusal.d.ts +0 -26
- package/dist/src/workspace/scope-refusal.js +0 -29
- package/dist/src/workspace/transaction.d.ts +0 -218
- package/dist/src/workspace/transaction.js +0 -177
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accepted-resolution writer: lockfile-only mutations of the selected scope.
|
|
3
|
+
* Each commit merges the lockfile snapshot per entry, keeps an accepted
|
|
4
|
+
* resolution that is semantically unchanged byte-identical, and is
|
|
5
|
+
* serialized by the workspace's mutation mutex.
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
*/
|
|
9
|
+
import * as ServiceMap from "effect/Context";
|
|
10
|
+
import * as DateTime from "effect/DateTime";
|
|
11
|
+
import * as Effect from "effect/Effect";
|
|
12
|
+
import * as Layer from "effect/Layer";
|
|
13
|
+
import { commitLockfileSnapshotUpdateAtPath } from "../lockfile/index.js";
|
|
14
|
+
import { lockEntries } from "./entry-accessors.js";
|
|
15
|
+
import { WorkspaceLocation } from "./location.js";
|
|
16
|
+
import { WorkspaceStateShared } from "./shared.js";
|
|
17
|
+
import { readLockfileCell } from "./state-cells.js";
|
|
18
|
+
export class AcceptedResolutionWriter extends ServiceMap.Service()("@agentxm/workspace-state/AcceptedResolutionWriter") {
|
|
19
|
+
}
|
|
20
|
+
const normalizeForStableCompare = (value) => {
|
|
21
|
+
if (DateTime.isDateTime(value))
|
|
22
|
+
return DateTime.formatIso(value);
|
|
23
|
+
if (Array.isArray(value))
|
|
24
|
+
return value.map(normalizeForStableCompare);
|
|
25
|
+
if (typeof value === "object" && value !== null) {
|
|
26
|
+
const normalized = {};
|
|
27
|
+
for (const [key, entryValue] of Object.entries(value).sort(([left], [right]) => left.localeCompare(right))) {
|
|
28
|
+
normalized[key] = normalizeForStableCompare(entryValue);
|
|
29
|
+
}
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
};
|
|
34
|
+
/** Structural equality that treats date-times by their ISO rendering. */
|
|
35
|
+
export const stableCompare = (left, right) => JSON.stringify(normalizeForStableCompare(left)) ===
|
|
36
|
+
JSON.stringify(normalizeForStableCompare(right));
|
|
37
|
+
export const lockEntrySemanticallyEqual = (current, next) => current !== undefined && stableCompare(current, next);
|
|
38
|
+
/** Keep the recorded object when the replacement carries the same facts. */
|
|
39
|
+
export const preserveAcceptedResolutionOnNoop = (current, next) => (lockEntrySemanticallyEqual(current, next) && current !== undefined ? current : next);
|
|
40
|
+
/** Types whose semantically unchanged resolution skips the commit entirely. */
|
|
41
|
+
const skipsUnchangedCommit = (type) => type === "skill" || type === "subagent";
|
|
42
|
+
export const makeAcceptedResolutionWriter = (location, mutex) => {
|
|
43
|
+
const current = readLockfileCell(location, location.runtimeDir);
|
|
44
|
+
const commit = (base, next) => commitLockfileSnapshotUpdateAtPath(location.lockPath, base, next);
|
|
45
|
+
const serialized = mutex.withPermits(1);
|
|
46
|
+
return {
|
|
47
|
+
setAccepted: (type, key, entry) => serialized(Effect.gen(function* () {
|
|
48
|
+
const lockfile = yield* current;
|
|
49
|
+
const accessor = lockEntries[type];
|
|
50
|
+
const previous = accessor.entries(lockfile)[key];
|
|
51
|
+
if (skipsUnchangedCommit(type) && lockEntrySemanticallyEqual(previous, entry))
|
|
52
|
+
return;
|
|
53
|
+
yield* commit(lockfile, accessor.set(lockfile, key, preserveAcceptedResolutionOnNoop(previous, entry)));
|
|
54
|
+
})).pipe(Effect.withSpan("AcceptedResolutionWriter.setAccepted")),
|
|
55
|
+
removeAccepted: (type, key) => serialized(Effect.gen(function* () {
|
|
56
|
+
const lockfile = yield* current;
|
|
57
|
+
const accessor = lockEntries[type];
|
|
58
|
+
if (accessor.entries(lockfile)[key] === undefined)
|
|
59
|
+
return;
|
|
60
|
+
yield* commit(lockfile, accessor.remove(lockfile, key));
|
|
61
|
+
})).pipe(Effect.withSpan("AcceptedResolutionWriter.removeAccepted")),
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
export const AcceptedResolutionWriterLive = Layer.effect(AcceptedResolutionWriter, Effect.gen(function* () {
|
|
65
|
+
return makeAcceptedResolutionWriter(yield* WorkspaceLocation, (yield* WorkspaceStateShared).mutex);
|
|
66
|
+
}));
|
|
67
|
+
//# sourceMappingURL=accepted-resolution-writer.js.map
|
|
@@ -12,7 +12,7 @@ import { KnowledgeManifestSchema } from "@agentxm/extension-model/unstable/knowl
|
|
|
12
12
|
import { McpServerManifestSchema } from "@agentxm/extension-model/unstable/mcps/manifest-schema";
|
|
13
13
|
import { PackManifestSchema } from "@agentxm/extension-model/unstable/packs/manifest-schema";
|
|
14
14
|
import { RuleManifestSchema } from "@agentxm/extension-model/unstable/rules/manifest-schema";
|
|
15
|
-
import { parseSkillMd } from "@agentxm/
|
|
15
|
+
import { parseSkillMd } from "@agentxm/extension-content";
|
|
16
16
|
import { SkillManifestSchema } from "@agentxm/extension-model/unstable/skills/manifest-schema";
|
|
17
17
|
import { SubagentManifestSchema } from "@agentxm/extension-model/unstable/subagents/manifest-schema";
|
|
18
18
|
import { printSourceParams } from "@agentxm/extension-model/unstable/sources/printer";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for configured extension entries.
|
|
3
|
+
*
|
|
4
|
+
* @experimental This API is unstable and may change without notice.
|
|
5
|
+
*/
|
|
6
|
+
export interface ConfiguredEntryEnabledState {
|
|
7
|
+
readonly enabled: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const isConfiguredEntryEnabled: (entry: ConfiguredEntryEnabledState) => boolean;
|
|
10
|
+
export declare const enabledConfiguredEntries: <TEntry extends ConfiguredEntryEnabledState>(entries: Readonly<Record<string, TEntry>>) => ReadonlyArray<readonly [string, TEntry]>;
|
|
11
|
+
//# sourceMappingURL=configured-entry.d.ts.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for configured extension entries.
|
|
3
|
+
*
|
|
4
|
+
* @experimental This API is unstable and may change without notice.
|
|
5
|
+
*/
|
|
6
|
+
export const isConfiguredEntryEnabled = (entry) => entry.enabled;
|
|
7
|
+
export const enabledConfiguredEntries = (entries) => Object.entries(entries).filter(([, entry]) => isConfiguredEntryEnabled(entry));
|
|
8
|
+
//# sourceMappingURL=configured-entry.js.map
|
|
@@ -22,5 +22,5 @@ export type SymlinkResult = "created" | "replaced" | "no-op" | "skipped";
|
|
|
22
22
|
export declare const createSymlink: (opts: {
|
|
23
23
|
readonly target: string;
|
|
24
24
|
readonly link: string;
|
|
25
|
-
}) => Effect.Effect<"skipped" | "created" | "replaced" | "no-op", import("
|
|
25
|
+
}) => Effect.Effect<"skipped" | "created" | "replaced" | "no-op", import("@agentxm/workspace-transactions").WorkspaceSnapshotError | SymlinkCreationError, FileSystem.FileSystem | Path.Path>;
|
|
26
26
|
//# sourceMappingURL=create-symlink.d.ts.map
|
|
@@ -3,8 +3,7 @@ import * as Path from "effect/Path";
|
|
|
3
3
|
import * as Effect from "effect/Effect";
|
|
4
4
|
import * as Option from "effect/Option";
|
|
5
5
|
import { SymlinkCreationError } from "./errors.js";
|
|
6
|
-
import { protectCreatedAncestors, protectWorkspacePath } from "
|
|
7
|
-
import { recordFootprint } from "./footprint-recorder.js";
|
|
6
|
+
import { protectCreatedAncestors, protectWorkspacePath, recordFootprint, } from "@agentxm/workspace-transactions";
|
|
8
7
|
import { resolveParentSymlinks } from "../utils/resolve-parent-symlinks.js";
|
|
9
8
|
/**
|
|
10
9
|
* Creates a relative symlink from `link` to `target`, handling the full
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ExtensionName, type ExtensionType } from "@agentxm/extension-model/unstable/extensions/common";
|
|
2
|
+
import type { Handle } from "@agentxm/extension-model/unstable/extensions/handle";
|
|
3
|
+
export type DesiredPackageAuthority = "registry" | "workspace";
|
|
4
|
+
export interface DecodedDesiredExtensionIdentity {
|
|
5
|
+
readonly authority: DesiredPackageAuthority;
|
|
6
|
+
readonly owner: Handle;
|
|
7
|
+
readonly type: ExtensionType;
|
|
8
|
+
readonly name: ExtensionName;
|
|
9
|
+
readonly fqn: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Decode the source-qualified identity stored in the desired-state graph.
|
|
13
|
+
*
|
|
14
|
+
* This is deliberately narrower than source-locator parsing: desired graph
|
|
15
|
+
* identities are either validated Registry FQNs or `workspace:` followed by a
|
|
16
|
+
* validated FQN. Unknown authorities fail closed.
|
|
17
|
+
*/
|
|
18
|
+
export declare const decodeDesiredExtensionIdentity: (identity: string) => DecodedDesiredExtensionIdentity | undefined;
|
|
19
|
+
//# sourceMappingURL=desired-identity.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { parseExtensionFqnParts, } from "@agentxm/extension-model/unstable/extensions/common";
|
|
2
|
+
const workspacePrefix = "workspace:";
|
|
3
|
+
/**
|
|
4
|
+
* Decode the source-qualified identity stored in the desired-state graph.
|
|
5
|
+
*
|
|
6
|
+
* This is deliberately narrower than source-locator parsing: desired graph
|
|
7
|
+
* identities are either validated Registry FQNs or `workspace:` followed by a
|
|
8
|
+
* validated FQN. Unknown authorities fail closed.
|
|
9
|
+
*/
|
|
10
|
+
export const decodeDesiredExtensionIdentity = (identity) => {
|
|
11
|
+
const authority = identity.startsWith(workspacePrefix)
|
|
12
|
+
? "workspace"
|
|
13
|
+
: "registry";
|
|
14
|
+
const fqn = authority === "workspace" ? identity.slice(workspacePrefix.length) : identity;
|
|
15
|
+
const parsed = parseExtensionFqnParts(fqn);
|
|
16
|
+
if (parsed === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
return {
|
|
19
|
+
authority,
|
|
20
|
+
owner: parsed.owner,
|
|
21
|
+
type: parsed.type,
|
|
22
|
+
name: parsed.name,
|
|
23
|
+
fqn,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=desired-identity.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Desired-state reader: the graph derived from settings and installed or
|
|
3
|
+
* prospective Pack manifests, validated against the accepted lock state, and
|
|
4
|
+
* the Pack-membership query commands ask before removing an extension.
|
|
5
|
+
*
|
|
6
|
+
* @experimental This API is unstable and may change without notice.
|
|
7
|
+
*/
|
|
8
|
+
import * as ServiceMap from "effect/Context";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import type * as FileSystem from "effect/FileSystem";
|
|
11
|
+
import * as Layer from "effect/Layer";
|
|
12
|
+
import type * as Path from "effect/Path";
|
|
13
|
+
import { type DesiredStateGraph, type ProspectivePackRef } from "./desired-state-graph.js";
|
|
14
|
+
import { DesiredPackGraphIncomplete } from "./errors.js";
|
|
15
|
+
import { WorkspaceLocation, type WorkspaceLocationService } from "./location.js";
|
|
16
|
+
import type { ExtensionTarget, WorkspaceStateReadFailure } from "./service-interface.js";
|
|
17
|
+
import { SettingsReader, type SettingsReaderService } from "./settings-reader.js";
|
|
18
|
+
export interface DesiredStateReaderService {
|
|
19
|
+
/** Build desired extension state from settings and installed or prospective Pack manifests. */
|
|
20
|
+
readonly graph: (options?: {
|
|
21
|
+
readonly prospectivePacks?: ReadonlyArray<ProspectivePackRef>;
|
|
22
|
+
}) => Effect.Effect<DesiredStateGraph, WorkspaceStateReadFailure, FileSystem.FileSystem | Path.Path>;
|
|
23
|
+
/** Whether an installed Pack's dependency maps reference the target. */
|
|
24
|
+
readonly isRequiredByInstalledPack: (target: ExtensionTarget) => Effect.Effect<boolean, WorkspaceStateReadFailure | DesiredPackGraphIncomplete, FileSystem.FileSystem | Path.Path>;
|
|
25
|
+
}
|
|
26
|
+
declare const DesiredStateReader_base: ServiceMap.ServiceClass<DesiredStateReader, "@agentxm/workspace-state/DesiredStateReader", DesiredStateReaderService>;
|
|
27
|
+
export declare class DesiredStateReader extends DesiredStateReader_base {
|
|
28
|
+
}
|
|
29
|
+
export declare const makeDesiredStateReader: (location: WorkspaceLocationService, settings: SettingsReaderService) => DesiredStateReaderService;
|
|
30
|
+
export declare const DesiredStateReaderLive: Layer.Layer<DesiredStateReader, never, WorkspaceLocation | SettingsReader>;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=desired-state-reader.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Desired-state reader: the graph derived from settings and installed or
|
|
3
|
+
* prospective Pack manifests, validated against the accepted lock state, and
|
|
4
|
+
* the Pack-membership query commands ask before removing an extension.
|
|
5
|
+
*
|
|
6
|
+
* @experimental This API is unstable and may change without notice.
|
|
7
|
+
*/
|
|
8
|
+
import * as ServiceMap from "effect/Context";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import * as Layer from "effect/Layer";
|
|
11
|
+
import * as Ref from "effect/Ref";
|
|
12
|
+
import { buildDesiredStateGraph, } from "./desired-state-graph.js";
|
|
13
|
+
import { validateDesiredPackLock } from "./desired-pack-lock.js";
|
|
14
|
+
import { DesiredPackGraphIncomplete } from "./errors.js";
|
|
15
|
+
import { WorkspaceLocation } from "./location.js";
|
|
16
|
+
import { SettingsReader } from "./settings-reader.js";
|
|
17
|
+
import { readLockfileCell } from "./state-cells.js";
|
|
18
|
+
export class DesiredStateReader extends ServiceMap.Service()("@agentxm/workspace-state/DesiredStateReader") {
|
|
19
|
+
}
|
|
20
|
+
export const makeDesiredStateReader = (location, settings) => {
|
|
21
|
+
const graph = (options) => Effect.gen(function* () {
|
|
22
|
+
const current = yield* settings.settings;
|
|
23
|
+
const configuredSources = yield* settings.configuredSources;
|
|
24
|
+
const layout = yield* Ref.get(location.layout);
|
|
25
|
+
const registryAuthorities = Object.fromEntries(configuredSources.flatMap((source) => source.type === "registry" ? [[source.name, source.location]] : []));
|
|
26
|
+
const built = yield* buildDesiredStateGraph({
|
|
27
|
+
baseDir: location.baseDir,
|
|
28
|
+
settings: current,
|
|
29
|
+
layout,
|
|
30
|
+
registryAuthorities,
|
|
31
|
+
...(options?.prospectivePacks === undefined
|
|
32
|
+
? {}
|
|
33
|
+
: { prospectivePacks: options.prospectivePacks }),
|
|
34
|
+
});
|
|
35
|
+
const lockfile = yield* readLockfileCell(location, location.runtimeDir);
|
|
36
|
+
return yield* validateDesiredPackLock({ graph: built, lockfile, layout });
|
|
37
|
+
}).pipe(Effect.withSpan("DesiredStateReader.graph"));
|
|
38
|
+
return {
|
|
39
|
+
graph,
|
|
40
|
+
isRequiredByInstalledPack: (target) => Effect.gen(function* () {
|
|
41
|
+
if (target.type === "pack")
|
|
42
|
+
return false;
|
|
43
|
+
const current = yield* graph();
|
|
44
|
+
if (!current.complete) {
|
|
45
|
+
return yield* new DesiredPackGraphIncomplete();
|
|
46
|
+
}
|
|
47
|
+
return current.nodes.some((node) => node.type === target.type &&
|
|
48
|
+
node.name === target.name &&
|
|
49
|
+
node.origins.some((origin) => origin.type === "pack"));
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export const DesiredStateReaderLive = Layer.effect(DesiredStateReader, Effect.gen(function* () {
|
|
54
|
+
return makeDesiredStateReader(yield* WorkspaceLocation, yield* SettingsReader);
|
|
55
|
+
}));
|
|
56
|
+
//# sourceMappingURL=desired-state-reader.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Desired-state writer: the coupled settings-and-lockfile mutations that
|
|
3
|
+
* declare or undeclare one extension in the selected scope. Declaring writes
|
|
4
|
+
* the desired entry and records its accepted resolution together;
|
|
5
|
+
* undeclaring removes both. The per-type shapes differ where the schemas
|
|
6
|
+
* do — Packs keep their enabled flag, MCP servers keep env and enabled and
|
|
7
|
+
* are keyed by resolution identity, Knowledge keeps its instruction entry —
|
|
8
|
+
* and each family's semantics are preserved here exactly.
|
|
9
|
+
*
|
|
10
|
+
* @experimental This API is unstable and may change without notice.
|
|
11
|
+
*/
|
|
12
|
+
import * as ServiceMap from "effect/Context";
|
|
13
|
+
import * as Effect from "effect/Effect";
|
|
14
|
+
import type * as FileSystem from "effect/FileSystem";
|
|
15
|
+
import * as Layer from "effect/Layer";
|
|
16
|
+
import type * as Path from "effect/Path";
|
|
17
|
+
import type * as Semaphore from "effect/Semaphore";
|
|
18
|
+
import type { InstallableExtensionType } from "@agentxm/extension-model/unstable/extensions/installable-types";
|
|
19
|
+
import { DesiredStateReader, type DesiredStateReaderService } from "./desired-state-reader.js";
|
|
20
|
+
import { WorkspaceLocation, type WorkspaceLocationService } from "./location.js";
|
|
21
|
+
import type { SetHookArgs, SetKnowledgeArgs, SetMcpServerArgs, SetPackArgs, SetRuleArgs, SetSkillArgs, SetSubagentArgs, WorkspaceStateMutationFailure } from "./service-interface.js";
|
|
22
|
+
import { WorkspaceStateShared } from "./shared.js";
|
|
23
|
+
/** The declaration each installable extension type accepts. */
|
|
24
|
+
export interface DeclareArgsByType {
|
|
25
|
+
readonly skill: SetSkillArgs;
|
|
26
|
+
readonly pack: SetPackArgs;
|
|
27
|
+
readonly "mcp-server": SetMcpServerArgs;
|
|
28
|
+
readonly subagent: SetSubagentArgs;
|
|
29
|
+
readonly rule: SetRuleArgs;
|
|
30
|
+
readonly hook: SetHookArgs;
|
|
31
|
+
readonly knowledge: SetKnowledgeArgs;
|
|
32
|
+
}
|
|
33
|
+
type Write = Effect.Effect<void, WorkspaceStateMutationFailure, FileSystem.FileSystem | Path.Path>;
|
|
34
|
+
export interface DesiredStateWriterService {
|
|
35
|
+
/** Declare the extension in settings and record its accepted resolution. */
|
|
36
|
+
readonly declare: <T extends InstallableExtensionType>(type: T, args: DeclareArgsByType[T]) => Write;
|
|
37
|
+
/** Remove the extension from settings and its accepted resolution. No-op when absent. */
|
|
38
|
+
readonly undeclare: (type: InstallableExtensionType, name: string) => Write;
|
|
39
|
+
}
|
|
40
|
+
declare const DesiredStateWriter_base: ServiceMap.ServiceClass<DesiredStateWriter, "@agentxm/workspace-state/DesiredStateWriter", DesiredStateWriterService>;
|
|
41
|
+
export declare class DesiredStateWriter extends DesiredStateWriter_base {
|
|
42
|
+
}
|
|
43
|
+
export declare const makeDesiredStateWriter: (location: WorkspaceLocationService, desiredState: DesiredStateReaderService, mutex: Semaphore.Semaphore) => DesiredStateWriterService;
|
|
44
|
+
export declare const DesiredStateWriterLive: Layer.Layer<DesiredStateWriter, never, WorkspaceLocation | DesiredStateReader | WorkspaceStateShared>;
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=desired-state-writer.d.ts.map
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Desired-state writer: the coupled settings-and-lockfile mutations that
|
|
3
|
+
* declare or undeclare one extension in the selected scope. Declaring writes
|
|
4
|
+
* the desired entry and records its accepted resolution together;
|
|
5
|
+
* undeclaring removes both. The per-type shapes differ where the schemas
|
|
6
|
+
* do — Packs keep their enabled flag, MCP servers keep env and enabled and
|
|
7
|
+
* are keyed by resolution identity, Knowledge keeps its instruction entry —
|
|
8
|
+
* and each family's semantics are preserved here exactly.
|
|
9
|
+
*
|
|
10
|
+
* @experimental This API is unstable and may change without notice.
|
|
11
|
+
*/
|
|
12
|
+
import * as ServiceMap from "effect/Context";
|
|
13
|
+
import * as Effect from "effect/Effect";
|
|
14
|
+
import * as Layer from "effect/Layer";
|
|
15
|
+
import * as Option from "effect/Option";
|
|
16
|
+
import { decodeExtensionNameSync, formatFqn } from "@agentxm/extension-model/unstable/extensions";
|
|
17
|
+
import { printSourceParams } from "@agentxm/extension-model/unstable/sources/printer";
|
|
18
|
+
import { commitLockfileSnapshotUpdateAtPath } from "../lockfile/index.js";
|
|
19
|
+
import { writeSettingsAtPath, } from "../settings/index.js";
|
|
20
|
+
import { lockEntrySemanticallyEqual, preserveAcceptedResolutionOnNoop, stableCompare, } from "./accepted-resolution-writer.js";
|
|
21
|
+
import { DesiredStateReader } from "./desired-state-reader.js";
|
|
22
|
+
import { lockEntries, settingsEntries } from "./entry-accessors.js";
|
|
23
|
+
import { lockEntryToSourceParams } from "./lock-entry-to-source-params.js";
|
|
24
|
+
import { WorkspaceLocation } from "./location.js";
|
|
25
|
+
import { WorkspaceStateShared } from "./shared.js";
|
|
26
|
+
import { readLockfileCell, readSettingsOrDefault } from "./state-cells.js";
|
|
27
|
+
export class DesiredStateWriter extends ServiceMap.Service()("@agentxm/workspace-state/DesiredStateWriter") {
|
|
28
|
+
}
|
|
29
|
+
/** The settings source locator a Registry resolution is declared under. */
|
|
30
|
+
const registryLocator = (sourceName, fqn, versionRange) => `${sourceName}:${Option.isSome(versionRange) ? `${fqn}@${versionRange.value}` : fqn}`;
|
|
31
|
+
export const makeDesiredStateWriter = (location, desiredState, mutex) => {
|
|
32
|
+
const settings = readSettingsOrDefault(location, location.runtimeDir);
|
|
33
|
+
const lockfile = readLockfileCell(location, location.runtimeDir);
|
|
34
|
+
const writeSettings = (next) => writeSettingsAtPath(location.settingsPath, next);
|
|
35
|
+
const commit = (base, next) => commitLockfileSnapshotUpdateAtPath(location.lockPath, base, next);
|
|
36
|
+
const serialized = mutex.withPermits(1);
|
|
37
|
+
/** Declare a sourced entry then record the resolution, always writing both. */
|
|
38
|
+
const declareSourced = (type, configured, accepted, makeEntry, name, lockEntry, versionRange) => Effect.gen(function* () {
|
|
39
|
+
const source = lockEntry.type === "registry"
|
|
40
|
+
? registryLocator(lockEntry.sourceName, formatFqn({ owner: lockEntry.owner, type, name: decodeExtensionNameSync(name) }), versionRange)
|
|
41
|
+
: printSourceParams(lockEntryToSourceParams(lockEntry));
|
|
42
|
+
yield* writeSettings(configured.set(yield* settings, name, makeEntry(source)));
|
|
43
|
+
const current = yield* lockfile;
|
|
44
|
+
const previous = accepted.entries(current)[name];
|
|
45
|
+
yield* commit(current, accepted.set(current, name, preserveAcceptedResolutionOnNoop(previous, lockEntry)));
|
|
46
|
+
});
|
|
47
|
+
const declareSkill = ({ name, lockEntry, versionRange }) => Effect.gen(function* () {
|
|
48
|
+
const source = lockEntry.type === "registry"
|
|
49
|
+
? registryLocator(lockEntry.sourceName, formatFqn({
|
|
50
|
+
owner: lockEntry.owner,
|
|
51
|
+
type: "skill",
|
|
52
|
+
name: decodeExtensionNameSync(name),
|
|
53
|
+
}), versionRange)
|
|
54
|
+
: printSourceParams(lockEntryToSourceParams(lockEntry));
|
|
55
|
+
const current = yield* settings;
|
|
56
|
+
const nextEntry = { source, enabled: true };
|
|
57
|
+
const currentLockfile = yield* lockfile;
|
|
58
|
+
const currentLockEntry = currentLockfile.skills[name];
|
|
59
|
+
if (!stableCompare(settingsEntries.skill.entries(current)[name], nextEntry)) {
|
|
60
|
+
yield* writeSettings(settingsEntries.skill.set(current, name, nextEntry));
|
|
61
|
+
}
|
|
62
|
+
if (lockEntrySemanticallyEqual(currentLockEntry, lockEntry))
|
|
63
|
+
return;
|
|
64
|
+
yield* commit(currentLockfile, lockEntries.skill.set(currentLockfile, name, preserveAcceptedResolutionOnNoop(currentLockEntry, lockEntry)));
|
|
65
|
+
});
|
|
66
|
+
const declareSubagent = ({ name, lockEntry, versionRange }) => Effect.gen(function* () {
|
|
67
|
+
const source = lockEntry.type === "registry"
|
|
68
|
+
? registryLocator(lockEntry.sourceName, formatFqn({
|
|
69
|
+
owner: lockEntry.owner,
|
|
70
|
+
type: "subagent",
|
|
71
|
+
name: decodeExtensionNameSync(name),
|
|
72
|
+
}), versionRange)
|
|
73
|
+
: printSourceParams(lockEntryToSourceParams(lockEntry));
|
|
74
|
+
const current = yield* settings;
|
|
75
|
+
const nextEntry = { source, enabled: true };
|
|
76
|
+
const currentLockfile = yield* lockfile;
|
|
77
|
+
const currentLockEntry = lockEntries.subagent.entries(currentLockfile)[name];
|
|
78
|
+
if (!stableCompare(settingsEntries.subagent.entries(current)[name], nextEntry)) {
|
|
79
|
+
yield* writeSettings(settingsEntries.subagent.set(current, name, nextEntry));
|
|
80
|
+
}
|
|
81
|
+
if (lockEntrySemanticallyEqual(currentLockEntry, lockEntry))
|
|
82
|
+
return;
|
|
83
|
+
yield* commit(currentLockfile, lockEntries.subagent.set(currentLockfile, name, preserveAcceptedResolutionOnNoop(currentLockEntry, lockEntry)));
|
|
84
|
+
});
|
|
85
|
+
const declareKnowledge = ({ name, lockEntry, versionRange }) => Effect.gen(function* () {
|
|
86
|
+
const source = lockEntry.type === "registry"
|
|
87
|
+
? registryLocator(lockEntry.sourceName, formatFqn({
|
|
88
|
+
owner: lockEntry.owner,
|
|
89
|
+
type: "knowledge",
|
|
90
|
+
name: decodeExtensionNameSync(name),
|
|
91
|
+
}), versionRange)
|
|
92
|
+
: printSourceParams(lockEntryToSourceParams(lockEntry));
|
|
93
|
+
const current = yield* settings;
|
|
94
|
+
const currentEntry = settingsEntries.knowledge.entries(current)[name];
|
|
95
|
+
yield* writeSettings(settingsEntries.knowledge.set(current, name, {
|
|
96
|
+
source,
|
|
97
|
+
enabled: true,
|
|
98
|
+
...(currentEntry?.instructionEntry === undefined
|
|
99
|
+
? {}
|
|
100
|
+
: { instructionEntry: currentEntry.instructionEntry }),
|
|
101
|
+
}));
|
|
102
|
+
const currentLockfile = yield* lockfile;
|
|
103
|
+
const previous = lockEntries.knowledge.entries(currentLockfile)[name];
|
|
104
|
+
yield* commit(currentLockfile, lockEntries.knowledge.set(currentLockfile, name, preserveAcceptedResolutionOnNoop(previous, lockEntry)));
|
|
105
|
+
});
|
|
106
|
+
const declarePack = (args) => Effect.gen(function* () {
|
|
107
|
+
const { versionRange, ...lockEntry } = args;
|
|
108
|
+
const name = lockEntry.name;
|
|
109
|
+
const source = registryLocator(lockEntry.sourceName, formatFqn({ owner: args.owner, type: "pack", name: decodeExtensionNameSync(name) }), versionRange);
|
|
110
|
+
const current = yield* settings;
|
|
111
|
+
const enabled = settingsEntries.pack.entries(current)[name]?.enabled ?? true;
|
|
112
|
+
yield* writeSettings(settingsEntries.pack.set(current, name, { source, enabled }));
|
|
113
|
+
const currentLockfile = yield* lockfile;
|
|
114
|
+
const previous = lockEntries.pack.entries(currentLockfile)[name];
|
|
115
|
+
yield* commit(currentLockfile, lockEntries.pack.set(currentLockfile, name, preserveAcceptedResolutionOnNoop(previous, lockEntry)));
|
|
116
|
+
});
|
|
117
|
+
const declareMcpServer = ({ name, resolutionKey, lockEntry, versionRange, env, enabled, }) => Effect.gen(function* () {
|
|
118
|
+
const current = yield* settings;
|
|
119
|
+
const existing = settingsEntries["mcp-server"].entries(current)[name];
|
|
120
|
+
yield* writeSettings(settingsEntries["mcp-server"].set(current, name, {
|
|
121
|
+
kind: "sourced",
|
|
122
|
+
source: lockEntry.type === "registry"
|
|
123
|
+
? registryLocator(lockEntry.sourceName, formatFqn({ owner: lockEntry.owner, type: "mcp-server", name: lockEntry.name }), versionRange)
|
|
124
|
+
: printSourceParams(lockEntryToSourceParams(lockEntry)),
|
|
125
|
+
enabled: enabled ?? existing?.enabled ?? true,
|
|
126
|
+
env: env ?? existing?.env ?? {},
|
|
127
|
+
}));
|
|
128
|
+
const currentLockfile = yield* lockfile;
|
|
129
|
+
const previous = lockEntries["mcp-server"].entries(currentLockfile)[resolutionKey];
|
|
130
|
+
yield* commit(currentLockfile, lockEntries["mcp-server"].set(currentLockfile, resolutionKey, preserveAcceptedResolutionOnNoop(previous, lockEntry)));
|
|
131
|
+
});
|
|
132
|
+
const declareByType = {
|
|
133
|
+
skill: declareSkill,
|
|
134
|
+
pack: declarePack,
|
|
135
|
+
"mcp-server": declareMcpServer,
|
|
136
|
+
subagent: declareSubagent,
|
|
137
|
+
rule: ({ name, lockEntry, versionRange }) => declareSourced("rule", settingsEntries.rule, lockEntries.rule, (source) => ({ source, enabled: true }), name, lockEntry, versionRange),
|
|
138
|
+
hook: ({ name, lockEntry, versionRange }) => declareSourced("hook", settingsEntries.hook, lockEntries.hook, (source) => ({ source, enabled: true }), name, lockEntry, versionRange),
|
|
139
|
+
knowledge: declareKnowledge,
|
|
140
|
+
};
|
|
141
|
+
/** Remove both documents' entries, writing each document unconditionally. */
|
|
142
|
+
const undeclareBoth = (type, name) => Effect.gen(function* () {
|
|
143
|
+
const current = yield* settings;
|
|
144
|
+
const currentLockfile = yield* lockfile;
|
|
145
|
+
yield* writeSettings(settingsEntries[type].remove(current, name));
|
|
146
|
+
yield* commit(currentLockfile, lockEntries[type].remove(currentLockfile, name));
|
|
147
|
+
});
|
|
148
|
+
/** Remove each document's entry only when present. */
|
|
149
|
+
const undeclarePresent = (type, name) => Effect.gen(function* () {
|
|
150
|
+
const current = yield* settings;
|
|
151
|
+
const configured = settingsEntries[type];
|
|
152
|
+
if (configured.entries(current)[name] !== undefined) {
|
|
153
|
+
yield* writeSettings(configured.remove(current, name));
|
|
154
|
+
}
|
|
155
|
+
const currentLockfile = yield* lockfile;
|
|
156
|
+
const accepted = lockEntries[type];
|
|
157
|
+
if (accepted.entries(currentLockfile)[name] !== undefined) {
|
|
158
|
+
yield* commit(currentLockfile, accepted.remove(currentLockfile, name));
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
/** A Pack not declared in settings is left alone entirely. */
|
|
162
|
+
const undeclarePack = (name) => Effect.gen(function* () {
|
|
163
|
+
const current = yield* settings;
|
|
164
|
+
if (Option.isNone(settingsEntries.pack.entry(current, name)))
|
|
165
|
+
return;
|
|
166
|
+
yield* writeSettings(settingsEntries.pack.remove(current, name));
|
|
167
|
+
const currentLockfile = yield* lockfile;
|
|
168
|
+
if (Option.isSome(lockEntries.pack.entry(currentLockfile, name))) {
|
|
169
|
+
yield* commit(currentLockfile, lockEntries.pack.remove(currentLockfile, name));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
/**
|
|
173
|
+
* An MCP server's accepted resolution is shared by every connection and
|
|
174
|
+
* Pack member that resolves to the same identity; it is removed only when
|
|
175
|
+
* this connection was its last user.
|
|
176
|
+
*/
|
|
177
|
+
const undeclareMcpServer = (name) => Effect.gen(function* () {
|
|
178
|
+
const graph = yield* desiredState.graph();
|
|
179
|
+
const desiredNode = graph.nodes.find((node) => node.type === "mcp-server" && node.name === name);
|
|
180
|
+
const closure = desiredNode === undefined || desiredNode.authority === "inline"
|
|
181
|
+
? undefined
|
|
182
|
+
: graph.mcpSourceClosures.find((candidate) => candidate.identity === desiredNode.identity);
|
|
183
|
+
const current = yield* settings;
|
|
184
|
+
if (Option.isSome(settingsEntries["mcp-server"].entry(current, name))) {
|
|
185
|
+
yield* writeSettings(settingsEntries["mcp-server"].remove(current, name));
|
|
186
|
+
}
|
|
187
|
+
const currentLockfile = yield* lockfile;
|
|
188
|
+
const resolutionKey = desiredNode?.identity;
|
|
189
|
+
const keepSharedResolution = closure !== undefined &&
|
|
190
|
+
(closure.localNames.length > 1 || closure.origins.some((origin) => origin.type === "pack"));
|
|
191
|
+
if (resolutionKey !== undefined &&
|
|
192
|
+
!keepSharedResolution &&
|
|
193
|
+
Option.isSome(lockEntries["mcp-server"].entry(currentLockfile, resolutionKey))) {
|
|
194
|
+
yield* commit(currentLockfile, lockEntries["mcp-server"].remove(currentLockfile, resolutionKey));
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
const undeclareByType = {
|
|
198
|
+
skill: (name) => undeclarePresent("skill", name),
|
|
199
|
+
pack: undeclarePack,
|
|
200
|
+
"mcp-server": undeclareMcpServer,
|
|
201
|
+
subagent: (name) => undeclarePresent("subagent", name),
|
|
202
|
+
rule: (name) => undeclareBoth("rule", name),
|
|
203
|
+
hook: (name) => undeclareBoth("hook", name),
|
|
204
|
+
knowledge: (name) => undeclareBoth("knowledge", name),
|
|
205
|
+
};
|
|
206
|
+
return {
|
|
207
|
+
declare: (type, args) => serialized(declareByType[type](args)).pipe(Effect.withSpan("DesiredStateWriter.declare")),
|
|
208
|
+
undeclare: (type, name) => serialized(undeclareByType[type](name)).pipe(Effect.withSpan("DesiredStateWriter.undeclare")),
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
export const DesiredStateWriterLive = Layer.effect(DesiredStateWriter, Effect.gen(function* () {
|
|
212
|
+
return makeDesiredStateWriter(yield* WorkspaceLocation, yield* DesiredStateReader, (yield* WorkspaceStateShared).mutex);
|
|
213
|
+
}));
|
|
214
|
+
//# sourceMappingURL=desired-state-writer.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-indexed access to the per-type settings and lockfile entry maps.
|
|
3
|
+
*
|
|
4
|
+
* Settings and the lockfile keep one map per installable extension type under
|
|
5
|
+
* type-specific keys. The accessors here let the narrow state services read
|
|
6
|
+
* and write those maps generically — `entries(type)`, `setEntry(type, …)` —
|
|
7
|
+
* while every map keeps its exact schema type, so a caller that names the
|
|
8
|
+
* type statically gets the matching entry type back.
|
|
9
|
+
*/
|
|
10
|
+
import * as Option from "effect/Option";
|
|
11
|
+
import type { InstallableExtensionType } from "@agentxm/extension-model/unstable/extensions/installable-types";
|
|
12
|
+
import type { HookLockEntry, KnowledgeLockEntry, Lockfile, McpServerLockEntry, PackLockEntry, RuleLockEntry, SkillLockEntry, SubagentLockEntry } from "../lockfile/schema.js";
|
|
13
|
+
import type { HookEntry, KnowledgeEntry, McpServerEntry, PackEntry, RuleEntry, Settings, SkillEntry, SubagentEntry } from "../settings/schema.js";
|
|
14
|
+
/** The settings entry type declared for each installable extension type. */
|
|
15
|
+
export interface SettingsEntryByType {
|
|
16
|
+
readonly skill: SkillEntry;
|
|
17
|
+
readonly pack: PackEntry;
|
|
18
|
+
readonly "mcp-server": McpServerEntry;
|
|
19
|
+
readonly subagent: SubagentEntry;
|
|
20
|
+
readonly rule: RuleEntry;
|
|
21
|
+
readonly hook: HookEntry;
|
|
22
|
+
readonly knowledge: KnowledgeEntry;
|
|
23
|
+
}
|
|
24
|
+
/** The accepted-resolution entry type recorded for each installable extension type. */
|
|
25
|
+
export interface LockEntryByType {
|
|
26
|
+
readonly skill: SkillLockEntry;
|
|
27
|
+
readonly pack: PackLockEntry;
|
|
28
|
+
readonly "mcp-server": McpServerLockEntry;
|
|
29
|
+
readonly subagent: SubagentLockEntry;
|
|
30
|
+
readonly rule: RuleLockEntry;
|
|
31
|
+
readonly hook: HookLockEntry;
|
|
32
|
+
readonly knowledge: KnowledgeLockEntry;
|
|
33
|
+
}
|
|
34
|
+
export type SettingsEntriesOf<T extends InstallableExtensionType> = Readonly<Record<string, SettingsEntryByType[T]>>;
|
|
35
|
+
export type LockEntriesOf<T extends InstallableExtensionType> = Readonly<Record<string, LockEntryByType[T]>>;
|
|
36
|
+
export interface EntriesAccessor<Document, Entry> {
|
|
37
|
+
readonly entries: (document: Document) => Readonly<Record<string, Entry>>;
|
|
38
|
+
readonly entry: (document: Document, name: string) => Option.Option<Entry>;
|
|
39
|
+
readonly set: (document: Document, name: string, entry: Entry) => Document;
|
|
40
|
+
readonly remove: (document: Document, name: string) => Document;
|
|
41
|
+
}
|
|
42
|
+
/** Settings entry maps by type; absent maps read as empty. */
|
|
43
|
+
export declare const settingsEntries: {
|
|
44
|
+
readonly [T in InstallableExtensionType]: EntriesAccessor<Settings, SettingsEntryByType[T]>;
|
|
45
|
+
};
|
|
46
|
+
/** Lockfile entry maps by type; absent maps read as empty. */
|
|
47
|
+
export declare const lockEntries: {
|
|
48
|
+
readonly [T in InstallableExtensionType]: EntriesAccessor<Lockfile, LockEntryByType[T]>;
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=entry-accessors.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-indexed access to the per-type settings and lockfile entry maps.
|
|
3
|
+
*
|
|
4
|
+
* Settings and the lockfile keep one map per installable extension type under
|
|
5
|
+
* type-specific keys. The accessors here let the narrow state services read
|
|
6
|
+
* and write those maps generically — `entries(type)`, `setEntry(type, …)` —
|
|
7
|
+
* while every map keeps its exact schema type, so a caller that names the
|
|
8
|
+
* type statically gets the matching entry type back.
|
|
9
|
+
*/
|
|
10
|
+
import * as Option from "effect/Option";
|
|
11
|
+
const accessor = (read, write) => ({
|
|
12
|
+
entries: read,
|
|
13
|
+
entry: (document, name) => Option.fromUndefinedOr(read(document)[name]),
|
|
14
|
+
set: (document, name, entry) => write(document, { ...read(document), [name]: entry }),
|
|
15
|
+
remove: (document, name) => {
|
|
16
|
+
const { [name]: removed, ...remaining } = read(document);
|
|
17
|
+
void removed;
|
|
18
|
+
return write(document, remaining);
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
/** Settings entry maps by type; absent maps read as empty. */
|
|
22
|
+
export const settingsEntries = {
|
|
23
|
+
skill: accessor((settings) => settings.skills ?? {}, (settings, skills) => ({ ...settings, skills })),
|
|
24
|
+
pack: accessor((settings) => settings.packs ?? {}, (settings, packs) => ({ ...settings, packs })),
|
|
25
|
+
"mcp-server": accessor((settings) => settings.mcpServers ?? {}, (settings, mcpServers) => ({ ...settings, mcpServers })),
|
|
26
|
+
subagent: accessor((settings) => settings.subagents ?? {}, (settings, subagents) => ({ ...settings, subagents })),
|
|
27
|
+
rule: accessor((settings) => settings.rules ?? {}, (settings, rules) => ({ ...settings, rules })),
|
|
28
|
+
hook: accessor((settings) => settings.hooks ?? {}, (settings, hooks) => ({ ...settings, hooks })),
|
|
29
|
+
knowledge: accessor((settings) => settings.knowledge ?? {}, (settings, knowledge) => ({ ...settings, knowledge })),
|
|
30
|
+
};
|
|
31
|
+
/** Lockfile entry maps by type; absent maps read as empty. */
|
|
32
|
+
export const lockEntries = {
|
|
33
|
+
skill: accessor((lockfile) => lockfile.skills, (lockfile, skills) => ({ ...lockfile, skills })),
|
|
34
|
+
pack: accessor((lockfile) => lockfile.packs ?? {}, (lockfile, packs) => ({ ...lockfile, packs })),
|
|
35
|
+
"mcp-server": accessor((lockfile) => lockfile.mcpServers ?? {}, (lockfile, mcpServers) => ({ ...lockfile, mcpServers })),
|
|
36
|
+
subagent: accessor((lockfile) => lockfile.subagents ?? {}, (lockfile, subagents) => ({ ...lockfile, subagents })),
|
|
37
|
+
rule: accessor((lockfile) => lockfile.rules ?? {}, (lockfile, rules) => ({ ...lockfile, rules })),
|
|
38
|
+
hook: accessor((lockfile) => lockfile.hooks ?? {}, (lockfile, hooks) => ({ ...lockfile, hooks })),
|
|
39
|
+
knowledge: accessor((lockfile) => lockfile.knowledge ?? {}, (lockfile, knowledge) => ({ ...lockfile, knowledge })),
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=entry-accessors.js.map
|