@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,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings reader: the selected scope's desired-state document and the
|
|
3
|
+
* settings-derived facts commands consult — merged source hosts, owner,
|
|
4
|
+
* publication default, release-age policy, configured agents, instruction
|
|
5
|
+
* and Knowledge discovery configuration, and the per-type entry maps.
|
|
6
|
+
*
|
|
7
|
+
* Every member keeps `FileSystem` and `Path` in `R`; the live layer captures
|
|
8
|
+
* only the workspace location and the shared source cache.
|
|
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 * as Option from "effect/Option";
|
|
17
|
+
import type * as Path from "effect/Path";
|
|
18
|
+
import * as Ref from "effect/Ref";
|
|
19
|
+
import type { ExtensionVisibility } from "@agentxm/extension-model/unstable/extensions/common";
|
|
20
|
+
import type { Handle } from "@agentxm/extension-model/unstable/extensions/handle";
|
|
21
|
+
import type { InstallableExtensionType } from "@agentxm/extension-model/unstable/extensions/installable-types";
|
|
22
|
+
import type { ScopedReleaseAgeExcludePattern } from "@agentxm/extension-model/unstable/extensions/release-age";
|
|
23
|
+
import { type ResolvedKnowledgeDiscoveryConfig } from "../knowledge/discovery-config.js";
|
|
24
|
+
import type { InstructionsConfigValue, MinimumReleaseAge, Settings, SourceHostConfig } from "../settings/index.js";
|
|
25
|
+
import { type SettingsEntriesOf } from "./entry-accessors.js";
|
|
26
|
+
import { WorkspaceLocation, type WorkspaceLocationService } from "./location.js";
|
|
27
|
+
import type { WorkspaceSettingsReadFailure } from "./service-interface.js";
|
|
28
|
+
import { WorkspaceStateShared } from "./shared.js";
|
|
29
|
+
type Read<A> = Effect.Effect<A, WorkspaceSettingsReadFailure, FileSystem.FileSystem | Path.Path>;
|
|
30
|
+
export interface SettingsReaderService {
|
|
31
|
+
/** The selected scope's settings, defaulting when the file is absent. */
|
|
32
|
+
readonly settings: Read<Settings>;
|
|
33
|
+
/** Merged sources from project, user-scope, and built-in defaults. Cached per workspace lifetime. */
|
|
34
|
+
readonly configuredSources: Read<ReadonlyArray<SourceHostConfig>>;
|
|
35
|
+
readonly sourceByName: (name: string) => Read<Option.Option<SourceHostConfig>>;
|
|
36
|
+
readonly registrySourceHosts: Read<ReadonlyArray<Extract<SourceHostConfig, {
|
|
37
|
+
type: "registry";
|
|
38
|
+
}>>>;
|
|
39
|
+
/** Owner: project settings, then user-scope settings, then none. */
|
|
40
|
+
readonly owner: Read<Option.Option<Handle>>;
|
|
41
|
+
/** Repository publication default for this exact workspace scope. */
|
|
42
|
+
readonly publishDefaultVisibility: Read<Option.Option<ExtensionVisibility>>;
|
|
43
|
+
/** minimumReleaseAge: project settings, then user-scope settings, then the default. */
|
|
44
|
+
readonly minimumReleaseAge: Read<MinimumReleaseAge>;
|
|
45
|
+
/** minimumReleaseAgeExclude with the same precedence; an explicit [] wins. */
|
|
46
|
+
readonly minimumReleaseAgeExclude: Read<ReadonlyArray<ScopedReleaseAgeExcludePattern>>;
|
|
47
|
+
readonly configuredAgents: Read<ReadonlyArray<string>>;
|
|
48
|
+
readonly instructionsConfig: Read<Option.Option<InstructionsConfigValue>>;
|
|
49
|
+
readonly knowledgeDiscoveryConfig: Read<ResolvedKnowledgeDiscoveryConfig>;
|
|
50
|
+
/** The configured entries of one type, defaulting to `{}`. */
|
|
51
|
+
readonly entries: <T extends InstallableExtensionType>(type: T) => Read<SettingsEntriesOf<T>>;
|
|
52
|
+
}
|
|
53
|
+
declare const SettingsReader_base: ServiceMap.ServiceClass<SettingsReader, "@agentxm/workspace-state/SettingsReader", SettingsReaderService>;
|
|
54
|
+
export declare class SettingsReader extends SettingsReader_base {
|
|
55
|
+
}
|
|
56
|
+
export declare const makeSettingsReader: (location: WorkspaceLocationService, sourcesCache: Ref.Ref<Option.Option<ReadonlyArray<SourceHostConfig>>>) => SettingsReaderService;
|
|
57
|
+
export declare const SettingsReaderLive: Layer.Layer<SettingsReader, never, WorkspaceLocation | WorkspaceStateShared>;
|
|
58
|
+
export {};
|
|
59
|
+
//# sourceMappingURL=settings-reader.d.ts.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings reader: the selected scope's desired-state document and the
|
|
3
|
+
* settings-derived facts commands consult — merged source hosts, owner,
|
|
4
|
+
* publication default, release-age policy, configured agents, instruction
|
|
5
|
+
* and Knowledge discovery configuration, and the per-type entry maps.
|
|
6
|
+
*
|
|
7
|
+
* Every member keeps `FileSystem` and `Path` in `R`; the live layer captures
|
|
8
|
+
* only the workspace location and the shared source cache.
|
|
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 * as Ref from "effect/Ref";
|
|
17
|
+
import { resolveKnowledgeDiscoveryConfig, } from "../knowledge/discovery-config.js";
|
|
18
|
+
import { DEFAULT_MINIMUM_RELEASE_AGE } from "../settings/index.js";
|
|
19
|
+
import { settingsEntries } from "./entry-accessors.js";
|
|
20
|
+
import { WorkspaceLocation } from "./location.js";
|
|
21
|
+
import { WorkspaceStateShared } from "./shared.js";
|
|
22
|
+
import { readSettingsOrDefault } from "./state-cells.js";
|
|
23
|
+
export class SettingsReader extends ServiceMap.Service()("@agentxm/workspace-state/SettingsReader") {
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Three-layer merge: project sources, then user-scope sources, then built-in
|
|
27
|
+
* sources. Name-based deduplication: earlier layers win.
|
|
28
|
+
*/
|
|
29
|
+
const mergeSources = (projectSources, globalSources, builtInSources) => {
|
|
30
|
+
const projectNames = new Set(projectSources.map((s) => s.name));
|
|
31
|
+
const filteredGlobal = globalSources.filter((s) => !projectNames.has(s.name));
|
|
32
|
+
const projectGlobalNames = new Set([...projectNames, ...filteredGlobal.map((s) => s.name)]);
|
|
33
|
+
return [
|
|
34
|
+
...projectSources,
|
|
35
|
+
...filteredGlobal,
|
|
36
|
+
...builtInSources.filter((s) => !projectGlobalNames.has(s.name)),
|
|
37
|
+
];
|
|
38
|
+
};
|
|
39
|
+
export const makeSettingsReader = (location, sourcesCache) => {
|
|
40
|
+
const settings = readSettingsOrDefault(location, location.runtimeDir);
|
|
41
|
+
const userSettings = readSettingsOrDefault(location, location.userRuntimeDir, "user");
|
|
42
|
+
const projectSettings = readSettingsOrDefault(location, location.projectRuntimeDir, "project");
|
|
43
|
+
const configuredSources = Effect.gen(function* () {
|
|
44
|
+
const cached = yield* Ref.get(sourcesCache);
|
|
45
|
+
if (Option.isSome(cached))
|
|
46
|
+
return cached.value;
|
|
47
|
+
const merged = mergeSources((yield* projectSettings).sources ?? [], (yield* userSettings).sources ?? [], location.builtInSources);
|
|
48
|
+
yield* Ref.set(sourcesCache, Option.some(merged));
|
|
49
|
+
return merged;
|
|
50
|
+
}).pipe(Effect.withSpan("SettingsReader.configuredSources"));
|
|
51
|
+
return {
|
|
52
|
+
settings,
|
|
53
|
+
configuredSources,
|
|
54
|
+
sourceByName: (name) => configuredSources.pipe(Effect.map((sources) => Option.fromUndefinedOr(sources.find((s) => s.name === name)))),
|
|
55
|
+
registrySourceHosts: configuredSources.pipe(Effect.map((sources) => sources.filter((s) => s.type === "registry"))),
|
|
56
|
+
owner: Effect.gen(function* () {
|
|
57
|
+
const project = yield* projectSettings;
|
|
58
|
+
if (project.owner)
|
|
59
|
+
return Option.some(project.owner);
|
|
60
|
+
const user = yield* userSettings;
|
|
61
|
+
if (user.owner)
|
|
62
|
+
return Option.some(user.owner);
|
|
63
|
+
return Option.none();
|
|
64
|
+
}),
|
|
65
|
+
publishDefaultVisibility: settings.pipe(Effect.map((value) => Option.fromUndefinedOr(value.publish?.defaultVisibility))),
|
|
66
|
+
minimumReleaseAge: Effect.gen(function* () {
|
|
67
|
+
const scoped = yield* settings;
|
|
68
|
+
if (scoped.minimumReleaseAge !== undefined)
|
|
69
|
+
return scoped.minimumReleaseAge;
|
|
70
|
+
if (location.scope === "project") {
|
|
71
|
+
const user = yield* userSettings;
|
|
72
|
+
if (user.minimumReleaseAge !== undefined)
|
|
73
|
+
return user.minimumReleaseAge;
|
|
74
|
+
}
|
|
75
|
+
return DEFAULT_MINIMUM_RELEASE_AGE;
|
|
76
|
+
}).pipe(Effect.withSpan("SettingsReader.minimumReleaseAge")),
|
|
77
|
+
minimumReleaseAgeExclude: Effect.gen(function* () {
|
|
78
|
+
const scoped = yield* settings;
|
|
79
|
+
if (scoped.minimumReleaseAgeExclude !== undefined) {
|
|
80
|
+
return scoped.minimumReleaseAgeExclude.map((pattern) => ({
|
|
81
|
+
pattern,
|
|
82
|
+
scope: location.scope,
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
if (location.scope === "project") {
|
|
86
|
+
const user = yield* userSettings;
|
|
87
|
+
if (user.minimumReleaseAgeExclude !== undefined) {
|
|
88
|
+
return user.minimumReleaseAgeExclude.map((pattern) => ({
|
|
89
|
+
pattern,
|
|
90
|
+
scope: "user",
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return [];
|
|
95
|
+
}).pipe(Effect.withSpan("SettingsReader.minimumReleaseAgeExclude")),
|
|
96
|
+
configuredAgents: settings.pipe(Effect.map((value) => value.agents ?? []), Effect.withSpan("SettingsReader.configuredAgents")),
|
|
97
|
+
instructionsConfig: settings.pipe(Effect.map((value) => Option.fromUndefinedOr(value.instructionFiles)), Effect.withSpan("SettingsReader.instructionsConfig")),
|
|
98
|
+
knowledgeDiscoveryConfig: settings.pipe(Effect.map((value) => resolveKnowledgeDiscoveryConfig({
|
|
99
|
+
...(value.knowledgeConfig?.instructions === false ? { instructions: false } : {}),
|
|
100
|
+
})), Effect.withSpan("SettingsReader.knowledgeDiscoveryConfig")),
|
|
101
|
+
entries: (type) => settings.pipe(Effect.map((value) => settingsEntries[type].entries(value)), Effect.withSpan("SettingsReader.entries")),
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
export const SettingsReaderLive = Layer.effect(SettingsReader, Effect.gen(function* () {
|
|
105
|
+
const location = yield* WorkspaceLocation;
|
|
106
|
+
const shared = yield* WorkspaceStateShared;
|
|
107
|
+
return makeSettingsReader(location, shared.sourcesCache);
|
|
108
|
+
}));
|
|
109
|
+
//# sourceMappingURL=settings-reader.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings writer: every settings-only mutation of the selected scope —
|
|
3
|
+
* owner, source hosts, instruction-file configuration, agent membership, and
|
|
4
|
+
* one configured entry at a time. Each write protects the settings file with
|
|
5
|
+
* the active transaction, publishes atomically, and is serialized by the
|
|
6
|
+
* workspace's mutation mutex.
|
|
7
|
+
*
|
|
8
|
+
* @experimental This API is unstable and may change without notice.
|
|
9
|
+
*/
|
|
10
|
+
import * as ServiceMap from "effect/Context";
|
|
11
|
+
import * as Effect from "effect/Effect";
|
|
12
|
+
import type * as FileSystem from "effect/FileSystem";
|
|
13
|
+
import * as Layer from "effect/Layer";
|
|
14
|
+
import type * as Path from "effect/Path";
|
|
15
|
+
import type * as Semaphore from "effect/Semaphore";
|
|
16
|
+
import type { Handle } from "@agentxm/extension-model/unstable/extensions/handle";
|
|
17
|
+
import type { InstallableExtensionType } from "@agentxm/extension-model/unstable/extensions/installable-types";
|
|
18
|
+
import { type InstructionsConfigValue, type SourceHostConfig } from "../settings/index.js";
|
|
19
|
+
import { type SettingsEntryByType } from "./entry-accessors.js";
|
|
20
|
+
import { InvalidAgentId, SettingsEntryMissing } from "./errors.js";
|
|
21
|
+
import { type WorkspaceLayout } from "./layout.js";
|
|
22
|
+
import { WorkspaceLocation, type WorkspaceLocationService } from "./location.js";
|
|
23
|
+
import type { WorkspaceSettingsMutationFailure } from "./service-interface.js";
|
|
24
|
+
import { WorkspaceStateShared } from "./shared.js";
|
|
25
|
+
type Write<A = void, E = never> = Effect.Effect<A, WorkspaceSettingsMutationFailure | E, FileSystem.FileSystem | Path.Path>;
|
|
26
|
+
export interface SettingsWriterService {
|
|
27
|
+
/** Record the owner in the selected scope's settings and in the resolved layout. */
|
|
28
|
+
readonly setOwner: (owner: Handle) => Write<WorkspaceLayout>;
|
|
29
|
+
/** Append a source to the selected scope's settings and drop the merged-source cache. */
|
|
30
|
+
readonly addConfiguredSource: (source: SourceHostConfig) => Write;
|
|
31
|
+
/** Set instruction-file config. `false` is explicit manual mode. */
|
|
32
|
+
readonly setInstructionsConfig: (config: InstructionsConfigValue) => Write;
|
|
33
|
+
/** Append an agent ID when absent. Fails typed when the ID is not configurable. */
|
|
34
|
+
readonly addConfiguredAgent: (agentId: string) => Write<void, InvalidAgentId>;
|
|
35
|
+
/** Remove an agent ID when present. Fails typed when the ID is not configurable. */
|
|
36
|
+
readonly removeConfiguredAgent: (agentId: string) => Write<void, InvalidAgentId>;
|
|
37
|
+
/** Create or overwrite one configured entry. */
|
|
38
|
+
readonly setEntry: <T extends InstallableExtensionType>(type: T, name: string, entry: SettingsEntryByType[T]) => Write;
|
|
39
|
+
/**
|
|
40
|
+
* Update one configured entry. A missing skill or MCP server entry fails
|
|
41
|
+
* typed; a missing entry of any other type is a no-op.
|
|
42
|
+
*/
|
|
43
|
+
readonly updateEntry: <T extends InstallableExtensionType>(type: T, name: string, update: (entry: SettingsEntryByType[T]) => SettingsEntryByType[T]) => Write<void, SettingsEntryMissing>;
|
|
44
|
+
/** Remove one configured entry. No-op when absent. */
|
|
45
|
+
readonly removeEntry: (type: InstallableExtensionType, name: string) => Write;
|
|
46
|
+
}
|
|
47
|
+
declare const SettingsWriter_base: ServiceMap.ServiceClass<SettingsWriter, "@agentxm/workspace-state/SettingsWriter", SettingsWriterService>;
|
|
48
|
+
export declare class SettingsWriter extends SettingsWriter_base {
|
|
49
|
+
}
|
|
50
|
+
export declare const makeSettingsWriter: (location: WorkspaceLocationService, mutex: Semaphore.Semaphore, invalidateSources: Effect.Effect<void>) => SettingsWriterService;
|
|
51
|
+
export declare const SettingsWriterLive: Layer.Layer<SettingsWriter, never, WorkspaceLocation | WorkspaceStateShared>;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=settings-writer.d.ts.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings writer: every settings-only mutation of the selected scope —
|
|
3
|
+
* owner, source hosts, instruction-file configuration, agent membership, and
|
|
4
|
+
* one configured entry at a time. Each write protects the settings file with
|
|
5
|
+
* the active transaction, publishes atomically, and is serialized by the
|
|
6
|
+
* workspace's mutation mutex.
|
|
7
|
+
*
|
|
8
|
+
* @experimental This API is unstable and may change without notice.
|
|
9
|
+
*/
|
|
10
|
+
import * as ServiceMap from "effect/Context";
|
|
11
|
+
import * as Effect from "effect/Effect";
|
|
12
|
+
import * as Layer from "effect/Layer";
|
|
13
|
+
import * as Option from "effect/Option";
|
|
14
|
+
import * as Ref from "effect/Ref";
|
|
15
|
+
import * as Schema from "effect/Schema";
|
|
16
|
+
import { ConfigurableAgentIdSchema } from "@agentxm/extension-model/unstable/extensions";
|
|
17
|
+
import { writeSettingsAtPath, } from "../settings/index.js";
|
|
18
|
+
import { settingsEntries } from "./entry-accessors.js";
|
|
19
|
+
import { InvalidAgentId, SettingsEntryMissing } from "./errors.js";
|
|
20
|
+
import { withLayoutOwner } from "./layout.js";
|
|
21
|
+
import { WorkspaceLocation } from "./location.js";
|
|
22
|
+
import { WorkspaceStateShared } from "./shared.js";
|
|
23
|
+
import { readSettingsOrDefault } from "./state-cells.js";
|
|
24
|
+
export class SettingsWriter extends ServiceMap.Service()("@agentxm/workspace-state/SettingsWriter") {
|
|
25
|
+
}
|
|
26
|
+
/** Entry types whose update of a missing entry is a typed failure. */
|
|
27
|
+
const missingEntryFailure = (type, name) => type === "skill" || type === "mcp-server"
|
|
28
|
+
? Option.some(new SettingsEntryMissing({ entryType: type, name }))
|
|
29
|
+
: Option.none();
|
|
30
|
+
export const makeSettingsWriter = (location, mutex, invalidateSources) => {
|
|
31
|
+
const current = readSettingsOrDefault(location, location.runtimeDir);
|
|
32
|
+
const write = (settings) => writeSettingsAtPath(location.settingsPath, settings);
|
|
33
|
+
const serialized = mutex.withPermits(1);
|
|
34
|
+
const validAgentId = (agentId) => Schema.decodeUnknownEffect(ConfigurableAgentIdSchema)(agentId).pipe(Effect.mapError((cause) => new InvalidAgentId({ agentId, cause })));
|
|
35
|
+
return {
|
|
36
|
+
setOwner: (owner) => serialized(Effect.gen(function* () {
|
|
37
|
+
yield* write({ ...(yield* current), owner });
|
|
38
|
+
return yield* Ref.updateAndGet(location.layout, (layout) => withLayoutOwner(layout, owner));
|
|
39
|
+
})).pipe(Effect.withSpan("SettingsWriter.setOwner")),
|
|
40
|
+
addConfiguredSource: (source) => serialized(Effect.gen(function* () {
|
|
41
|
+
const settings = yield* current;
|
|
42
|
+
yield* write({ ...settings, sources: [...(settings.sources ?? []), source] });
|
|
43
|
+
yield* invalidateSources;
|
|
44
|
+
})).pipe(Effect.withSpan("SettingsWriter.addConfiguredSource")),
|
|
45
|
+
setInstructionsConfig: (config) => serialized(Effect.flatMap(current, (settings) => write({ ...settings, instructionFiles: config }))).pipe(Effect.withSpan("SettingsWriter.setInstructionsConfig")),
|
|
46
|
+
addConfiguredAgent: (agentId) => serialized(Effect.gen(function* () {
|
|
47
|
+
const validId = yield* validAgentId(agentId);
|
|
48
|
+
const settings = yield* current;
|
|
49
|
+
const agents = settings.agents ?? [];
|
|
50
|
+
if (agents.includes(validId))
|
|
51
|
+
return;
|
|
52
|
+
yield* write({ ...settings, agents: [...agents, validId] });
|
|
53
|
+
})),
|
|
54
|
+
removeConfiguredAgent: (agentId) => serialized(Effect.gen(function* () {
|
|
55
|
+
const validId = yield* validAgentId(agentId);
|
|
56
|
+
const settings = yield* current;
|
|
57
|
+
const agents = settings.agents ?? [];
|
|
58
|
+
if (!agents.includes(validId))
|
|
59
|
+
return;
|
|
60
|
+
yield* write({
|
|
61
|
+
...settings,
|
|
62
|
+
agents: agents.filter((configured) => configured !== validId),
|
|
63
|
+
});
|
|
64
|
+
})),
|
|
65
|
+
setEntry: (type, name, entry) => serialized(Effect.flatMap(current, (settings) => write(settingsEntries[type].set(settings, name, entry)))).pipe(Effect.withSpan("SettingsWriter.setEntry")),
|
|
66
|
+
updateEntry: (type, name, update) => serialized(Effect.gen(function* () {
|
|
67
|
+
const settings = yield* current;
|
|
68
|
+
const accessor = settingsEntries[type];
|
|
69
|
+
const existing = accessor.entry(settings, name);
|
|
70
|
+
if (Option.isNone(existing)) {
|
|
71
|
+
const failure = missingEntryFailure(type, name);
|
|
72
|
+
return Option.isSome(failure) ? yield* failure.value : undefined;
|
|
73
|
+
}
|
|
74
|
+
yield* write(accessor.set(settings, name, update(existing.value)));
|
|
75
|
+
})).pipe(Effect.withSpan("SettingsWriter.updateEntry")),
|
|
76
|
+
removeEntry: (type, name) => serialized(Effect.gen(function* () {
|
|
77
|
+
const settings = yield* current;
|
|
78
|
+
const accessor = settingsEntries[type];
|
|
79
|
+
if (accessor.entries(settings)[name] === undefined)
|
|
80
|
+
return;
|
|
81
|
+
yield* write(accessor.remove(settings, name));
|
|
82
|
+
})).pipe(Effect.withSpan("SettingsWriter.removeEntry")),
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
export const SettingsWriterLive = Layer.effect(SettingsWriter, Effect.gen(function* () {
|
|
86
|
+
const location = yield* WorkspaceLocation;
|
|
87
|
+
const shared = yield* WorkspaceStateShared;
|
|
88
|
+
return makeSettingsWriter(location, shared.mutex, Ref.set(shared.sourcesCache, Option.none()));
|
|
89
|
+
}));
|
|
90
|
+
//# sourceMappingURL=settings-writer.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @experimental This API is unstable and may change without notice.
|
|
5
5
|
*/
|
|
6
6
|
import { CONFIGURABLE_AGENTS_BY_ID, } from "@agentxm/extension-model/unstable/agent-capabilities";
|
|
7
|
-
import { userScopeRefusal } from "
|
|
7
|
+
import { userScopeRefusal } from "@agentxm/agent-integration";
|
|
8
8
|
import { EXTENSION_TYPE_TABLE, extensionTypeLabels, extensionTypes, } from "@agentxm/extension-model/unstable/extensions/common";
|
|
9
9
|
const isConfigurableAgentId = (id) => Object.hasOwn(CONFIGURABLE_AGENTS_BY_ID, id);
|
|
10
10
|
const nativeSupportsScope = (native, scope) => native.availability.via !== "none" && "scopes" in native && native.scopes.includes(scope);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State shared by the narrow workspace services of one workspace lifetime:
|
|
3
|
+
* the in-process mutation mutex every writer serializes on, and the merged
|
|
4
|
+
* source-host cache the settings reader fills and the settings writer
|
|
5
|
+
* invalidates. Provided by the live composition; not part of the public API.
|
|
6
|
+
*/
|
|
7
|
+
import * as ServiceMap from "effect/Context";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Option from "effect/Option";
|
|
10
|
+
import * as Ref from "effect/Ref";
|
|
11
|
+
import * as Semaphore from "effect/Semaphore";
|
|
12
|
+
import type { SourceHostConfig } from "../settings/index.js";
|
|
13
|
+
export interface WorkspaceStateSharedService {
|
|
14
|
+
/** Serializes every settings and lockfile mutation of one runtime. */
|
|
15
|
+
readonly mutex: Semaphore.Semaphore;
|
|
16
|
+
/** Merged sources, cached per workspace lifetime until a source is added. */
|
|
17
|
+
readonly sourcesCache: Ref.Ref<Option.Option<ReadonlyArray<SourceHostConfig>>>;
|
|
18
|
+
}
|
|
19
|
+
declare const WorkspaceStateShared_base: ServiceMap.ServiceClass<WorkspaceStateShared, "@agentxm/workspace-state/WorkspaceStateShared", WorkspaceStateSharedService>;
|
|
20
|
+
export declare class WorkspaceStateShared extends WorkspaceStateShared_base {
|
|
21
|
+
}
|
|
22
|
+
export declare const makeWorkspaceStateShared: Effect.Effect<WorkspaceStateSharedService>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State shared by the narrow workspace services of one workspace lifetime:
|
|
3
|
+
* the in-process mutation mutex every writer serializes on, and the merged
|
|
4
|
+
* source-host cache the settings reader fills and the settings writer
|
|
5
|
+
* invalidates. Provided by the live composition; not part of the public API.
|
|
6
|
+
*/
|
|
7
|
+
import * as ServiceMap from "effect/Context";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Option from "effect/Option";
|
|
10
|
+
import * as Ref from "effect/Ref";
|
|
11
|
+
import * as Semaphore from "effect/Semaphore";
|
|
12
|
+
export class WorkspaceStateShared extends ServiceMap.Service()("@agentxm/workspace-state/WorkspaceStateShared") {
|
|
13
|
+
}
|
|
14
|
+
export const makeWorkspaceStateShared = Effect.gen(function* () {
|
|
15
|
+
return {
|
|
16
|
+
mutex: yield* Semaphore.make(1),
|
|
17
|
+
sourcesCache: yield* Ref.make(Option.none()),
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings and lockfile cells: the scoped read model's two authoritative
|
|
3
|
+
* documents, read for either runtime directory the workspace knows about.
|
|
4
|
+
* Every narrow state service reads through these two functions, so the
|
|
5
|
+
* read-model configuration and agent-root resolution are composed in exactly
|
|
6
|
+
* one place and `FileSystem`/`Path` stay in `R`.
|
|
7
|
+
*/
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import type * as FileSystem from "effect/FileSystem";
|
|
10
|
+
import * as Option from "effect/Option";
|
|
11
|
+
import * as Path from "effect/Path";
|
|
12
|
+
import { type AbsolutePath } from "@agentxm/extension-model/unstable/path-types";
|
|
13
|
+
import type { WorkspaceScope } from "@agentxm/extension-model/unstable/workspace-scope";
|
|
14
|
+
import type { Lockfile } from "../lockfile/schema.js";
|
|
15
|
+
import { type Settings } from "../settings/index.js";
|
|
16
|
+
import type { LockfileReadError, SettingsReadError } from "./read-model/errors.js";
|
|
17
|
+
import { type WorkspaceReadModel } from "./read-model/service.js";
|
|
18
|
+
import type { WorkspaceLockfileReadFailure, WorkspaceSettingsReadFailure, WorkspaceStateReadFailure } from "./service-interface.js";
|
|
19
|
+
/** The runtime directories a workspace's settings sources can live in. */
|
|
20
|
+
export interface StateCellPaths {
|
|
21
|
+
readonly scope: WorkspaceScope;
|
|
22
|
+
readonly projectRoot: AbsolutePath;
|
|
23
|
+
readonly userHome: AbsolutePath;
|
|
24
|
+
readonly projectRuntimeDir: string;
|
|
25
|
+
readonly userRuntimeDir: string;
|
|
26
|
+
}
|
|
27
|
+
/** The settings document of one runtime directory, when present. */
|
|
28
|
+
export declare const readSettingsCell: (cells: StateCellPaths, dir: string, sharedScope?: WorkspaceScope) => Effect.Effect<Option.Option<Settings>, WorkspaceSettingsReadFailure, FileSystem.FileSystem | Path.Path>;
|
|
29
|
+
/** The settings document of one runtime directory, defaulting when absent. */
|
|
30
|
+
export declare const readSettingsOrDefault: (cells: StateCellPaths, dir: string, sharedScope?: WorkspaceScope) => Effect.Effect<Settings, WorkspaceSettingsReadFailure, FileSystem.FileSystem | Path.Path>;
|
|
31
|
+
/** The lockfile of one runtime directory, empty when absent. */
|
|
32
|
+
export declare const readLockfileCell: (cells: StateCellPaths, dir: string, sharedScope?: WorkspaceScope) => Effect.Effect<Lockfile, WorkspaceLockfileReadFailure, FileSystem.FileSystem | Path.Path>;
|
|
33
|
+
/** Run one read against the selected scope's read model. */
|
|
34
|
+
export declare const readScopedModel: <A>(cells: StateCellPaths, runtimeDir: string, f: (scoped: WorkspaceReadModel) => Effect.Effect<A, SettingsReadError | LockfileReadError>) => Effect.Effect<A, WorkspaceStateReadFailure, FileSystem.FileSystem | Path.Path>;
|
|
35
|
+
//# sourceMappingURL=state-cells.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings and lockfile cells: the scoped read model's two authoritative
|
|
3
|
+
* documents, read for either runtime directory the workspace knows about.
|
|
4
|
+
* Every narrow state service reads through these two functions, so the
|
|
5
|
+
* read-model configuration and agent-root resolution are composed in exactly
|
|
6
|
+
* one place and `FileSystem`/`Path` stay in `R`.
|
|
7
|
+
*/
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Layer from "effect/Layer";
|
|
10
|
+
import * as Option from "effect/Option";
|
|
11
|
+
import * as Path from "effect/Path";
|
|
12
|
+
import { makeAbsolutePath } from "@agentxm/extension-model/unstable/path-types";
|
|
13
|
+
import { LOCKFILE_VERSION } from "../lockfile/index.js";
|
|
14
|
+
import { createDefaultSettings } from "../settings/index.js";
|
|
15
|
+
import { AgentRootResolverLive } from "./read-model/agent-root-resolver.js";
|
|
16
|
+
import { makeWorkspaceReadModel, WorkspaceReadModelConfig, } from "./read-model/service.js";
|
|
17
|
+
const createEmptyLockfile = () => ({
|
|
18
|
+
lockfileVersion: LOCKFILE_VERSION,
|
|
19
|
+
skills: {},
|
|
20
|
+
});
|
|
21
|
+
const scopeForDir = (cells, dir, sharedScope = cells.scope) => dir === cells.userRuntimeDir && dir === cells.projectRuntimeDir
|
|
22
|
+
? sharedScope
|
|
23
|
+
: dir === cells.userRuntimeDir
|
|
24
|
+
? "user"
|
|
25
|
+
: "project";
|
|
26
|
+
const readModelFor = (cells, scope) => Effect.gen(function* () {
|
|
27
|
+
const path = yield* Path.Path;
|
|
28
|
+
return yield* makeWorkspaceReadModel(scope).pipe(Effect.provide(Layer.mergeAll(Layer.succeed(WorkspaceReadModelConfig, {
|
|
29
|
+
projectRoot: cells.projectRoot,
|
|
30
|
+
userHome: cells.userHome,
|
|
31
|
+
allowedRoot: makeAbsolutePath(path, "/"),
|
|
32
|
+
}), AgentRootResolverLive)));
|
|
33
|
+
});
|
|
34
|
+
/** The settings document of one runtime directory, when present. */
|
|
35
|
+
export const readSettingsCell = (cells, dir, sharedScope) => readModelFor(cells, scopeForDir(cells, dir, sharedScope)).pipe(Effect.flatMap((readModel) => readModel.state.settings));
|
|
36
|
+
/** The settings document of one runtime directory, defaulting when absent. */
|
|
37
|
+
export const readSettingsOrDefault = (cells, dir, sharedScope) => readSettingsCell(cells, dir, sharedScope).pipe(Effect.map(Option.getOrElse(() => createDefaultSettings())));
|
|
38
|
+
/** The lockfile of one runtime directory, empty when absent. */
|
|
39
|
+
export const readLockfileCell = (cells, dir, sharedScope) => readModelFor(cells, scopeForDir(cells, dir, sharedScope)).pipe(Effect.flatMap((readModel) => readModel.state.lockfile), Effect.map(Option.getOrElse(createEmptyLockfile)));
|
|
40
|
+
/** Run one read against the selected scope's read model. */
|
|
41
|
+
export const readScopedModel = (cells, runtimeDir, f) => readModelFor(cells, scopeForDir(cells, runtimeDir)).pipe(Effect.flatMap(f));
|
|
42
|
+
//# sourceMappingURL=state-cells.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized subagent path computation.
|
|
3
|
+
*
|
|
4
|
+
* Provides types and a pure function for computing subagent directory paths
|
|
5
|
+
* based on source type (registry vs non-registry).
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
*/
|
|
9
|
+
import { type ExtensionPathSource } from "./extension-paths.js";
|
|
10
|
+
import type { AbsolutePath } from "@agentxm/extension-model/unstable/path-types";
|
|
11
|
+
import type { WorkspaceLayout } from "./layout.js";
|
|
12
|
+
/**
|
|
13
|
+
* Minimal structural discriminant for determining subagent path layout.
|
|
14
|
+
*
|
|
15
|
+
* Every acquired ref carries enough source coordinates to derive its
|
|
16
|
+
* source-qualified canonical path.
|
|
17
|
+
*
|
|
18
|
+
* @experimental This API is unstable and may change without notice.
|
|
19
|
+
*/
|
|
20
|
+
export type SubagentPathSource = ExtensionPathSource;
|
|
21
|
+
/**
|
|
22
|
+
* Computed paths for an installed subagent directory.
|
|
23
|
+
*
|
|
24
|
+
* - `canonicalPath`: root of the installed subagent
|
|
25
|
+
* - `subagentSrcPath`: where actual subagent source files live
|
|
26
|
+
*
|
|
27
|
+
* Native packages use `<canonicalPath>/src`; portable acquired formats use
|
|
28
|
+
* the source-qualified package root directly when supported.
|
|
29
|
+
*
|
|
30
|
+
* @experimental This API is unstable and may change without notice.
|
|
31
|
+
*/
|
|
32
|
+
export interface SubagentDirPaths {
|
|
33
|
+
readonly canonicalPath: AbsolutePath;
|
|
34
|
+
readonly subagentSrcPath: AbsolutePath;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Content filename for subagent instructions.
|
|
38
|
+
*
|
|
39
|
+
* @experimental This API is unstable and may change without notice.
|
|
40
|
+
*/
|
|
41
|
+
export declare const subagentContentFilename: (name: string) => string;
|
|
42
|
+
/**
|
|
43
|
+
* Content path for subagent instructions.
|
|
44
|
+
*
|
|
45
|
+
* @experimental This API is unstable and may change without notice.
|
|
46
|
+
*/
|
|
47
|
+
export declare const subagentContentPath: (join: (...paths: string[]) => string, root: string, name: string) => AbsolutePath;
|
|
48
|
+
export declare const computeSubagentPathsForLayout: (join: (...paths: string[]) => string, layout: WorkspaceLayout, source: SubagentPathSource, sanitizedName: string) => SubagentDirPaths;
|
|
49
|
+
//# sourceMappingURL=subagent-paths.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized subagent path computation.
|
|
3
|
+
*
|
|
4
|
+
* Provides types and a pure function for computing subagent directory paths
|
|
5
|
+
* based on source type (registry vs non-registry).
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
*/
|
|
9
|
+
import { computeExtensionPathsForLayout, extensionContentFilename, extensionContentPath, } from "./extension-paths.js";
|
|
10
|
+
/**
|
|
11
|
+
* Content filename for subagent instructions.
|
|
12
|
+
*
|
|
13
|
+
* @experimental This API is unstable and may change without notice.
|
|
14
|
+
*/
|
|
15
|
+
export const subagentContentFilename = extensionContentFilename;
|
|
16
|
+
/**
|
|
17
|
+
* Content path for subagent instructions.
|
|
18
|
+
*
|
|
19
|
+
* @experimental This API is unstable and may change without notice.
|
|
20
|
+
*/
|
|
21
|
+
export const subagentContentPath = (join, root, name) => extensionContentPath(join, root, name);
|
|
22
|
+
export const computeSubagentPathsForLayout = (join, layout, source, sanitizedName) => {
|
|
23
|
+
const paths = computeExtensionPathsForLayout(join, layout, source, "subagents", sanitizedName);
|
|
24
|
+
return { canonicalPath: paths.canonicalPath, subagentSrcPath: paths.extensionSrcPath };
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=subagent-paths.js.map
|
|
@@ -190,15 +190,6 @@ export const makeBaseWorkspaceMock = (axmDir = "/tmp/axm", overrides) => {
|
|
|
190
190
|
}));
|
|
191
191
|
return { complete: true, nodes, mcpSourceClosures: [], problems: [] };
|
|
192
192
|
});
|
|
193
|
-
const runTransaction = (args) => Effect.gen(function* () {
|
|
194
|
-
const value = yield* args.transition;
|
|
195
|
-
yield* args.validate(value);
|
|
196
|
-
return value;
|
|
197
|
-
});
|
|
198
|
-
// The mock acquires nothing: unit tests share literal workspace paths, and
|
|
199
|
-
// a real lock would contend across parallel test files. Tests exercising
|
|
200
|
-
// real transition semantics override this with the real acquirer.
|
|
201
|
-
const acquireTransition = () => Effect.succeed(Option.none());
|
|
202
193
|
const entryFrom = (read) => (name) => read().pipe(Effect.map((entries) => Option.fromUndefinedOr(entries[name])));
|
|
203
194
|
const lockedSkills = serviceOverrides.getLockedSkills ?? (() => Effect.succeed({}));
|
|
204
195
|
const lockedMcpServers = serviceOverrides.getLockedMcpServers ?? (() => Effect.succeed({}));
|
|
@@ -222,8 +213,6 @@ export const makeBaseWorkspaceMock = (axmDir = "/tmp/axm", overrides) => {
|
|
|
222
213
|
authoredRoot: (type) => decodeAbsolutePathSync(path.resolve(baseDir, type === "mcp-server" ? "mcps" : `${type}s`)),
|
|
223
214
|
},
|
|
224
215
|
records,
|
|
225
|
-
runTransaction,
|
|
226
|
-
acquireTransition,
|
|
227
216
|
getLockfileState: () => Effect.succeed("ok"),
|
|
228
217
|
getDesiredStateGraph: getSynthesizedDesiredStateGraph,
|
|
229
218
|
getConfiguredSources: () => Effect.succeed([]),
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace records: the read model's inventory and lifecycle-tagged rows
|
|
3
|
+
* for every installable extension type, over the selected scope.
|
|
4
|
+
*
|
|
5
|
+
* @experimental This API is unstable and may change without notice.
|
|
6
|
+
*/
|
|
7
|
+
import * as ServiceMap from "effect/Context";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as FileSystem from "effect/FileSystem";
|
|
10
|
+
import * as Layer from "effect/Layer";
|
|
11
|
+
import * as Path from "effect/Path";
|
|
12
|
+
import type { InstallableExtensionType } from "@agentxm/extension-model/unstable/extensions/installable-types";
|
|
13
|
+
import { DesiredStateReader, type DesiredStateReaderService } from "./desired-state-reader.js";
|
|
14
|
+
import { WorkspaceLocation, type WorkspaceLocationService } from "./location.js";
|
|
15
|
+
import type { ExtensionInventory } from "./read-model/extensions/inventory.js";
|
|
16
|
+
import type { ReadModelRecordRow } from "./read-model-record-types.js";
|
|
17
|
+
import type { WorkspaceStateReadFailure } from "./service-interface.js";
|
|
18
|
+
type Read<A> = Effect.Effect<A, WorkspaceStateReadFailure, FileSystem.FileSystem | Path.Path>;
|
|
19
|
+
export interface WorkspaceRecordsService {
|
|
20
|
+
/** Deterministic inventory across every installable extension type or one selected type. */
|
|
21
|
+
readonly getInventory: (options: {
|
|
22
|
+
readonly type?: InstallableExtensionType;
|
|
23
|
+
}) => Read<ExtensionInventory>;
|
|
24
|
+
/** Read-only physical inventory for one extension type. */
|
|
25
|
+
readonly getExtensionInventory: (type: InstallableExtensionType, options: {
|
|
26
|
+
readonly agents?: ReadonlyArray<string>;
|
|
27
|
+
}) => Read<ExtensionInventory>;
|
|
28
|
+
/**
|
|
29
|
+
* Every read-model row for one extension type, tagged with its lifecycle
|
|
30
|
+
* (`configured` / `implicit` / `unmanaged`). Total over
|
|
31
|
+
* `InstallableExtensionType` and non-throwing.
|
|
32
|
+
*/
|
|
33
|
+
readonly rows: (type: InstallableExtensionType) => Read<ReadonlyArray<ReadModelRecordRow>>;
|
|
34
|
+
}
|
|
35
|
+
declare const WorkspaceRecords_base: ServiceMap.ServiceClass<WorkspaceRecords, "@agentxm/workspace-state/WorkspaceRecords", WorkspaceRecordsService>;
|
|
36
|
+
export declare class WorkspaceRecords extends WorkspaceRecords_base {
|
|
37
|
+
}
|
|
38
|
+
export declare const makeWorkspaceRecords: (location: WorkspaceLocationService, desiredState: DesiredStateReaderService) => WorkspaceRecordsService;
|
|
39
|
+
export declare const WorkspaceRecordsLive: Layer.Layer<WorkspaceRecords, never, WorkspaceLocation | DesiredStateReader>;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=workspace-records.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace records: the read model's inventory and lifecycle-tagged rows
|
|
3
|
+
* for every installable extension type, over the selected scope.
|
|
4
|
+
*
|
|
5
|
+
* @experimental This API is unstable and may change without notice.
|
|
6
|
+
*/
|
|
7
|
+
import * as ServiceMap from "effect/Context";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as FileSystem from "effect/FileSystem";
|
|
10
|
+
import * as Layer from "effect/Layer";
|
|
11
|
+
import * as Path from "effect/Path";
|
|
12
|
+
import { DesiredStateReader } from "./desired-state-reader.js";
|
|
13
|
+
import { WorkspaceLocation } from "./location.js";
|
|
14
|
+
import { makeReadModelRecordReaders } from "./read-model-record-readers.js";
|
|
15
|
+
import { readScopedModel } from "./state-cells.js";
|
|
16
|
+
export class WorkspaceRecords extends ServiceMap.Service()("@agentxm/workspace-state/WorkspaceRecords") {
|
|
17
|
+
}
|
|
18
|
+
export const makeWorkspaceRecords = (location, desiredState) => {
|
|
19
|
+
// The record readers take the platform path service and scoped reads with
|
|
20
|
+
// the platform already discharged; bind both at each call so `FileSystem`
|
|
21
|
+
// and `Path` stay requirements of every member rather than captures.
|
|
22
|
+
const withReaders = (use) => Effect.gen(function* () {
|
|
23
|
+
const fs = yield* FileSystem.FileSystem;
|
|
24
|
+
const path = yield* Path.Path;
|
|
25
|
+
const provide = (effect) => effect.pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path));
|
|
26
|
+
const readers = makeReadModelRecordReaders({
|
|
27
|
+
baseDir: location.baseDir,
|
|
28
|
+
path,
|
|
29
|
+
readScopedContext: (f) => provide(readScopedModel(location, location.runtimeDir, f)),
|
|
30
|
+
getDesiredStateGraph: () => provide(desiredState.graph()),
|
|
31
|
+
});
|
|
32
|
+
return yield* use(readers);
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
getInventory: (options) => withReaders((readers) => readers.getInventory(options)),
|
|
36
|
+
getExtensionInventory: (type, options) => withReaders((readers) => readers.getExtensionInventory(type, options)),
|
|
37
|
+
rows: (type) => withReaders((readers) => readers.getReadModelRecordRows(type)),
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
export const WorkspaceRecordsLive = Layer.effect(WorkspaceRecords, Effect.gen(function* () {
|
|
41
|
+
return makeWorkspaceRecords(yield* WorkspaceLocation, yield* DesiredStateReader);
|
|
42
|
+
}));
|
|
43
|
+
//# sourceMappingURL=workspace-records.js.map
|