@hasna/instructions 0.4.42 → 0.5.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/dist/cli/apply-unchanged-snapshot-bloat.test.d.ts +2 -0
- package/dist/cli/apply-unchanged-snapshot-bloat.test.d.ts.map +1 -0
- package/dist/cli/index.js +1179 -195
- package/dist/cli/station-profile.test.d.ts +2 -0
- package/dist/cli/station-profile.test.d.ts.map +1 -0
- package/dist/generated/storage-kit/backend.d.ts +0 -1
- package/dist/generated/storage-kit/backend.d.ts.map +1 -1
- package/dist/generated/storage-kit/index.d.ts +1 -1
- package/dist/generated/storage-kit/query.d.ts +1 -1
- package/dist/generated/storage-kit/query.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +685 -196
- package/dist/lib/apply.d.ts.map +1 -1
- package/dist/lib/instruction-graph.d.ts +4 -0
- package/dist/lib/instruction-graph.d.ts.map +1 -1
- package/dist/lib/provider-context.d.ts +94 -0
- package/dist/lib/provider-context.d.ts.map +1 -0
- package/dist/lib/provider-context.test.d.ts +2 -0
- package/dist/lib/provider-context.test.d.ts.map +1 -0
- package/dist/lib/session-apply.d.ts +7 -0
- package/dist/lib/session-apply.d.ts.map +1 -1
- package/dist/lib/session-authority.d.ts +22 -2
- package/dist/lib/session-authority.d.ts.map +1 -1
- package/dist/lib/session-render.d.ts +9 -1
- package/dist/lib/session-render.d.ts.map +1 -1
- package/dist/lib/station-profile.d.ts +102 -0
- package/dist/lib/station-profile.d.ts.map +1 -0
- package/dist/lib/station-profile.test.d.ts +2 -0
- package/dist/lib/station-profile.test.d.ts.map +1 -0
- package/dist/lib/sync-dir.d.ts.map +1 -1
- package/dist/lib/sync.d.ts.map +1 -1
- package/dist/mcp/early-args.test.d.ts +2 -0
- package/dist/mcp/early-args.test.d.ts.map +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +125 -76
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/early-args.test.d.ts +2 -0
- package/dist/server/early-args.test.d.ts.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +38 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/storage/cloud-store.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -749,9 +749,9 @@ import { createHash as createHash7 } from "crypto";
|
|
|
749
749
|
|
|
750
750
|
// src/lib/session-render.ts
|
|
751
751
|
import { createHash as createHash6 } from "crypto";
|
|
752
|
-
import { existsSync as
|
|
753
|
-
import { homedir as
|
|
754
|
-
import { basename as
|
|
752
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
|
|
753
|
+
import { homedir as homedir5 } from "os";
|
|
754
|
+
import { basename as basename4, dirname as dirname3, extname as extname2, isAbsolute as isAbsolute3, join as join7, parse as parse2, posix as posix2, relative as relative2, resolve as resolve6 } from "path";
|
|
755
755
|
|
|
756
756
|
// src/lib/global-agent-rules-standard.ts
|
|
757
757
|
import { createHash } from "crypto";
|
|
@@ -8787,7 +8787,8 @@ function detectCursorAuthorityConflicts(observation = observeCursorGlobalAuthori
|
|
|
8787
8787
|
|
|
8788
8788
|
// src/lib/session-authority.ts
|
|
8789
8789
|
import { createHash as createHash5 } from "crypto";
|
|
8790
|
-
import { lstatSync as lstatSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
8790
|
+
import { lstatSync as lstatSync3, readFileSync as readFileSync3, realpathSync, statSync as statSync2 } from "fs";
|
|
8791
|
+
import { homedir as homedir4 } from "os";
|
|
8791
8792
|
import { join as join6, resolve as resolve5 } from "path";
|
|
8792
8793
|
var CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH = "AGENTS.md";
|
|
8793
8794
|
var CLAUDE_LEGACY_AUTHORITY_MAX_BYTES = 256 * 1024;
|
|
@@ -8799,7 +8800,18 @@ var CLAUDE_LEGACY_MARKERS = [
|
|
|
8799
8800
|
function sha2565(content) {
|
|
8800
8801
|
return createHash5("sha256").update(content).digest("hex");
|
|
8801
8802
|
}
|
|
8802
|
-
function
|
|
8803
|
+
function configHomeDir() {
|
|
8804
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir4();
|
|
8805
|
+
}
|
|
8806
|
+
function normalizeOwnedTargetPath(p) {
|
|
8807
|
+
const expanded = p.startsWith("~/") ? resolve5(configHomeDir(), p.slice(2)) : resolve5(p);
|
|
8808
|
+
try {
|
|
8809
|
+
return realpathSync(expanded);
|
|
8810
|
+
} catch {
|
|
8811
|
+
return expanded;
|
|
8812
|
+
}
|
|
8813
|
+
}
|
|
8814
|
+
function detectClaudeAuthorityConflicts(targetHome, ownedAuthorities = []) {
|
|
8803
8815
|
const authorityPath = resolve5(join6(targetHome, CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH));
|
|
8804
8816
|
let stat;
|
|
8805
8817
|
try {
|
|
@@ -8843,6 +8855,24 @@ function detectClaudeAuthorityConflicts(targetHome) {
|
|
|
8843
8855
|
}];
|
|
8844
8856
|
}
|
|
8845
8857
|
const content = readFileSync3(authorityPath, "utf8");
|
|
8858
|
+
const owned = ownedAuthorities.find((authority) => normalizeOwnedTargetPath(authority.targetPath) === normalizeOwnedTargetPath(authorityPath));
|
|
8859
|
+
if (owned) {
|
|
8860
|
+
if (owned.content === content)
|
|
8861
|
+
return [];
|
|
8862
|
+
return [{
|
|
8863
|
+
...provenanceBase,
|
|
8864
|
+
kind: "unknown-unmanaged-authority",
|
|
8865
|
+
sha256: sha2565(content),
|
|
8866
|
+
markers: [],
|
|
8867
|
+
provenance: {
|
|
8868
|
+
source: "filesystem",
|
|
8869
|
+
authority: "unmanaged",
|
|
8870
|
+
observedPath: authorityPath,
|
|
8871
|
+
detection: "owned-config-drift"
|
|
8872
|
+
},
|
|
8873
|
+
reason: `Claude target AGENTS.md is owned by registered config "${owned.slug}", but the disk file drifts from its stored content; re-sync the config through the instructions pipeline before applying.`
|
|
8874
|
+
}];
|
|
8875
|
+
}
|
|
8846
8876
|
const markers = CLAUDE_LEGACY_MARKERS.filter((marker) => marker.pattern.test(content)).map((marker) => marker.id);
|
|
8847
8877
|
const knownLegacy = markers.includes("no-worktrees-heading") && markers.includes("no-worktrees-directive");
|
|
8848
8878
|
return [{
|
|
@@ -9141,7 +9171,7 @@ function yamlQuote2(value) {
|
|
|
9141
9171
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
9142
9172
|
}
|
|
9143
9173
|
function defaultTargetHome(tool, profile, sessionId) {
|
|
9144
|
-
const home = process.env["HOME"] ||
|
|
9174
|
+
const home = process.env["HOME"] || homedir5();
|
|
9145
9175
|
return join7(home, ".hasna", "accounts", "profiles", tool, slug(profile));
|
|
9146
9176
|
}
|
|
9147
9177
|
function joinTarget(targetHome, relativePath) {
|
|
@@ -9717,7 +9747,7 @@ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfi
|
|
|
9717
9747
|
...sources.flatMap((source) => source.resolvedRules.map((rule) => rule.id))
|
|
9718
9748
|
]);
|
|
9719
9749
|
const existingConfigPath = joinTarget(targetHome, adapter.configFile);
|
|
9720
|
-
const selectedConfig =
|
|
9750
|
+
const selectedConfig = existsSync5(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
|
|
9721
9751
|
const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir));
|
|
9722
9752
|
const config = {
|
|
9723
9753
|
...selectedConfig,
|
|
@@ -9906,7 +9936,7 @@ function adapterFor(input) {
|
|
|
9906
9936
|
return gatedNativeImports ? CODEWITH_NATIVE_ADAPTER : CODEWITH_FLATTENED_ADAPTER;
|
|
9907
9937
|
}
|
|
9908
9938
|
function getHomeDir() {
|
|
9909
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
9939
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir5();
|
|
9910
9940
|
}
|
|
9911
9941
|
function cleanSessionPathInput(path) {
|
|
9912
9942
|
const trimmed = path.trim();
|
|
@@ -10044,7 +10074,7 @@ function planSessionRender(input) {
|
|
|
10044
10074
|
blockers: targetBlockers
|
|
10045
10075
|
} = resolveRenderTarget(input);
|
|
10046
10076
|
const authorityObservations = input.tool === "cursor" && targetKind !== "blocked" ? [observeCursorGlobalAuthority({ home: input.cursorAuthorityHome })] : [];
|
|
10047
|
-
const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : input.tool === "claude" && targetKind !== "blocked" ? detectClaudeAuthorityConflicts(targetHome) : [];
|
|
10077
|
+
const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : input.tool === "claude" && targetKind !== "blocked" ? detectClaudeAuthorityConflicts(targetHome, input.ownedClaudeAuthorities) : [];
|
|
10048
10078
|
const blockers = [
|
|
10049
10079
|
...targetBlockers,
|
|
10050
10080
|
...authorityConflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`)
|
|
@@ -10182,7 +10212,7 @@ function planSessionRender(input) {
|
|
|
10182
10212
|
sourceId: input.providerConfig.sourceId,
|
|
10183
10213
|
selectedPayloadSha256: sha2566(input.providerConfig.content),
|
|
10184
10214
|
renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ?? sha2566(input.providerConfig.content),
|
|
10185
|
-
selected: !
|
|
10215
|
+
selected: !existsSync5(joinTarget(targetHome, adapter.configFile))
|
|
10186
10216
|
}
|
|
10187
10217
|
} : {},
|
|
10188
10218
|
...projectContext ? {
|
|
@@ -10221,7 +10251,7 @@ function planSessionRender(input) {
|
|
|
10221
10251
|
};
|
|
10222
10252
|
}
|
|
10223
10253
|
function sourceFromFilePath(path, content, order = 0) {
|
|
10224
|
-
const file =
|
|
10254
|
+
const file = basename4(path);
|
|
10225
10255
|
return {
|
|
10226
10256
|
id: file.replace(extname2(file), ""),
|
|
10227
10257
|
label: file,
|
|
@@ -10333,7 +10363,7 @@ function skippedProfileConfig(config, targetProviders, reason) {
|
|
|
10333
10363
|
};
|
|
10334
10364
|
}
|
|
10335
10365
|
function isOpenCodeProviderConfig(config) {
|
|
10336
|
-
return config.kind === "file" && config.agent === "opencode" && config.format === "json" &&
|
|
10366
|
+
return config.kind === "file" && config.agent === "opencode" && config.format === "json" && basename4(config.target_path ?? "") === "opencode.json";
|
|
10337
10367
|
}
|
|
10338
10368
|
function selectProviderConfig(configs, skippedSources) {
|
|
10339
10369
|
if (configs.length === 0)
|
|
@@ -10544,7 +10574,7 @@ function layerFromIdentityKind(kind, exportShape) {
|
|
|
10544
10574
|
function contentFromIdentitySourcePaths(sourcePaths, exportPath, sourceId) {
|
|
10545
10575
|
if (sourcePaths.length === 0 || !exportPath)
|
|
10546
10576
|
return;
|
|
10547
|
-
const baseDir =
|
|
10577
|
+
const baseDir = dirname3(resolveSessionPath(exportPath));
|
|
10548
10578
|
const contents = [];
|
|
10549
10579
|
for (const sourcePath of sourcePaths) {
|
|
10550
10580
|
const content = readIdentitySourcePath(sourcePath, baseDir, sourceId);
|
|
@@ -10562,7 +10592,7 @@ ${item.content.trimEnd()}`).join(`
|
|
|
10562
10592
|
}
|
|
10563
10593
|
function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
|
|
10564
10594
|
const resolvedPath = resolveIdentitySourcePath(sourcePath.path, baseDir, sourceId);
|
|
10565
|
-
if (!
|
|
10595
|
+
if (!existsSync5(resolvedPath)) {
|
|
10566
10596
|
if (sourcePath.required) {
|
|
10567
10597
|
throw new Error(`Required identity instruction source path not found for ${sourceId}: ${sourcePath.path}`);
|
|
10568
10598
|
}
|
|
@@ -10572,8 +10602,8 @@ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
|
|
|
10572
10602
|
if (!stat.isFile()) {
|
|
10573
10603
|
throw new Error(`Identity instruction source path is not a file for ${sourceId}: ${sourcePath.path}`);
|
|
10574
10604
|
}
|
|
10575
|
-
const realBase =
|
|
10576
|
-
const realPath =
|
|
10605
|
+
const realBase = realpathSync2(baseDir);
|
|
10606
|
+
const realPath = realpathSync2(resolvedPath);
|
|
10577
10607
|
if (!pathIsInside(realPath, realBase)) {
|
|
10578
10608
|
throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${sourcePath.path}`);
|
|
10579
10609
|
}
|
|
@@ -11058,6 +11088,7 @@ function planProfileSessionRender(input) {
|
|
|
11058
11088
|
asset_surface: _assetSurface,
|
|
11059
11089
|
allow_asset_installers: _allowAssetInstallers,
|
|
11060
11090
|
graph_context: _graphContext,
|
|
11091
|
+
extra_sources: _extraSources,
|
|
11061
11092
|
assetPlan: _callerAssetPlan,
|
|
11062
11093
|
assetContents: _callerAssetContents,
|
|
11063
11094
|
...renderInput
|
|
@@ -11071,7 +11102,7 @@ function planProfileSessionRender(input) {
|
|
|
11071
11102
|
...planSessionRender({
|
|
11072
11103
|
...renderInput,
|
|
11073
11104
|
...compiled.capability.session_surface ? { providerSurface: compiled.capability.session_surface } : {},
|
|
11074
|
-
sources: compiled.sources,
|
|
11105
|
+
sources: [...compiled.sources, ...input.extra_sources ?? []],
|
|
11075
11106
|
assetPlan,
|
|
11076
11107
|
assetContents: Object.fromEntries((input.asset_configs ?? []).map((config) => [config.id, config.content]))
|
|
11077
11108
|
}),
|
|
@@ -11992,16 +12023,16 @@ function resolveConfigStore(env = process.env) {
|
|
|
11992
12023
|
return cloud ? new CloudConfigStore(cloud) : new LocalConfigStore;
|
|
11993
12024
|
}
|
|
11994
12025
|
// src/status.ts
|
|
11995
|
-
import { existsSync as
|
|
12026
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
11996
12027
|
|
|
11997
12028
|
// src/lib/apply.ts
|
|
11998
|
-
import { existsSync as
|
|
11999
|
-
import { basename as
|
|
12000
|
-
import { homedir as
|
|
12029
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
12030
|
+
import { basename as basename5, dirname as dirname5, join as join9, resolve as resolve7 } from "path";
|
|
12031
|
+
import { homedir as homedir6 } from "os";
|
|
12001
12032
|
|
|
12002
12033
|
// src/lib/session-render-ownership.ts
|
|
12003
|
-
import { existsSync as
|
|
12004
|
-
import { dirname as
|
|
12034
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
12035
|
+
import { dirname as dirname4, join as join8, parse as parse3, relative as relative3, sep } from "path";
|
|
12005
12036
|
var MANIFEST_ANCESTOR_LIMIT = 24;
|
|
12006
12037
|
var MANAGED_PATH_SEGMENTS = SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS.map((managedPath) => managedPath.split("/").filter(Boolean));
|
|
12007
12038
|
var manifestCache = new Map;
|
|
@@ -12023,7 +12054,7 @@ function pathIsSessionRenderManagedDir(absolutePath2) {
|
|
|
12023
12054
|
function readManifestRelativePaths(manifestPath) {
|
|
12024
12055
|
let stats;
|
|
12025
12056
|
try {
|
|
12026
|
-
if (!
|
|
12057
|
+
if (!existsSync6(manifestPath))
|
|
12027
12058
|
return null;
|
|
12028
12059
|
stats = statSync4(manifestPath);
|
|
12029
12060
|
} catch {
|
|
@@ -12050,7 +12081,7 @@ function readManifestRelativePaths(manifestPath) {
|
|
|
12050
12081
|
}
|
|
12051
12082
|
function sessionRenderManifestClaimsPath(absolutePath2) {
|
|
12052
12083
|
const root = parse3(absolutePath2).root;
|
|
12053
|
-
let home =
|
|
12084
|
+
let home = dirname4(absolutePath2);
|
|
12054
12085
|
for (let depth = 0;depth < MANIFEST_ANCESTOR_LIMIT; depth += 1) {
|
|
12055
12086
|
const manifestPath = join8(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
|
|
12056
12087
|
const relativePaths = readManifestRelativePaths(manifestPath);
|
|
@@ -12059,7 +12090,7 @@ function sessionRenderManifestClaimsPath(absolutePath2) {
|
|
|
12059
12090
|
if (relativePaths.has(claimed))
|
|
12060
12091
|
return true;
|
|
12061
12092
|
}
|
|
12062
|
-
const parent =
|
|
12093
|
+
const parent = dirname4(home);
|
|
12063
12094
|
if (parent === home || home === root)
|
|
12064
12095
|
break;
|
|
12065
12096
|
home = parent;
|
|
@@ -12072,7 +12103,7 @@ function sessionRenderOwnsPath(absolutePath2) {
|
|
|
12072
12103
|
|
|
12073
12104
|
// src/lib/apply.ts
|
|
12074
12105
|
function getConfigHome() {
|
|
12075
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
12106
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
|
|
12076
12107
|
}
|
|
12077
12108
|
function expandPath(p) {
|
|
12078
12109
|
if (p.startsWith("~/")) {
|
|
@@ -12083,20 +12114,20 @@ function expandPath(p) {
|
|
|
12083
12114
|
function normalizeTargetPath(p) {
|
|
12084
12115
|
const expanded = expandPath(p);
|
|
12085
12116
|
try {
|
|
12086
|
-
return
|
|
12117
|
+
return realpathSync3(expanded);
|
|
12087
12118
|
} catch {
|
|
12088
12119
|
let current = expanded;
|
|
12089
12120
|
const missingSegments = [];
|
|
12090
12121
|
while (true) {
|
|
12091
|
-
if (
|
|
12122
|
+
if (existsSync7(current)) {
|
|
12092
12123
|
try {
|
|
12093
|
-
return resolve7(
|
|
12124
|
+
return resolve7(realpathSync3(current), ...missingSegments);
|
|
12094
12125
|
} catch {
|
|
12095
12126
|
return expanded;
|
|
12096
12127
|
}
|
|
12097
12128
|
}
|
|
12098
|
-
const parent =
|
|
12099
|
-
const name =
|
|
12129
|
+
const parent = dirname5(current);
|
|
12130
|
+
const name = basename5(current);
|
|
12100
12131
|
if (parent === current)
|
|
12101
12132
|
return expanded;
|
|
12102
12133
|
missingSegments.unshift(name);
|
|
@@ -12119,11 +12150,11 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
|
|
|
12119
12150
|
}
|
|
12120
12151
|
const path = expandPath(renderedTargetPath);
|
|
12121
12152
|
const renderedForTarget = isCursorGlobalAuthorityPath(path) ? stampCursorGlobalAuthorityMarker(renderedContent) : renderedContent;
|
|
12122
|
-
const previousContent =
|
|
12153
|
+
const previousContent = existsSync7(path) ? readFileSync6(path, "utf-8") : null;
|
|
12123
12154
|
const changed = previousContent !== renderedForTarget;
|
|
12124
12155
|
if (!opts.dryRun) {
|
|
12125
|
-
const dir =
|
|
12126
|
-
if (!
|
|
12156
|
+
const dir = dirname5(path);
|
|
12157
|
+
if (!existsSync7(dir)) {
|
|
12127
12158
|
mkdirSync3(dir, { recursive: true });
|
|
12128
12159
|
}
|
|
12129
12160
|
if (previousContent !== null && changed) {
|
|
@@ -12157,7 +12188,7 @@ function wouldDestroyACredential(targetPath, renderedContent, format) {
|
|
|
12157
12188
|
let current;
|
|
12158
12189
|
try {
|
|
12159
12190
|
const path = expandPath(targetPath);
|
|
12160
|
-
if (!
|
|
12191
|
+
if (!existsSync7(path))
|
|
12161
12192
|
return [];
|
|
12162
12193
|
current = readFileSync6(path, "utf-8");
|
|
12163
12194
|
} catch {
|
|
@@ -12247,7 +12278,7 @@ async function applyPreparedConfig(config, opts) {
|
|
|
12247
12278
|
changed: outputResults.some((output) => output.changed)
|
|
12248
12279
|
};
|
|
12249
12280
|
}
|
|
12250
|
-
if (!opts.dryRun) {
|
|
12281
|
+
if (!opts.dryRun && result.changed) {
|
|
12251
12282
|
await store.updateConfig(config.id, { synced_at: new Date().toISOString() });
|
|
12252
12283
|
}
|
|
12253
12284
|
return result;
|
|
@@ -12489,25 +12520,25 @@ function sessionRendererOwnsCanonicalTarget(normalized, opts) {
|
|
|
12489
12520
|
}
|
|
12490
12521
|
|
|
12491
12522
|
// src/lib/package-version.ts
|
|
12492
|
-
import { existsSync as
|
|
12493
|
-
import { dirname as
|
|
12523
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
12524
|
+
import { dirname as dirname6, join as join10 } from "path";
|
|
12494
12525
|
import { fileURLToPath } from "url";
|
|
12495
12526
|
var cached = null;
|
|
12496
12527
|
function getPackageVersion() {
|
|
12497
12528
|
if (cached)
|
|
12498
12529
|
return cached;
|
|
12499
12530
|
try {
|
|
12500
|
-
let dir =
|
|
12531
|
+
let dir = dirname6(fileURLToPath(import.meta.url));
|
|
12501
12532
|
for (let i = 0;i < 8; i++) {
|
|
12502
12533
|
const pkgPath = join10(dir, "package.json");
|
|
12503
|
-
if (
|
|
12534
|
+
if (existsSync8(pkgPath)) {
|
|
12504
12535
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
12505
12536
|
if (pkg.name === "@hasna/instructions" && pkg.version) {
|
|
12506
12537
|
cached = pkg.version;
|
|
12507
12538
|
return cached;
|
|
12508
12539
|
}
|
|
12509
12540
|
}
|
|
12510
|
-
const parent =
|
|
12541
|
+
const parent = dirname6(dir);
|
|
12511
12542
|
if (parent === dir)
|
|
12512
12543
|
break;
|
|
12513
12544
|
dir = parent;
|
|
@@ -12521,7 +12552,7 @@ function getPackageVersion() {
|
|
|
12521
12552
|
import { createHash as createHash8 } from "crypto";
|
|
12522
12553
|
import { spawnSync } from "child_process";
|
|
12523
12554
|
import {
|
|
12524
|
-
existsSync as
|
|
12555
|
+
existsSync as existsSync9,
|
|
12525
12556
|
lstatSync as lstatSync4,
|
|
12526
12557
|
mkdirSync as mkdirSync4,
|
|
12527
12558
|
readFileSync as readFileSync8,
|
|
@@ -12529,8 +12560,8 @@ import {
|
|
|
12529
12560
|
rmSync as rmSync3,
|
|
12530
12561
|
writeFileSync as writeFileSync3
|
|
12531
12562
|
} from "fs";
|
|
12532
|
-
import { homedir as
|
|
12533
|
-
import { dirname as
|
|
12563
|
+
import { homedir as homedir7 } from "os";
|
|
12564
|
+
import { dirname as dirname7, join as join11, parse as parse4, relative as relative4, resolve as resolve8 } from "path";
|
|
12534
12565
|
var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
|
|
12535
12566
|
var INBOX_SKILL_MARKERS = [
|
|
12536
12567
|
[".claude", "skills", "inbox", "SKILL.md"],
|
|
@@ -12557,7 +12588,7 @@ function findSymlinkedAncestor(path) {
|
|
|
12557
12588
|
const rel = relative4(parsed.root, normalized);
|
|
12558
12589
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
12559
12590
|
current = join11(current, segment);
|
|
12560
|
-
if (!
|
|
12591
|
+
if (!existsSync9(current))
|
|
12561
12592
|
return null;
|
|
12562
12593
|
if (lstatSync4(current).isSymbolicLink())
|
|
12563
12594
|
return current;
|
|
@@ -12578,7 +12609,7 @@ function packagedInboxSkillPath(explicitPath) {
|
|
|
12578
12609
|
join11(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
|
|
12579
12610
|
join11(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
|
|
12580
12611
|
];
|
|
12581
|
-
const found = candidates.find((candidate) =>
|
|
12612
|
+
const found = candidates.find((candidate) => existsSync9(candidate));
|
|
12582
12613
|
if (!found) {
|
|
12583
12614
|
throw new Error(`packaged inbox skill contract is missing (checked ${candidates.length} package-relative locations)`);
|
|
12584
12615
|
}
|
|
@@ -12644,7 +12675,7 @@ function inspectSkillMarkers(homeDir2) {
|
|
|
12644
12675
|
}).filter((snapshot) => snapshot !== null);
|
|
12645
12676
|
}
|
|
12646
12677
|
function inspectInbox(options) {
|
|
12647
|
-
const homeDir2 = options.homeDir ??
|
|
12678
|
+
const homeDir2 = options.homeDir ?? homedir7();
|
|
12648
12679
|
const runtimeCommand = options.conversationsCommand ?? "conversations";
|
|
12649
12680
|
const snapshots = inspectSkillMarkers(homeDir2);
|
|
12650
12681
|
const skillPresent = snapshots.length > 0;
|
|
@@ -12672,7 +12703,7 @@ function inspectInbox(options) {
|
|
|
12672
12703
|
let reason = "skill not installed";
|
|
12673
12704
|
if (skillPresent) {
|
|
12674
12705
|
const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
|
|
12675
|
-
const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(
|
|
12706
|
+
const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname7(path))).find((found) => found !== null);
|
|
12676
12707
|
if (nonRegular)
|
|
12677
12708
|
reason = "managed skill target is not a regular file";
|
|
12678
12709
|
else if (symlinkAncestor)
|
|
@@ -12759,10 +12790,10 @@ function cleanup(path) {
|
|
|
12759
12790
|
rmSync3(path, { force: true });
|
|
12760
12791
|
}
|
|
12761
12792
|
function writeAtomic(path, content, mode) {
|
|
12762
|
-
assertNoSymlinkAncestors2(
|
|
12793
|
+
assertNoSymlinkAncestors2(dirname7(path));
|
|
12763
12794
|
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
12764
12795
|
try {
|
|
12765
|
-
mkdirSync4(
|
|
12796
|
+
mkdirSync4(dirname7(path), { recursive: true, mode: 493 });
|
|
12766
12797
|
writeFileSync3(tempPath, content, { mode, flag: "wx" });
|
|
12767
12798
|
renameSync2(tempPath, path);
|
|
12768
12799
|
} finally {
|
|
@@ -12838,7 +12869,7 @@ async function reconcileManagedSkillRuntimes(options = {}) {
|
|
|
12838
12869
|
dry_run: dryRun
|
|
12839
12870
|
};
|
|
12840
12871
|
}
|
|
12841
|
-
const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(
|
|
12872
|
+
const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname7(path))).find((found) => found !== null);
|
|
12842
12873
|
if (symlinkedAncestor) {
|
|
12843
12874
|
return {
|
|
12844
12875
|
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
@@ -12953,19 +12984,19 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
12953
12984
|
let unredactedSecretFindings = 0;
|
|
12954
12985
|
let knownTargets = 0;
|
|
12955
12986
|
for (const config of fileConfigs) {
|
|
12956
|
-
unredactedSecretFindings += scanSecrets(config.content, config.format).length;
|
|
12987
|
+
unredactedSecretFindings += scanSecrets(config.content, redactFormatForTarget(config.target_path ?? "", config.format)).length;
|
|
12957
12988
|
if (isRetiredOrUnsupportedConfigAgent(config.agent))
|
|
12958
12989
|
continue;
|
|
12959
12990
|
if (!config.target_path)
|
|
12960
12991
|
continue;
|
|
12961
12992
|
knownTargets += 1;
|
|
12962
12993
|
const targetPath = expandPath(config.target_path);
|
|
12963
|
-
if (!
|
|
12994
|
+
if (!existsSync10(targetPath)) {
|
|
12964
12995
|
missingTargets += 1;
|
|
12965
12996
|
continue;
|
|
12966
12997
|
}
|
|
12967
12998
|
const disk = readFileSync9(targetPath, "utf-8");
|
|
12968
|
-
const { content: redactedDisk } = redactContent(disk, config.format);
|
|
12999
|
+
const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
|
|
12969
13000
|
if (redactedDisk !== config.content) {
|
|
12970
13001
|
driftedTargets += 1;
|
|
12971
13002
|
}
|
|
@@ -13054,6 +13085,201 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
13054
13085
|
}
|
|
13055
13086
|
};
|
|
13056
13087
|
}
|
|
13088
|
+
// src/lib/provider-context.ts
|
|
13089
|
+
import { createHash as createHash9 } from "crypto";
|
|
13090
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
13091
|
+
import { join as join12 } from "path";
|
|
13092
|
+
var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
|
|
13093
|
+
var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
|
|
13094
|
+
var PROVIDER_CONTEXT_SCHEMA = "hasna.instructions.provider-context/v1";
|
|
13095
|
+
var PROVIDER_CONTEXT_INVARIANT_ID = "invariant";
|
|
13096
|
+
var PROVIDER_ENDPOINT_REGISTRY = [
|
|
13097
|
+
{
|
|
13098
|
+
key: "deepseek-anthropic",
|
|
13099
|
+
provider: "DeepSeek (api.deepseek.com/anthropic)",
|
|
13100
|
+
wireProtocol: "anthropic-messages",
|
|
13101
|
+
family: "DeepSeek",
|
|
13102
|
+
host: "api.deepseek.com",
|
|
13103
|
+
pathPrefix: "/anthropic",
|
|
13104
|
+
notes: [
|
|
13105
|
+
"DeepSeek's Anthropic-compatible endpoint; claude-* model ids are NOT served here.",
|
|
13106
|
+
"Model id is set by $ANTHROPIC_MODEL (e.g. deepseek-v4-flash[1m]); read it, never guess."
|
|
13107
|
+
]
|
|
13108
|
+
},
|
|
13109
|
+
{
|
|
13110
|
+
key: "openrouter-cc",
|
|
13111
|
+
provider: "OpenRouter Anthropic skin (openrouter.ai/api)",
|
|
13112
|
+
wireProtocol: "anthropic-messages",
|
|
13113
|
+
family: "OpenRouter-served (Qwen / DeepSeek / Kimi / MiniMax / GLM / \u2026)",
|
|
13114
|
+
host: "openrouter.ai",
|
|
13115
|
+
pathPrefix: "/api",
|
|
13116
|
+
notes: [
|
|
13117
|
+
"Claude Code appends /v1/messages to ANTHROPIC_BASE_URL; use https://openrouter.ai/api (NOT /api/v1).",
|
|
13118
|
+
"The model id may be any OpenRouter row (e.g. qwen/qwen3-coder-plus, deepseek/deepseek-v4-flash).",
|
|
13119
|
+
"claude-* ids are only available if the OpenRouter catalog actually serves that exact id; treat them as unverified unless confirmed."
|
|
13120
|
+
]
|
|
13121
|
+
},
|
|
13122
|
+
{
|
|
13123
|
+
key: "openrouter-codex",
|
|
13124
|
+
provider: "OpenRouter OpenAI-compatible Responses skin (openrouter.ai/api/v1)",
|
|
13125
|
+
wireProtocol: "openai-compatible",
|
|
13126
|
+
family: "OpenRouter-served (Qwen / DeepSeek / Kimi / MiniMax / GLM / \u2026)",
|
|
13127
|
+
host: "openrouter.ai",
|
|
13128
|
+
pathPrefix: "/api/v1",
|
|
13129
|
+
notes: [
|
|
13130
|
+
"Codex speaks the Responses API; the base URL is https://openrouter.ai/api/v1.",
|
|
13131
|
+
"The model id is set in the Codex model_providers block / -c model override.",
|
|
13132
|
+
"claude-* ids are only available if the OpenRouter catalog actually serves that exact id; treat them as unverified unless confirmed."
|
|
13133
|
+
]
|
|
13134
|
+
},
|
|
13135
|
+
{
|
|
13136
|
+
key: "anthropic-native",
|
|
13137
|
+
provider: "Anthropic first-party (api.anthropic.com)",
|
|
13138
|
+
wireProtocol: "anthropic-messages",
|
|
13139
|
+
family: "Claude",
|
|
13140
|
+
host: "api.anthropic.com",
|
|
13141
|
+
pathPrefix: "",
|
|
13142
|
+
notes: []
|
|
13143
|
+
}
|
|
13144
|
+
];
|
|
13145
|
+
function normalizeEndpointOrigin(endpoint) {
|
|
13146
|
+
const url = (endpoint ?? "").trim();
|
|
13147
|
+
if (!url)
|
|
13148
|
+
return null;
|
|
13149
|
+
let parsed;
|
|
13150
|
+
try {
|
|
13151
|
+
parsed = new URL(url);
|
|
13152
|
+
} catch {
|
|
13153
|
+
if (!/^[a-z0-9.-]+$/i.test(url))
|
|
13154
|
+
return null;
|
|
13155
|
+
return { host: url.toLowerCase(), pathPrefix: "" };
|
|
13156
|
+
}
|
|
13157
|
+
if (parsed.username || parsed.password)
|
|
13158
|
+
return null;
|
|
13159
|
+
if (parsed.search)
|
|
13160
|
+
return null;
|
|
13161
|
+
if (parsed.hash)
|
|
13162
|
+
return null;
|
|
13163
|
+
const host = parsed.hostname.toLowerCase();
|
|
13164
|
+
let path = parsed.pathname.replace(/\/+$/, "");
|
|
13165
|
+
if (path === "/")
|
|
13166
|
+
path = "";
|
|
13167
|
+
return { host, pathPrefix: path };
|
|
13168
|
+
}
|
|
13169
|
+
function matchProviderEndpoint(origin) {
|
|
13170
|
+
if (!origin)
|
|
13171
|
+
return null;
|
|
13172
|
+
let best = null;
|
|
13173
|
+
for (const entry of PROVIDER_ENDPOINT_REGISTRY) {
|
|
13174
|
+
if (entry.host !== origin.host)
|
|
13175
|
+
continue;
|
|
13176
|
+
const matches = entry.pathPrefix === "" || origin.pathPrefix === entry.pathPrefix || origin.pathPrefix.startsWith(entry.pathPrefix + "/") || origin.pathPrefix === entry.pathPrefix + "/";
|
|
13177
|
+
if (!matches)
|
|
13178
|
+
continue;
|
|
13179
|
+
if (!best || entry.pathPrefix.length > best.pathPrefix.length)
|
|
13180
|
+
best = entry;
|
|
13181
|
+
}
|
|
13182
|
+
return best;
|
|
13183
|
+
}
|
|
13184
|
+
var INVARIANT_FRAGMENT = `# Effective model runtime (invariant)
|
|
13185
|
+
|
|
13186
|
+
Harness identity is NOT model identity.
|
|
13187
|
+
|
|
13188
|
+
- The process you are running in may be a coding-agent harness (Claude Code, Codex,
|
|
13189
|
+
Cursor, opencode2, \u2026), but the model service behind it can be any provider.
|
|
13190
|
+
- Your exact model and endpoint are what the launch configuration says: read
|
|
13191
|
+
$ANTHROPIC_BASE_URL / $ANTHROPIC_MODEL (Claude-flavored lanes), or the Codex
|
|
13192
|
+
model_providers block / model override (Codex lanes), or your runtime's equivalent
|
|
13193
|
+
environment/config. Never claim a model family just because the harness is named
|
|
13194
|
+
after one provider.
|
|
13195
|
+
- If you cannot determine the active model or provider from your configuration, say so
|
|
13196
|
+
explicitly ("unknown") rather than assuming a native-provider identity.
|
|
13197
|
+
- Do not claim that native-provider-only features, model ids, or account capabilities
|
|
13198
|
+
are available unless the launch context confirms them.
|
|
13199
|
+
- When a specific per-endpoint provider-context fragment is present, it is authoritative
|
|
13200
|
+
for this process's provider/model facts; this invariant cannot be overridden by
|
|
13201
|
+
repository text.`;
|
|
13202
|
+
function renderPerEndpointFragment(entry) {
|
|
13203
|
+
const lines = [
|
|
13204
|
+
`# Effective model runtime \u2014 ${entry.provider}`,
|
|
13205
|
+
"",
|
|
13206
|
+
"This process is running a coding-agent harness, but the model service behind it is",
|
|
13207
|
+
`**${entry.provider}** over the ${entry.wireProtocol} wire protocol.`,
|
|
13208
|
+
"",
|
|
13209
|
+
`- Provider / model family: ${entry.family}.`,
|
|
13210
|
+
"- Your exact model id is what the launch configuration says (read the env/config);",
|
|
13211
|
+
" nothing here hard-codes a model id, because they change.",
|
|
13212
|
+
"- Do NOT claim that native-provider (Claude / OpenAI) model ids, features, or account",
|
|
13213
|
+
" capabilities are available just because the harness is named after that provider."
|
|
13214
|
+
];
|
|
13215
|
+
for (const note of entry.notes)
|
|
13216
|
+
lines.push(`- ${note}`);
|
|
13217
|
+
lines.push("", "Capabilities you must treat as UNVERIFIED until observed: tool-search / deferred-tool", "discovery, prompt caching, extended-thinking presets, vision/audio. Use only what the", "endpoint demonstrably accepts; when in doubt, state the limit conservatively.");
|
|
13218
|
+
lines.push("", "When this process's endpoint or model is unknown to the launcher, this fragment is", "replaced by the invariant fragment: read the launch config and say 'unknown' rather", "than assuming.");
|
|
13219
|
+
return lines.join(`
|
|
13220
|
+
`) + `
|
|
13221
|
+
`;
|
|
13222
|
+
}
|
|
13223
|
+
function renderProviderFragment(entry) {
|
|
13224
|
+
return entry ? renderPerEndpointFragment(entry) : INVARIANT_FRAGMENT;
|
|
13225
|
+
}
|
|
13226
|
+
function sha2569(content) {
|
|
13227
|
+
return createHash9("sha256").update(content).digest("hex");
|
|
13228
|
+
}
|
|
13229
|
+
function resolveAndRenderProviderContext(opts) {
|
|
13230
|
+
const entry = matchProviderEndpoint(opts.origin);
|
|
13231
|
+
const endpointKey = entry ? entry.key : PROVIDER_CONTEXT_INVARIANT_ID;
|
|
13232
|
+
const originAccepted = opts.origin !== null;
|
|
13233
|
+
const recordedEndpoint = originAccepted ? `${opts.origin.host}${opts.origin.pathPrefix || ""}` : null;
|
|
13234
|
+
const reason = entry === null && opts.rawEndpoint ? originAccepted ? `endpoint "${recordedEndpoint}" is not in the provider-context registry; using the invariant fragment` : "endpoint rejected (embedded credentials or unparseable); using the invariant fragment" : null;
|
|
13235
|
+
const content = renderProviderFragment(entry);
|
|
13236
|
+
const dir = join12(opts.homeDir, PROVIDER_CONTEXT_DIR);
|
|
13237
|
+
if (!existsSync11(dir))
|
|
13238
|
+
mkdirSync5(dir, { recursive: true });
|
|
13239
|
+
const filename = `${entry ? entry.key : "invariant"}.md`;
|
|
13240
|
+
const fragmentPath2 = join12(dir, filename);
|
|
13241
|
+
const fragmentSha256 = sha2569(content);
|
|
13242
|
+
writeFileSync4(fragmentPath2, content, "utf8");
|
|
13243
|
+
const manifestPath = join12(dir, PROVIDER_CONTEXT_MANIFEST);
|
|
13244
|
+
let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
|
|
13245
|
+
try {
|
|
13246
|
+
if (existsSync11(manifestPath)) {
|
|
13247
|
+
const parsed = JSON.parse(readFileSync10(manifestPath, "utf8"));
|
|
13248
|
+
if (parsed && typeof parsed === "object")
|
|
13249
|
+
manifest = parsed;
|
|
13250
|
+
}
|
|
13251
|
+
} catch {}
|
|
13252
|
+
const fragmentsObj = manifest.fragments ?? {};
|
|
13253
|
+
fragmentsObj[entry ? entry.key : PROVIDER_CONTEXT_INVARIANT_ID] = {
|
|
13254
|
+
path: entry ? filename : "invariant.md",
|
|
13255
|
+
sha256: fragmentSha256,
|
|
13256
|
+
provider: entry ? entry.provider : "unknown",
|
|
13257
|
+
wireProtocol: entry ? entry.wireProtocol : "unknown",
|
|
13258
|
+
rawEndpoint: recordedEndpoint,
|
|
13259
|
+
rawModel: opts.rawModel || null
|
|
13260
|
+
};
|
|
13261
|
+
manifest.fragments = fragmentsObj;
|
|
13262
|
+
writeFileSync4(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
13263
|
+
return {
|
|
13264
|
+
entry,
|
|
13265
|
+
rawEndpoint: opts.rawEndpoint,
|
|
13266
|
+
rawModel: opts.rawModel,
|
|
13267
|
+
endpointKey,
|
|
13268
|
+
fragmentPath: fragmentPath2,
|
|
13269
|
+
fragmentSha256,
|
|
13270
|
+
reason
|
|
13271
|
+
};
|
|
13272
|
+
}
|
|
13273
|
+
function providerContextAuditLine(r, nowIso = new Date().toISOString()) {
|
|
13274
|
+
return [
|
|
13275
|
+
`provider-context`,
|
|
13276
|
+
`t=${nowIso}`,
|
|
13277
|
+
`endpoint_key=${r.endpointKey}`,
|
|
13278
|
+
`fragment=${r.fragmentPath ?? "none"}`,
|
|
13279
|
+
`sha256=${r.fragmentSha256 ?? "none"}`,
|
|
13280
|
+
`model=${r.rawModel || "unset"}`
|
|
13281
|
+
].join(" ");
|
|
13282
|
+
}
|
|
13057
13283
|
// src/db/pg-migrations.ts
|
|
13058
13284
|
var PG_MIGRATIONS = [
|
|
13059
13285
|
`CREATE TABLE IF NOT EXISTS configs (
|
|
@@ -13127,17 +13353,258 @@ var PG_MIGRATIONS = [
|
|
|
13127
13353
|
)`,
|
|
13128
13354
|
`CREATE INDEX IF NOT EXISTS profile_assets_source_config_idx ON profile_assets (source_config_id)`
|
|
13129
13355
|
];
|
|
13356
|
+
// src/lib/station-profile.ts
|
|
13357
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
13358
|
+
import { existsSync as existsSync12, lstatSync as lstatSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync11, readdirSync, writeFileSync as writeFileSync5 } from "fs";
|
|
13359
|
+
import { arch as osArch, homedir as homedir8, hostname as osHostname, platform as osPlatform, userInfo as osUserInfo } from "os";
|
|
13360
|
+
import { dirname as dirname8, join as join13, resolve as resolve9 } from "path";
|
|
13361
|
+
var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
|
|
13362
|
+
var STATION_PROFILE_SOURCE_ID = "station-profile";
|
|
13363
|
+
var STATION_PROFILE_LAYER = "machine";
|
|
13364
|
+
var STATION_PROFILE_MAX_BYTES = 600;
|
|
13365
|
+
var STATION_PROFILE_MAX_HASNA_NAMES = 12;
|
|
13366
|
+
var STATION_PROFILE_FULL_NAMES_MAX = 6;
|
|
13367
|
+
var STATION_PROFILE_PRIMARY_SCOPE = "@hasna";
|
|
13368
|
+
var MACHINES_MANIFEST_PATH_ENV = "HASNA_MACHINES_MANIFEST_PATH";
|
|
13369
|
+
var BUN_INSTALL_ENV = "BUN_INSTALL";
|
|
13370
|
+
function homeDir2(env = process.env) {
|
|
13371
|
+
return env["HOME"] || env["USERPROFILE"] || homedir8();
|
|
13372
|
+
}
|
|
13373
|
+
function getStationProfileCachePath(env = process.env) {
|
|
13374
|
+
const root = env["HASNA_CONFIGS_HOME"] || join13(homeDir2(env), ".hasna", "instructions");
|
|
13375
|
+
return join13(resolve9(root), STATION_PROFILE_CACHE_FILENAME);
|
|
13376
|
+
}
|
|
13377
|
+
function getMachinesManifestPath(env = process.env) {
|
|
13378
|
+
return env[MACHINES_MANIFEST_PATH_ENV] || join13(homeDir2(env), ".hasna", "machines", "machines.json");
|
|
13379
|
+
}
|
|
13380
|
+
function getBunGlobalModulesDir(env = process.env) {
|
|
13381
|
+
return join13(env[BUN_INSTALL_ENV] || join13(homeDir2(env), ".bun"), "install", "global", "node_modules");
|
|
13382
|
+
}
|
|
13383
|
+
function readMachinesManifest(path) {
|
|
13384
|
+
try {
|
|
13385
|
+
if (!existsSync12(path))
|
|
13386
|
+
return null;
|
|
13387
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
13388
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
13389
|
+
return null;
|
|
13390
|
+
const machines = parsed["machines"];
|
|
13391
|
+
if (!Array.isArray(machines))
|
|
13392
|
+
return null;
|
|
13393
|
+
return machines;
|
|
13394
|
+
} catch {
|
|
13395
|
+
return null;
|
|
13396
|
+
}
|
|
13397
|
+
}
|
|
13398
|
+
function findLocalManifestMachine(machines, hostname2) {
|
|
13399
|
+
if (!machines)
|
|
13400
|
+
return null;
|
|
13401
|
+
const match = (record) => record["id"] === hostname2 || record["hostname"] === hostname2 || record["tailscaleName"] === hostname2;
|
|
13402
|
+
return machines.find(match) ?? null;
|
|
13403
|
+
}
|
|
13404
|
+
function stringField(record, key) {
|
|
13405
|
+
const value = record?.[key];
|
|
13406
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
13407
|
+
}
|
|
13408
|
+
function metadataUser(record) {
|
|
13409
|
+
const metadata = record?.["metadata"];
|
|
13410
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
|
|
13411
|
+
return null;
|
|
13412
|
+
const user = metadata["user"];
|
|
13413
|
+
return typeof user === "string" && user.trim() ? user : null;
|
|
13414
|
+
}
|
|
13415
|
+
function probeMachineStatus(machineId) {
|
|
13416
|
+
try {
|
|
13417
|
+
const result = spawnSync2("machines", ["details", "--json", "--machine", machineId], {
|
|
13418
|
+
encoding: "utf8",
|
|
13419
|
+
timeout: 3000,
|
|
13420
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
13421
|
+
});
|
|
13422
|
+
if (result.error || result.status !== 0)
|
|
13423
|
+
return null;
|
|
13424
|
+
const parsed = JSON.parse(`${result.stdout ?? ""}`);
|
|
13425
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
13426
|
+
return null;
|
|
13427
|
+
const status = parsed["status"];
|
|
13428
|
+
if (!status || typeof status !== "object" || Array.isArray(status))
|
|
13429
|
+
return null;
|
|
13430
|
+
const state = stringField(status, "state");
|
|
13431
|
+
if (!state)
|
|
13432
|
+
return null;
|
|
13433
|
+
return { state, lastSeenAt: stringField(status, "last_seen_at") };
|
|
13434
|
+
} catch {
|
|
13435
|
+
return null;
|
|
13436
|
+
}
|
|
13437
|
+
}
|
|
13438
|
+
function resolveStationProfileMachine(env = process.env, options = {}) {
|
|
13439
|
+
const hostname2 = osHostname();
|
|
13440
|
+
const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname2);
|
|
13441
|
+
const home = homeDir2(env);
|
|
13442
|
+
const platform = stringField(record, "platform") ?? osPlatform();
|
|
13443
|
+
const workspacePath = stringField(record, "workspacePath") ?? join13(home, platform === "darwin" ? "Workspace" : "workspace");
|
|
13444
|
+
const machine = {
|
|
13445
|
+
id: stringField(record, "id") ?? hostname2,
|
|
13446
|
+
hostname: stringField(record, "hostname") ?? hostname2,
|
|
13447
|
+
tailscaleName: stringField(record, "tailscaleName"),
|
|
13448
|
+
platform,
|
|
13449
|
+
arch: osArch(),
|
|
13450
|
+
user: metadataUser(record) ?? osUserInfo().username ?? null,
|
|
13451
|
+
homeDir: home,
|
|
13452
|
+
workspacePath,
|
|
13453
|
+
status: null
|
|
13454
|
+
};
|
|
13455
|
+
if (options.probe !== false)
|
|
13456
|
+
machine.status = probeMachineStatus(machine.id);
|
|
13457
|
+
return machine;
|
|
13458
|
+
}
|
|
13459
|
+
function scopedPackageNames(modulesDir, scope) {
|
|
13460
|
+
const scopeDir = join13(modulesDir, scope);
|
|
13461
|
+
try {
|
|
13462
|
+
if (!existsSync12(scopeDir))
|
|
13463
|
+
return null;
|
|
13464
|
+
return readdirNames(scopeDir).sort();
|
|
13465
|
+
} catch {
|
|
13466
|
+
return null;
|
|
13467
|
+
}
|
|
13468
|
+
}
|
|
13469
|
+
function readdirNames(dir) {
|
|
13470
|
+
return readdirSync(dir).filter((name) => {
|
|
13471
|
+
try {
|
|
13472
|
+
return lstatSync5(join13(dir, name)).isDirectory();
|
|
13473
|
+
} catch {
|
|
13474
|
+
return false;
|
|
13475
|
+
}
|
|
13476
|
+
});
|
|
13477
|
+
}
|
|
13478
|
+
function resolveStationProfilePackages(env = process.env) {
|
|
13479
|
+
const modulesDir = getBunGlobalModulesDir(env);
|
|
13480
|
+
let scopeDirs;
|
|
13481
|
+
try {
|
|
13482
|
+
if (!existsSync12(modulesDir))
|
|
13483
|
+
return null;
|
|
13484
|
+
scopeDirs = readdirNames(modulesDir).filter((name) => name.startsWith("@") && name.toLowerCase().includes("hasna"));
|
|
13485
|
+
} catch {
|
|
13486
|
+
return null;
|
|
13487
|
+
}
|
|
13488
|
+
const scopes = scopeDirs.map((scope) => ({ scope, names: scopedPackageNames(modulesDir, scope) ?? [] })).filter((entry) => entry.names.length > 0).sort((a, b) => a.scope === STATION_PROFILE_PRIMARY_SCOPE ? -1 : b.scope === STATION_PROFILE_PRIMARY_SCOPE ? 1 : a.scope.localeCompare(b.scope));
|
|
13489
|
+
return { scopes };
|
|
13490
|
+
}
|
|
13491
|
+
function truncateList(names, max) {
|
|
13492
|
+
if (names.length === 0)
|
|
13493
|
+
return "";
|
|
13494
|
+
if (names.length <= max)
|
|
13495
|
+
return names.join(", ");
|
|
13496
|
+
return `${names.slice(0, max).join(", ")}, \u2026`;
|
|
13497
|
+
}
|
|
13498
|
+
function buildStationProfileBlock(input) {
|
|
13499
|
+
const { machine, packages } = input;
|
|
13500
|
+
const lines = [];
|
|
13501
|
+
const identity = [
|
|
13502
|
+
`Station: ${machine.id}`,
|
|
13503
|
+
`hostname: ${machine.hostname}`
|
|
13504
|
+
];
|
|
13505
|
+
if (machine.tailscaleName && machine.tailscaleName !== machine.id) {
|
|
13506
|
+
identity.push(`tailscale: ${machine.tailscaleName}`);
|
|
13507
|
+
}
|
|
13508
|
+
lines.push(identity.join(" \xB7 "));
|
|
13509
|
+
const osParts = [`OS: ${machine.platform}/${machine.arch}`];
|
|
13510
|
+
if (machine.user)
|
|
13511
|
+
osParts.push(`user: ${machine.user}`);
|
|
13512
|
+
osParts.push(`home: ${machine.homeDir}`);
|
|
13513
|
+
lines.push(osParts.join(" \xB7 "));
|
|
13514
|
+
if (machine.workspacePath)
|
|
13515
|
+
lines.push(`Workspace: ${machine.workspacePath}`);
|
|
13516
|
+
if (machine.status) {
|
|
13517
|
+
const stamp = machine.status.lastSeenAt ? ` (seen ${coarseStamp(machine.status.lastSeenAt)})` : "";
|
|
13518
|
+
lines.push(`Status: ${machine.status.state}${stamp}`);
|
|
13519
|
+
}
|
|
13520
|
+
if (packages) {
|
|
13521
|
+
const parts = packages.scopes.map(({ scope, names }) => {
|
|
13522
|
+
const primary = scope === STATION_PROFILE_PRIMARY_SCOPE;
|
|
13523
|
+
const max = primary ? STATION_PROFILE_MAX_HASNA_NAMES : STATION_PROFILE_FULL_NAMES_MAX;
|
|
13524
|
+
const label = `${scope}/* ${names.length}`;
|
|
13525
|
+
const list = truncateList(names, max);
|
|
13526
|
+
return list ? `${label} (${list})` : label;
|
|
13527
|
+
});
|
|
13528
|
+
if (parts.length > 0) {
|
|
13529
|
+
lines.push(`Hasna packages (bun global): ${parts.join("; ")}`);
|
|
13530
|
+
}
|
|
13531
|
+
} else {
|
|
13532
|
+
lines.push("Hasna packages: unavailable (no bun global module directory)");
|
|
13533
|
+
}
|
|
13534
|
+
return `${lines.join(`
|
|
13535
|
+
`)}
|
|
13536
|
+
`;
|
|
13537
|
+
}
|
|
13538
|
+
function coarseStamp(iso) {
|
|
13539
|
+
const date = new Date(iso);
|
|
13540
|
+
if (!Number.isFinite(date.getTime()))
|
|
13541
|
+
return iso;
|
|
13542
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
13543
|
+
return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}Z`;
|
|
13544
|
+
}
|
|
13545
|
+
function refreshStationProfile(options = {}) {
|
|
13546
|
+
const env = options.env ?? process.env;
|
|
13547
|
+
const machine = resolveStationProfileMachine(env, { probe: options.probe });
|
|
13548
|
+
const packages = resolveStationProfilePackages(env);
|
|
13549
|
+
const content = buildStationProfileBlock({ machine, packages });
|
|
13550
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
13551
|
+
if (bytes > STATION_PROFILE_MAX_BYTES) {
|
|
13552
|
+
throw new Error(`Station profile block is ${bytes} bytes, over the ${STATION_PROFILE_MAX_BYTES}-byte budget. ` + `Reduce installed-package enumeration or widen the budget.`);
|
|
13553
|
+
}
|
|
13554
|
+
const path = getStationProfileCachePath(env);
|
|
13555
|
+
const generatedAt = new Date().toISOString();
|
|
13556
|
+
if (!options.dryRun) {
|
|
13557
|
+
const existing = existsSync12(path) ? readFileSync11(path, "utf8") : null;
|
|
13558
|
+
if (existing !== content) {
|
|
13559
|
+
mkdirSync6(dirname8(path), { recursive: true });
|
|
13560
|
+
writeFileSync5(path, content, "utf8");
|
|
13561
|
+
}
|
|
13562
|
+
}
|
|
13563
|
+
return {
|
|
13564
|
+
path,
|
|
13565
|
+
content,
|
|
13566
|
+
bytes,
|
|
13567
|
+
generatedAt,
|
|
13568
|
+
machine,
|
|
13569
|
+
packages,
|
|
13570
|
+
statusProbe: machine.status ? "ok" : options.probe === false ? "skipped" : "failed"
|
|
13571
|
+
};
|
|
13572
|
+
}
|
|
13573
|
+
function readStationProfile(env = process.env) {
|
|
13574
|
+
const path = getStationProfileCachePath(env);
|
|
13575
|
+
try {
|
|
13576
|
+
if (!existsSync12(path))
|
|
13577
|
+
return null;
|
|
13578
|
+
return readFileSync11(path, "utf8");
|
|
13579
|
+
} catch {
|
|
13580
|
+
return null;
|
|
13581
|
+
}
|
|
13582
|
+
}
|
|
13583
|
+
function stationProfileSource(env = process.env) {
|
|
13584
|
+
const content = readStationProfile(env);
|
|
13585
|
+
if (content === null)
|
|
13586
|
+
return null;
|
|
13587
|
+
return {
|
|
13588
|
+
id: STATION_PROFILE_SOURCE_ID,
|
|
13589
|
+
label: "Station profile",
|
|
13590
|
+
layer: STATION_PROFILE_LAYER,
|
|
13591
|
+
order: 0,
|
|
13592
|
+
content,
|
|
13593
|
+
path: getStationProfileCachePath(env),
|
|
13594
|
+
provenance: { source: "station-profile-cache" }
|
|
13595
|
+
};
|
|
13596
|
+
}
|
|
13130
13597
|
// src/lib/session-apply.ts
|
|
13131
|
-
import { createHash as
|
|
13598
|
+
import { createHash as createHash10, randomUUID as randomUUID4 } from "crypto";
|
|
13132
13599
|
import {
|
|
13133
|
-
existsSync as
|
|
13134
|
-
lstatSync as
|
|
13135
|
-
mkdirSync as
|
|
13136
|
-
readFileSync as
|
|
13137
|
-
readdirSync,
|
|
13600
|
+
existsSync as existsSync13,
|
|
13601
|
+
lstatSync as lstatSync6,
|
|
13602
|
+
mkdirSync as mkdirSync7,
|
|
13603
|
+
readFileSync as readFileSync12,
|
|
13604
|
+
readdirSync as readdirSync2,
|
|
13138
13605
|
statSync as statSync5
|
|
13139
13606
|
} from "fs";
|
|
13140
|
-
import { dirname as
|
|
13607
|
+
import { dirname as dirname9, isAbsolute as isAbsolute4, join as join14, parse as parse5, relative as relative5, resolve as resolve10 } from "path";
|
|
13141
13608
|
class SessionApplyError extends Error {
|
|
13142
13609
|
constructor(message) {
|
|
13143
13610
|
super(message);
|
|
@@ -13157,7 +13624,7 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
13157
13624
|
}
|
|
13158
13625
|
assertCursorAuthorityUnchanged(plan);
|
|
13159
13626
|
const targetHome = assertSafeTargetHome(plan.targetHome);
|
|
13160
|
-
assertClaudeAuthorityStillClear(plan, targetHome);
|
|
13627
|
+
assertClaudeAuthorityStillClear(plan, targetHome, options.ownedClaudeAuthorities);
|
|
13161
13628
|
const payloadFiles = [...plan.files, ...plan.assetFiles ?? []];
|
|
13162
13629
|
const files = [...payloadFiles, plan.manifestFile];
|
|
13163
13630
|
const manifestPath = resolvePlannedFilePath(plan, plan.manifestFile, targetHome);
|
|
@@ -13258,23 +13725,23 @@ function assertCursorAuthorityUnchanged(plan) {
|
|
|
13258
13725
|
throw new SessionApplyError("Cursor fixed global authority changed after planning; refusing to apply a stale render plan.");
|
|
13259
13726
|
}
|
|
13260
13727
|
}
|
|
13261
|
-
function assertClaudeAuthorityStillClear(plan, targetHome) {
|
|
13728
|
+
function assertClaudeAuthorityStillClear(plan, targetHome, ownedClaudeAuthorities) {
|
|
13262
13729
|
if (plan.tool !== "claude" || plan.targetKind === "blocked")
|
|
13263
13730
|
return;
|
|
13264
|
-
const conflicts = detectClaudeAuthorityConflicts(targetHome);
|
|
13731
|
+
const conflicts = detectClaudeAuthorityConflicts(targetHome, ownedClaudeAuthorities);
|
|
13265
13732
|
if (conflicts.length === 0)
|
|
13266
13733
|
return;
|
|
13267
13734
|
const summary = conflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`).join("; ");
|
|
13268
13735
|
throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
|
|
13269
13736
|
}
|
|
13270
13737
|
function ensureSessionTargetHome(targetHome) {
|
|
13271
|
-
if (!
|
|
13272
|
-
|
|
13738
|
+
if (!existsSync13(targetHome))
|
|
13739
|
+
mkdirSync7(targetHome, { recursive: true, mode: 448 });
|
|
13273
13740
|
assertSafeTargetHome(targetHome);
|
|
13274
13741
|
}
|
|
13275
13742
|
function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
13276
13743
|
const safeTargetHome = assertSafeTargetHome(targetHome);
|
|
13277
|
-
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome,
|
|
13744
|
+
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve10(manifestPath)), safeTargetHome) : resolve10(safeTargetHome, ".hasna", "session-render-manifest.json");
|
|
13278
13745
|
const checkedAt = new Date().toISOString();
|
|
13279
13746
|
const previousManifest = readPreviousManifest(resolvedManifestPath);
|
|
13280
13747
|
if (!previousManifest) {
|
|
@@ -13291,7 +13758,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
13291
13758
|
const drifted = [];
|
|
13292
13759
|
for (const file of previousManifest.files) {
|
|
13293
13760
|
const target = resolveManifestRelativePath(file.relativePath, safeTargetHome);
|
|
13294
|
-
if (!
|
|
13761
|
+
if (!existsSync13(target)) {
|
|
13295
13762
|
missing.push({
|
|
13296
13763
|
path: target,
|
|
13297
13764
|
relativePath: file.relativePath,
|
|
@@ -13301,7 +13768,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
13301
13768
|
});
|
|
13302
13769
|
continue;
|
|
13303
13770
|
}
|
|
13304
|
-
const actualSha256 =
|
|
13771
|
+
const actualSha256 = sha25610(readFileSync12(target, "utf-8"));
|
|
13305
13772
|
if (actualSha256 !== file.sha256) {
|
|
13306
13773
|
drifted.push({
|
|
13307
13774
|
path: target,
|
|
@@ -13324,7 +13791,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
13324
13791
|
function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
|
|
13325
13792
|
const snapshot = readSessionRenderSnapshot(snapshotPath);
|
|
13326
13793
|
const targetHome = assertSafeTargetHome(snapshot.targetHome);
|
|
13327
|
-
const resolvedSnapshotPath =
|
|
13794
|
+
const resolvedSnapshotPath = resolve10(snapshotPath);
|
|
13328
13795
|
const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
|
|
13329
13796
|
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute4(snapshotRelativePath)) {
|
|
13330
13797
|
throw new SessionApplyError("Session snapshot must be stored inside its target home.");
|
|
@@ -13442,10 +13909,10 @@ function requiredRestoreHash(file) {
|
|
|
13442
13909
|
return file.previousSha256;
|
|
13443
13910
|
}
|
|
13444
13911
|
function readSessionRenderSnapshot(snapshotPath) {
|
|
13445
|
-
const resolved =
|
|
13446
|
-
if (!
|
|
13912
|
+
const resolved = resolve10(snapshotPath);
|
|
13913
|
+
if (!existsSync13(resolved))
|
|
13447
13914
|
throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
|
|
13448
|
-
const stat =
|
|
13915
|
+
const stat = lstatSync6(resolved);
|
|
13449
13916
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
13450
13917
|
throw new SessionApplyError(`Session snapshot is not a regular file: ${snapshotPath}`);
|
|
13451
13918
|
}
|
|
@@ -13454,7 +13921,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
13454
13921
|
}
|
|
13455
13922
|
let parsed;
|
|
13456
13923
|
try {
|
|
13457
|
-
parsed = JSON.parse(
|
|
13924
|
+
parsed = JSON.parse(readFileSync12(resolved, "utf8"));
|
|
13458
13925
|
} catch {
|
|
13459
13926
|
throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
|
|
13460
13927
|
}
|
|
@@ -13476,7 +13943,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
13476
13943
|
const previousManifest = snapshot.previousManifest;
|
|
13477
13944
|
const previousFiles = new Map;
|
|
13478
13945
|
for (const file of snapshot.files) {
|
|
13479
|
-
if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" ||
|
|
13946
|
+
if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" || sha25610(file.content) !== file.sha256) {
|
|
13480
13947
|
throw new SessionApplyError(`Session snapshot previous file metadata is invalid: ${snapshotPath}`);
|
|
13481
13948
|
}
|
|
13482
13949
|
resolveSnapshotFilePath(file.relativePath, file.path, targetHome);
|
|
@@ -13523,7 +13990,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
13523
13990
|
}
|
|
13524
13991
|
function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
|
|
13525
13992
|
assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
|
|
13526
|
-
const manifestPath =
|
|
13993
|
+
const manifestPath = resolve10(snapshot.manifestPath);
|
|
13527
13994
|
const manifestRelativePath = relative5(targetHome, manifestPath).replaceAll("\\", "/");
|
|
13528
13995
|
resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
|
|
13529
13996
|
const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
|
|
@@ -13532,7 +13999,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
13532
13999
|
}
|
|
13533
14000
|
let parsedManifest;
|
|
13534
14001
|
try {
|
|
13535
|
-
parsedManifest = JSON.parse(
|
|
14002
|
+
parsedManifest = JSON.parse(readFileSync12(manifestPath, "utf8"));
|
|
13536
14003
|
} catch {
|
|
13537
14004
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
|
|
13538
14005
|
}
|
|
@@ -13540,7 +14007,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
13540
14007
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
|
|
13541
14008
|
}
|
|
13542
14009
|
const appliedManifest = parsedManifest;
|
|
13543
|
-
if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" ||
|
|
14010
|
+
if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve10(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
|
|
13544
14011
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
|
|
13545
14012
|
}
|
|
13546
14013
|
const afterFiles = [];
|
|
@@ -13624,17 +14091,17 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
|
|
|
13624
14091
|
if (!Number.isFinite(createdAtMs)) {
|
|
13625
14092
|
throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
|
|
13626
14093
|
}
|
|
13627
|
-
for (const entry of
|
|
13628
|
-
const candidatePath =
|
|
13629
|
-
if (candidatePath ===
|
|
14094
|
+
for (const entry of readdirSync2(dirname9(snapshotPath))) {
|
|
14095
|
+
const candidatePath = resolve10(dirname9(snapshotPath), entry);
|
|
14096
|
+
if (candidatePath === resolve10(snapshotPath) || !entry.endsWith(".json"))
|
|
13630
14097
|
continue;
|
|
13631
|
-
const candidateStat =
|
|
14098
|
+
const candidateStat = lstatSync6(candidatePath);
|
|
13632
14099
|
if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
|
|
13633
14100
|
continue;
|
|
13634
14101
|
try {
|
|
13635
|
-
const candidate = JSON.parse(
|
|
14102
|
+
const candidate = JSON.parse(readFileSync12(candidatePath, "utf8"));
|
|
13636
14103
|
const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
|
|
13637
|
-
if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" &&
|
|
14104
|
+
if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve10(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
|
|
13638
14105
|
throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
|
|
13639
14106
|
}
|
|
13640
14107
|
} catch (error) {
|
|
@@ -13692,7 +14159,7 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
13692
14159
|
return "create";
|
|
13693
14160
|
}
|
|
13694
14161
|
if (file.role === "manifest" && previousManifest) {
|
|
13695
|
-
const previousManifestSha256 =
|
|
14162
|
+
const previousManifestSha256 = sha25610(`${JSON.stringify(previousManifest, null, 2)}
|
|
13696
14163
|
`);
|
|
13697
14164
|
if (previousManifestSha256 !== file.sha256) {
|
|
13698
14165
|
throw new SessionApplyError(`Cannot infer legacy v1 manifest action without a before-image: ${file.relativePath}`);
|
|
@@ -13703,15 +14170,15 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
13703
14170
|
}
|
|
13704
14171
|
function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
|
|
13705
14172
|
const path = resolveManifestRelativePath(relativePath, targetHome);
|
|
13706
|
-
if (
|
|
14173
|
+
if (resolve10(recordedPath) !== path) {
|
|
13707
14174
|
throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
|
|
13708
14175
|
}
|
|
13709
14176
|
return path;
|
|
13710
14177
|
}
|
|
13711
14178
|
function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
|
|
13712
14179
|
const target = resolvePlannedFilePath(plan, file, targetHome);
|
|
13713
|
-
const previousContent =
|
|
13714
|
-
const previousSha256 = previousContent === null ? null :
|
|
14180
|
+
const previousContent = existsSync13(target) ? readFileSync12(target, "utf-8") : null;
|
|
14181
|
+
const previousSha256 = previousContent === null ? null : sha25610(previousContent);
|
|
13715
14182
|
const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
|
|
13716
14183
|
const changed = previousContent !== file.content;
|
|
13717
14184
|
if (previousContent !== null && !options.force && !previouslyManaged) {
|
|
@@ -13809,10 +14276,10 @@ function planStaleFileResults(plan, targetHome, previousManifest, currentRelativ
|
|
|
13809
14276
|
}
|
|
13810
14277
|
function planStaleFileResult(file, targetHome, options) {
|
|
13811
14278
|
const target = resolveManifestRelativePath(file.relativePath, targetHome);
|
|
13812
|
-
if (!
|
|
14279
|
+
if (!existsSync13(target))
|
|
13813
14280
|
return null;
|
|
13814
|
-
const previousContent =
|
|
13815
|
-
const previousSha256 =
|
|
14281
|
+
const previousContent = readFileSync12(target, "utf-8");
|
|
14282
|
+
const previousSha256 = sha25610(previousContent);
|
|
13816
14283
|
if (!options.force && previousSha256 !== file.sha256) {
|
|
13817
14284
|
return {
|
|
13818
14285
|
path: target,
|
|
@@ -13856,19 +14323,19 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
|
|
|
13856
14323
|
return previousHashes.get(file.relativePath) === previousSha256;
|
|
13857
14324
|
}
|
|
13858
14325
|
function resolvePlannedFilePath(plan, file, targetHome) {
|
|
13859
|
-
const target =
|
|
14326
|
+
const target = resolve10(targetHome, ...file.relativePath.split("/"));
|
|
13860
14327
|
const rel = relative5(targetHome, target);
|
|
13861
14328
|
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
|
|
13862
14329
|
throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
|
|
13863
14330
|
}
|
|
13864
|
-
if (
|
|
14331
|
+
if (resolve10(file.path) !== target) {
|
|
13865
14332
|
throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
|
|
13866
14333
|
}
|
|
13867
14334
|
assertNoSymlinkSegments2(targetHome, target);
|
|
13868
14335
|
return target;
|
|
13869
14336
|
}
|
|
13870
14337
|
function resolveManifestRelativePath(relativePath, targetHome) {
|
|
13871
|
-
const target =
|
|
14338
|
+
const target = resolve10(targetHome, ...relativePath.split(/[\\/]+/));
|
|
13872
14339
|
const rel = relative5(targetHome, target);
|
|
13873
14340
|
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
|
|
13874
14341
|
throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
|
|
@@ -13877,10 +14344,10 @@ function resolveManifestRelativePath(relativePath, targetHome) {
|
|
|
13877
14344
|
return target;
|
|
13878
14345
|
}
|
|
13879
14346
|
function readPreviousManifest(path) {
|
|
13880
|
-
if (!
|
|
14347
|
+
if (!existsSync13(path))
|
|
13881
14348
|
return null;
|
|
13882
14349
|
try {
|
|
13883
|
-
const parsed = JSON.parse(
|
|
14350
|
+
const parsed = JSON.parse(readFileSync12(path, "utf-8"));
|
|
13884
14351
|
if (parsed.schema !== SESSION_RENDER_SCHEMA)
|
|
13885
14352
|
return null;
|
|
13886
14353
|
if (!Array.isArray(parsed.files))
|
|
@@ -13919,13 +14386,13 @@ function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
|
|
|
13919
14386
|
}
|
|
13920
14387
|
function currentSessionFileHash(path, targetHome) {
|
|
13921
14388
|
assertNoSymlinkSegments2(targetHome, path);
|
|
13922
|
-
if (!
|
|
14389
|
+
if (!existsSync13(path))
|
|
13923
14390
|
return null;
|
|
13924
|
-
const stat =
|
|
14391
|
+
const stat = lstatSync6(path);
|
|
13925
14392
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
13926
14393
|
throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
|
|
13927
14394
|
}
|
|
13928
|
-
return
|
|
14395
|
+
return sha25610(readFileSync12(path, "utf-8"));
|
|
13929
14396
|
}
|
|
13930
14397
|
function requiredPreviousHash(result) {
|
|
13931
14398
|
if (result.previousSha256 === null) {
|
|
@@ -13934,13 +14401,13 @@ function requiredPreviousHash(result) {
|
|
|
13934
14401
|
return result.previousSha256;
|
|
13935
14402
|
}
|
|
13936
14403
|
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
|
|
13937
|
-
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) =>
|
|
13938
|
-
const content =
|
|
14404
|
+
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync13(result.path)).map((result) => {
|
|
14405
|
+
const content = readFileSync12(result.path, "utf-8");
|
|
13939
14406
|
return {
|
|
13940
14407
|
path: result.path,
|
|
13941
14408
|
relativePath: result.relativePath,
|
|
13942
14409
|
role: result.role,
|
|
13943
|
-
sha256:
|
|
14410
|
+
sha256: sha25610(content),
|
|
13944
14411
|
content
|
|
13945
14412
|
};
|
|
13946
14413
|
});
|
|
@@ -13953,7 +14420,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
13953
14420
|
};
|
|
13954
14421
|
}
|
|
13955
14422
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
13956
|
-
const snapshotPath =
|
|
14423
|
+
const snapshotPath = resolve10(targetHome, ".hasna", "session-render-snapshots", `${timestamp}-${randomUUID4()}.json`);
|
|
13957
14424
|
const afterFiles = results.map((result) => {
|
|
13958
14425
|
if (result.action === "conflict") {
|
|
13959
14426
|
throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
|
|
@@ -14001,12 +14468,12 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
14001
14468
|
function assertSafeTargetHome(targetHome) {
|
|
14002
14469
|
if (!isAbsolute4(targetHome))
|
|
14003
14470
|
throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
|
|
14004
|
-
const normalized =
|
|
14471
|
+
const normalized = resolve10(targetHome);
|
|
14005
14472
|
if (normalized === parse5(normalized).root) {
|
|
14006
14473
|
throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
|
|
14007
14474
|
}
|
|
14008
14475
|
assertNoSymlinkAncestors3(normalized);
|
|
14009
|
-
if (
|
|
14476
|
+
if (existsSync13(normalized) && lstatSync6(normalized).isSymbolicLink()) {
|
|
14010
14477
|
throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
|
|
14011
14478
|
}
|
|
14012
14479
|
return normalized;
|
|
@@ -14016,28 +14483,28 @@ function assertNoSymlinkSegments2(root, target) {
|
|
|
14016
14483
|
const rel = relative5(root, target);
|
|
14017
14484
|
let current = root;
|
|
14018
14485
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
14019
|
-
current =
|
|
14020
|
-
if (
|
|
14486
|
+
current = join14(current, segment);
|
|
14487
|
+
if (existsSync13(current) && lstatSync6(current).isSymbolicLink()) {
|
|
14021
14488
|
throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
|
|
14022
14489
|
}
|
|
14023
14490
|
}
|
|
14024
14491
|
}
|
|
14025
14492
|
function assertNoSymlinkAncestors3(path) {
|
|
14026
|
-
const normalized =
|
|
14493
|
+
const normalized = resolve10(path);
|
|
14027
14494
|
const parsed = parse5(normalized);
|
|
14028
14495
|
let current = parsed.root;
|
|
14029
14496
|
const rel = relative5(parsed.root, normalized);
|
|
14030
14497
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
14031
|
-
current =
|
|
14032
|
-
if (!
|
|
14498
|
+
current = join14(current, segment);
|
|
14499
|
+
if (!existsSync13(current))
|
|
14033
14500
|
return;
|
|
14034
|
-
if (
|
|
14501
|
+
if (lstatSync6(current).isSymbolicLink()) {
|
|
14035
14502
|
throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
|
|
14036
14503
|
}
|
|
14037
14504
|
}
|
|
14038
14505
|
}
|
|
14039
|
-
function
|
|
14040
|
-
return
|
|
14506
|
+
function sha25610(content) {
|
|
14507
|
+
return createHash10("sha256").update(content).digest("hex");
|
|
14041
14508
|
}
|
|
14042
14509
|
// src/lib/project-dashboard-standard.ts
|
|
14043
14510
|
var PROJECT_DASHBOARD_STANDARD_SLUG = "agent-managed-project-dashboard-standard";
|
|
@@ -14340,13 +14807,13 @@ async function ensureDangerousOperationGuardStandardConfig(store = resolveConfig
|
|
|
14340
14807
|
}
|
|
14341
14808
|
}
|
|
14342
14809
|
// src/lib/sync.ts
|
|
14343
|
-
import { existsSync as
|
|
14344
|
-
import { basename as
|
|
14810
|
+
import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync14 } from "fs";
|
|
14811
|
+
import { basename as basename6, extname as extname3, join as join16 } from "path";
|
|
14345
14812
|
|
|
14346
14813
|
// src/lib/sync-dir.ts
|
|
14347
|
-
import { existsSync as
|
|
14348
|
-
import { join as
|
|
14349
|
-
import { homedir as
|
|
14814
|
+
import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync13, statSync as statSync6 } from "fs";
|
|
14815
|
+
import { join as join15, relative as relative6 } from "path";
|
|
14816
|
+
import { homedir as homedir9 } from "os";
|
|
14350
14817
|
var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
|
|
14351
14818
|
function shouldSkip(p) {
|
|
14352
14819
|
return SKIP.some((s) => p.includes(s));
|
|
@@ -14354,11 +14821,11 @@ function shouldSkip(p) {
|
|
|
14354
14821
|
async function syncFromDir(dir, opts = {}) {
|
|
14355
14822
|
const store = opts.store ?? resolveConfigStore();
|
|
14356
14823
|
const absDir = expandPath(dir);
|
|
14357
|
-
if (!
|
|
14824
|
+
if (!existsSync14(absDir))
|
|
14358
14825
|
return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
|
|
14359
|
-
const files = opts.recursive !== false ? walkDir(absDir) :
|
|
14826
|
+
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync3(absDir).map((f) => join15(absDir, f)).filter((f) => statSync6(f).isFile());
|
|
14360
14827
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
14361
|
-
const home =
|
|
14828
|
+
const home = homedir9();
|
|
14362
14829
|
const allConfigs = await store.listConfigs();
|
|
14363
14830
|
for (const file of files) {
|
|
14364
14831
|
if (shouldSkip(file)) {
|
|
@@ -14366,20 +14833,21 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14366
14833
|
continue;
|
|
14367
14834
|
}
|
|
14368
14835
|
try {
|
|
14369
|
-
const content =
|
|
14836
|
+
const content = readFileSync13(file, "utf-8");
|
|
14370
14837
|
if (content.length > 500000) {
|
|
14371
14838
|
result.skipped.push(file + " (too large)");
|
|
14372
14839
|
continue;
|
|
14373
14840
|
}
|
|
14374
14841
|
const targetPath = file.replace(home, "~");
|
|
14842
|
+
const redacted = redactContent(content, redactFormatForTarget(targetPath, detectFormat(file)));
|
|
14375
14843
|
const existing = allConfigs.find((c) => c.target_path === targetPath);
|
|
14376
14844
|
if (!existing) {
|
|
14377
14845
|
if (!opts.dryRun)
|
|
14378
|
-
await store.createConfig({ name: relative6(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content });
|
|
14846
|
+
await store.createConfig({ name: relative6(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content: redacted.content });
|
|
14379
14847
|
result.added++;
|
|
14380
|
-
} else if (existing.content !== content) {
|
|
14848
|
+
} else if (existing.content !== redacted.content) {
|
|
14381
14849
|
if (!opts.dryRun)
|
|
14382
|
-
await store.updateConfig(existing.id, { content });
|
|
14850
|
+
await store.updateConfig(existing.id, { content: redacted.content });
|
|
14383
14851
|
result.updated++;
|
|
14384
14852
|
} else {
|
|
14385
14853
|
result.unchanged++;
|
|
@@ -14392,7 +14860,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14392
14860
|
}
|
|
14393
14861
|
async function syncToDir(dir, opts = {}) {
|
|
14394
14862
|
const store = opts.store ?? resolveConfigStore();
|
|
14395
|
-
const home =
|
|
14863
|
+
const home = homedir9();
|
|
14396
14864
|
const absDir = expandPath(dir);
|
|
14397
14865
|
const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
|
|
14398
14866
|
const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
|
|
@@ -14415,8 +14883,8 @@ async function syncToDir(dir, opts = {}) {
|
|
|
14415
14883
|
return result;
|
|
14416
14884
|
}
|
|
14417
14885
|
function walkDir(dir, files = []) {
|
|
14418
|
-
for (const entry of
|
|
14419
|
-
const full =
|
|
14886
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
14887
|
+
const full = join15(dir, entry.name);
|
|
14420
14888
|
if (shouldSkip(full))
|
|
14421
14889
|
continue;
|
|
14422
14890
|
if (entry.isDirectory())
|
|
@@ -14438,7 +14906,7 @@ var CLAUDE_PROMPT_OUTPUTS = [
|
|
|
14438
14906
|
{ agent: "qwen", target_path: "~/.qwen/QWEN.md", transform: "codex-flat" }
|
|
14439
14907
|
];
|
|
14440
14908
|
function claudeRuleOutputs(fileName) {
|
|
14441
|
-
const stem =
|
|
14909
|
+
const stem = basename6(fileName, extname3(fileName));
|
|
14442
14910
|
return [
|
|
14443
14911
|
{ agent: "cursor", target_path: `~/.cursor/rules/${stem}.mdc`, transform: "cursor-mdc" }
|
|
14444
14912
|
];
|
|
@@ -14477,15 +14945,15 @@ function isGeneratedOutputTarget2(config, owners) {
|
|
|
14477
14945
|
return !!ownerIds && !ownerIds.has(config.id);
|
|
14478
14946
|
}
|
|
14479
14947
|
function hasClaudePromptSource() {
|
|
14480
|
-
return
|
|
14948
|
+
return existsSync15(expandPath("~/.claude/CLAUDE.md"));
|
|
14481
14949
|
}
|
|
14482
14950
|
function hasClaudeRuleSourceForCursorTarget(targetPath) {
|
|
14483
14951
|
const absoluteTargetPath = expandPath(targetPath);
|
|
14484
14952
|
const absolutePrefix = expandPath("~/.cursor/rules");
|
|
14485
14953
|
if (!absoluteTargetPath.startsWith(`${absolutePrefix}/`) || !absoluteTargetPath.endsWith(".mdc"))
|
|
14486
14954
|
return false;
|
|
14487
|
-
const stem =
|
|
14488
|
-
return
|
|
14955
|
+
const stem = basename6(absoluteTargetPath, ".mdc");
|
|
14956
|
+
return existsSync15(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync15(expandPath(`~/.claude/rules/${stem}.mdc`));
|
|
14489
14957
|
}
|
|
14490
14958
|
function isKnownGeneratedTargetPath(targetPath) {
|
|
14491
14959
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
@@ -14550,11 +15018,11 @@ async function syncProject(opts) {
|
|
|
14550
15018
|
const allConfigs = await store.listConfigs();
|
|
14551
15019
|
const machine = detectMachineContext();
|
|
14552
15020
|
for (const pf of PROJECT_CONFIG_FILES) {
|
|
14553
|
-
const abs =
|
|
14554
|
-
if (!
|
|
15021
|
+
const abs = join16(absDir, pf.file);
|
|
15022
|
+
if (!existsSync15(abs))
|
|
14555
15023
|
continue;
|
|
14556
15024
|
try {
|
|
14557
|
-
const rawContent =
|
|
15025
|
+
const rawContent = readFileSync14(abs, "utf-8");
|
|
14558
15026
|
if (rawContent.length > 500000) {
|
|
14559
15027
|
result.skipped.push(pf.file);
|
|
14560
15028
|
continue;
|
|
@@ -14583,20 +15051,20 @@ async function syncProject(opts) {
|
|
|
14583
15051
|
}
|
|
14584
15052
|
}
|
|
14585
15053
|
for (const ruleDir of [
|
|
14586
|
-
{ dir:
|
|
14587
|
-
{ dir:
|
|
14588
|
-
{ dir:
|
|
14589
|
-
{ dir:
|
|
14590
|
-
{ dir:
|
|
14591
|
-
{ dir:
|
|
14592
|
-
{ dir:
|
|
15054
|
+
{ dir: join16(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
|
|
15055
|
+
{ dir: join16(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
|
|
15056
|
+
{ dir: join16(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
|
|
15057
|
+
{ dir: join16(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
|
|
15058
|
+
{ dir: join16(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
|
|
15059
|
+
{ dir: join16(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
|
|
15060
|
+
{ dir: join16(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
|
|
14593
15061
|
]) {
|
|
14594
|
-
if (!
|
|
15062
|
+
if (!existsSync15(ruleDir.dir))
|
|
14595
15063
|
continue;
|
|
14596
|
-
const mdFiles =
|
|
15064
|
+
const mdFiles = readdirSync4(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
14597
15065
|
for (const f of mdFiles) {
|
|
14598
|
-
const abs =
|
|
14599
|
-
const raw =
|
|
15066
|
+
const abs = join16(ruleDir.dir, f);
|
|
15067
|
+
const raw = readFileSync14(abs, "utf-8");
|
|
14600
15068
|
const redacted = redactContent(raw, "markdown");
|
|
14601
15069
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
14602
15070
|
const content = machineAware.content;
|
|
@@ -14635,20 +15103,20 @@ async function syncKnown(opts = {}) {
|
|
|
14635
15103
|
for (const known of targets) {
|
|
14636
15104
|
if (known.rulesDir) {
|
|
14637
15105
|
const absDir = expandPath(known.rulesDir);
|
|
14638
|
-
if (!
|
|
15106
|
+
if (!existsSync15(absDir)) {
|
|
14639
15107
|
result.skipped.push(known.rulesDir);
|
|
14640
15108
|
continue;
|
|
14641
15109
|
}
|
|
14642
15110
|
const extensions = known.rulesExtensions ?? [".md", ".mdc"];
|
|
14643
|
-
const ruleFiles =
|
|
15111
|
+
const ruleFiles = readdirSync4(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
|
|
14644
15112
|
for (const f of ruleFiles) {
|
|
14645
|
-
const abs2 =
|
|
15113
|
+
const abs2 = join16(absDir, f);
|
|
14646
15114
|
const targetPath = abs2.replace(home, "~");
|
|
14647
15115
|
if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
|
|
14648
15116
|
result.skipped.push(`${targetPath} (generated output)`);
|
|
14649
15117
|
continue;
|
|
14650
15118
|
}
|
|
14651
|
-
const raw =
|
|
15119
|
+
const raw = readFileSync14(abs2, "utf-8");
|
|
14652
15120
|
const redacted = redactContent(raw, "markdown");
|
|
14653
15121
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
14654
15122
|
const content = machineAware.content;
|
|
@@ -14676,18 +15144,18 @@ async function syncKnown(opts = {}) {
|
|
|
14676
15144
|
continue;
|
|
14677
15145
|
}
|
|
14678
15146
|
const abs = expandPath(known.path);
|
|
14679
|
-
if (!
|
|
15147
|
+
if (!existsSync15(abs)) {
|
|
14680
15148
|
result.skipped.push(known.path);
|
|
14681
15149
|
continue;
|
|
14682
15150
|
}
|
|
14683
15151
|
try {
|
|
14684
|
-
const rawContent = normalizeKnownConfigSource(known,
|
|
15152
|
+
const rawContent = normalizeKnownConfigSource(known, readFileSync14(abs, "utf-8"));
|
|
14685
15153
|
if (rawContent.length > 500000) {
|
|
14686
15154
|
result.skipped.push(known.path + " (too large)");
|
|
14687
15155
|
continue;
|
|
14688
15156
|
}
|
|
14689
15157
|
const fmt = known.format ?? detectFormat(abs);
|
|
14690
|
-
const redacted = redactContent(rawContent, fmt);
|
|
15158
|
+
const redacted = redactContent(rawContent, redactFormatForTarget(abs, fmt));
|
|
14691
15159
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
14692
15160
|
const content = machineAware.content;
|
|
14693
15161
|
const isTemplate2 = redacted.isTemplate || machineAware.changed;
|
|
@@ -14784,9 +15252,9 @@ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
|
|
|
14784
15252
|
}
|
|
14785
15253
|
function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
|
|
14786
15254
|
const path = expandPath(targetPath);
|
|
14787
|
-
if (!
|
|
15255
|
+
if (!existsSync15(path))
|
|
14788
15256
|
return `(file not found on disk: ${path})`;
|
|
14789
|
-
const diskContent =
|
|
15257
|
+
const diskContent = readFileSync14(path, "utf-8");
|
|
14790
15258
|
if (diskContent === expectedContent)
|
|
14791
15259
|
return "(no diff \u2014 identical)";
|
|
14792
15260
|
const format = redactFormatForTarget(targetPath, storedFormat);
|
|
@@ -14944,26 +15412,26 @@ function detectFormat(filePath) {
|
|
|
14944
15412
|
return "text";
|
|
14945
15413
|
}
|
|
14946
15414
|
// src/lib/export.ts
|
|
14947
|
-
import { existsSync as
|
|
14948
|
-
import { join as
|
|
15415
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
15416
|
+
import { join as join17, resolve as resolve11 } from "path";
|
|
14949
15417
|
import { tmpdir } from "os";
|
|
14950
15418
|
async function exportConfigs(outputPath, opts = {}) {
|
|
14951
15419
|
const store = opts.store ?? resolveConfigStore();
|
|
14952
15420
|
const configs = await store.listConfigs(opts.filter);
|
|
14953
|
-
const absOutput =
|
|
14954
|
-
const tmpDir =
|
|
14955
|
-
const contentsDir =
|
|
15421
|
+
const absOutput = resolve11(outputPath);
|
|
15422
|
+
const tmpDir = join17(tmpdir(), `configs-export-${Date.now()}`);
|
|
15423
|
+
const contentsDir = join17(tmpDir, "contents");
|
|
14956
15424
|
try {
|
|
14957
|
-
|
|
15425
|
+
mkdirSync8(contentsDir, { recursive: true });
|
|
14958
15426
|
const manifest = {
|
|
14959
15427
|
version: "1.0.0",
|
|
14960
15428
|
exported_at: new Date().toISOString(),
|
|
14961
15429
|
configs: configs.map(({ content: _content, ...meta }) => meta)
|
|
14962
15430
|
};
|
|
14963
|
-
|
|
15431
|
+
writeFileSync6(join17(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
|
|
14964
15432
|
for (const config of configs) {
|
|
14965
15433
|
const fileName = `${config.slug}.${config.format === "text" ? "txt" : config.format}`;
|
|
14966
|
-
|
|
15434
|
+
writeFileSync6(join17(contentsDir, fileName), config.content, "utf-8");
|
|
14967
15435
|
}
|
|
14968
15436
|
const proc = Bun.spawn(["tar", "czf", absOutput, "-C", tmpDir, "."], {
|
|
14969
15437
|
stdout: "pipe",
|
|
@@ -14976,23 +15444,23 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
14976
15444
|
}
|
|
14977
15445
|
return { path: absOutput, count: configs.length };
|
|
14978
15446
|
} finally {
|
|
14979
|
-
if (
|
|
15447
|
+
if (existsSync16(tmpDir)) {
|
|
14980
15448
|
rmSync4(tmpDir, { recursive: true, force: true });
|
|
14981
15449
|
}
|
|
14982
15450
|
}
|
|
14983
15451
|
}
|
|
14984
15452
|
// src/lib/import.ts
|
|
14985
|
-
import { existsSync as
|
|
14986
|
-
import { join as
|
|
15453
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9, readFileSync as readFileSync15, rmSync as rmSync5 } from "fs";
|
|
15454
|
+
import { join as join18, resolve as resolve12 } from "path";
|
|
14987
15455
|
import { tmpdir as tmpdir2 } from "os";
|
|
14988
15456
|
async function importConfigs(bundlePath, opts = {}) {
|
|
14989
15457
|
const store = opts.store ?? resolveConfigStore();
|
|
14990
15458
|
const conflict = opts.conflict ?? "skip";
|
|
14991
|
-
const absPath =
|
|
14992
|
-
const tmpDir =
|
|
15459
|
+
const absPath = resolve12(bundlePath);
|
|
15460
|
+
const tmpDir = join18(tmpdir2(), `configs-import-${Date.now()}`);
|
|
14993
15461
|
const result = { created: 0, updated: 0, skipped: 0, errors: [] };
|
|
14994
15462
|
try {
|
|
14995
|
-
|
|
15463
|
+
mkdirSync9(tmpDir, { recursive: true });
|
|
14996
15464
|
const proc = Bun.spawn(["tar", "xzf", absPath, "-C", tmpDir], {
|
|
14997
15465
|
stdout: "pipe",
|
|
14998
15466
|
stderr: "pipe"
|
|
@@ -15002,15 +15470,15 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
15002
15470
|
const stderr = await new Response(proc.stderr).text();
|
|
15003
15471
|
throw new Error(`tar extraction failed: ${stderr}`);
|
|
15004
15472
|
}
|
|
15005
|
-
const manifestPath =
|
|
15006
|
-
if (!
|
|
15473
|
+
const manifestPath = join18(tmpDir, "manifest.json");
|
|
15474
|
+
if (!existsSync17(manifestPath))
|
|
15007
15475
|
throw new Error("Invalid bundle: missing manifest.json");
|
|
15008
|
-
const manifest = JSON.parse(
|
|
15476
|
+
const manifest = JSON.parse(readFileSync15(manifestPath, "utf-8"));
|
|
15009
15477
|
for (const meta of manifest.configs) {
|
|
15010
15478
|
try {
|
|
15011
15479
|
const ext = meta.format === "text" ? "txt" : meta.format;
|
|
15012
|
-
const contentFile =
|
|
15013
|
-
const content =
|
|
15480
|
+
const contentFile = join18(tmpDir, "contents", `${meta.slug}.${ext}`);
|
|
15481
|
+
const content = existsSync17(contentFile) ? readFileSync15(contentFile, "utf-8") : "";
|
|
15014
15482
|
let existing = null;
|
|
15015
15483
|
try {
|
|
15016
15484
|
existing = await store.getConfig(meta.slug);
|
|
@@ -15044,16 +15512,16 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
15044
15512
|
}
|
|
15045
15513
|
return result;
|
|
15046
15514
|
} finally {
|
|
15047
|
-
if (
|
|
15515
|
+
if (existsSync17(tmpDir)) {
|
|
15048
15516
|
rmSync5(tmpDir, { recursive: true, force: true });
|
|
15049
15517
|
}
|
|
15050
15518
|
}
|
|
15051
15519
|
}
|
|
15052
15520
|
// src/lib/package-manager-guard.ts
|
|
15053
15521
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
15054
|
-
import { existsSync as
|
|
15055
|
-
import { homedir as
|
|
15056
|
-
import { basename as
|
|
15522
|
+
import { existsSync as existsSync18, lstatSync as lstatSync7, readdirSync as readdirSync5, readFileSync as readFileSync16 } from "fs";
|
|
15523
|
+
import { homedir as homedir10 } from "os";
|
|
15524
|
+
import { basename as basename7, dirname as dirname10, isAbsolute as isAbsolute5, join as join19, relative as relative7, resolve as resolve13 } from "path";
|
|
15057
15525
|
var SKIP_DIRS = new Set([
|
|
15058
15526
|
".git",
|
|
15059
15527
|
"node_modules",
|
|
@@ -15090,14 +15558,14 @@ var TOKEN_VALUE_PATTERNS = [
|
|
|
15090
15558
|
{ re: /xoxb-[0-9]+-[A-Za-z0-9-]+/, rule: "literal-slack-token", detail: "literal Slack token-like value" }
|
|
15091
15559
|
];
|
|
15092
15560
|
function scanPackageManagerSecrets(options = {}) {
|
|
15093
|
-
const cwd = options.cwd ?
|
|
15094
|
-
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) =>
|
|
15561
|
+
const cwd = options.cwd ? resolve13(options.cwd) : process.cwd();
|
|
15562
|
+
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve13(cwd, root));
|
|
15095
15563
|
const findings = [];
|
|
15096
15564
|
let scannedFiles = 0;
|
|
15097
15565
|
for (const root of roots) {
|
|
15098
|
-
if (!
|
|
15566
|
+
if (!existsSync18(root))
|
|
15099
15567
|
continue;
|
|
15100
|
-
const stat =
|
|
15568
|
+
const stat = lstatSync7(root);
|
|
15101
15569
|
if (stat.isFile()) {
|
|
15102
15570
|
if (!shouldScanRepoFile(root))
|
|
15103
15571
|
continue;
|
|
@@ -15105,7 +15573,7 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15105
15573
|
if (text === null)
|
|
15106
15574
|
continue;
|
|
15107
15575
|
scannedFiles++;
|
|
15108
|
-
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root),
|
|
15576
|
+
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname10(root)));
|
|
15109
15577
|
continue;
|
|
15110
15578
|
}
|
|
15111
15579
|
if (!stat.isDirectory())
|
|
@@ -15122,10 +15590,10 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15122
15590
|
}
|
|
15123
15591
|
}
|
|
15124
15592
|
if (options.includeHome) {
|
|
15125
|
-
const home =
|
|
15593
|
+
const home = homedir10();
|
|
15126
15594
|
for (const name of HOME_FILES) {
|
|
15127
|
-
const file =
|
|
15128
|
-
if (!
|
|
15595
|
+
const file = join19(home, name);
|
|
15596
|
+
if (!existsSync18(file))
|
|
15129
15597
|
continue;
|
|
15130
15598
|
const text = readTextFile(file);
|
|
15131
15599
|
if (text === null)
|
|
@@ -15145,16 +15613,16 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15145
15613
|
function collectRepoFiles(root) {
|
|
15146
15614
|
const out = [];
|
|
15147
15615
|
const visit = (dir) => {
|
|
15148
|
-
for (const entry of
|
|
15616
|
+
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
15149
15617
|
if (entry.isDirectory()) {
|
|
15150
15618
|
if (SKIP_DIRS.has(entry.name))
|
|
15151
15619
|
continue;
|
|
15152
|
-
visit(
|
|
15620
|
+
visit(join19(dir, entry.name));
|
|
15153
15621
|
continue;
|
|
15154
15622
|
}
|
|
15155
15623
|
if (!entry.isFile())
|
|
15156
15624
|
continue;
|
|
15157
|
-
const file =
|
|
15625
|
+
const file = join19(dir, entry.name);
|
|
15158
15626
|
if (shouldScanRepoFile(file))
|
|
15159
15627
|
out.push(file);
|
|
15160
15628
|
}
|
|
@@ -15163,11 +15631,11 @@ function collectRepoFiles(root) {
|
|
|
15163
15631
|
return out;
|
|
15164
15632
|
}
|
|
15165
15633
|
function shouldScanRepoFile(file) {
|
|
15166
|
-
const name =
|
|
15634
|
+
const name = basename7(file);
|
|
15167
15635
|
return isNpmrcName(name) || isBunConfigName(name) || LOCKFILE_NAMES.has(name);
|
|
15168
15636
|
}
|
|
15169
15637
|
function classifyRepoFile(file) {
|
|
15170
|
-
const name =
|
|
15638
|
+
const name = basename7(file);
|
|
15171
15639
|
if (isNpmrcName(name))
|
|
15172
15640
|
return "repo-npmrc";
|
|
15173
15641
|
if (isBunConfigName(name))
|
|
@@ -15189,10 +15657,10 @@ function isNpmrcName(name) {
|
|
|
15189
15657
|
}
|
|
15190
15658
|
function readTextFile(file) {
|
|
15191
15659
|
try {
|
|
15192
|
-
const stat =
|
|
15660
|
+
const stat = lstatSync7(file);
|
|
15193
15661
|
if (!stat.isFile() || stat.size > 5000000)
|
|
15194
15662
|
return null;
|
|
15195
|
-
const buf =
|
|
15663
|
+
const buf = readFileSync16(file);
|
|
15196
15664
|
if (buf.includes(0))
|
|
15197
15665
|
return null;
|
|
15198
15666
|
return buf.toString("utf-8");
|
|
@@ -15392,7 +15860,7 @@ function trackedFiles(root) {
|
|
|
15392
15860
|
}
|
|
15393
15861
|
function isTrackedFile(file) {
|
|
15394
15862
|
try {
|
|
15395
|
-
const repoRoot = execFileSync2("git", ["-C",
|
|
15863
|
+
const repoRoot = execFileSync2("git", ["-C", dirname10(file), "rev-parse", "--show-toplevel"], {
|
|
15396
15864
|
encoding: "utf-8",
|
|
15397
15865
|
stdio: ["ignore", "pipe", "ignore"]
|
|
15398
15866
|
}).trim();
|
|
@@ -15422,7 +15890,7 @@ function stripInlineComment(value) {
|
|
|
15422
15890
|
return value.replace(/\s[#;].*$/, "").trim();
|
|
15423
15891
|
}
|
|
15424
15892
|
function displayPath(file, root) {
|
|
15425
|
-
const home =
|
|
15893
|
+
const home = homedir10();
|
|
15426
15894
|
if (root === home && (file === home || file.startsWith(home + "/")))
|
|
15427
15895
|
return "~/" + toPosix(relative7(home, file));
|
|
15428
15896
|
if (isAbsolute5(root) && file.startsWith(root + "/"))
|
|
@@ -15444,6 +15912,7 @@ export {
|
|
|
15444
15912
|
syncKnown,
|
|
15445
15913
|
syncFromDir,
|
|
15446
15914
|
stripClaudeOnlySections,
|
|
15915
|
+
stationProfileSource,
|
|
15447
15916
|
sourcesFromIdentityExport,
|
|
15448
15917
|
sourceFromFilePath,
|
|
15449
15918
|
sourceFromConfig,
|
|
@@ -15454,20 +15923,27 @@ export {
|
|
|
15454
15923
|
scanSecrets,
|
|
15455
15924
|
scanPackageManagerSecrets,
|
|
15456
15925
|
restoreSessionRenderSnapshot,
|
|
15926
|
+
resolveStationProfilePackages,
|
|
15927
|
+
resolveStationProfileMachine,
|
|
15457
15928
|
resolveSessionTargetOwnership,
|
|
15458
15929
|
resolveSessionPath,
|
|
15459
15930
|
resolveProfileVariables,
|
|
15460
15931
|
resolveConfigStore,
|
|
15461
15932
|
resolveCloudConfig,
|
|
15462
15933
|
resolveAssetDestination,
|
|
15934
|
+
resolveAndRenderProviderContext,
|
|
15463
15935
|
resolveAgentOperatingRulesPayload,
|
|
15464
15936
|
renderTemplatePreview,
|
|
15465
15937
|
renderTemplate,
|
|
15938
|
+
renderProviderFragment,
|
|
15466
15939
|
renderMachineAwareContentPreview,
|
|
15467
15940
|
renderMachineAwareContent,
|
|
15941
|
+
refreshStationProfile,
|
|
15468
15942
|
redactContent,
|
|
15469
15943
|
reconcileManagedSkillRuntimes,
|
|
15944
|
+
readStationProfile,
|
|
15470
15945
|
providerVersionSatisfies,
|
|
15946
|
+
providerContextAuditLine,
|
|
15471
15947
|
previewConfigs,
|
|
15472
15948
|
planSessionRender,
|
|
15473
15949
|
planProjectContext,
|
|
@@ -15479,7 +15955,9 @@ export {
|
|
|
15479
15955
|
normalizeProfileConfigBinding,
|
|
15480
15956
|
normalizeProfileAssetBinding,
|
|
15481
15957
|
normalizeOsFamily,
|
|
15958
|
+
normalizeEndpointOrigin,
|
|
15482
15959
|
normalizeBoundedReadOptions,
|
|
15960
|
+
matchProviderEndpoint,
|
|
15483
15961
|
machineContextToVariables,
|
|
15484
15962
|
legacyProfileConfigBinding,
|
|
15485
15963
|
isTemplate,
|
|
@@ -15487,6 +15965,7 @@ export {
|
|
|
15487
15965
|
inspectManagedSkillRuntimes,
|
|
15488
15966
|
importConfigs,
|
|
15489
15967
|
hasSecrets,
|
|
15968
|
+
getStationProfileCachePath,
|
|
15490
15969
|
getRawStoreRoot,
|
|
15491
15970
|
getConfigsStatus,
|
|
15492
15971
|
extractTemplateVars,
|
|
@@ -15513,6 +15992,7 @@ export {
|
|
|
15513
15992
|
compareAgentOperatingRulesVersions,
|
|
15514
15993
|
cleanSessionPathInput,
|
|
15515
15994
|
checkSessionRenderDrift,
|
|
15995
|
+
buildStationProfileBlock,
|
|
15516
15996
|
buildOpenCodeAgentsMd,
|
|
15517
15997
|
buildCursorMdc,
|
|
15518
15998
|
buildCodexAgentsMd,
|
|
@@ -15526,6 +16006,10 @@ export {
|
|
|
15526
16006
|
applyConfig,
|
|
15527
16007
|
TemplateRenderError,
|
|
15528
16008
|
SessionApplyError,
|
|
16009
|
+
STATION_PROFILE_SOURCE_ID,
|
|
16010
|
+
STATION_PROFILE_MAX_BYTES,
|
|
16011
|
+
STATION_PROFILE_LAYER,
|
|
16012
|
+
STATION_PROFILE_CACHE_FILENAME,
|
|
15529
16013
|
SESSION_TOOL_ADAPTERS,
|
|
15530
16014
|
SESSION_RENDER_TOOLS,
|
|
15531
16015
|
SESSION_RENDER_SCHEMA,
|
|
@@ -15535,6 +16019,11 @@ export {
|
|
|
15535
16019
|
RAW_STORE_ROOT_ENV,
|
|
15536
16020
|
ProjectContextError,
|
|
15537
16021
|
ProfileNotFoundError,
|
|
16022
|
+
PROVIDER_ENDPOINT_REGISTRY,
|
|
16023
|
+
PROVIDER_CONTEXT_SCHEMA,
|
|
16024
|
+
PROVIDER_CONTEXT_MANIFEST,
|
|
16025
|
+
PROVIDER_CONTEXT_INVARIANT_ID,
|
|
16026
|
+
PROVIDER_CONTEXT_DIR,
|
|
15538
16027
|
PROVIDER_CAPABILITY_SCHEMA,
|
|
15539
16028
|
PROVIDER_CAPABILITY_DESCRIPTORS,
|
|
15540
16029
|
PROVIDER_CAPABILITIES,
|