@agentxm/workspace-state 0.28.4-bootstrap.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +110 -0
- package/README.md +11 -0
- package/dist/src/index.d.ts +72 -0
- package/dist/src/index.js +88 -0
- package/dist/src/knowledge/discovery-config.d.ts +12 -0
- package/dist/src/knowledge/discovery-config.js +9 -0
- package/dist/src/lockfile/accepted-registry-version.d.ts +12 -0
- package/dist/src/lockfile/accepted-registry-version.js +13 -0
- package/dist/src/lockfile/entry-fields.d.ts +189 -0
- package/dist/src/lockfile/entry-fields.js +89 -0
- package/dist/src/lockfile/errors.d.ts +48 -0
- package/dist/src/lockfile/errors.js +27 -0
- package/dist/src/lockfile/index.d.ts +18 -0
- package/dist/src/lockfile/index.js +16 -0
- package/dist/src/lockfile/lockfile.d.ts +2255 -0
- package/dist/src/lockfile/lockfile.js +235 -0
- package/dist/src/lockfile/resolved-version.d.ts +4 -0
- package/dist/src/lockfile/resolved-version.js +7 -0
- package/dist/src/lockfile/schema.d.ts +3062 -0
- package/dist/src/lockfile/schema.js +406 -0
- package/dist/src/schema/index.d.ts +3 -0
- package/dist/src/schema/index.js +2 -0
- package/dist/src/schema/read-and-validate-json-file.d.ts +20 -0
- package/dist/src/schema/read-and-validate-json-file.js +85 -0
- package/dist/src/schema/types.d.ts +26 -0
- package/dist/src/schema/types.js +2 -0
- package/dist/src/settings/errors.d.ts +22 -0
- package/dist/src/settings/errors.js +15 -0
- package/dist/src/settings/format-preserving-json.d.ts +40 -0
- package/dist/src/settings/format-preserving-json.js +80 -0
- package/dist/src/settings/index.d.ts +15 -0
- package/dist/src/settings/index.js +14 -0
- package/dist/src/settings/schema.d.ts +687 -0
- package/dist/src/settings/schema.js +977 -0
- package/dist/src/settings/settings.d.ts +38 -0
- package/dist/src/settings/settings.js +213 -0
- package/dist/src/testing.d.ts +17 -0
- package/dist/src/testing.js +17 -0
- package/dist/src/utils/atomic-write.d.ts +67 -0
- package/dist/src/utils/atomic-write.js +71 -0
- package/dist/src/utils/environment.d.ts +16 -0
- package/dist/src/utils/environment.js +19 -0
- package/dist/src/utils/path-safety.d.ts +25 -0
- package/dist/src/utils/path-safety.js +34 -0
- package/dist/src/utils/resolve-parent-symlinks.d.ts +16 -0
- package/dist/src/utils/resolve-parent-symlinks.js +36 -0
- package/dist/src/workspace/accepted-canonical-ref.d.ts +58 -0
- package/dist/src/workspace/accepted-canonical-ref.js +229 -0
- package/dist/src/workspace/artifact-change.d.ts +14 -0
- package/dist/src/workspace/artifact-change.js +18 -0
- package/dist/src/workspace/canonical-observation.d.ts +39 -0
- package/dist/src/workspace/canonical-observation.js +307 -0
- package/dist/src/workspace/configured-agent-outcome.d.ts +22 -0
- package/dist/src/workspace/configured-agent-outcome.js +33 -0
- package/dist/src/workspace/configured-agent-outcomes-provider.d.ts +50 -0
- package/dist/src/workspace/configured-agent-outcomes-provider.js +26 -0
- package/dist/src/workspace/configured-agent-outcomes.d.ts +48 -0
- package/dist/src/workspace/configured-agent-outcomes.js +113 -0
- package/dist/src/workspace/configured-entry-resolution/types.d.ts +32 -0
- package/dist/src/workspace/configured-entry-resolution/types.js +2 -0
- package/dist/src/workspace/configured-entry-resolution/workspace-ref.d.ts +32 -0
- package/dist/src/workspace/configured-entry-resolution/workspace-ref.js +176 -0
- package/dist/src/workspace/constants.d.ts +6 -0
- package/dist/src/workspace/constants.js +6 -0
- package/dist/src/workspace/create-symlink.d.ts +26 -0
- package/dist/src/workspace/create-symlink.js +87 -0
- package/dist/src/workspace/desired-pack-lock.d.ts +18 -0
- package/dist/src/workspace/desired-pack-lock.js +132 -0
- package/dist/src/workspace/desired-state-enabled.d.ts +18 -0
- package/dist/src/workspace/desired-state-enabled.js +16 -0
- package/dist/src/workspace/desired-state-graph.d.ts +114 -0
- package/dist/src/workspace/desired-state-graph.js +384 -0
- package/dist/src/workspace/desired-state-problem-text.d.ts +6 -0
- package/dist/src/workspace/desired-state-problem-text.js +27 -0
- package/dist/src/workspace/errors.d.ts +184 -0
- package/dist/src/workspace/errors.js +79 -0
- package/dist/src/workspace/extension-name.d.ts +28 -0
- package/dist/src/workspace/extension-name.js +66 -0
- package/dist/src/workspace/extension-paths.d.ts +82 -0
- package/dist/src/workspace/extension-paths.js +172 -0
- package/dist/src/workspace/footprint-recorder.d.ts +30 -0
- package/dist/src/workspace/footprint-recorder.js +32 -0
- package/dist/src/workspace/layout.d.ts +43 -0
- package/dist/src/workspace/layout.js +164 -0
- package/dist/src/workspace/lock-entry-to-ref.d.ts +40 -0
- package/dist/src/workspace/lock-entry-to-ref.js +433 -0
- package/dist/src/workspace/lock-entry-to-source-params.d.ts +26 -0
- package/dist/src/workspace/lock-entry-to-source-params.js +81 -0
- package/dist/src/workspace/locked-entries.d.ts +30 -0
- package/dist/src/workspace/locked-entries.js +34 -0
- package/dist/src/workspace/materialized-file-target.d.ts +14 -0
- package/dist/src/workspace/materialized-file-target.js +14 -0
- package/dist/src/workspace/materialized-tree.d.ts +23 -0
- package/dist/src/workspace/materialized-tree.js +90 -0
- package/dist/src/workspace/mcp-entry-semantics.d.ts +31 -0
- package/dist/src/workspace/mcp-entry-semantics.js +60 -0
- package/dist/src/workspace/observed-installed.d.ts +9 -0
- package/dist/src/workspace/observed-installed.js +9 -0
- package/dist/src/workspace/pack-manifest-content-identity.d.ts +12 -0
- package/dist/src/workspace/pack-manifest-content-identity.js +20 -0
- package/dist/src/workspace/pack-paths.d.ts +24 -0
- package/dist/src/workspace/pack-paths.js +16 -0
- package/dist/src/workspace/package-hash.d.ts +25 -0
- package/dist/src/workspace/package-hash.js +45 -0
- package/dist/src/workspace/paths.d.ts +24 -0
- package/dist/src/workspace/paths.js +52 -0
- package/dist/src/workspace/read-model/__fixtures__/builder.d.ts +199 -0
- package/dist/src/workspace/read-model/__fixtures__/builder.js +588 -0
- package/dist/src/workspace/read-model/__fixtures__/decoders.d.ts +15 -0
- package/dist/src/workspace/read-model/__fixtures__/decoders.js +15 -0
- package/dist/src/workspace/read-model/__fixtures__/occurrences.d.ts +72 -0
- package/dist/src/workspace/read-model/__fixtures__/occurrences.js +134 -0
- package/dist/src/workspace/read-model/__fixtures__/scenario-harness.d.ts +99 -0
- package/dist/src/workspace/read-model/__fixtures__/scenario-harness.js +142 -0
- package/dist/src/workspace/read-model/__fixtures__/test-layer.d.ts +60 -0
- package/dist/src/workspace/read-model/__fixtures__/test-layer.js +57 -0
- package/dist/src/workspace/read-model/agent-presence.d.ts +32 -0
- package/dist/src/workspace/read-model/agent-presence.js +21 -0
- package/dist/src/workspace/read-model/agent-root-resolver.d.ts +38 -0
- package/dist/src/workspace/read-model/agent-root-resolver.js +35 -0
- package/dist/src/workspace/read-model/agents/index.d.ts +38 -0
- package/dist/src/workspace/read-model/agents/index.js +45 -0
- package/dist/src/workspace/read-model/agents/shared.d.ts +27 -0
- package/dist/src/workspace/read-model/agents/shared.js +63 -0
- package/dist/src/workspace/read-model/agents/types.d.ts +133 -0
- package/dist/src/workspace/read-model/agents/types.js +51 -0
- package/dist/src/workspace/read-model/diagnostics.d.ts +18 -0
- package/dist/src/workspace/read-model/diagnostics.js +14 -0
- package/dist/src/workspace/read-model/discovery/index.d.ts +5 -0
- package/dist/src/workspace/read-model/discovery/index.js +5 -0
- package/dist/src/workspace/read-model/discovery/plugin-manifests.d.ts +16 -0
- package/dist/src/workspace/read-model/discovery/plugin-manifests.js +168 -0
- package/dist/src/workspace/read-model/discovery/skills.d.ts +58 -0
- package/dist/src/workspace/read-model/discovery/skills.js +263 -0
- package/dist/src/workspace/read-model/discovery/subagents.d.ts +37 -0
- package/dist/src/workspace/read-model/discovery/subagents.js +48 -0
- package/dist/src/workspace/read-model/errors.d.ts +93 -0
- package/dist/src/workspace/read-model/errors.js +30 -0
- package/dist/src/workspace/read-model/extensions/actual-helpers.d.ts +4 -0
- package/dist/src/workspace/read-model/extensions/actual-helpers.js +4 -0
- package/dist/src/workspace/read-model/extensions/hook.d.ts +104 -0
- package/dist/src/workspace/read-model/extensions/hook.js +119 -0
- package/dist/src/workspace/read-model/extensions/index.d.ts +19 -0
- package/dist/src/workspace/read-model/extensions/index.js +43 -0
- package/dist/src/workspace/read-model/extensions/indexByName.d.ts +31 -0
- package/dist/src/workspace/read-model/extensions/indexByName.js +30 -0
- package/dist/src/workspace/read-model/extensions/inventory.d.ts +99 -0
- package/dist/src/workspace/read-model/extensions/inventory.js +111 -0
- package/dist/src/workspace/read-model/extensions/knowledge.d.ts +101 -0
- package/dist/src/workspace/read-model/extensions/knowledge.js +121 -0
- package/dist/src/workspace/read-model/extensions/mcp-server.d.ts +106 -0
- package/dist/src/workspace/read-model/extensions/mcp-server.js +122 -0
- package/dist/src/workspace/read-model/extensions/pack.d.ts +104 -0
- package/dist/src/workspace/read-model/extensions/pack.js +123 -0
- package/dist/src/workspace/read-model/extensions/package-root.d.ts +39 -0
- package/dist/src/workspace/read-model/extensions/package-root.js +48 -0
- package/dist/src/workspace/read-model/extensions/projection.d.ts +137 -0
- package/dist/src/workspace/read-model/extensions/projection.js +180 -0
- package/dist/src/workspace/read-model/extensions/rule.d.ts +104 -0
- package/dist/src/workspace/read-model/extensions/rule.js +113 -0
- package/dist/src/workspace/read-model/extensions/skill.d.ts +170 -0
- package/dist/src/workspace/read-model/extensions/skill.js +148 -0
- package/dist/src/workspace/read-model/extensions/subagent.d.ts +97 -0
- package/dist/src/workspace/read-model/extensions/subagent.js +120 -0
- package/dist/src/workspace/read-model/scanners/agent-dir.d.ts +49 -0
- package/dist/src/workspace/read-model/scanners/agent-dir.js +180 -0
- package/dist/src/workspace/read-model/scanners/agent-root.d.ts +73 -0
- package/dist/src/workspace/read-model/scanners/agent-root.js +148 -0
- package/dist/src/workspace/read-model/scanners/agent-settings.d.ts +43 -0
- package/dist/src/workspace/read-model/scanners/agent-settings.js +54 -0
- package/dist/src/workspace/read-model/scanners/canonical-extensions.d.ts +48 -0
- package/dist/src/workspace/read-model/scanners/canonical-extensions.js +237 -0
- package/dist/src/workspace/read-model/scanners/fs-helpers.d.ts +63 -0
- package/dist/src/workspace/read-model/scanners/fs-helpers.js +106 -0
- package/dist/src/workspace/read-model/scanners/index.d.ts +15 -0
- package/dist/src/workspace/read-model/scanners/index.js +34 -0
- package/dist/src/workspace/read-model/scanners/mcp-config.d.ts +47 -0
- package/dist/src/workspace/read-model/scanners/mcp-config.js +212 -0
- package/dist/src/workspace/read-model/scanners/types.d.ts +170 -0
- package/dist/src/workspace/read-model/scanners/types.js +86 -0
- package/dist/src/workspace/read-model/service.d.ts +103 -0
- package/dist/src/workspace/read-model/service.js +391 -0
- package/dist/src/workspace/read-model/state.d.ts +35 -0
- package/dist/src/workspace/read-model/state.js +97 -0
- package/dist/src/workspace/read-model/types.d.ts +75 -0
- package/dist/src/workspace/read-model/types.js +26 -0
- package/dist/src/workspace/read-model-record-readers.d.ts +34 -0
- package/dist/src/workspace/read-model-record-readers.js +467 -0
- package/dist/src/workspace/read-model-record-rows.d.ts +45 -0
- package/dist/src/workspace/read-model-record-rows.js +33 -0
- package/dist/src/workspace/read-model-record-types.d.ts +45 -0
- package/dist/src/workspace/read-model-record-types.js +13 -0
- package/dist/src/workspace/remove-if-exists.d.ts +13 -0
- package/dist/src/workspace/remove-if-exists.js +26 -0
- package/dist/src/workspace/rendered-files.d.ts +53 -0
- package/dist/src/workspace/rendered-files.js +50 -0
- package/dist/src/workspace/scope-refusal.d.ts +26 -0
- package/dist/src/workspace/scope-refusal.js +29 -0
- package/dist/src/workspace/service-interface.d.ts +479 -0
- package/dist/src/workspace/service-interface.js +27 -0
- package/dist/src/workspace/service.d.ts +1665 -0
- package/dist/src/workspace/service.js +1263 -0
- package/dist/src/workspace/setup-scope-support.d.ts +27 -0
- package/dist/src/workspace/setup-scope-support.js +299 -0
- package/dist/src/workspace/skill-paths.d.ts +33 -0
- package/dist/src/workspace/skill-paths.js +14 -0
- package/dist/src/workspace/source-metadata.d.ts +7 -0
- package/dist/src/workspace/source-metadata.js +10 -0
- package/dist/src/workspace/source-to-lock-entry.d.ts +32 -0
- package/dist/src/workspace/source-to-lock-entry.js +68 -0
- package/dist/src/workspace/test-stubs.d.ts +144 -0
- package/dist/src/workspace/test-stubs.js +412 -0
- package/dist/src/workspace/transaction.d.ts +218 -0
- package/dist/src/workspace/transaction.js +177 -0
- package/package.json +57 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings management for AXM workspace configuration.
|
|
3
|
+
*
|
|
4
|
+
* @experimental This API is unstable and may change without notice.
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as FileSystem from "effect/FileSystem";
|
|
9
|
+
import * as Path from "effect/Path";
|
|
10
|
+
import * as Schema from "effect/Schema";
|
|
11
|
+
import { SettingsWriteError } from "./errors.js";
|
|
12
|
+
import { type Settings } from "./schema.js";
|
|
13
|
+
import { applyJsonPatchToText } from "./format-preserving-json.js";
|
|
14
|
+
/**
|
|
15
|
+
* Create default settings object.
|
|
16
|
+
*
|
|
17
|
+
* @experimental This API is unstable and may change without notice.
|
|
18
|
+
*/
|
|
19
|
+
export declare const createDefaultSettings: () => Settings;
|
|
20
|
+
type ExistingSettingsRenderResult = {
|
|
21
|
+
readonly content: string;
|
|
22
|
+
readonly fallbackReason?: "edit_failed" | "edited_content_invalid";
|
|
23
|
+
};
|
|
24
|
+
/** @internal */
|
|
25
|
+
export declare const renderExistingSettings: (priorText: string, prior: Schema.Json, target: Schema.Json, canonicalContent: string, applyPatch?: typeof applyJsonPatchToText) => ExistingSettingsRenderResult;
|
|
26
|
+
/**
|
|
27
|
+
* Write settings to the selected scope's settings path.
|
|
28
|
+
*
|
|
29
|
+
* Creates the directory if it doesn't exist. Pretty-prints JSON with 2-space indent.
|
|
30
|
+
*
|
|
31
|
+
* @param settingsPath - Exact project or user settings path
|
|
32
|
+
* @param settings - Settings object to write
|
|
33
|
+
*
|
|
34
|
+
* @experimental This API is unstable and may change without notice.
|
|
35
|
+
*/
|
|
36
|
+
export declare const writeSettingsAtPath: (settingsPath: string, settings: Settings) => Effect.Effect<void, SettingsWriteError | import("../workspace/transaction.js").WorkspaceSnapshotError, FileSystem.FileSystem | Path.Path>;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings management for AXM workspace configuration.
|
|
3
|
+
*
|
|
4
|
+
* @experimental This API is unstable and may change without notice.
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as FileSystem from "effect/FileSystem";
|
|
9
|
+
import * as JsonPatch from "effect/JsonPatch";
|
|
10
|
+
import * as Path from "effect/Path";
|
|
11
|
+
import * as Schema from "effect/Schema";
|
|
12
|
+
import { sweepStaleAtomicWriteTemps, writeFileAtomic } from "../utils/atomic-write.js";
|
|
13
|
+
import { SettingsWriteError } from "./errors.js";
|
|
14
|
+
import { protectWorkspacePath } from "../workspace/transaction.js";
|
|
15
|
+
import { recordFootprint } from "../workspace/footprint-recorder.js";
|
|
16
|
+
import { SETTINGS_KEY_ORDER, SETTINGS_KNOWN_KEYS, SettingsSchema, } from "./schema.js";
|
|
17
|
+
import { applyJsonPatchToText } from "./format-preserving-json.js";
|
|
18
|
+
// -----------------------------------------------------------------------------
|
|
19
|
+
// Default Settings
|
|
20
|
+
// -----------------------------------------------------------------------------
|
|
21
|
+
/**
|
|
22
|
+
* Create default settings object.
|
|
23
|
+
*
|
|
24
|
+
* @experimental This API is unstable and may change without notice.
|
|
25
|
+
*/
|
|
26
|
+
export const createDefaultSettings = () => ({});
|
|
27
|
+
// -----------------------------------------------------------------------------
|
|
28
|
+
// Key Ordering
|
|
29
|
+
// -----------------------------------------------------------------------------
|
|
30
|
+
/**
|
|
31
|
+
* Reorder settings keys to match the canonical schema order.
|
|
32
|
+
*
|
|
33
|
+
* Creates a new object with keys in `SETTINGS_KEY_ORDER`, omitting keys
|
|
34
|
+
* not present in the input.
|
|
35
|
+
*
|
|
36
|
+
* @experimental This API is unstable and may change without notice.
|
|
37
|
+
*/
|
|
38
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39
|
+
const isEmptySettingsConfig = (key, value) => {
|
|
40
|
+
if (key !== "knowledgeConfig" || !isRecord(value))
|
|
41
|
+
return false;
|
|
42
|
+
return value["instructions"] !== false;
|
|
43
|
+
};
|
|
44
|
+
const orderSettingsRecord = (settings) => {
|
|
45
|
+
const ordered = SETTINGS_KEY_ORDER.reduce((accumulated, key) => {
|
|
46
|
+
const value = settings[key];
|
|
47
|
+
return value === undefined || isEmptySettingsConfig(key, value)
|
|
48
|
+
? accumulated
|
|
49
|
+
: { ...accumulated, [key]: value };
|
|
50
|
+
}, {});
|
|
51
|
+
// Unknown top-level keys are preserved after the canonical keys, in their
|
|
52
|
+
// original relative order, so a write never discards data it did not create.
|
|
53
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
54
|
+
if (!SETTINGS_KNOWN_KEYS.has(key) && value !== undefined) {
|
|
55
|
+
ordered[key] = value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return ordered;
|
|
59
|
+
};
|
|
60
|
+
const isJsonArray = (value) => Array.isArray(value);
|
|
61
|
+
const isJsonObject = (value) => typeof value === "object" && value !== null && !isJsonArray(value);
|
|
62
|
+
const parseJson = (text) => {
|
|
63
|
+
try {
|
|
64
|
+
return Schema.decodeUnknownSync(Schema.Json)(JSON.parse(text));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const serializeCanonicalSettings = (settings) => JSON.stringify(settings, null, 2) + "\n";
|
|
71
|
+
const ensureSingleTrailingNewline = (content) => content.replace(/[\r\n]+$/, "") + "\n";
|
|
72
|
+
const hasCanonicalTopLevelOrder = (settings) => {
|
|
73
|
+
if (!isJsonObject(settings))
|
|
74
|
+
return false;
|
|
75
|
+
let previousIndex = -1;
|
|
76
|
+
let foundUnknownKey = false;
|
|
77
|
+
for (const key of Object.keys(settings)) {
|
|
78
|
+
const index = SETTINGS_KEY_ORDER.indexOf(key);
|
|
79
|
+
if (index === -1) {
|
|
80
|
+
foundUnknownKey = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (foundUnknownKey || index <= previousIndex)
|
|
84
|
+
return false;
|
|
85
|
+
previousIndex = index;
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
};
|
|
89
|
+
const insertionIndexFor = (priorWasCanonical, path, properties) => {
|
|
90
|
+
const key = path.length === 1 ? path[0] : undefined;
|
|
91
|
+
if (!priorWasCanonical || typeof key !== "string")
|
|
92
|
+
return properties.length;
|
|
93
|
+
const keyIndex = SETTINGS_KEY_ORDER.indexOf(key);
|
|
94
|
+
if (keyIndex === -1)
|
|
95
|
+
return properties.length;
|
|
96
|
+
const nextIndex = properties.findIndex((property) => {
|
|
97
|
+
const propertyIndex = SETTINGS_KEY_ORDER.indexOf(property);
|
|
98
|
+
return propertyIndex === -1 || propertyIndex > keyIndex;
|
|
99
|
+
});
|
|
100
|
+
return nextIndex === -1 ? properties.length : nextIndex;
|
|
101
|
+
};
|
|
102
|
+
/** @internal */
|
|
103
|
+
export const renderExistingSettings = (priorText, prior, target, canonicalContent, applyPatch = applyJsonPatchToText) => {
|
|
104
|
+
const patch = JsonPatch.get(prior, target);
|
|
105
|
+
if (patch.length === 0) {
|
|
106
|
+
return { content: ensureSingleTrailingNewline(priorText) };
|
|
107
|
+
}
|
|
108
|
+
const priorWasCanonical = hasCanonicalTopLevelOrder(prior);
|
|
109
|
+
const edited = applyPatch(priorText, prior, patch, {
|
|
110
|
+
getInsertionIndex: (path, properties) => insertionIndexFor(priorWasCanonical, path, properties),
|
|
111
|
+
});
|
|
112
|
+
if (edited._tag === "Failure") {
|
|
113
|
+
return { content: canonicalContent, fallbackReason: "edit_failed" };
|
|
114
|
+
}
|
|
115
|
+
const parsed = parseJson(edited.text);
|
|
116
|
+
if (parsed === undefined || JsonPatch.get(parsed, target).length !== 0) {
|
|
117
|
+
return { content: canonicalContent, fallbackReason: "edited_content_invalid" };
|
|
118
|
+
}
|
|
119
|
+
if (isJsonObject(target) && Object.keys(target).length === 0) {
|
|
120
|
+
return { content: canonicalContent };
|
|
121
|
+
}
|
|
122
|
+
return { content: ensureSingleTrailingNewline(edited.text) };
|
|
123
|
+
};
|
|
124
|
+
// -----------------------------------------------------------------------------
|
|
125
|
+
// Core Functions
|
|
126
|
+
// -----------------------------------------------------------------------------
|
|
127
|
+
/**
|
|
128
|
+
* Write settings to the selected scope's settings path.
|
|
129
|
+
*
|
|
130
|
+
* Creates the directory if it doesn't exist. Pretty-prints JSON with 2-space indent.
|
|
131
|
+
*
|
|
132
|
+
* @param settingsPath - Exact project or user settings path
|
|
133
|
+
* @param settings - Settings object to write
|
|
134
|
+
*
|
|
135
|
+
* @experimental This API is unstable and may change without notice.
|
|
136
|
+
*/
|
|
137
|
+
export const writeSettingsAtPath = (settingsPath, settings) => Effect.gen(function* () {
|
|
138
|
+
const fs = yield* FileSystem.FileSystem;
|
|
139
|
+
const path = yield* Path.Path;
|
|
140
|
+
const settingsDir = path.dirname(settingsPath);
|
|
141
|
+
// Ensure directory exists
|
|
142
|
+
yield* fs
|
|
143
|
+
.makeDirectory(settingsDir, { recursive: true })
|
|
144
|
+
.pipe(Effect.mapError((cause) => new SettingsWriteError({ path: settingsDir, step: "mkdir", cause })));
|
|
145
|
+
// Encode through schema (converts Option -> nullable, URL -> string, etc.)
|
|
146
|
+
const encoded = yield* Schema.encodeEffect(SettingsSchema)(settings).pipe(Effect.mapError((cause) => new SettingsWriteError({ path: settingsPath, step: "encode", cause })));
|
|
147
|
+
const ordered = orderSettingsRecord(encoded);
|
|
148
|
+
const canonicalContent = serializeCanonicalSettings(ordered);
|
|
149
|
+
const targetResult = yield* Effect.result(Schema.decodeUnknownEffect(Schema.Json)(ordered));
|
|
150
|
+
let content = canonicalContent;
|
|
151
|
+
if (targetResult._tag === "Failure") {
|
|
152
|
+
yield* Effect.logDebug("Falling back to canonical settings serialization", {
|
|
153
|
+
settingsPath,
|
|
154
|
+
reason: "target_not_json",
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const existsResult = yield* Effect.result(fs.exists(settingsPath));
|
|
159
|
+
if (existsResult._tag === "Failure") {
|
|
160
|
+
yield* Effect.logDebug("Falling back to canonical settings serialization", {
|
|
161
|
+
settingsPath,
|
|
162
|
+
reason: "existence_check_failed",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
else if (existsResult.success) {
|
|
166
|
+
const readResult = yield* Effect.result(fs.readFileString(settingsPath));
|
|
167
|
+
if (readResult._tag === "Failure") {
|
|
168
|
+
yield* Effect.logDebug("Falling back to canonical settings serialization", {
|
|
169
|
+
settingsPath,
|
|
170
|
+
reason: "read_failed",
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else if (readResult.success.trim() !== "") {
|
|
174
|
+
const prior = parseJson(readResult.success);
|
|
175
|
+
if (prior === undefined) {
|
|
176
|
+
yield* Effect.logDebug("Falling back to canonical settings serialization", {
|
|
177
|
+
settingsPath,
|
|
178
|
+
reason: "parse_failed",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
const rendered = renderExistingSettings(readResult.success, prior, targetResult.success, canonicalContent);
|
|
183
|
+
content = rendered.content;
|
|
184
|
+
if (rendered.fallbackReason !== undefined) {
|
|
185
|
+
yield* Effect.logDebug("Falling back to canonical settings serialization", {
|
|
186
|
+
settingsPath,
|
|
187
|
+
reason: rendered.fallbackReason,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
yield* protectWorkspacePath(settingsPath);
|
|
195
|
+
const existed = yield* fs.exists(settingsPath).pipe(Effect.orElseSucceed(() => true));
|
|
196
|
+
// Write to a temp file then atomically rename into place, so an interrupted
|
|
197
|
+
// write can never truncate or corrupt the user's existing settings file.
|
|
198
|
+
// The temp file is removed on any failure or interruption.
|
|
199
|
+
yield* sweepStaleAtomicWriteTemps(fs, settingsPath);
|
|
200
|
+
yield* writeFileAtomic(fs, {
|
|
201
|
+
targetPath: settingsPath,
|
|
202
|
+
content,
|
|
203
|
+
mapError: (failure) => failure.step === "rename"
|
|
204
|
+
? new SettingsWriteError({ path: settingsPath, step: "rename", cause: failure.cause })
|
|
205
|
+
: new SettingsWriteError({
|
|
206
|
+
path: failure.tempPath,
|
|
207
|
+
step: "write-temp",
|
|
208
|
+
cause: failure.cause,
|
|
209
|
+
}),
|
|
210
|
+
});
|
|
211
|
+
yield* recordFootprint({ path: settingsPath, change: existed ? "modified" : "created" });
|
|
212
|
+
});
|
|
213
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentxm/workspace-state deterministic test layers and fixtures.
|
|
3
|
+
*
|
|
4
|
+
* In-memory and fixture-backed implementations of this package's own
|
|
5
|
+
* services for tests and executable specifications. Production source never
|
|
6
|
+
* imports this module.
|
|
7
|
+
*
|
|
8
|
+
* @experimental This API is unstable and may change without notice.
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
export * from "./workspace/test-stubs.js";
|
|
12
|
+
export { WorkspaceReadModelTest, type WorkspaceReadModelTestOptions, } from "./workspace/read-model/__fixtures__/test-layer.js";
|
|
13
|
+
export * from "./workspace/read-model/__fixtures__/builder.js";
|
|
14
|
+
export * from "./workspace/read-model/__fixtures__/decoders.js";
|
|
15
|
+
export * from "./workspace/read-model/__fixtures__/occurrences.js";
|
|
16
|
+
export * from "./workspace/read-model/__fixtures__/scenario-harness.js";
|
|
17
|
+
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentxm/workspace-state deterministic test layers and fixtures.
|
|
3
|
+
*
|
|
4
|
+
* In-memory and fixture-backed implementations of this package's own
|
|
5
|
+
* services for tests and executable specifications. Production source never
|
|
6
|
+
* imports this module.
|
|
7
|
+
*
|
|
8
|
+
* @experimental This API is unstable and may change without notice.
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
export * from "./workspace/test-stubs.js";
|
|
12
|
+
export { WorkspaceReadModelTest, } from "./workspace/read-model/__fixtures__/test-layer.js";
|
|
13
|
+
export * from "./workspace/read-model/__fixtures__/builder.js";
|
|
14
|
+
export * from "./workspace/read-model/__fixtures__/decoders.js";
|
|
15
|
+
export * from "./workspace/read-model/__fixtures__/occurrences.js";
|
|
16
|
+
export * from "./workspace/read-model/__fixtures__/scenario-harness.js";
|
|
17
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared atomic single-file replacement: write a uniquely named temp file in
|
|
3
|
+
* the target's directory, then rename it over the target.
|
|
4
|
+
*
|
|
5
|
+
* The temp file lives next to the target so the rename stays on one
|
|
6
|
+
* filesystem (and therefore atomic), and is removed on any failure or
|
|
7
|
+
* interruption. Temp names follow `<target>.tmp.<unique>`; sweepers that
|
|
8
|
+
* clean stale temps (e.g. the lockfile writer) rely on that prefix.
|
|
9
|
+
*
|
|
10
|
+
* @experimental This API is unstable and may change without notice.
|
|
11
|
+
*/
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import type * as FileSystem from "effect/FileSystem";
|
|
14
|
+
import * as Path from "effect/Path";
|
|
15
|
+
import type { PlatformError } from "effect/PlatformError";
|
|
16
|
+
/**
|
|
17
|
+
* Step at which an atomic write failed. `check-target` and `read-target` can
|
|
18
|
+
* only occur with `skipIfUnchanged: "fail-on-read-error"`.
|
|
19
|
+
*
|
|
20
|
+
* @experimental This API is unstable and may change without notice.
|
|
21
|
+
*/
|
|
22
|
+
export type AtomicWriteStep = "check-target" | "read-target" | "write-temp" | "rename";
|
|
23
|
+
/**
|
|
24
|
+
* Failure passed to `mapError` so each call site keeps its own error shape.
|
|
25
|
+
*
|
|
26
|
+
* @experimental This API is unstable and may change without notice.
|
|
27
|
+
*/
|
|
28
|
+
export interface AtomicWriteFailure {
|
|
29
|
+
readonly step: AtomicWriteStep;
|
|
30
|
+
readonly targetPath: string;
|
|
31
|
+
readonly tempPath: string;
|
|
32
|
+
readonly cause: PlatformError;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Content-equality short-circuit mode: `fail-on-read-error` surfaces
|
|
36
|
+
* check/read failures via `mapError`; `ignore-read-errors` treats an
|
|
37
|
+
* unreadable target as changed and proceeds to write.
|
|
38
|
+
*
|
|
39
|
+
* @experimental This API is unstable and may change without notice.
|
|
40
|
+
*/
|
|
41
|
+
export type SkipIfUnchanged = "fail-on-read-error" | "ignore-read-errors";
|
|
42
|
+
/**
|
|
43
|
+
* Options for `writeFileAtomic`.
|
|
44
|
+
*
|
|
45
|
+
* @experimental This API is unstable and may change without notice.
|
|
46
|
+
*/
|
|
47
|
+
export interface WriteFileAtomicOptions<E> {
|
|
48
|
+
readonly targetPath: string;
|
|
49
|
+
readonly content: string | Uint8Array;
|
|
50
|
+
/** When set, leave the target untouched if it already has this content. */
|
|
51
|
+
readonly skipIfUnchanged?: SkipIfUnchanged;
|
|
52
|
+
/** Best-effort remove of the target before rename (Windows cannot always rename over an existing file). */
|
|
53
|
+
readonly removeTargetBeforeRename?: boolean;
|
|
54
|
+
readonly mapError: (failure: AtomicWriteFailure) => E;
|
|
55
|
+
}
|
|
56
|
+
export declare const atomicWriteTempPrefix: (targetPath: string) => string;
|
|
57
|
+
/** Remove only stale temp siblings belonging to one atomic-write target. */
|
|
58
|
+
export declare const sweepStaleAtomicWriteTemps: (fs: FileSystem.FileSystem, targetPath: string) => Effect.Effect<void, never, Path.Path>;
|
|
59
|
+
/**
|
|
60
|
+
* Atomically replace `targetPath` with `content` via a same-directory temp
|
|
61
|
+
* file and rename. Failures at each step are mapped by the caller, so error
|
|
62
|
+
* codes, details, and suggestions stay call-site specific.
|
|
63
|
+
*
|
|
64
|
+
* @experimental This API is unstable and may change without notice.
|
|
65
|
+
*/
|
|
66
|
+
export declare const writeFileAtomic: <E>(fs: FileSystem.FileSystem, options: WriteFileAtomicOptions<E>) => Effect.Effect<"written" | "skipped", E>;
|
|
67
|
+
//# sourceMappingURL=atomic-write.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared atomic single-file replacement: write a uniquely named temp file in
|
|
3
|
+
* the target's directory, then rename it over the target.
|
|
4
|
+
*
|
|
5
|
+
* The temp file lives next to the target so the rename stays on one
|
|
6
|
+
* filesystem (and therefore atomic), and is removed on any failure or
|
|
7
|
+
* interruption. Temp names follow `<target>.tmp.<unique>`; sweepers that
|
|
8
|
+
* clean stale temps (e.g. the lockfile writer) rely on that prefix.
|
|
9
|
+
*
|
|
10
|
+
* @experimental This API is unstable and may change without notice.
|
|
11
|
+
*/
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import * as Option from "effect/Option";
|
|
14
|
+
import * as Path from "effect/Path";
|
|
15
|
+
/** In-process sequence so concurrent fibers never share a temp path. */
|
|
16
|
+
let tempSequence = 0;
|
|
17
|
+
export const atomicWriteTempPrefix = (targetPath) => `${targetPath}.tmp.`;
|
|
18
|
+
/** Remove only stale temp siblings belonging to one atomic-write target. */
|
|
19
|
+
export const sweepStaleAtomicWriteTemps = (fs, targetPath) => Effect.gen(function* () {
|
|
20
|
+
const path = yield* Path.Path;
|
|
21
|
+
const parent = path.dirname(targetPath);
|
|
22
|
+
const filenamePrefix = `${path.basename(targetPath)}.tmp.`;
|
|
23
|
+
const entries = yield* fs.readDirectory(parent).pipe(Effect.orElseSucceed(() => []));
|
|
24
|
+
yield* Effect.forEach(entries.filter((entry) => entry.startsWith(filenamePrefix)), (entry) => fs.remove(path.join(parent, entry), { force: true }).pipe(Effect.ignore), { discard: true });
|
|
25
|
+
});
|
|
26
|
+
const makeTempPath = (targetPath) => {
|
|
27
|
+
tempSequence += 1;
|
|
28
|
+
const pid = typeof process === "object" ? process.pid.toString(36) : "x";
|
|
29
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
30
|
+
return `${atomicWriteTempPrefix(targetPath)}${pid}.${tempSequence.toString(36)}.${random}`;
|
|
31
|
+
};
|
|
32
|
+
const bytesEqual = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
|
|
33
|
+
const targetHasContent = (fs, targetPath, content) => typeof content === "string"
|
|
34
|
+
? Effect.map(fs.readFileString(targetPath), (current) => current === content)
|
|
35
|
+
: Effect.map(fs.readFile(targetPath), (current) => bytesEqual(current, content));
|
|
36
|
+
/**
|
|
37
|
+
* Atomically replace `targetPath` with `content` via a same-directory temp
|
|
38
|
+
* file and rename. Failures at each step are mapped by the caller, so error
|
|
39
|
+
* codes, details, and suggestions stay call-site specific.
|
|
40
|
+
*
|
|
41
|
+
* @experimental This API is unstable and may change without notice.
|
|
42
|
+
*/
|
|
43
|
+
export const writeFileAtomic = (fs, options) => Effect.gen(function* () {
|
|
44
|
+
const { content, mapError, targetPath } = options;
|
|
45
|
+
const tempPath = makeTempPath(targetPath);
|
|
46
|
+
const fail = (step) => (cause) => mapError({ step, targetPath, tempPath, cause });
|
|
47
|
+
if (options.skipIfUnchanged === "fail-on-read-error") {
|
|
48
|
+
const exists = yield* fs.exists(targetPath).pipe(Effect.mapError(fail("check-target")));
|
|
49
|
+
if (exists) {
|
|
50
|
+
const unchanged = yield* targetHasContent(fs, targetPath, content).pipe(Effect.mapError(fail("read-target")));
|
|
51
|
+
if (unchanged)
|
|
52
|
+
return "skipped";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else if (options.skipIfUnchanged === "ignore-read-errors") {
|
|
56
|
+
const unchanged = yield* targetHasContent(fs, targetPath, content).pipe(Effect.option);
|
|
57
|
+
if (Option.isSome(unchanged) && unchanged.value)
|
|
58
|
+
return "skipped";
|
|
59
|
+
}
|
|
60
|
+
yield* Effect.gen(function* () {
|
|
61
|
+
yield* (typeof content === "string"
|
|
62
|
+
? fs.writeFileString(tempPath, content)
|
|
63
|
+
: fs.writeFile(tempPath, content)).pipe(Effect.mapError(fail("write-temp")));
|
|
64
|
+
if (options.removeTargetBeforeRename === true) {
|
|
65
|
+
yield* fs.remove(targetPath).pipe(Effect.ignore);
|
|
66
|
+
}
|
|
67
|
+
yield* fs.rename(tempPath, targetPath).pipe(Effect.mapError(fail("rename")));
|
|
68
|
+
}).pipe(Effect.ensuring(fs.remove(tempPath, { force: true }).pipe(Effect.ignore)));
|
|
69
|
+
return "written";
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=atomic-write.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Private environment and home-directory helpers for workspace-state.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately duplicated from the CLI-destined environment module: the
|
|
5
|
+
* kernel may not depend on application utilities, and these helpers are
|
|
6
|
+
* within the sanctioned duplication budget for small pure functions.
|
|
7
|
+
*
|
|
8
|
+
* @experimental This API is unstable and may change without notice.
|
|
9
|
+
*/
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as Option from "effect/Option";
|
|
12
|
+
/** Read an optional env var. Centralized access point for process.env. */
|
|
13
|
+
export declare const envOption: (name: string) => Effect.Effect<Option.Option<string>>;
|
|
14
|
+
/** Resolve the user's home directory. */
|
|
15
|
+
export declare const getHome: Effect.Effect<string, never, never>;
|
|
16
|
+
//# sourceMappingURL=environment.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Private environment and home-directory helpers for workspace-state.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately duplicated from the CLI-destined environment module: the
|
|
5
|
+
* kernel may not depend on application utilities, and these helpers are
|
|
6
|
+
* within the sanctioned duplication budget for small pure functions.
|
|
7
|
+
*
|
|
8
|
+
* @experimental This API is unstable and may change without notice.
|
|
9
|
+
*/
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import * as Effect from "effect/Effect";
|
|
12
|
+
import * as Option from "effect/Option";
|
|
13
|
+
// eslint-disable-next-line no-restricted-properties -- Centralized env var access point; all callers use these helpers
|
|
14
|
+
const readEnv = (name) => process.env[name];
|
|
15
|
+
/** Read an optional env var. Centralized access point for process.env. */
|
|
16
|
+
export const envOption = (name) => Effect.sync(() => Option.fromUndefinedOr(readEnv(name)));
|
|
17
|
+
/** Resolve the user's home directory. */
|
|
18
|
+
export const getHome = Effect.sync(() => os.homedir());
|
|
19
|
+
//# sourceMappingURL=environment.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
import * as Path from "effect/Path";
|
|
4
|
+
import { type AbsolutePath } from "@agentxm/extension-model/unstable/path-types";
|
|
5
|
+
/**
|
|
6
|
+
* Validates that a resolved target path stays within a base directory.
|
|
7
|
+
* Uses path separator boundary check to prevent prefix false positives.
|
|
8
|
+
*/
|
|
9
|
+
export declare const isPathSafe: (path: Path.Path, base: string, target: string) => boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Resolve `target` and return it only when it stays inside `base`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const safeChildPath: (base: AbsolutePath, target: string) => Effect.Effect<Option.Option<AbsolutePath>, never, Path.Path>;
|
|
14
|
+
declare const PathTraversalDetected_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
15
|
+
readonly _tag: "PathTraversalDetected";
|
|
16
|
+
} & Readonly<A>;
|
|
17
|
+
/** A resolved path escaped its workspace base directory. */
|
|
18
|
+
export declare class PathTraversalDetected extends PathTraversalDetected_base<{
|
|
19
|
+
readonly path: string;
|
|
20
|
+
}> {
|
|
21
|
+
}
|
|
22
|
+
/** Fail with `PathTraversalDetected` when `targetPath` escapes `baseDir`. */
|
|
23
|
+
export declare const validatePathSafety: (path: Path.Path, baseDir: string, targetPath: string) => Effect.Effect<void, PathTraversalDetected>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=path-safety.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as Data from "effect/Data";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import * as Path from "effect/Path";
|
|
5
|
+
import { makeAbsolutePath } from "@agentxm/extension-model/unstable/path-types";
|
|
6
|
+
/**
|
|
7
|
+
* Validates that a resolved target path stays within a base directory.
|
|
8
|
+
* Uses path separator boundary check to prevent prefix false positives.
|
|
9
|
+
*/
|
|
10
|
+
export const isPathSafe = (path, base, target) => {
|
|
11
|
+
const resolvedBase = path.resolve(base);
|
|
12
|
+
const resolvedTarget = path.resolve(target);
|
|
13
|
+
return resolvedTarget === resolvedBase || resolvedTarget.startsWith(resolvedBase + path.sep);
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Resolve `target` and return it only when it stays inside `base`.
|
|
17
|
+
*/
|
|
18
|
+
export const safeChildPath = (base, target) => Effect.gen(function* () {
|
|
19
|
+
const path = yield* Path.Path;
|
|
20
|
+
const resolvedBase = path.resolve(base);
|
|
21
|
+
const resolvedTarget = path.isAbsolute(target)
|
|
22
|
+
? path.resolve(target)
|
|
23
|
+
: path.resolve(resolvedBase, target);
|
|
24
|
+
const safe = resolvedTarget === resolvedBase || resolvedTarget.startsWith(resolvedBase + path.sep);
|
|
25
|
+
return safe ? Option.some(makeAbsolutePath(path, resolvedTarget)) : Option.none();
|
|
26
|
+
});
|
|
27
|
+
/** A resolved path escaped its workspace base directory. */
|
|
28
|
+
export class PathTraversalDetected extends Data.TaggedError("PathTraversalDetected") {
|
|
29
|
+
}
|
|
30
|
+
/** Fail with `PathTraversalDetected` when `targetPath` escapes `baseDir`. */
|
|
31
|
+
export const validatePathSafety = (path, baseDir, targetPath) => isPathSafe(path, baseDir, targetPath)
|
|
32
|
+
? Effect.void
|
|
33
|
+
: new PathTraversalDetected({ path: targetPath });
|
|
34
|
+
//# sourceMappingURL=path-safety.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PlatformError } from "effect/PlatformError";
|
|
2
|
+
import * as FileSystem from "effect/FileSystem";
|
|
3
|
+
import * as Path from "effect/Path";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
/**
|
|
6
|
+
* Resolves the parent directory of a path through symlinks while
|
|
7
|
+
* preserving the final path component (basename).
|
|
8
|
+
*
|
|
9
|
+
* Needed for computing correct relative symlink paths when parent
|
|
10
|
+
* directories may themselves be symlinks. Missing parent directories are
|
|
11
|
+
* preserved below the nearest existing ancestor, whose real path is used as
|
|
12
|
+
* the common base. This also handles platform aliases such as macOS `/tmp` →
|
|
13
|
+
* `/private/tmp` before the link parent has been created.
|
|
14
|
+
*/
|
|
15
|
+
export declare const resolveParentSymlinks: (filePath: string) => Effect.Effect<string, PlatformError, FileSystem.FileSystem | Path.Path>;
|
|
16
|
+
//# sourceMappingURL=resolve-parent-symlinks.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as FileSystem from "effect/FileSystem";
|
|
2
|
+
import * as Path from "effect/Path";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Option from "effect/Option";
|
|
5
|
+
/**
|
|
6
|
+
* Resolves the parent directory of a path through symlinks while
|
|
7
|
+
* preserving the final path component (basename).
|
|
8
|
+
*
|
|
9
|
+
* Needed for computing correct relative symlink paths when parent
|
|
10
|
+
* directories may themselves be symlinks. Missing parent directories are
|
|
11
|
+
* preserved below the nearest existing ancestor, whose real path is used as
|
|
12
|
+
* the common base. This also handles platform aliases such as macOS `/tmp` →
|
|
13
|
+
* `/private/tmp` before the link parent has been created.
|
|
14
|
+
*/
|
|
15
|
+
export const resolveParentSymlinks = (filePath) => Effect.gen(function* () {
|
|
16
|
+
const fs = yield* FileSystem.FileSystem;
|
|
17
|
+
const p = yield* Path.Path;
|
|
18
|
+
const basename = p.basename(filePath);
|
|
19
|
+
const missingParents = [];
|
|
20
|
+
let current = p.dirname(filePath);
|
|
21
|
+
while (true) {
|
|
22
|
+
const resolved = yield* fs.realPath(current).pipe(Effect.option);
|
|
23
|
+
if (Option.isSome(resolved)) {
|
|
24
|
+
return p.join(resolved.value, ...missingParents, basename);
|
|
25
|
+
}
|
|
26
|
+
const parent = p.dirname(current);
|
|
27
|
+
if (parent === current) {
|
|
28
|
+
return yield* fs
|
|
29
|
+
.realPath(current)
|
|
30
|
+
.pipe(Effect.map((resolvedRoot) => p.join(resolvedRoot, ...missingParents, basename)));
|
|
31
|
+
}
|
|
32
|
+
missingParents.unshift(p.basename(current));
|
|
33
|
+
current = parent;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
//# sourceMappingURL=resolve-parent-symlinks.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as FileSystem from "effect/FileSystem";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import * as Path from "effect/Path";
|
|
5
|
+
import type { ExtensionRef } from "@agentxm/extension-model/unstable/extensions/refs/extension-ref";
|
|
6
|
+
import { AcceptedResolutionMissing, InlineExtensionSourceMissing, SupersededCanonicalRemovalFailed, type PackageContentHashFailed, type WorkspaceSourceInvalid } from "./errors.js";
|
|
7
|
+
import type { PathTraversalDetected } from "../utils/path-safety.js";
|
|
8
|
+
import type { WorkspaceSnapshotError } from "./transaction.js";
|
|
9
|
+
import { type LockEntryToRefError } from "./lock-entry-to-ref.js";
|
|
10
|
+
import { type AcceptedExtensionResolution, type CanonicalObservation } from "./canonical-observation.js";
|
|
11
|
+
import type { DesiredExtensionNode } from "./desired-state-graph.js";
|
|
12
|
+
import type { WorkspaceLockfileReadFailure, WorkspaceMutationsService, WorkspaceStateReadFailure } from "./service-interface.js";
|
|
13
|
+
interface AcceptedCanonicalRefArgs {
|
|
14
|
+
readonly workspace: WorkspaceMutationsService;
|
|
15
|
+
readonly type: DesiredExtensionNode["type"];
|
|
16
|
+
readonly name: string;
|
|
17
|
+
}
|
|
18
|
+
/** Every failure the accepted-canonical reconstruction functions can produce. */
|
|
19
|
+
export type AcceptedCanonicalRefError = WorkspaceStateReadFailure | LockEntryToRefError | AcceptedResolutionMissing | InlineExtensionSourceMissing | WorkspaceSourceInvalid | PathTraversalDetected | PackageContentHashFailed;
|
|
20
|
+
export interface AcceptedCanonicalObservation {
|
|
21
|
+
readonly desired: DesiredExtensionNode;
|
|
22
|
+
readonly accepted?: AcceptedExtensionResolution;
|
|
23
|
+
readonly observation: CanonicalObservation;
|
|
24
|
+
}
|
|
25
|
+
export interface UsableAcceptedCanonicalObservation extends AcceptedCanonicalObservation {
|
|
26
|
+
readonly observation: CanonicalObservation & {
|
|
27
|
+
readonly status: "usable";
|
|
28
|
+
readonly path: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export interface UsableAcceptedCanonical extends UsableAcceptedCanonicalObservation {
|
|
32
|
+
readonly ref: ExtensionRef;
|
|
33
|
+
}
|
|
34
|
+
/** Canonical package AXM may delete when removing desired state. Authored source is durable. */
|
|
35
|
+
export declare const removableAcceptedCanonicalPath: (canonical: Option.Option<AcceptedCanonicalObservation>) => Option.Option<string>;
|
|
36
|
+
/** Exact acquired canonical path reconstructed directly from accepted lock authority. */
|
|
37
|
+
export declare const acceptedLockedCanonicalPath: (args: AcceptedCanonicalRefArgs) => Effect.Effect<Option.Option<string>, WorkspaceLockfileReadFailure, Path.Path>;
|
|
38
|
+
/**
|
|
39
|
+
* Capture exact cleanup for a superseded accepted package. The old path is
|
|
40
|
+
* read before the lock transition; the returned effect runs afterward and
|
|
41
|
+
* removes only that path when the replacement canonical path differs.
|
|
42
|
+
*/
|
|
43
|
+
export declare const prepareAcceptedCanonicalTransition: (args: AcceptedCanonicalRefArgs & {
|
|
44
|
+
readonly ref: ExtensionRef;
|
|
45
|
+
}) => Effect.Effect<Effect.Effect<void, WorkspaceSnapshotError | SupersededCanonicalRemovalFailed>, WorkspaceLockfileReadFailure, FileSystem.FileSystem | Path.Path>;
|
|
46
|
+
/** Reconstruct a ref directly from accepted lock authority without desired reachability. */
|
|
47
|
+
export declare const acceptedLockedResolutionRef: (args: AcceptedCanonicalRefArgs) => Effect.Effect<Option.Option<ExtensionRef>, AcceptedCanonicalRefError, FileSystem.FileSystem | Path.Path>;
|
|
48
|
+
/**
|
|
49
|
+
* Reconstruct the immutable source reference recorded for a desired extension,
|
|
50
|
+
* even when its canonical materialization is absent or divergent.
|
|
51
|
+
*/
|
|
52
|
+
export declare const acceptedResolutionRef: (args: AcceptedCanonicalRefArgs) => Effect.Effect<Option.Option<ExtensionRef>, AcceptedCanonicalRefError, FileSystem.FileSystem | Path.Path>;
|
|
53
|
+
export declare const acceptedCanonicalObservation: ({ workspace, type, name, }: AcceptedCanonicalRefArgs) => Effect.Effect<Option.Option<AcceptedCanonicalObservation>, AcceptedCanonicalRefError, FileSystem.FileSystem | Path.Path>;
|
|
54
|
+
export declare const usableAcceptedCanonicalObservation: (args: AcceptedCanonicalRefArgs) => Effect.Effect<Option.Option<UsableAcceptedCanonicalObservation>, AcceptedCanonicalRefError, FileSystem.FileSystem | Path.Path>;
|
|
55
|
+
export declare const usableAcceptedCanonical: (args: AcceptedCanonicalRefArgs) => Effect.Effect<Option.Option<UsableAcceptedCanonical>, AcceptedCanonicalRefError, FileSystem.FileSystem | Path.Path>;
|
|
56
|
+
export declare const usableAcceptedCanonicalRef: (args: AcceptedCanonicalRefArgs) => Effect.Effect<Option.Option<ExtensionRef>, AcceptedCanonicalRefError, FileSystem.FileSystem | Path.Path>;
|
|
57
|
+
export {};
|
|
58
|
+
//# sourceMappingURL=accepted-canonical-ref.d.ts.map
|