@agentxm/workspace-state 0.28.12 → 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 -219
- package/dist/src/workspace/transaction.js +0 -177
|
@@ -1,1304 +1,163 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WorkspaceMutations
|
|
2
|
+
* TRANSITIONAL: the `WorkspaceMutations` facade composed over the narrow
|
|
3
|
+
* workspace-state services. Every member delegates to `WorkspaceLocation`,
|
|
4
|
+
* `SettingsReader`, `LockfileReader`, `DesiredStateReader`,
|
|
5
|
+
* `WorkspaceRecords`, `ExtensionPaths`, `SettingsWriter`,
|
|
6
|
+
* `AcceptedResolutionWriter`, or `DesiredStateWriter`; the facade adds no
|
|
7
|
+
* behavior of its own. It exists so callers keep compiling while feature
|
|
8
|
+
* slices migrate to the services, and is removed when the last domain
|
|
9
|
+
* package reads and writes through them. Its transaction members are gone:
|
|
10
|
+
* callers run `runWorkspaceTransaction` from
|
|
11
|
+
* `@agentxm/workspace-transactions`.
|
|
3
12
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* and commits lockfile snapshots through the lockfile module so cross-process
|
|
7
|
-
* updates are serialized and merged per entry. It serializes in-process
|
|
8
|
-
* mutations with one service-owned semaphore and transaction admission with a
|
|
9
|
-
* distinct service-owned semaphore so nested mutation calls cannot deadlock.
|
|
10
|
-
* No other service should perform settings or lockfile I/O in production; the per-service semaphores in
|
|
11
|
-
* `settings/service.ts` and `lockfile/service.ts` have been removed.
|
|
12
|
-
*
|
|
13
|
-
* Supporting logic is split into focused modules:
|
|
14
|
-
* - `source-metadata.ts` — source metadata derivation helpers
|
|
15
|
-
* - `initialization.ts` — workspace initialization (agent detection, settings creation)
|
|
13
|
+
* The facade keeps its members `R = never` by binding the platform of the
|
|
14
|
+
* context it is built in — the one capture this transitional layer allows.
|
|
16
15
|
*
|
|
17
16
|
* @experimental This API is unstable and may change without notice.
|
|
18
17
|
* @packageDocumentation
|
|
19
18
|
*/
|
|
19
|
+
import * as Effect from "effect/Effect";
|
|
20
20
|
import * as FileSystem from "effect/FileSystem";
|
|
21
21
|
import * as Path from "effect/Path";
|
|
22
|
-
import * as
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import { LockfileValidationError } from "../lockfile/errors.js";
|
|
33
|
-
import { createDefaultSettings, writeSettingsAtPath, } from "../settings/index.js";
|
|
34
|
-
import { DEFAULT_MINIMUM_RELEASE_AGE } from "@agentxm/registry-protocol/unstable/registry/release-age-policy";
|
|
35
|
-
import {} from "@agentxm/extension-model/unstable/extensions/release-age";
|
|
36
|
-
import { printSourceParams } from "@agentxm/extension-model/unstable/sources/printer";
|
|
37
|
-
import { lockEntryToSourceParams } from "./lock-entry-to-source-params.js";
|
|
38
|
-
import { makeAbsolutePath } from "@agentxm/extension-model/unstable/path-types";
|
|
39
|
-
import { resolveKnowledgeDiscoveryConfig } from "../knowledge/discovery-config.js";
|
|
40
|
-
import { getProjectRuntimeDir, resolveUserHome } from "./paths.js";
|
|
41
|
-
import { resolveProjectWorkspaceLayout, resolveProjectWorkspaceStatePaths, resolveUserWorkspaceLayout, withLayoutOwner, } from "./layout.js";
|
|
42
|
-
import * as DateTime from "effect/DateTime";
|
|
43
|
-
import * as Effect from "effect/Effect";
|
|
44
|
-
import * as Layer from "effect/Layer";
|
|
45
|
-
import { AgentRootResolverLive } from "./read-model/agent-root-resolver.js";
|
|
46
|
-
import { makeWorkspaceReadModel, WorkspaceReadModelConfig, } from "./read-model/service.js";
|
|
47
|
-
import {} from "./service-interface.js";
|
|
48
|
-
import { makeReadModelRecordReaders } from "./read-model-record-readers.js";
|
|
49
|
-
import { buildDesiredStateGraph } from "./desired-state-graph.js";
|
|
50
|
-
import { validateDesiredPackLock } from "./desired-pack-lock.js";
|
|
51
|
-
const createEmptyLockfile = () => ({
|
|
52
|
-
lockfileVersion: LOCKFILE_VERSION,
|
|
53
|
-
skills: {},
|
|
54
|
-
});
|
|
55
|
-
const normalizeForStableCompare = (value) => {
|
|
56
|
-
if (DateTime.isDateTime(value))
|
|
57
|
-
return DateTime.formatIso(value);
|
|
58
|
-
if (Array.isArray(value))
|
|
59
|
-
return value.map(normalizeForStableCompare);
|
|
60
|
-
if (typeof value === "object" && value !== null) {
|
|
61
|
-
const normalized = {};
|
|
62
|
-
for (const [key, entryValue] of Object.entries(value).sort(([left], [right]) => left.localeCompare(right))) {
|
|
63
|
-
normalized[key] = normalizeForStableCompare(entryValue);
|
|
64
|
-
}
|
|
65
|
-
return normalized;
|
|
66
|
-
}
|
|
67
|
-
return value;
|
|
68
|
-
};
|
|
69
|
-
const stableCompare = (left, right) => JSON.stringify(normalizeForStableCompare(left)) ===
|
|
70
|
-
JSON.stringify(normalizeForStableCompare(right));
|
|
71
|
-
const lockEntrySemanticallyEqual = (current, next) => current !== undefined && stableCompare(current, next);
|
|
72
|
-
const skillLockEntrySemanticallyEqual = (current, next) => lockEntrySemanticallyEqual(current, next);
|
|
73
|
-
const preserveAcceptedResolutionOnNoop = (current, next) => (lockEntrySemanticallyEqual(current, next) && current !== undefined ? current : next);
|
|
74
|
-
const subagentLockEntrySemanticallyEqual = (current, next) => lockEntrySemanticallyEqual(current, next);
|
|
75
|
-
const requireInitializedWorkspace = (settingsPath, settings, lockfile) => settings.pipe(Effect.flatMap(Option.match({
|
|
76
|
-
onNone: () => lockfile.pipe(Effect.matchEffect({
|
|
77
|
-
onFailure: (error) => error instanceof LockfileVersionUnsupported &&
|
|
78
|
-
error.observedVersion > error.supportedVersion
|
|
79
|
-
? Effect.fail(error)
|
|
80
|
-
: Effect.fail(new WorkspaceNotInitialized({ settingsPath })),
|
|
81
|
-
onSuccess: () => Effect.fail(new WorkspaceNotInitialized({ settingsPath })),
|
|
82
|
-
})),
|
|
83
|
-
onSome: () => Effect.void,
|
|
84
|
-
})));
|
|
22
|
+
import * as Ref from "effect/Ref";
|
|
23
|
+
import { AcceptedResolutionWriter } from "./accepted-resolution-writer.js";
|
|
24
|
+
import { DesiredStateReader } from "./desired-state-reader.js";
|
|
25
|
+
import { ExtensionPaths } from "./extension-paths-service.js";
|
|
26
|
+
import { LockfileReader } from "./lockfile-reader.js";
|
|
27
|
+
import { WorkspaceLocation } from "./location.js";
|
|
28
|
+
import { SettingsReader } from "./settings-reader.js";
|
|
29
|
+
import { SettingsWriter } from "./settings-writer.js";
|
|
30
|
+
import { DesiredStateWriter } from "./desired-state-writer.js";
|
|
31
|
+
import { WorkspaceRecords } from "./workspace-records.js";
|
|
85
32
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* The workspace must already be initialized. Missing or invalid settings and
|
|
89
|
-
* invalid or unsupported lockfiles fail fast with a typed
|
|
90
|
-
* `WorkspaceMutationsError`. The two operations-side
|
|
91
|
-
* capabilities — the transaction runner and transition acquirer — are
|
|
92
|
-
* injected through `makeCapabilities`; the composition seam in
|
|
93
|
-
* `./operations/load-workspace.ts` supplies the live implementation.
|
|
33
|
+
* Compose the transitional facade from the narrow services in context.
|
|
34
|
+
* Only the live composition calls this.
|
|
94
35
|
*/
|
|
95
|
-
export const
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
36
|
+
export const makeWorkspaceMutationsFacade = Effect.gen(function* () {
|
|
37
|
+
const location = yield* WorkspaceLocation;
|
|
38
|
+
const settings = yield* SettingsReader;
|
|
39
|
+
const lockfile = yield* LockfileReader;
|
|
40
|
+
const desiredState = yield* DesiredStateReader;
|
|
41
|
+
const records = yield* WorkspaceRecords;
|
|
42
|
+
const paths = yield* ExtensionPaths;
|
|
43
|
+
const settingsWriter = yield* SettingsWriter;
|
|
44
|
+
const accepted = yield* AcceptedResolutionWriter;
|
|
45
|
+
const desiredStateWriter = yield* DesiredStateWriter;
|
|
102
46
|
const fs = yield* FileSystem.FileSystem;
|
|
103
47
|
const path = yield* Path.Path;
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
projectRoot: options.projectRoot,
|
|
112
|
-
userHome,
|
|
113
|
-
allowedRoot: makeAbsolutePath(path, "/"),
|
|
114
|
-
}), AgentRootResolverLive.pipe(Layer.provide(fsLayer)));
|
|
115
|
-
const scopeForDir = (dir, sharedScope = options.scope) => dir === userRuntimeDir && dir === localRuntimeDir
|
|
116
|
-
? sharedScope
|
|
117
|
-
: dir === userRuntimeDir
|
|
118
|
-
? "user"
|
|
119
|
-
: "project";
|
|
120
|
-
const readSettingsCell = (dir, sharedScope) => makeWorkspaceReadModel(scopeForDir(dir, sharedScope)).pipe(Effect.flatMap((readModel) => readModel.state.settings), Effect.provide(contextEnv));
|
|
121
|
-
const readLockfileCell = (dir, sharedScope) => makeWorkspaceReadModel(scopeForDir(dir, sharedScope)).pipe(Effect.flatMap((readModel) => readModel.state.lockfile), Effect.map(Option.getOrElse(createEmptyLockfile)), Effect.provide(contextEnv));
|
|
122
|
-
if (options.allowUninitialized !== true) {
|
|
123
|
-
yield* requireInitializedWorkspace(settingsPath, readSettingsCell(workspaceDir), readLockfileCell(workspaceDir));
|
|
124
|
-
yield* readLockfileCell(workspaceDir);
|
|
125
|
-
}
|
|
126
|
-
const projectSettings = yield* readSettingsCell(localRuntimeDir, "project").pipe(Effect.map(Option.getOrElse(() => createDefaultSettings())));
|
|
127
|
-
const userSettings = yield* readSettingsCell(userRuntimeDir, "user").pipe(Effect.map(Option.getOrElse(() => createDefaultSettings())));
|
|
128
|
-
const projectLayout = yield* resolveProjectWorkspaceLayout(options.projectRoot, projectSettings);
|
|
129
|
-
const userLayout = yield* resolveUserWorkspaceLayout(userHome, userSettings);
|
|
130
|
-
// Reassigned by `setOwner`, which is why every reader goes through this
|
|
131
|
-
// binding rather than a captured snapshot.
|
|
132
|
-
let layout = options.scope === "project" ? projectLayout : userLayout;
|
|
133
|
-
// Built-in sources: parameterized via options, falling back to git forges only
|
|
134
|
-
const builtInSources = options.builtInSources ?? [
|
|
135
|
-
{ name: "github", type: "github", url: new URL("https://github.com") },
|
|
136
|
-
{ name: "gitlab", type: "gitlab", url: new URL("https://gitlab.com") },
|
|
137
|
-
{ name: "bitbucket", type: "bitbucket", url: new URL("https://bitbucket.org") },
|
|
138
|
-
];
|
|
139
|
-
// Mutable cache for merged sources (invalidated by addConfiguredSource)
|
|
140
|
-
let cachedSources = null;
|
|
141
|
-
const withMutex = semaphore.withPermits(1);
|
|
142
|
-
/**
|
|
143
|
-
* Read settings from a directory, returning default settings if not found.
|
|
144
|
-
*/
|
|
145
|
-
const readSettingsSafe = (dir, sharedScope) => readSettingsCell(dir, sharedScope).pipe(Effect.map(Option.getOrElse(() => createDefaultSettings())));
|
|
146
|
-
/**
|
|
147
|
-
* Read lockfile from a directory, returning empty lockfile if not found.
|
|
148
|
-
*/
|
|
149
|
-
const readLockfileSafe = (dir) => readLockfileCell(dir);
|
|
150
|
-
const writeScopedSettings = (settings) => writeSettingsAtPath(settingsPath, settings);
|
|
151
|
-
const commitWorkspaceState = (base, next) => commitLockfileSnapshotUpdateAtPath(lockPath, base, next).pipe(Effect.provide(fsLayer));
|
|
152
|
-
const { runTransaction, acquireTransition } = yield* makeCapabilities({
|
|
153
|
-
workspaceDir,
|
|
154
|
-
settingsPath,
|
|
155
|
-
lockPath,
|
|
156
|
-
});
|
|
157
|
-
/**
|
|
158
|
-
* Look up `key` in `record`, failing with `onMissing`'s error when absent.
|
|
159
|
-
*/
|
|
160
|
-
const getEntryOrFail = (record, key, onMissing) => key in record && record[key] !== undefined
|
|
161
|
-
? Effect.succeed(record[key])
|
|
162
|
-
: Effect.fail(onMissing());
|
|
163
|
-
/**
|
|
164
|
-
* Probe lockfile state without mutating disk.
|
|
165
|
-
*/
|
|
166
|
-
const getLockfileState = () => Effect.gen(function* () {
|
|
167
|
-
const exists = yield* fs
|
|
168
|
-
.exists(lockPath)
|
|
169
|
-
.pipe(Effect.mapError((cause) => new LockfileValidationError({ path: lockPath, step: "probe", cause })));
|
|
170
|
-
if (!exists) {
|
|
171
|
-
return "missing";
|
|
172
|
-
}
|
|
173
|
-
// An unreadable or corrupt lockfile is actionable workspace state,
|
|
174
|
-
// not a violated invariant.
|
|
175
|
-
return yield* readLockfileSafe(workspaceDir).pipe(Effect.as("ok"), Effect.catchTag([
|
|
176
|
-
"LockfileIoError",
|
|
177
|
-
"LockfileParseError",
|
|
178
|
-
"LockfileDecodeError",
|
|
179
|
-
"LockfileVersionUnsupported",
|
|
180
|
-
], () => Effect.succeed("invalid")));
|
|
181
|
-
}).pipe(Effect.withSpan("WorkspaceMutations.getLockfileState"));
|
|
182
|
-
const readScopedContext = (f) => makeWorkspaceReadModel(scopeForDir(workspaceDir)).pipe(Effect.flatMap(f), Effect.provide(contextEnv));
|
|
183
|
-
const readDesiredStateGraph = (graphOptions) => Effect.gen(function* () {
|
|
184
|
-
const settings = yield* readSettingsSafe(workspaceDir);
|
|
185
|
-
const configuredSources = yield* getConfiguredSources();
|
|
186
|
-
const registryAuthorities = Object.fromEntries(configuredSources.flatMap((source) => source.type === "registry" ? [[source.name, source.location]] : []));
|
|
187
|
-
const graph = yield* buildDesiredStateGraph({
|
|
188
|
-
baseDir,
|
|
189
|
-
settings,
|
|
190
|
-
layout,
|
|
191
|
-
registryAuthorities,
|
|
192
|
-
...(graphOptions?.prospectivePacks === undefined
|
|
193
|
-
? {}
|
|
194
|
-
: { prospectivePacks: graphOptions.prospectivePacks }),
|
|
195
|
-
}).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path));
|
|
196
|
-
const lockfile = yield* readLockfileSafe(workspaceDir);
|
|
197
|
-
return yield* validateDesiredPackLock({ graph, lockfile, layout }).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path));
|
|
198
|
-
});
|
|
199
|
-
const readModelRecordReaders = makeReadModelRecordReaders({
|
|
200
|
-
baseDir,
|
|
201
|
-
path,
|
|
202
|
-
readScopedContext,
|
|
203
|
-
getDesiredStateGraph: readDesiredStateGraph,
|
|
204
|
-
});
|
|
205
|
-
const records = {
|
|
206
|
-
getInventory: readModelRecordReaders.getInventory,
|
|
207
|
-
getExtensionInventory: readModelRecordReaders.getExtensionInventory,
|
|
208
|
-
rows: readModelRecordReaders.getReadModelRecordRows,
|
|
209
|
-
};
|
|
210
|
-
/**
|
|
211
|
-
* Resolve the immutable registry name for a skill's directory.
|
|
212
|
-
*
|
|
213
|
-
* Registry skill directories are tied to the registry name, not the
|
|
214
|
-
* user-facing alias. This helper tries the lockfile first, then falls
|
|
215
|
-
* back to parsing the settings source string, and finally returns the
|
|
216
|
-
* passed-in name (correct for fresh installs).
|
|
217
|
-
*/
|
|
218
|
-
const resolveRegistryDirName = (name) => Effect.gen(function* () {
|
|
219
|
-
// Try lockfile
|
|
220
|
-
const lockEntry = yield* readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => Option.fromUndefinedOr(lf.skills[name])));
|
|
221
|
-
if (Option.isSome(lockEntry) && lockEntry.value.type === "registry") {
|
|
222
|
-
return lockEntry.value.name;
|
|
223
|
-
}
|
|
224
|
-
// Fallback to settings source string
|
|
225
|
-
const settings = yield* readSettingsSafe(workspaceDir);
|
|
226
|
-
const skills = settings.skills ?? {};
|
|
227
|
-
const entry = skills[name];
|
|
228
|
-
if (entry !== undefined) {
|
|
229
|
-
const parsed = parseSourceQualifiedRegistrySourcePatternParts(entry.source);
|
|
230
|
-
if (parsed?.name !== undefined) {
|
|
231
|
-
return parsed.name;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
// Final fallback: passed-in name (correct for fresh installs)
|
|
235
|
-
return name;
|
|
236
|
-
});
|
|
237
|
-
/**
|
|
238
|
-
* Three-layer merge: project sources -> user-scope sources -> built-in sources.
|
|
239
|
-
* Name-based deduplication: earlier layers win.
|
|
240
|
-
*/
|
|
241
|
-
const getConfiguredSources = () => Effect.gen(function* () {
|
|
242
|
-
if (cachedSources !== null)
|
|
243
|
-
return cachedSources;
|
|
244
|
-
const projectSettings = yield* readSettingsSafe(localRuntimeDir, "project");
|
|
245
|
-
const globalSettings = yield* readSettingsSafe(userRuntimeDir, "user");
|
|
246
|
-
const projectSources = projectSettings.sources ?? [];
|
|
247
|
-
const globalSources = globalSettings.sources ?? [];
|
|
248
|
-
const projectNames = new Set(projectSources.map((s) => s.name));
|
|
249
|
-
const filteredGlobal = globalSources.filter((s) => !projectNames.has(s.name));
|
|
250
|
-
const projectGlobalNames = new Set([...projectNames, ...filteredGlobal.map((s) => s.name)]);
|
|
251
|
-
const merged = [
|
|
252
|
-
...projectSources,
|
|
253
|
-
...filteredGlobal,
|
|
254
|
-
...builtInSources.filter((s) => !projectGlobalNames.has(s.name)),
|
|
255
|
-
];
|
|
256
|
-
cachedSources = merged;
|
|
257
|
-
return merged;
|
|
258
|
-
}).pipe(Effect.withSpan("WorkspaceMutations.getConfiguredSources"));
|
|
48
|
+
const bind = (effect) => effect.pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path));
|
|
49
|
+
const entries = (type) => bind(settings.entries(type));
|
|
50
|
+
const locked = (type) => bind(lockfile.entries(type));
|
|
51
|
+
const lockedEntry = (type, name) => bind(lockfile.entry(type, name));
|
|
52
|
+
const setEntry = (type, name, entry) => bind(settingsWriter.setEntry(type, name, entry));
|
|
53
|
+
/** Types whose missing entry is a no-op today keep that behavior through the facade. */
|
|
54
|
+
const updateIgnoringMissing = (type, name, update) => bind(settingsWriter.updateEntry(type, name, update)).pipe(Effect.catchTag("SettingsEntryMissing", () => Effect.void));
|
|
259
55
|
return {
|
|
260
|
-
scope:
|
|
261
|
-
path:
|
|
262
|
-
baseDir,
|
|
56
|
+
scope: location.scope,
|
|
57
|
+
path: location.runtimeDir,
|
|
58
|
+
baseDir: location.baseDir,
|
|
263
59
|
get layout() {
|
|
264
|
-
return layout;
|
|
60
|
+
return Ref.getUnsafe(location.layout);
|
|
265
61
|
},
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
335
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setInstructionsConfig")),
|
|
336
|
-
getConfiguredMcpServerEntries: () => readSettingsSafe(workspaceDir).pipe(Effect.map((s) => s.mcpServers ?? {}), Effect.withSpan("WorkspaceMutations.getConfiguredMcpServerEntries")),
|
|
337
|
-
getConfiguredRuleEntries: () => readSettingsSafe(workspaceDir).pipe(Effect.map((s) => s.rules ?? {}), Effect.withSpan("WorkspaceMutations.getConfiguredRuleEntries")),
|
|
338
|
-
getLockedRules: () => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => lf.rules ?? {})),
|
|
339
|
-
getLockedRuleEntry: (name) => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => Option.fromUndefinedOr((lf.rules ?? {})[name]))),
|
|
340
|
-
setRule: ({ name, lockEntry, versionRange }) => withMutex(Effect.gen(function* () {
|
|
341
|
-
const source = lockEntry.type === "registry"
|
|
342
|
-
? (() => {
|
|
343
|
-
const fqn = formatFqn({
|
|
344
|
-
owner: lockEntry.owner,
|
|
345
|
-
type: "rule",
|
|
346
|
-
name: decodeExtensionNameSync(name),
|
|
347
|
-
});
|
|
348
|
-
const locator = Option.isSome(versionRange)
|
|
349
|
-
? `${fqn}@${versionRange.value}`
|
|
350
|
-
: fqn;
|
|
351
|
-
return `${lockEntry.sourceName}:${locator}`;
|
|
352
|
-
})()
|
|
353
|
-
: printSourceParams(lockEntryToSourceParams(lockEntry));
|
|
354
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
355
|
-
const currentRules = currentSettings.rules ?? {};
|
|
356
|
-
yield* writeScopedSettings({
|
|
357
|
-
...currentSettings,
|
|
358
|
-
rules: {
|
|
359
|
-
...currentRules,
|
|
360
|
-
[name]: { source, enabled: true },
|
|
361
|
-
},
|
|
362
|
-
}).pipe(Effect.provide(fsLayer));
|
|
363
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
364
|
-
const currentLockedRules = currentLockfile.rules ?? {};
|
|
365
|
-
const previous = currentLockedRules[name];
|
|
366
|
-
const updatedLockfile = {
|
|
367
|
-
...currentLockfile,
|
|
368
|
-
rules: {
|
|
369
|
-
...currentLockedRules,
|
|
370
|
-
[name]: preserveAcceptedResolutionOnNoop(previous, lockEntry),
|
|
371
|
-
},
|
|
372
|
-
};
|
|
373
|
-
yield* commitWorkspaceState(currentLockfile, updatedLockfile);
|
|
374
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setRule")),
|
|
375
|
-
setRuleLock: ({ name, lockEntry }) => withMutex(Effect.gen(function* () {
|
|
376
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
377
|
-
const currentLockedRules = currentLockfile.rules ?? {};
|
|
378
|
-
const previous = currentLockedRules[name];
|
|
379
|
-
const updatedLockfile = {
|
|
380
|
-
...currentLockfile,
|
|
381
|
-
rules: {
|
|
382
|
-
...currentLockedRules,
|
|
383
|
-
[name]: preserveAcceptedResolutionOnNoop(previous, lockEntry),
|
|
384
|
-
},
|
|
385
|
-
};
|
|
386
|
-
yield* commitWorkspaceState(currentLockfile, updatedLockfile);
|
|
387
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setRuleLock")),
|
|
388
|
-
removeRule: (name) => withMutex(Effect.gen(function* () {
|
|
389
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
390
|
-
const currentRules = currentSettings.rules ?? {};
|
|
391
|
-
const remainingSettings = name in currentRules
|
|
392
|
-
? (() => {
|
|
393
|
-
const { [name]: _, ...remainingRules } = currentRules;
|
|
394
|
-
void _;
|
|
395
|
-
return { ...currentSettings, rules: remainingRules };
|
|
396
|
-
})()
|
|
397
|
-
: currentSettings;
|
|
398
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
399
|
-
const currentLockedRules = currentLockfile.rules ?? {};
|
|
400
|
-
const remainingLockfile = name in currentLockedRules
|
|
401
|
-
? (() => {
|
|
402
|
-
const { [name]: _, ...remainingRules } = currentLockedRules;
|
|
403
|
-
void _;
|
|
404
|
-
return { ...currentLockfile, rules: remainingRules };
|
|
405
|
-
})()
|
|
406
|
-
: currentLockfile;
|
|
407
|
-
yield* writeScopedSettings(remainingSettings).pipe(Effect.provide(fsLayer));
|
|
408
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, remainingLockfile).pipe(Effect.provide(fsLayer));
|
|
409
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeRule")),
|
|
410
|
-
removeRuleSettings: (name) => withMutex(Effect.gen(function* () {
|
|
411
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
412
|
-
const currentRules = currentSettings.rules ?? {};
|
|
413
|
-
if (!(name in currentRules))
|
|
414
|
-
return;
|
|
415
|
-
const { [name]: _, ...remainingRules } = currentRules;
|
|
416
|
-
void _;
|
|
417
|
-
yield* writeScopedSettings({
|
|
418
|
-
...currentSettings,
|
|
419
|
-
rules: remainingRules,
|
|
420
|
-
}).pipe(Effect.provide(fsLayer));
|
|
421
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeRuleSettings")),
|
|
422
|
-
removeRuleLock: (name) => withMutex(Effect.gen(function* () {
|
|
423
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
424
|
-
const currentLockedRules = currentLockfile.rules ?? {};
|
|
425
|
-
if (!(name in currentLockedRules))
|
|
426
|
-
return;
|
|
427
|
-
const { [name]: _, ...remainingRules } = currentLockedRules;
|
|
428
|
-
void _;
|
|
429
|
-
const updatedLockfile = {
|
|
430
|
-
...currentLockfile,
|
|
431
|
-
rules: remainingRules,
|
|
432
|
-
};
|
|
433
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, updatedLockfile).pipe(Effect.provide(fsLayer));
|
|
434
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeRuleLock")),
|
|
435
|
-
updateRuleEntry: (name, updater) => withMutex(Effect.gen(function* () {
|
|
436
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
437
|
-
const currentRules = currentSettings.rules ?? {};
|
|
438
|
-
const existingEntry = currentRules[name];
|
|
439
|
-
if (existingEntry === undefined)
|
|
440
|
-
return;
|
|
441
|
-
yield* writeScopedSettings({
|
|
442
|
-
...currentSettings,
|
|
443
|
-
rules: { ...currentRules, [name]: updater(existingEntry) },
|
|
444
|
-
}).pipe(Effect.provide(fsLayer));
|
|
445
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.updateRuleEntry")),
|
|
446
|
-
setRuleEntry: (name, entry) => withMutex(Effect.gen(function* () {
|
|
447
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
448
|
-
const currentRules = currentSettings.rules ?? {};
|
|
449
|
-
yield* writeScopedSettings({
|
|
450
|
-
...currentSettings,
|
|
451
|
-
rules: { ...currentRules, [name]: entry },
|
|
452
|
-
}).pipe(Effect.provide(fsLayer));
|
|
453
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setRuleEntry")),
|
|
454
|
-
getConfiguredHookEntries: () => readSettingsSafe(workspaceDir).pipe(Effect.map((s) => s.hooks ?? {}), Effect.withSpan("WorkspaceMutations.getConfiguredHookEntries")),
|
|
455
|
-
getLockedHooks: () => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => lf.hooks ?? {})),
|
|
456
|
-
getLockedHookEntry: (name) => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => Option.fromUndefinedOr((lf.hooks ?? {})[name]))),
|
|
457
|
-
setHook: ({ name, lockEntry, versionRange }) => withMutex(Effect.gen(function* () {
|
|
458
|
-
const source = lockEntry.type === "registry"
|
|
459
|
-
? (() => {
|
|
460
|
-
const fqn = formatFqn({
|
|
461
|
-
owner: lockEntry.owner,
|
|
462
|
-
type: "hook",
|
|
463
|
-
name: decodeExtensionNameSync(name),
|
|
464
|
-
});
|
|
465
|
-
const locator = Option.isSome(versionRange)
|
|
466
|
-
? `${fqn}@${versionRange.value}`
|
|
467
|
-
: fqn;
|
|
468
|
-
return `${lockEntry.sourceName}:${locator}`;
|
|
469
|
-
})()
|
|
470
|
-
: printSourceParams(lockEntryToSourceParams(lockEntry));
|
|
471
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
472
|
-
const currentHooks = currentSettings.hooks ?? {};
|
|
473
|
-
yield* writeScopedSettings({
|
|
474
|
-
...currentSettings,
|
|
475
|
-
hooks: {
|
|
476
|
-
...currentHooks,
|
|
477
|
-
[name]: { source, enabled: true },
|
|
478
|
-
},
|
|
479
|
-
}).pipe(Effect.provide(fsLayer));
|
|
480
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
481
|
-
const currentLockedHooks = currentLockfile.hooks ?? {};
|
|
482
|
-
const previous = currentLockedHooks[name];
|
|
483
|
-
const updatedLockfile = {
|
|
484
|
-
...currentLockfile,
|
|
485
|
-
hooks: {
|
|
486
|
-
...currentLockedHooks,
|
|
487
|
-
[name]: preserveAcceptedResolutionOnNoop(previous, lockEntry),
|
|
488
|
-
},
|
|
489
|
-
};
|
|
490
|
-
yield* commitWorkspaceState(currentLockfile, updatedLockfile);
|
|
491
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setHook")),
|
|
492
|
-
setHookLock: ({ name, lockEntry }) => withMutex(Effect.gen(function* () {
|
|
493
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
494
|
-
const currentLockedHooks = currentLockfile.hooks ?? {};
|
|
495
|
-
const previous = currentLockedHooks[name];
|
|
496
|
-
const updatedLockfile = {
|
|
497
|
-
...currentLockfile,
|
|
498
|
-
hooks: {
|
|
499
|
-
...currentLockedHooks,
|
|
500
|
-
[name]: preserveAcceptedResolutionOnNoop(previous, lockEntry),
|
|
501
|
-
},
|
|
502
|
-
};
|
|
503
|
-
yield* commitWorkspaceState(currentLockfile, updatedLockfile);
|
|
504
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setHookLock")),
|
|
505
|
-
removeHook: (name) => withMutex(Effect.gen(function* () {
|
|
506
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
507
|
-
const currentHooks = currentSettings.hooks ?? {};
|
|
508
|
-
const remainingSettings = name in currentHooks
|
|
509
|
-
? (() => {
|
|
510
|
-
const { [name]: _, ...remainingHooks } = currentHooks;
|
|
511
|
-
void _;
|
|
512
|
-
return { ...currentSettings, hooks: remainingHooks };
|
|
513
|
-
})()
|
|
514
|
-
: currentSettings;
|
|
515
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
516
|
-
const currentLockedHooks = currentLockfile.hooks ?? {};
|
|
517
|
-
const remainingLockfile = name in currentLockedHooks
|
|
518
|
-
? (() => {
|
|
519
|
-
const { [name]: _, ...remainingHooks } = currentLockedHooks;
|
|
520
|
-
void _;
|
|
521
|
-
return { ...currentLockfile, hooks: remainingHooks };
|
|
522
|
-
})()
|
|
523
|
-
: currentLockfile;
|
|
524
|
-
yield* writeScopedSettings(remainingSettings).pipe(Effect.provide(fsLayer));
|
|
525
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, remainingLockfile).pipe(Effect.provide(fsLayer));
|
|
526
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeHook")),
|
|
527
|
-
removeHookSettings: (name) => withMutex(Effect.gen(function* () {
|
|
528
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
529
|
-
const currentHooks = currentSettings.hooks ?? {};
|
|
530
|
-
if (!(name in currentHooks))
|
|
531
|
-
return;
|
|
532
|
-
const { [name]: _, ...remainingHooks } = currentHooks;
|
|
533
|
-
void _;
|
|
534
|
-
yield* writeScopedSettings({
|
|
535
|
-
...currentSettings,
|
|
536
|
-
hooks: remainingHooks,
|
|
537
|
-
}).pipe(Effect.provide(fsLayer));
|
|
538
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeHookSettings")),
|
|
539
|
-
removeHookLock: (name) => withMutex(Effect.gen(function* () {
|
|
540
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
541
|
-
const currentLockedHooks = currentLockfile.hooks ?? {};
|
|
542
|
-
if (!(name in currentLockedHooks))
|
|
543
|
-
return;
|
|
544
|
-
const { [name]: _, ...remainingHooks } = currentLockedHooks;
|
|
545
|
-
void _;
|
|
546
|
-
const updatedLockfile = {
|
|
547
|
-
...currentLockfile,
|
|
548
|
-
hooks: remainingHooks,
|
|
549
|
-
};
|
|
550
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, updatedLockfile).pipe(Effect.provide(fsLayer));
|
|
551
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeHookLock")),
|
|
552
|
-
updateHookEntry: (name, updater) => withMutex(Effect.gen(function* () {
|
|
553
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
554
|
-
const currentHooks = currentSettings.hooks ?? {};
|
|
555
|
-
const existingEntry = currentHooks[name];
|
|
556
|
-
if (existingEntry === undefined)
|
|
557
|
-
return;
|
|
558
|
-
yield* writeScopedSettings({
|
|
559
|
-
...currentSettings,
|
|
560
|
-
hooks: { ...currentHooks, [name]: updater(existingEntry) },
|
|
561
|
-
}).pipe(Effect.provide(fsLayer));
|
|
562
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.updateHookEntry")),
|
|
563
|
-
setHookEntry: (name, entry) => withMutex(Effect.gen(function* () {
|
|
564
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
565
|
-
const currentHooks = currentSettings.hooks ?? {};
|
|
566
|
-
yield* writeScopedSettings({
|
|
567
|
-
...currentSettings,
|
|
568
|
-
hooks: { ...currentHooks, [name]: entry },
|
|
569
|
-
}).pipe(Effect.provide(fsLayer));
|
|
570
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setHookEntry")),
|
|
571
|
-
getConfiguredKnowledgeEntries: () => readSettingsSafe(workspaceDir).pipe(Effect.map((settings) => settings.knowledge ?? {}), Effect.withSpan("WorkspaceMutations.getConfiguredKnowledgeEntries")),
|
|
572
|
-
getKnowledgeDiscoveryConfig: () => readSettingsSafe(workspaceDir).pipe(Effect.map((settings) => resolveKnowledgeDiscoveryConfig({
|
|
573
|
-
...(settings.knowledgeConfig?.instructions === false ? { instructions: false } : {}),
|
|
574
|
-
})), Effect.withSpan("WorkspaceMutations.getKnowledgeDiscoveryConfig")),
|
|
575
|
-
getLockedKnowledge: () => readLockfileSafe(workspaceDir).pipe(Effect.map((lockfile) => lockfile.knowledge ?? {})),
|
|
576
|
-
getLockedKnowledgeEntry: (name) => readLockfileSafe(workspaceDir).pipe(Effect.map((lockfile) => Option.fromUndefinedOr((lockfile.knowledge ?? {})[name]))),
|
|
577
|
-
setKnowledge: ({ name, lockEntry, versionRange }) => withMutex(Effect.gen(function* () {
|
|
578
|
-
const source = lockEntry.type === "registry"
|
|
579
|
-
? (() => {
|
|
580
|
-
const fqn = formatFqn({
|
|
581
|
-
owner: lockEntry.owner,
|
|
582
|
-
type: "knowledge",
|
|
583
|
-
name: decodeExtensionNameSync(name),
|
|
584
|
-
});
|
|
585
|
-
const locator = Option.isSome(versionRange)
|
|
586
|
-
? `${fqn}@${versionRange.value}`
|
|
587
|
-
: fqn;
|
|
588
|
-
return `${lockEntry.sourceName}:${locator}`;
|
|
589
|
-
})()
|
|
590
|
-
: printSourceParams(lockEntryToSourceParams(lockEntry));
|
|
591
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
592
|
-
const currentKnowledge = currentSettings.knowledge ?? {};
|
|
593
|
-
const currentEntry = currentKnowledge[name];
|
|
594
|
-
yield* writeScopedSettings({
|
|
595
|
-
...currentSettings,
|
|
596
|
-
knowledge: {
|
|
597
|
-
...currentKnowledge,
|
|
598
|
-
[name]: {
|
|
599
|
-
source,
|
|
600
|
-
enabled: true,
|
|
601
|
-
...(currentEntry?.instructionEntry === undefined
|
|
602
|
-
? {}
|
|
603
|
-
: { instructionEntry: currentEntry.instructionEntry }),
|
|
604
|
-
},
|
|
605
|
-
},
|
|
606
|
-
}).pipe(Effect.provide(fsLayer));
|
|
607
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
608
|
-
const currentLocked = currentLockfile.knowledge ?? {};
|
|
609
|
-
const previous = currentLocked[name];
|
|
610
|
-
yield* commitWorkspaceState(currentLockfile, {
|
|
611
|
-
...currentLockfile,
|
|
612
|
-
knowledge: {
|
|
613
|
-
...currentLocked,
|
|
614
|
-
[name]: preserveAcceptedResolutionOnNoop(previous, lockEntry),
|
|
615
|
-
},
|
|
616
|
-
});
|
|
617
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setKnowledge")),
|
|
618
|
-
setKnowledgeLock: ({ name, lockEntry }) => withMutex(Effect.gen(function* () {
|
|
619
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
620
|
-
const currentLocked = currentLockfile.knowledge ?? {};
|
|
621
|
-
const previous = currentLocked[name];
|
|
622
|
-
yield* commitWorkspaceState(currentLockfile, {
|
|
623
|
-
...currentLockfile,
|
|
624
|
-
knowledge: {
|
|
625
|
-
...currentLocked,
|
|
626
|
-
[name]: preserveAcceptedResolutionOnNoop(previous, lockEntry),
|
|
627
|
-
},
|
|
628
|
-
});
|
|
629
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setKnowledgeLock")),
|
|
630
|
-
removeKnowledge: (name) => withMutex(Effect.gen(function* () {
|
|
631
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
632
|
-
const configured = currentSettings.knowledge ?? {};
|
|
633
|
-
const nextSettings = name in configured
|
|
634
|
-
? (() => {
|
|
635
|
-
const { [name]: removed, ...remaining } = configured;
|
|
636
|
-
void removed;
|
|
637
|
-
return { ...currentSettings, knowledge: remaining };
|
|
638
|
-
})()
|
|
639
|
-
: currentSettings;
|
|
640
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
641
|
-
const locked = currentLockfile.knowledge ?? {};
|
|
642
|
-
const nextLockfile = name in locked
|
|
643
|
-
? (() => {
|
|
644
|
-
const { [name]: removed, ...remaining } = locked;
|
|
645
|
-
void removed;
|
|
646
|
-
return { ...currentLockfile, knowledge: remaining };
|
|
647
|
-
})()
|
|
648
|
-
: currentLockfile;
|
|
649
|
-
yield* writeScopedSettings(nextSettings).pipe(Effect.provide(fsLayer));
|
|
650
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, nextLockfile).pipe(Effect.provide(fsLayer));
|
|
651
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeKnowledge")),
|
|
652
|
-
removeKnowledgeSettings: (name) => withMutex(Effect.gen(function* () {
|
|
653
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
654
|
-
const configured = currentSettings.knowledge ?? {};
|
|
655
|
-
if (!(name in configured))
|
|
656
|
-
return;
|
|
657
|
-
const { [name]: removed, ...remaining } = configured;
|
|
658
|
-
void removed;
|
|
659
|
-
yield* writeScopedSettings({
|
|
660
|
-
...currentSettings,
|
|
661
|
-
knowledge: remaining,
|
|
662
|
-
}).pipe(Effect.provide(fsLayer));
|
|
663
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeKnowledgeSettings")),
|
|
664
|
-
removeKnowledgeLock: (name) => withMutex(Effect.gen(function* () {
|
|
665
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
666
|
-
const locked = currentLockfile.knowledge ?? {};
|
|
667
|
-
if (!(name in locked))
|
|
668
|
-
return;
|
|
669
|
-
const { [name]: removed, ...remaining } = locked;
|
|
670
|
-
void removed;
|
|
671
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, {
|
|
672
|
-
...currentLockfile,
|
|
673
|
-
knowledge: remaining,
|
|
674
|
-
}).pipe(Effect.provide(fsLayer));
|
|
675
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeKnowledgeLock")),
|
|
676
|
-
updateKnowledgeEntry: (name, updater) => withMutex(Effect.gen(function* () {
|
|
677
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
678
|
-
const configured = currentSettings.knowledge ?? {};
|
|
679
|
-
const existing = configured[name];
|
|
680
|
-
if (existing === undefined)
|
|
681
|
-
return;
|
|
682
|
-
yield* writeScopedSettings({
|
|
683
|
-
...currentSettings,
|
|
684
|
-
knowledge: { ...configured, [name]: updater(existing) },
|
|
685
|
-
}).pipe(Effect.provide(fsLayer));
|
|
686
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.updateKnowledgeEntry")),
|
|
687
|
-
setKnowledgeEntry: (name, entry) => withMutex(Effect.gen(function* () {
|
|
688
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
689
|
-
const configured = currentSettings.knowledge ?? {};
|
|
690
|
-
yield* writeScopedSettings({
|
|
691
|
-
...currentSettings,
|
|
692
|
-
knowledge: { ...configured, [name]: entry },
|
|
693
|
-
}).pipe(Effect.provide(fsLayer));
|
|
694
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setKnowledgeEntry")),
|
|
695
|
-
getLockedSkills: () => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => lf.skills)),
|
|
696
|
-
getLockedSkill: (name) => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => Option.fromUndefinedOr(lf.skills[name]))),
|
|
697
|
-
getSkillDir: (name, source) => Effect.gen(function* () {
|
|
698
|
-
if (source !== undefined) {
|
|
699
|
-
const dirName = source.refType === "registry" ? yield* resolveRegistryDirName(name) : name;
|
|
700
|
-
return computeSkillPathsForLayout(path.join, layout, source, sanitizeName(dirName));
|
|
701
|
-
}
|
|
702
|
-
const lockEntry = yield* readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => Option.fromUndefinedOr(lf.skills[name])));
|
|
703
|
-
if (Option.isNone(lockEntry)) {
|
|
704
|
-
return yield* new LockedSkillMissing({ name });
|
|
705
|
-
}
|
|
706
|
-
const entry = lockEntry.value;
|
|
707
|
-
const entrySource = (() => {
|
|
708
|
-
switch (entry.type) {
|
|
709
|
-
case "registry":
|
|
710
|
-
return {
|
|
711
|
-
refType: "registry",
|
|
712
|
-
owner: entry.owner,
|
|
713
|
-
source: {
|
|
714
|
-
type: "registry",
|
|
715
|
-
name: entry.sourceName,
|
|
716
|
-
location: entry.endpoint,
|
|
717
|
-
owner: Option.some(entry.owner),
|
|
718
|
-
},
|
|
719
|
-
};
|
|
720
|
-
case "local":
|
|
721
|
-
return {
|
|
722
|
-
refType: "local",
|
|
723
|
-
source: { type: "local", path: entry.path },
|
|
724
|
-
sourcePath: entry.path,
|
|
725
|
-
};
|
|
726
|
-
case "github":
|
|
727
|
-
case "gitlab":
|
|
728
|
-
case "bitbucket":
|
|
729
|
-
return {
|
|
730
|
-
refType: "git-hosted",
|
|
731
|
-
source: {
|
|
732
|
-
type: entry.type,
|
|
733
|
-
name: entry.sourceName,
|
|
734
|
-
url: entry.endpoint,
|
|
735
|
-
owner: entry.owner,
|
|
736
|
-
repo: entry.repo,
|
|
737
|
-
ref: Option.fromUndefinedOr(entry.ref),
|
|
738
|
-
subPath: Option.fromUndefinedOr(entry.path),
|
|
739
|
-
},
|
|
740
|
-
...(entry.path === undefined ? {} : { sourcePath: entry.path }),
|
|
741
|
-
};
|
|
742
|
-
case "azurerepos":
|
|
743
|
-
return {
|
|
744
|
-
refType: "git-hosted",
|
|
745
|
-
source: {
|
|
746
|
-
type: "azurerepos",
|
|
747
|
-
name: entry.sourceName,
|
|
748
|
-
url: entry.endpoint,
|
|
749
|
-
organization: entry.organization,
|
|
750
|
-
project: entry.project,
|
|
751
|
-
repo: entry.repo,
|
|
752
|
-
ref: Option.fromUndefinedOr(entry.ref),
|
|
753
|
-
subPath: Option.fromUndefinedOr(entry.path),
|
|
754
|
-
},
|
|
755
|
-
...(entry.path === undefined ? {} : { sourcePath: entry.path }),
|
|
756
|
-
};
|
|
757
|
-
case "git":
|
|
758
|
-
return {
|
|
759
|
-
refType: "git-hosted",
|
|
760
|
-
source: {
|
|
761
|
-
type: "git",
|
|
762
|
-
url: new URL(entry.url),
|
|
763
|
-
ref: Option.fromUndefinedOr(entry.ref),
|
|
764
|
-
},
|
|
765
|
-
...(entry.path === undefined ? {} : { sourcePath: entry.path }),
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
})();
|
|
769
|
-
const dirName = entry.type === "registry" ? entry.name : entry.packageName;
|
|
770
|
-
return computeSkillPathsForLayout(path.join, layout, entrySource, sanitizeName(dirName));
|
|
771
|
-
}),
|
|
772
|
-
setSkill: ({ name, lockEntry, versionRange }) => withMutex(Effect.gen(function* () {
|
|
773
|
-
// Update settings — thread version constraint through so it survives the round-trip
|
|
774
|
-
const sourceInput = lockEntryToSourceParams(lockEntry);
|
|
775
|
-
const source = lockEntry.type === "registry"
|
|
776
|
-
? (() => {
|
|
777
|
-
const fqn = formatFqn({
|
|
778
|
-
owner: lockEntry.owner,
|
|
779
|
-
type: "skill",
|
|
780
|
-
name: decodeExtensionNameSync(name),
|
|
781
|
-
});
|
|
782
|
-
const locator = Option.isSome(versionRange)
|
|
783
|
-
? `${fqn}@${versionRange.value}`
|
|
784
|
-
: fqn;
|
|
785
|
-
return `${lockEntry.sourceName}:${locator}`;
|
|
786
|
-
})()
|
|
787
|
-
: printSourceParams(sourceInput);
|
|
788
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
789
|
-
const currentSkills = currentSettings.skills ?? {};
|
|
790
|
-
const nextSkillEntry = { source, enabled: true };
|
|
791
|
-
const updatedSettings = {
|
|
792
|
-
...currentSettings,
|
|
793
|
-
skills: { ...currentSkills, [name]: nextSkillEntry },
|
|
794
|
-
};
|
|
795
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
796
|
-
const currentLockEntry = currentLockfile.skills[name];
|
|
797
|
-
const settingsChanged = !stableCompare(currentSkills[name], nextSkillEntry);
|
|
798
|
-
const lockChanged = !skillLockEntrySemanticallyEqual(currentLockEntry, lockEntry);
|
|
799
|
-
if (settingsChanged) {
|
|
800
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
801
|
-
}
|
|
802
|
-
if (!lockChanged)
|
|
803
|
-
return;
|
|
804
|
-
const updatedLockfile = {
|
|
805
|
-
...currentLockfile,
|
|
806
|
-
skills: {
|
|
807
|
-
...currentLockfile.skills,
|
|
808
|
-
[name]: preserveAcceptedResolutionOnNoop(currentLockEntry, lockEntry),
|
|
809
|
-
},
|
|
810
|
-
};
|
|
811
|
-
yield* commitWorkspaceState(currentLockfile, updatedLockfile);
|
|
812
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setSkill")),
|
|
813
|
-
setSkillLock: ({ name, lockEntry }) => withMutex(Effect.gen(function* () {
|
|
814
|
-
// Update lockfile only (skip settings) — used for pack dependencies
|
|
815
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
816
|
-
const currentLockEntry = currentLockfile.skills[name];
|
|
817
|
-
if (skillLockEntrySemanticallyEqual(currentLockEntry, lockEntry)) {
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
const updatedLockfile = {
|
|
821
|
-
...currentLockfile,
|
|
822
|
-
skills: {
|
|
823
|
-
...currentLockfile.skills,
|
|
824
|
-
[name]: preserveAcceptedResolutionOnNoop(currentLockEntry, lockEntry),
|
|
825
|
-
},
|
|
826
|
-
};
|
|
827
|
-
yield* commitWorkspaceState(currentLockfile, updatedLockfile);
|
|
828
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setSkillLock")),
|
|
829
|
-
removeSkill: (name) => withMutex(Effect.gen(function* () {
|
|
830
|
-
// Update settings
|
|
831
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
832
|
-
const currentSkills = currentSettings.skills ?? {};
|
|
833
|
-
const hasSettingsEntry = name in currentSkills;
|
|
834
|
-
if (hasSettingsEntry) {
|
|
835
|
-
const { [name]: _, ...remainingSkills } = currentSkills;
|
|
836
|
-
void _;
|
|
837
|
-
const updatedSettings = { ...currentSettings, skills: remainingSkills };
|
|
838
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
839
|
-
}
|
|
840
|
-
// Update lockfile
|
|
841
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
842
|
-
const hasLockfileEntry = name in currentLockfile.skills;
|
|
843
|
-
if (hasLockfileEntry) {
|
|
844
|
-
const { [name]: __, ...remainingLockSkills } = currentLockfile.skills;
|
|
845
|
-
void __;
|
|
846
|
-
const updatedLockfile = { ...currentLockfile, skills: remainingLockSkills };
|
|
847
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, updatedLockfile).pipe(Effect.provide(fsLayer));
|
|
848
|
-
}
|
|
849
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeSkill")),
|
|
850
|
-
removeSkillFromSettings: (name) => withMutex(Effect.gen(function* () {
|
|
851
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
852
|
-
const currentSkills = currentSettings.skills ?? {};
|
|
853
|
-
if (!(name in currentSkills))
|
|
854
|
-
return; // no-op
|
|
855
|
-
const { [name]: _, ...remainingSkills } = currentSkills;
|
|
856
|
-
void _;
|
|
857
|
-
const updatedSettings = { ...currentSettings, skills: remainingSkills };
|
|
858
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
859
|
-
})),
|
|
860
|
-
updateSkillEntry: (name, updater) => withMutex(Effect.gen(function* () {
|
|
861
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
862
|
-
const currentSkills = currentSettings.skills ?? {};
|
|
863
|
-
const currentEntry = yield* getEntryOrFail(currentSkills, name, () => new SettingsEntryMissing({ entryType: "skill", name }));
|
|
864
|
-
const updated = updater(currentEntry);
|
|
865
|
-
const updatedSettings = {
|
|
866
|
-
...currentSettings,
|
|
867
|
-
skills: { ...currentSkills, [name]: updated },
|
|
868
|
-
};
|
|
869
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
870
|
-
})),
|
|
871
|
-
setSkillEntry: (name, entry) => withMutex(Effect.gen(function* () {
|
|
872
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
873
|
-
const currentSkills = currentSettings.skills ?? {};
|
|
874
|
-
const updatedSettings = {
|
|
875
|
-
...currentSettings,
|
|
876
|
-
skills: { ...currentSkills, [name]: entry },
|
|
877
|
-
};
|
|
878
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
879
|
-
})),
|
|
880
|
-
addConfiguredAgent: (agentId) => withMutex(Effect.gen(function* () {
|
|
881
|
-
const validId = yield* Schema.decodeUnknownEffect(ConfigurableAgentIdSchema)(agentId).pipe(Effect.mapError((cause) => new InvalidAgentId({ agentId, cause })));
|
|
882
|
-
const current = yield* readSettingsSafe(workspaceDir);
|
|
883
|
-
const agents = current.agents ?? [];
|
|
884
|
-
if (agents.includes(validId))
|
|
885
|
-
return;
|
|
886
|
-
const updatedSettings = { ...current, agents: [...agents, validId] };
|
|
887
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
888
|
-
})),
|
|
889
|
-
removeConfiguredAgent: (agentId) => withMutex(Effect.gen(function* () {
|
|
890
|
-
const validId = yield* Schema.decodeUnknownEffect(ConfigurableAgentIdSchema)(agentId).pipe(Effect.mapError((cause) => new InvalidAgentId({ agentId, cause })));
|
|
891
|
-
const current = yield* readSettingsSafe(workspaceDir);
|
|
892
|
-
const agents = current.agents ?? [];
|
|
893
|
-
if (!agents.includes(validId))
|
|
894
|
-
return;
|
|
895
|
-
const updatedSettings = {
|
|
896
|
-
...current,
|
|
897
|
-
agents: agents.filter((configured) => configured !== validId),
|
|
898
|
-
};
|
|
899
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
900
|
-
})),
|
|
901
|
-
getConfiguredPackEntries: () => readSettingsSafe(workspaceDir).pipe(Effect.map((s) => s.packs ?? {}), Effect.withSpan("WorkspaceMutations.getConfiguredPackEntries")),
|
|
902
|
-
getLockedPacks: () => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => lf.packs ?? {})),
|
|
903
|
-
getLockedPack: (name) => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => Option.fromUndefinedOr((lf.packs ?? {})[name]))),
|
|
904
|
-
setPack: (args) => withMutex(Effect.gen(function* () {
|
|
62
|
+
getLockfileState: () => bind(lockfile.state),
|
|
63
|
+
getDesiredStateGraph: (options) => bind(desiredState.graph(options)),
|
|
64
|
+
getConfiguredSources: () => bind(settings.configuredSources),
|
|
65
|
+
getConfiguredSourceByName: (name) => bind(settings.sourceByName(name)),
|
|
66
|
+
getRegistrySourceHosts: () => bind(settings.registrySourceHosts),
|
|
67
|
+
records: {
|
|
68
|
+
getInventory: (options) => bind(records.getInventory(options)),
|
|
69
|
+
getExtensionInventory: (type, options) => bind(records.getExtensionInventory(type, options)),
|
|
70
|
+
rows: (type) => bind(records.rows(type)),
|
|
71
|
+
},
|
|
72
|
+
getConfiguredOwner: () => bind(settings.owner),
|
|
73
|
+
setOwner: (owner) => bind(settingsWriter.setOwner(owner)).pipe(Effect.asVoid),
|
|
74
|
+
getPublishDefaultVisibility: () => bind(settings.publishDefaultVisibility),
|
|
75
|
+
getMinimumReleaseAge: () => bind(settings.minimumReleaseAge),
|
|
76
|
+
getMinimumReleaseAgeExclude: () => bind(settings.minimumReleaseAgeExclude),
|
|
77
|
+
addConfiguredSource: (source) => bind(settingsWriter.addConfiguredSource(source)),
|
|
78
|
+
getConfiguredSkillEntries: () => entries("skill"),
|
|
79
|
+
getConfiguredAgents: () => bind(settings.configuredAgents),
|
|
80
|
+
getInstructionsConfig: () => bind(settings.instructionsConfig),
|
|
81
|
+
setInstructionsConfig: (config) => bind(settingsWriter.setInstructionsConfig(config)),
|
|
82
|
+
getConfiguredMcpServerEntries: () => entries("mcp-server"),
|
|
83
|
+
getConfiguredRuleEntries: () => entries("rule"),
|
|
84
|
+
getLockedRules: () => locked("rule"),
|
|
85
|
+
getLockedRuleEntry: (name) => lockedEntry("rule", name),
|
|
86
|
+
setRule: (args) => bind(desiredStateWriter.declare("rule", args)),
|
|
87
|
+
setRuleLock: ({ name, lockEntry }) => bind(accepted.setAccepted("rule", name, lockEntry)),
|
|
88
|
+
removeRule: (name) => bind(desiredStateWriter.undeclare("rule", name)),
|
|
89
|
+
removeRuleSettings: (name) => bind(settingsWriter.removeEntry("rule", name)),
|
|
90
|
+
removeRuleLock: (name) => bind(accepted.removeAccepted("rule", name)),
|
|
91
|
+
updateRuleEntry: (name, update) => updateIgnoringMissing("rule", name, update),
|
|
92
|
+
setRuleEntry: (name, entry) => setEntry("rule", name, entry),
|
|
93
|
+
getConfiguredHookEntries: () => entries("hook"),
|
|
94
|
+
getLockedHooks: () => locked("hook"),
|
|
95
|
+
getLockedHookEntry: (name) => lockedEntry("hook", name),
|
|
96
|
+
setHook: (args) => bind(desiredStateWriter.declare("hook", args)),
|
|
97
|
+
setHookLock: ({ name, lockEntry }) => bind(accepted.setAccepted("hook", name, lockEntry)),
|
|
98
|
+
removeHook: (name) => bind(desiredStateWriter.undeclare("hook", name)),
|
|
99
|
+
removeHookSettings: (name) => bind(settingsWriter.removeEntry("hook", name)),
|
|
100
|
+
removeHookLock: (name) => bind(accepted.removeAccepted("hook", name)),
|
|
101
|
+
updateHookEntry: (name, update) => updateIgnoringMissing("hook", name, update),
|
|
102
|
+
setHookEntry: (name, entry) => setEntry("hook", name, entry),
|
|
103
|
+
getKnowledgeDiscoveryConfig: () => bind(settings.knowledgeDiscoveryConfig),
|
|
104
|
+
getConfiguredKnowledgeEntries: () => entries("knowledge"),
|
|
105
|
+
getLockedKnowledge: () => locked("knowledge"),
|
|
106
|
+
getLockedKnowledgeEntry: (name) => lockedEntry("knowledge", name),
|
|
107
|
+
setKnowledge: (args) => bind(desiredStateWriter.declare("knowledge", args)),
|
|
108
|
+
setKnowledgeLock: ({ name, lockEntry }) => bind(accepted.setAccepted("knowledge", name, lockEntry)),
|
|
109
|
+
removeKnowledge: (name) => bind(desiredStateWriter.undeclare("knowledge", name)),
|
|
110
|
+
removeKnowledgeSettings: (name) => bind(settingsWriter.removeEntry("knowledge", name)),
|
|
111
|
+
removeKnowledgeLock: (name) => bind(accepted.removeAccepted("knowledge", name)),
|
|
112
|
+
updateKnowledgeEntry: (name, update) => updateIgnoringMissing("knowledge", name, update),
|
|
113
|
+
setKnowledgeEntry: (name, entry) => setEntry("knowledge", name, entry),
|
|
114
|
+
getLockedSkills: () => locked("skill"),
|
|
115
|
+
getLockedSkill: (name) => lockedEntry("skill", name),
|
|
116
|
+
getSkillDir: (name, source) => bind(paths.skillDir(name, source)),
|
|
117
|
+
setSkill: (args) => bind(desiredStateWriter.declare("skill", args)),
|
|
118
|
+
setSkillLock: ({ name, lockEntry }) => bind(accepted.setAccepted("skill", name, lockEntry)),
|
|
119
|
+
removeSkill: (name) => bind(desiredStateWriter.undeclare("skill", name)),
|
|
120
|
+
removeSkillFromSettings: (name) => bind(settingsWriter.removeEntry("skill", name)),
|
|
121
|
+
updateSkillEntry: (name, update) => bind(settingsWriter.updateEntry("skill", name, update)),
|
|
122
|
+
setSkillEntry: (name, entry) => setEntry("skill", name, entry),
|
|
123
|
+
addConfiguredAgent: (agentId) => bind(settingsWriter.addConfiguredAgent(agentId)),
|
|
124
|
+
removeConfiguredAgent: (agentId) => bind(settingsWriter.removeConfiguredAgent(agentId)),
|
|
125
|
+
getConfiguredPackEntries: () => entries("pack"),
|
|
126
|
+
getLockedPacks: () => locked("pack"),
|
|
127
|
+
getLockedPack: (name) => lockedEntry("pack", name),
|
|
128
|
+
setPack: (args) => bind(desiredStateWriter.declare("pack", args)),
|
|
129
|
+
setPackLock: (args) => {
|
|
905
130
|
const { versionRange, ...lockEntry } = args;
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
const { versionRange: _, ...lockEntry } = args;
|
|
937
|
-
void _;
|
|
938
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
939
|
-
const currentLockedPacks = currentLockfile.packs ?? {};
|
|
940
|
-
const updatedLockfile = {
|
|
941
|
-
...currentLockfile,
|
|
942
|
-
packs: {
|
|
943
|
-
...currentLockedPacks,
|
|
944
|
-
[lockEntry.name]: preserveAcceptedResolutionOnNoop(currentLockedPacks[lockEntry.name], lockEntry),
|
|
945
|
-
},
|
|
946
|
-
};
|
|
947
|
-
yield* commitWorkspaceState(currentLockfile, updatedLockfile);
|
|
948
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setPackLock")),
|
|
949
|
-
setPackEntry: (name, entry) => withMutex(Effect.gen(function* () {
|
|
950
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
951
|
-
const currentPacks = currentSettings.packs ?? {};
|
|
952
|
-
const updatedSettings = {
|
|
953
|
-
...currentSettings,
|
|
954
|
-
packs: { ...currentPacks, [name]: entry },
|
|
955
|
-
};
|
|
956
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
957
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setPackEntry")),
|
|
958
|
-
removePack: (name) => withMutex(Effect.gen(function* () {
|
|
959
|
-
// Update settings
|
|
960
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
961
|
-
const currentPacks = currentSettings.packs ?? {};
|
|
962
|
-
if (!(name in currentPacks))
|
|
963
|
-
return; // no-op
|
|
964
|
-
const { [name]: _, ...remainingPacks } = currentPacks;
|
|
965
|
-
void _;
|
|
966
|
-
const updatedSettings = { ...currentSettings, packs: remainingPacks };
|
|
967
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
968
|
-
// Update lockfile
|
|
969
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
970
|
-
const currentLockedPacks = currentLockfile.packs ?? {};
|
|
971
|
-
if (name in currentLockedPacks) {
|
|
972
|
-
const { [name]: __, ...remainingLockedPacks } = currentLockedPacks;
|
|
973
|
-
void __;
|
|
974
|
-
const updatedLockfile = { ...currentLockfile, packs: remainingLockedPacks };
|
|
975
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, updatedLockfile).pipe(Effect.provide(fsLayer));
|
|
976
|
-
}
|
|
977
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removePack")),
|
|
978
|
-
getPackDir: (name, owner, sourceName) => Effect.succeed(computePackPathsForLayout(path.join, layout, sourceName, owner, name)),
|
|
979
|
-
// -----------------------------------------------------------------------
|
|
980
|
-
// Subagent methods
|
|
981
|
-
// -----------------------------------------------------------------------
|
|
982
|
-
getLockedSubagents: () => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => lf.subagents ?? {})),
|
|
983
|
-
getLockedSubagent: (name) => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => Option.fromUndefinedOr((lf.subagents ?? {})[name]))),
|
|
984
|
-
getConfiguredSubagentEntries: () => readSettingsSafe(workspaceDir).pipe(Effect.map((s) => s.subagents ?? {}), Effect.withSpan("WorkspaceMutations.getConfiguredSubagentEntries")),
|
|
985
|
-
setSubagent: ({ name, lockEntry, versionRange }) => withMutex(Effect.gen(function* () {
|
|
986
|
-
// Update settings
|
|
987
|
-
const source = lockEntry.type === "registry"
|
|
988
|
-
? (() => {
|
|
989
|
-
const fqn = formatFqn({
|
|
990
|
-
owner: lockEntry.owner,
|
|
991
|
-
type: "subagent",
|
|
992
|
-
name: decodeExtensionNameSync(name),
|
|
993
|
-
});
|
|
994
|
-
const locator = Option.isSome(versionRange)
|
|
995
|
-
? `${fqn}@${versionRange.value}`
|
|
996
|
-
: fqn;
|
|
997
|
-
return `${lockEntry.sourceName}:${locator}`;
|
|
998
|
-
})()
|
|
999
|
-
: printSourceParams(lockEntryToSourceParams(lockEntry));
|
|
1000
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
1001
|
-
const currentSubagents = currentSettings.subagents ?? {};
|
|
1002
|
-
const nextSubagentEntry = { source, enabled: true };
|
|
1003
|
-
const updatedSettings = {
|
|
1004
|
-
...currentSettings,
|
|
1005
|
-
subagents: { ...currentSubagents, [name]: nextSubagentEntry },
|
|
1006
|
-
};
|
|
1007
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
1008
|
-
const currentLockedSubagents = currentLockfile.subagents ?? {};
|
|
1009
|
-
const currentLockEntry = currentLockedSubagents[name];
|
|
1010
|
-
const settingsChanged = !stableCompare(currentSubagents[name], nextSubagentEntry);
|
|
1011
|
-
const lockChanged = !subagentLockEntrySemanticallyEqual(currentLockEntry, lockEntry);
|
|
1012
|
-
if (settingsChanged) {
|
|
1013
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
1014
|
-
}
|
|
1015
|
-
if (!lockChanged)
|
|
1016
|
-
return;
|
|
1017
|
-
const updatedLockfile = {
|
|
1018
|
-
...currentLockfile,
|
|
1019
|
-
subagents: {
|
|
1020
|
-
...currentLockedSubagents,
|
|
1021
|
-
[name]: preserveAcceptedResolutionOnNoop(currentLockEntry, lockEntry),
|
|
1022
|
-
},
|
|
1023
|
-
};
|
|
1024
|
-
yield* commitWorkspaceState(currentLockfile, updatedLockfile);
|
|
1025
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setSubagent")),
|
|
1026
|
-
setSubagentLock: ({ name, lockEntry }) => withMutex(Effect.gen(function* () {
|
|
1027
|
-
// Update lockfile only (skip settings) — used for pack dependencies
|
|
1028
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
1029
|
-
const currentLockedSubagents = currentLockfile.subagents ?? {};
|
|
1030
|
-
if (subagentLockEntrySemanticallyEqual(currentLockedSubagents[name], lockEntry)) {
|
|
1031
|
-
return;
|
|
1032
|
-
}
|
|
1033
|
-
const updatedLockfile = {
|
|
1034
|
-
...currentLockfile,
|
|
1035
|
-
subagents: {
|
|
1036
|
-
...currentLockedSubagents,
|
|
1037
|
-
[name]: preserveAcceptedResolutionOnNoop(currentLockedSubagents[name], lockEntry),
|
|
1038
|
-
},
|
|
1039
|
-
};
|
|
1040
|
-
yield* commitWorkspaceState(currentLockfile, updatedLockfile);
|
|
1041
|
-
})),
|
|
1042
|
-
removeSubagent: (name) => withMutex(Effect.gen(function* () {
|
|
1043
|
-
// Update settings
|
|
1044
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
1045
|
-
const currentSubagents = currentSettings.subagents ?? {};
|
|
1046
|
-
const hasSettingsEntry = name in currentSubagents;
|
|
1047
|
-
if (hasSettingsEntry) {
|
|
1048
|
-
const { [name]: _, ...remainingSubagents } = currentSubagents;
|
|
1049
|
-
void _;
|
|
1050
|
-
const updatedSettings = { ...currentSettings, subagents: remainingSubagents };
|
|
1051
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
1052
|
-
}
|
|
1053
|
-
// Update lockfile
|
|
1054
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
1055
|
-
const currentLockedSubagents = currentLockfile.subagents ?? {};
|
|
1056
|
-
if (name in currentLockedSubagents) {
|
|
1057
|
-
const { [name]: __, ...remainingLockedSubagents } = currentLockedSubagents;
|
|
1058
|
-
void __;
|
|
1059
|
-
const updatedLockfile = {
|
|
1060
|
-
...currentLockfile,
|
|
1061
|
-
subagents: remainingLockedSubagents,
|
|
1062
|
-
};
|
|
1063
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, updatedLockfile).pipe(Effect.provide(fsLayer));
|
|
1064
|
-
}
|
|
1065
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeSubagent")),
|
|
1066
|
-
updateSubagentEntry: (name, updater) => withMutex(Effect.gen(function* () {
|
|
1067
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
1068
|
-
const currentSubagents = currentSettings.subagents ?? {};
|
|
1069
|
-
const existingEntry = currentSubagents[name];
|
|
1070
|
-
if (existingEntry === undefined)
|
|
1071
|
-
return;
|
|
1072
|
-
const updated = updater(existingEntry);
|
|
1073
|
-
const updatedSettings = {
|
|
1074
|
-
...currentSettings,
|
|
1075
|
-
subagents: { ...currentSubagents, [name]: updated },
|
|
1076
|
-
};
|
|
1077
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
1078
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.updateSubagentEntry")),
|
|
1079
|
-
setSubagentEntry: (name, entry) => withMutex(Effect.gen(function* () {
|
|
1080
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
1081
|
-
const currentSubagents = currentSettings.subagents ?? {};
|
|
1082
|
-
const updatedSettings = {
|
|
1083
|
-
...currentSettings,
|
|
1084
|
-
subagents: { ...currentSubagents, [name]: entry },
|
|
1085
|
-
};
|
|
1086
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
1087
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setSubagentEntry")),
|
|
1088
|
-
removeSubagentSettings: (name) => withMutex(Effect.gen(function* () {
|
|
1089
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
1090
|
-
const currentSubagents = currentSettings.subagents ?? {};
|
|
1091
|
-
if (!(name in currentSubagents))
|
|
1092
|
-
return; // no-op
|
|
1093
|
-
const { [name]: _, ...remainingSubagents } = currentSubagents;
|
|
1094
|
-
void _;
|
|
1095
|
-
const updatedSettings = { ...currentSettings, subagents: remainingSubagents };
|
|
1096
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
1097
|
-
})),
|
|
1098
|
-
removeSubagentLock: (name) => withMutex(Effect.gen(function* () {
|
|
1099
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
1100
|
-
const currentLockedSubagents = currentLockfile.subagents ?? {};
|
|
1101
|
-
if (!(name in currentLockedSubagents))
|
|
1102
|
-
return;
|
|
1103
|
-
const { [name]: _, ...remaining } = currentLockedSubagents;
|
|
1104
|
-
void _;
|
|
1105
|
-
const updatedLockfile = { ...currentLockfile, subagents: remaining };
|
|
1106
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, updatedLockfile).pipe(Effect.provide(fsLayer));
|
|
1107
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeSubagentLock")),
|
|
1108
|
-
// -----------------------------------------------------------------------
|
|
1109
|
-
// MCP Server methods
|
|
1110
|
-
// -----------------------------------------------------------------------
|
|
1111
|
-
getLockedMcpServers: () => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => lf.mcpServers ?? {})),
|
|
1112
|
-
getLockedMcpServer: (resolutionKey) => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => Option.fromUndefinedOr((lf.mcpServers ?? {})[resolutionKey]))),
|
|
1113
|
-
getLockedMcpServerForConnection: (localName) => Effect.gen(function* () {
|
|
1114
|
-
const graph = yield* readDesiredStateGraph();
|
|
1115
|
-
const node = graph.nodes.find((candidate) => candidate.type === "mcp-server" && candidate.name === localName);
|
|
1116
|
-
if (node === undefined || node.authority === "inline")
|
|
1117
|
-
return Option.none();
|
|
1118
|
-
const lockfile = yield* readLockfileSafe(workspaceDir);
|
|
1119
|
-
return Option.fromUndefinedOr((lockfile.mcpServers ?? {})[node.identity]);
|
|
1120
|
-
}),
|
|
1121
|
-
setMcpServer: ({ name, resolutionKey, lockEntry, versionRange, env, enabled, }) => withMutex(Effect.gen(function* () {
|
|
1122
|
-
// Update settings (uses "mcpServers" key)
|
|
1123
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
1124
|
-
const currentMcpServers = currentSettings.mcpServers ?? {};
|
|
1125
|
-
const currentEnabled = currentMcpServers[name]?.enabled ?? true;
|
|
1126
|
-
const currentEnv = currentMcpServers[name]?.env ?? {};
|
|
1127
|
-
const settingsEntry = {
|
|
1128
|
-
kind: "sourced",
|
|
1129
|
-
source: lockEntry.type === "registry"
|
|
1130
|
-
? (() => {
|
|
1131
|
-
const fqn = formatFqn({
|
|
1132
|
-
owner: lockEntry.owner,
|
|
1133
|
-
type: "mcp-server",
|
|
1134
|
-
name: lockEntry.name,
|
|
1135
|
-
});
|
|
1136
|
-
const locator = Option.isSome(versionRange)
|
|
1137
|
-
? `${fqn}@${versionRange.value}`
|
|
1138
|
-
: fqn;
|
|
1139
|
-
return `${lockEntry.sourceName}:${locator}`;
|
|
1140
|
-
})()
|
|
1141
|
-
: printSourceParams(lockEntryToSourceParams(lockEntry)),
|
|
1142
|
-
enabled: enabled ?? currentEnabled,
|
|
1143
|
-
env: env ?? currentEnv,
|
|
1144
|
-
};
|
|
1145
|
-
const updatedSettings = {
|
|
1146
|
-
...currentSettings,
|
|
1147
|
-
mcpServers: {
|
|
1148
|
-
...currentMcpServers,
|
|
1149
|
-
[name]: settingsEntry,
|
|
1150
|
-
},
|
|
1151
|
-
};
|
|
1152
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
1153
|
-
// Update lockfile (uses "mcpServers" key)
|
|
1154
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
1155
|
-
const currentLockedMcpServers = currentLockfile.mcpServers ?? {};
|
|
1156
|
-
const updatedLockfile = {
|
|
1157
|
-
...currentLockfile,
|
|
1158
|
-
mcpServers: {
|
|
1159
|
-
...currentLockedMcpServers,
|
|
1160
|
-
[resolutionKey]: preserveAcceptedResolutionOnNoop(currentLockedMcpServers[resolutionKey], lockEntry),
|
|
1161
|
-
},
|
|
1162
|
-
};
|
|
1163
|
-
yield* commitWorkspaceState(currentLockfile, updatedLockfile);
|
|
1164
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.setMcpServer")),
|
|
1165
|
-
setMcpServerLock: ({ name: _name, resolutionKey, lockEntry }) => withMutex(Effect.gen(function* () {
|
|
1166
|
-
// Update lockfile only (skip settings) — used for pack dependencies
|
|
1167
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
1168
|
-
const currentLockedMcpServers = currentLockfile.mcpServers ?? {};
|
|
1169
|
-
const updatedLockfile = {
|
|
1170
|
-
...currentLockfile,
|
|
1171
|
-
mcpServers: {
|
|
1172
|
-
...currentLockedMcpServers,
|
|
1173
|
-
[resolutionKey]: preserveAcceptedResolutionOnNoop(currentLockedMcpServers[resolutionKey], lockEntry),
|
|
1174
|
-
},
|
|
1175
|
-
};
|
|
1176
|
-
yield* commitWorkspaceState(currentLockfile, updatedLockfile);
|
|
1177
|
-
})),
|
|
1178
|
-
updateMcpServerEntry: (name, updater) => withMutex(Effect.gen(function* () {
|
|
1179
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
1180
|
-
const currentMcpServers = currentSettings.mcpServers ?? {};
|
|
1181
|
-
const currentEntry = yield* getEntryOrFail(currentMcpServers, name, () => new SettingsEntryMissing({ entryType: "mcp-server", name }));
|
|
1182
|
-
const updated = updater(currentEntry);
|
|
1183
|
-
const updatedSettings = {
|
|
1184
|
-
...currentSettings,
|
|
1185
|
-
mcpServers: { ...currentMcpServers, [name]: updated },
|
|
1186
|
-
};
|
|
1187
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
1188
|
-
})),
|
|
1189
|
-
setMcpServerEntry: (name, entry) => withMutex(Effect.gen(function* () {
|
|
1190
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
1191
|
-
const currentMcpServers = currentSettings.mcpServers ?? {};
|
|
1192
|
-
const updatedSettings = {
|
|
1193
|
-
...currentSettings,
|
|
1194
|
-
mcpServers: { ...currentMcpServers, [name]: entry },
|
|
1195
|
-
};
|
|
1196
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
1197
|
-
})),
|
|
1198
|
-
removeMcpServer: (name) => withMutex(Effect.gen(function* () {
|
|
1199
|
-
const graph = yield* readDesiredStateGraph();
|
|
1200
|
-
const desiredNode = graph.nodes.find((node) => node.type === "mcp-server" && node.name === name);
|
|
1201
|
-
const closure = desiredNode === undefined || desiredNode.authority === "inline"
|
|
1202
|
-
? undefined
|
|
1203
|
-
: graph.mcpSourceClosures.find((candidate) => candidate.identity === desiredNode.identity);
|
|
1204
|
-
// Update settings (uses "mcpServers" key)
|
|
1205
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
1206
|
-
const currentMcpServers = currentSettings.mcpServers ?? {};
|
|
1207
|
-
const hasSettingsEntry = name in currentMcpServers;
|
|
1208
|
-
if (hasSettingsEntry) {
|
|
1209
|
-
const { [name]: _, ...remainingMcpServers } = currentMcpServers;
|
|
1210
|
-
void _;
|
|
1211
|
-
const updatedSettings = {
|
|
1212
|
-
...currentSettings,
|
|
1213
|
-
mcpServers: remainingMcpServers,
|
|
1214
|
-
};
|
|
1215
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
1216
|
-
}
|
|
1217
|
-
// Update lockfile (uses "mcpServers" key)
|
|
1218
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
1219
|
-
const currentLockedMcpServers = currentLockfile.mcpServers ?? {};
|
|
1220
|
-
const resolutionKey = desiredNode?.identity;
|
|
1221
|
-
const keepSharedResolution = closure !== undefined &&
|
|
1222
|
-
(closure.localNames.length > 1 ||
|
|
1223
|
-
closure.origins.some((origin) => origin.type === "pack"));
|
|
1224
|
-
if (resolutionKey !== undefined &&
|
|
1225
|
-
!keepSharedResolution &&
|
|
1226
|
-
resolutionKey in currentLockedMcpServers) {
|
|
1227
|
-
const { [resolutionKey]: __, ...remainingLockedMcpServers } = currentLockedMcpServers;
|
|
1228
|
-
void __;
|
|
1229
|
-
const updatedLockfile = {
|
|
1230
|
-
...currentLockfile,
|
|
1231
|
-
mcpServers: remainingLockedMcpServers,
|
|
1232
|
-
};
|
|
1233
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, updatedLockfile).pipe(Effect.provide(fsLayer));
|
|
1234
|
-
}
|
|
1235
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeMcpServer")),
|
|
1236
|
-
// -----------------------------------------------------------------------
|
|
1237
|
-
// Granular removal methods (settings-only or lockfile-only)
|
|
1238
|
-
// -----------------------------------------------------------------------
|
|
1239
|
-
removeSkillLock: (name) => withMutex(Effect.gen(function* () {
|
|
1240
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
1241
|
-
if (!(name in currentLockfile.skills))
|
|
1242
|
-
return;
|
|
1243
|
-
const { [name]: _, ...remainingSkills } = currentLockfile.skills;
|
|
1244
|
-
void _;
|
|
1245
|
-
const updatedLockfile = { ...currentLockfile, skills: remainingSkills };
|
|
1246
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, updatedLockfile).pipe(Effect.provide(fsLayer));
|
|
1247
|
-
})).pipe(Effect.withSpan("WorkspaceMutations.removeSkillLock")),
|
|
1248
|
-
removeMcpServerSettings: (name) => withMutex(Effect.gen(function* () {
|
|
1249
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
1250
|
-
const currentMcpServers = currentSettings.mcpServers ?? {};
|
|
1251
|
-
if (!(name in currentMcpServers))
|
|
1252
|
-
return;
|
|
1253
|
-
const { [name]: _, ...remainingMcpServers } = currentMcpServers;
|
|
1254
|
-
void _;
|
|
1255
|
-
const updatedSettings = { ...currentSettings, mcpServers: remainingMcpServers };
|
|
1256
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
1257
|
-
})),
|
|
1258
|
-
removeMcpServerLock: (name) => withMutex(Effect.gen(function* () {
|
|
1259
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
1260
|
-
const currentLockedMcpServers = currentLockfile.mcpServers ?? {};
|
|
1261
|
-
if (!(name in currentLockedMcpServers))
|
|
1262
|
-
return;
|
|
1263
|
-
const { [name]: _, ...remainingMcpServers } = currentLockedMcpServers;
|
|
1264
|
-
void _;
|
|
1265
|
-
const updatedLockfile = { ...currentLockfile, mcpServers: remainingMcpServers };
|
|
1266
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, updatedLockfile).pipe(Effect.provide(fsLayer));
|
|
1267
|
-
})),
|
|
1268
|
-
removePackSettings: (name) => withMutex(Effect.gen(function* () {
|
|
1269
|
-
const currentSettings = yield* readSettingsSafe(workspaceDir);
|
|
1270
|
-
const currentPacks = currentSettings.packs ?? {};
|
|
1271
|
-
if (!(name in currentPacks))
|
|
1272
|
-
return;
|
|
1273
|
-
const { [name]: _, ...remainingPacks } = currentPacks;
|
|
1274
|
-
void _;
|
|
1275
|
-
const updatedSettings = { ...currentSettings, packs: remainingPacks };
|
|
1276
|
-
yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
|
|
1277
|
-
})),
|
|
1278
|
-
removePackLock: (name) => withMutex(Effect.gen(function* () {
|
|
1279
|
-
const currentLockfile = yield* readLockfileSafe(workspaceDir);
|
|
1280
|
-
const currentLockedPacks = currentLockfile.packs ?? {};
|
|
1281
|
-
if (!(name in currentLockedPacks))
|
|
1282
|
-
return;
|
|
1283
|
-
const { [name]: _, ...remainingPacks } = currentLockedPacks;
|
|
1284
|
-
void _;
|
|
1285
|
-
const updatedLockfile = { ...currentLockfile, packs: remainingPacks };
|
|
1286
|
-
yield* commitLockfileSnapshotUpdateAtPath(lockPath, currentLockfile, updatedLockfile).pipe(Effect.provide(fsLayer));
|
|
1287
|
-
})),
|
|
1288
|
-
// -----------------------------------------------------------------------
|
|
1289
|
-
// Pack dependency queries
|
|
1290
|
-
// -----------------------------------------------------------------------
|
|
1291
|
-
isExtensionRequiredByInstalledPack: (target) => Effect.gen(function* () {
|
|
1292
|
-
if (target.type === "pack")
|
|
1293
|
-
return false;
|
|
1294
|
-
const graph = yield* readDesiredStateGraph();
|
|
1295
|
-
if (!graph.complete) {
|
|
1296
|
-
return yield* new DesiredPackGraphIncomplete();
|
|
1297
|
-
}
|
|
1298
|
-
return graph.nodes.some((node) => node.type === target.type &&
|
|
1299
|
-
node.name === target.name &&
|
|
1300
|
-
node.origins.some((origin) => origin.type === "pack"));
|
|
1301
|
-
}),
|
|
131
|
+
void versionRange;
|
|
132
|
+
return bind(accepted.setAccepted("pack", lockEntry.name, lockEntry));
|
|
133
|
+
},
|
|
134
|
+
setPackEntry: (name, entry) => setEntry("pack", name, entry),
|
|
135
|
+
removePack: (name) => bind(desiredStateWriter.undeclare("pack", name)),
|
|
136
|
+
getPackDir: (name, owner, sourceName) => paths.packDir(name, owner, sourceName).pipe(Effect.provideService(Path.Path, path)),
|
|
137
|
+
getLockedSubagents: () => locked("subagent"),
|
|
138
|
+
getLockedSubagent: (name) => lockedEntry("subagent", name),
|
|
139
|
+
getConfiguredSubagentEntries: () => entries("subagent"),
|
|
140
|
+
setSubagent: (args) => bind(desiredStateWriter.declare("subagent", args)),
|
|
141
|
+
setSubagentLock: ({ name, lockEntry }) => bind(accepted.setAccepted("subagent", name, lockEntry)),
|
|
142
|
+
removeSubagent: (name) => bind(desiredStateWriter.undeclare("subagent", name)),
|
|
143
|
+
updateSubagentEntry: (name, update) => updateIgnoringMissing("subagent", name, update),
|
|
144
|
+
setSubagentEntry: (name, entry) => setEntry("subagent", name, entry),
|
|
145
|
+
removeSubagentSettings: (name) => bind(settingsWriter.removeEntry("subagent", name)),
|
|
146
|
+
removeSubagentLock: (name) => bind(accepted.removeAccepted("subagent", name)),
|
|
147
|
+
getLockedMcpServers: () => locked("mcp-server"),
|
|
148
|
+
getLockedMcpServer: (resolutionKey) => lockedEntry("mcp-server", resolutionKey),
|
|
149
|
+
getLockedMcpServerForConnection: (localName) => bind(lockfile.mcpServerForConnection(localName)),
|
|
150
|
+
setMcpServer: (args) => bind(desiredStateWriter.declare("mcp-server", args)),
|
|
151
|
+
setMcpServerLock: ({ resolutionKey, lockEntry }) => bind(accepted.setAccepted("mcp-server", resolutionKey, lockEntry)),
|
|
152
|
+
updateMcpServerEntry: (name, update) => bind(settingsWriter.updateEntry("mcp-server", name, update)),
|
|
153
|
+
setMcpServerEntry: (name, entry) => setEntry("mcp-server", name, entry),
|
|
154
|
+
removeMcpServer: (name) => bind(desiredStateWriter.undeclare("mcp-server", name)),
|
|
155
|
+
removeSkillLock: (name) => bind(accepted.removeAccepted("skill", name)),
|
|
156
|
+
removeMcpServerSettings: (name) => bind(settingsWriter.removeEntry("mcp-server", name)),
|
|
157
|
+
removeMcpServerLock: (name) => bind(accepted.removeAccepted("mcp-server", name)),
|
|
158
|
+
removePackSettings: (name) => bind(settingsWriter.removeEntry("pack", name)),
|
|
159
|
+
removePackLock: (name) => bind(accepted.removeAccepted("pack", name)),
|
|
160
|
+
isExtensionRequiredByInstalledPack: (target) => bind(desiredState.isRequiredByInstalledPack(target)),
|
|
1302
161
|
};
|
|
1303
162
|
});
|
|
1304
163
|
//# sourceMappingURL=service.js.map
|