@hasna/instructions 0.7.2 → 0.7.5
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/README.md +13 -0
- package/dist/chunks/{apply-wj7wh5km.js → apply-1cpkx082.js} +2 -2
- package/dist/chunks/{apply-ps6jjt6k.js → apply-xraar7p0.js} +2 -2
- package/dist/chunks/{index-pq18fdbj.js → index-16j8x10s.js} +64 -3
- package/dist/chunks/{index-n6nh6f5r.js → index-6qfy0542.js} +193 -15
- package/dist/chunks/{index-ek15201v.js → index-rvb4znxa.js} +2 -2
- package/dist/chunks/{index-ape24zc0.js → index-src5t3my.js} +1 -1
- package/dist/chunks/{index-y8410ve0.js → index-x0v4m19f.js} +1 -1
- package/dist/chunks/{index-18j7x3xw.js → index-ysnp8y58.js} +2 -2
- package/dist/chunks/{local-1c1qxzvx.js → local-66fck74w.js} +1 -1
- package/dist/chunks/{local-hc67bynj.js → local-jqfx3kbq.js} +1 -1
- package/dist/chunks/{sync-azd9t4p8.js → sync-8763n4sc.js} +3 -3
- package/dist/chunks/{sync-12m3vs6g.js → sync-8ca3ew84.js} +3 -3
- package/dist/cli/index.js +526 -554
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +727 -587
- package/dist/lib/asset-plan.d.ts +4 -1
- package/dist/lib/asset-plan.d.ts.map +1 -1
- package/dist/lib/harness-discovery.d.ts +52 -0
- package/dist/lib/harness-discovery.d.ts.map +1 -0
- package/dist/lib/machine.d.ts.map +1 -1
- package/dist/lib/managed-skill-runtimes.d.ts +12 -28
- package/dist/lib/managed-skill-runtimes.d.ts.map +1 -1
- package/dist/lib/session-apply.d.ts +11 -0
- package/dist/lib/session-apply.d.ts.map +1 -1
- package/dist/lib/session-refresh.d.ts.map +1 -1
- package/dist/lib/session-render.d.ts +7 -0
- package/dist/lib/session-render.d.ts.map +1 -1
- package/dist/mcp/index.js +7 -7
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +153 -0
- package/dist/sdk/v1.generated.d.ts +6 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/index.js +77 -0
- package/dist/server/openapi.d.ts +27 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7823,6 +7823,10 @@ function normalizeProfileAssetBinding(value) {
|
|
|
7823
7823
|
const rollback = record["rollback"];
|
|
7824
7824
|
if (!ASSET_ROLLBACK_POLICIES.includes(rollback))
|
|
7825
7825
|
throw new Error(`Invalid asset rollback policy: ${String(rollback)}`);
|
|
7826
|
+
const nativeAgent = record["nativeAgent"] === undefined ? undefined : normalizeNativeAgentMetadata(record["nativeAgent"]);
|
|
7827
|
+
if (nativeAgent && (kind !== "custom-agent" || strategy !== "emit-file" || !["claude", "sumi", "opencode"].includes(provider))) {
|
|
7828
|
+
throw new Error("nativeAgent metadata requires a supported emitted custom-agent definition.");
|
|
7829
|
+
}
|
|
7826
7830
|
return {
|
|
7827
7831
|
schema: PROFILE_ASSET_BINDING_SCHEMA,
|
|
7828
7832
|
assetKey,
|
|
@@ -7842,6 +7846,7 @@ function normalizeProfileAssetBinding(value) {
|
|
|
7842
7846
|
root,
|
|
7843
7847
|
relativePath: nonEmptyString(destinationRecord["relativePath"], "destination.relativePath")
|
|
7844
7848
|
},
|
|
7849
|
+
...nativeAgent ? { nativeAgent } : {},
|
|
7845
7850
|
uninstall,
|
|
7846
7851
|
rollback
|
|
7847
7852
|
};
|
|
@@ -7917,6 +7922,7 @@ function compileAssetPlan(input) {
|
|
|
7917
7922
|
allowed: binding.source.allowed
|
|
7918
7923
|
},
|
|
7919
7924
|
destination: binding.destination,
|
|
7925
|
+
...binding.nativeAgent ? { nativeAgent: binding.nativeAgent } : {},
|
|
7920
7926
|
support,
|
|
7921
7927
|
action,
|
|
7922
7928
|
mutationMode: input.mode,
|
|
@@ -7972,6 +7978,18 @@ function validateAssetSourceAndDestination(bundle, binding, diagnostics) {
|
|
|
7972
7978
|
pathSafe = false;
|
|
7973
7979
|
diagnostics.push(diagnostic("error", "ASSET_DESTINATION_UNSAFE", binding.assetKey, error.message));
|
|
7974
7980
|
}
|
|
7981
|
+
if (pathSafe && binding.kind === "custom-agent" && binding.destination.strategy === "emit-file" && (binding.selector.provider === "claude" || binding.selector.provider === "sumi" || binding.nativeAgent) && (binding.destination.root !== "target-home" || !/^agents\/[A-Za-z0-9][A-Za-z0-9_-]*\.md$/.test(safeRelativePath(binding.destination.relativePath)))) {
|
|
7982
|
+
pathSafe = false;
|
|
7983
|
+
diagnostics.push(diagnostic("error", "ASSET_CUSTOM_AGENT_DESTINATION_UNSUPPORTED", binding.assetKey, "Native custom-agent definitions require agents/<name>.md beneath an explicit target-home."));
|
|
7984
|
+
}
|
|
7985
|
+
if (binding.nativeAgent) {
|
|
7986
|
+
if (posix.basename(binding.destination.relativePath, ".md") !== binding.nativeAgent.name) {
|
|
7987
|
+
diagnostics.push(diagnostic("error", "ASSET_NATIVE_AGENT_NAME_MISMATCH", binding.assetKey, "Native role name must match its destination filename."));
|
|
7988
|
+
}
|
|
7989
|
+
if (/^\uFEFF?---[ \t]*\r?\n/.test(bundle.content)) {
|
|
7990
|
+
diagnostics.push(diagnostic("error", "ASSET_NATIVE_AGENT_DOUBLE_HEADER", binding.assetKey, "Canonical role prose must not already contain frontmatter when nativeAgent metadata is supplied."));
|
|
7991
|
+
}
|
|
7992
|
+
}
|
|
7975
7993
|
const expectedLocator = bundle.locator;
|
|
7976
7994
|
if (!binding.source.immutable || binding.source.locator !== expectedLocator) {
|
|
7977
7995
|
diagnostics.push(diagnostic("error", "ASSET_SOURCE_MUTABLE_OR_UNPINNED", binding.assetKey, `Asset source must be pinned to ${expectedLocator}.`));
|
|
@@ -8027,6 +8045,72 @@ function safeRelativePath(value) {
|
|
|
8027
8045
|
}
|
|
8028
8046
|
return normalized;
|
|
8029
8047
|
}
|
|
8048
|
+
function normalizeNativeAgentMetadata(value) {
|
|
8049
|
+
const record = objectRecord(value, "nativeAgent");
|
|
8050
|
+
if (Object.keys(record).some((key) => !["name", "description", "frontmatter"].includes(key)))
|
|
8051
|
+
throw new Error("Unknown nativeAgent metadata field.");
|
|
8052
|
+
const name = nonEmptyString(record["name"], "nativeAgent.name");
|
|
8053
|
+
const description = nonEmptyString(record["description"], "nativeAgent.description");
|
|
8054
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(name) || name.length > 128)
|
|
8055
|
+
throw new Error("Invalid nativeAgent.name.");
|
|
8056
|
+
if (description.length > 4096 || /[\r\n\0]/.test(description))
|
|
8057
|
+
throw new Error("Invalid nativeAgent.description.");
|
|
8058
|
+
if (record["frontmatter"] === undefined)
|
|
8059
|
+
return { name, description };
|
|
8060
|
+
const frontmatter = record["frontmatter"];
|
|
8061
|
+
if (typeof frontmatter !== "string" || frontmatter.length > 16384 || frontmatter.includes("\x00"))
|
|
8062
|
+
throw new Error("Invalid nativeAgent.frontmatter.");
|
|
8063
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n$/.exec(frontmatter);
|
|
8064
|
+
if (!match)
|
|
8065
|
+
throw new Error("nativeAgent.frontmatter requires one complete newline-terminated YAML header.");
|
|
8066
|
+
const scalar = (raw) => {
|
|
8067
|
+
if (raw.startsWith('"')) {
|
|
8068
|
+
try {
|
|
8069
|
+
const parsed = JSON.parse(raw);
|
|
8070
|
+
return typeof parsed === "string" ? parsed : undefined;
|
|
8071
|
+
} catch {
|
|
8072
|
+
return;
|
|
8073
|
+
}
|
|
8074
|
+
}
|
|
8075
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9 _.,:;()/?@+-]*$/.test(raw) || /:(?:\s|$)/.test(raw))
|
|
8076
|
+
return;
|
|
8077
|
+
if (/^(?:true|yes|on|y)$/i.test(raw))
|
|
8078
|
+
return true;
|
|
8079
|
+
if (/^(?:false|no|off|n)$/i.test(raw))
|
|
8080
|
+
return false;
|
|
8081
|
+
if (/^null$/i.test(raw))
|
|
8082
|
+
return null;
|
|
8083
|
+
if (/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(raw))
|
|
8084
|
+
return Number(raw);
|
|
8085
|
+
if (/^[0-9]/.test(raw))
|
|
8086
|
+
return;
|
|
8087
|
+
return raw;
|
|
8088
|
+
};
|
|
8089
|
+
const fields = new Map;
|
|
8090
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
8091
|
+
if (!line.trim() || line.startsWith("#"))
|
|
8092
|
+
continue;
|
|
8093
|
+
const field = /^([A-Za-z][A-Za-z0-9_-]*):[ \t]+(.+)$/.exec(line);
|
|
8094
|
+
if (!field || fields.has(field[1]) || scalar(field[2]) === undefined)
|
|
8095
|
+
throw new Error("nativeAgent.frontmatter requires unambiguous, unique flat scalar fields.");
|
|
8096
|
+
fields.set(field[1], scalar(field[2]));
|
|
8097
|
+
}
|
|
8098
|
+
if (fields.get("name") !== name || fields.get("description") !== description)
|
|
8099
|
+
throw new Error("nativeAgent.frontmatter name and description must match explicit metadata.");
|
|
8100
|
+
return { name, description, frontmatter };
|
|
8101
|
+
}
|
|
8102
|
+
function renderNativeAgentContent(content, metadata) {
|
|
8103
|
+
if (!metadata)
|
|
8104
|
+
return content;
|
|
8105
|
+
const value = normalizeNativeAgentMetadata(metadata);
|
|
8106
|
+
if (/^\uFEFF?---[ \t]*\r?\n/.test(content))
|
|
8107
|
+
throw new Error("Canonical role prose already contains frontmatter.");
|
|
8108
|
+
return (value.frontmatter ?? `---
|
|
8109
|
+
name: ${JSON.stringify(value.name)}
|
|
8110
|
+
description: ${JSON.stringify(value.description)}
|
|
8111
|
+
---
|
|
8112
|
+
`) + content;
|
|
8113
|
+
}
|
|
8030
8114
|
function diagnostic(severity, code, assetKey, message) {
|
|
8031
8115
|
return { severity, code, assetKey, message };
|
|
8032
8116
|
}
|
|
@@ -8072,6 +8156,7 @@ var init_asset_plan = __esm(() => {
|
|
|
8072
8156
|
ASSET_CAPABILITY_DESCRIPTORS = Object.freeze([
|
|
8073
8157
|
assetCapability("claude", "code", "skill", "supported", ["emit-file"], "Claude Code project or profile skill files."),
|
|
8074
8158
|
assetCapability("claude", "code", "workflow", "supported", ["emit-file"], "Claude Code command and workflow files."),
|
|
8159
|
+
assetCapability("claude", "code", "custom-agent", "supported", ["emit-file"], "Claude Code Markdown subagents with native frontmatter in agents/.", ">=2.1.276 <3.0.0"),
|
|
8075
8160
|
assetCapability("claude", "code", "hook", "supported", ["emit-file"], "Claude Code hook configuration fragments."),
|
|
8076
8161
|
assetCapability("claude", "code", "plugin", "conditional", ["install-marketplace"], "Claude marketplace installation requires an explicit installer."),
|
|
8077
8162
|
assetCapability("codex", "cli", "skill", "supported", ["emit-file"], "Codex skill bundle files."),
|
|
@@ -8082,6 +8167,7 @@ var init_asset_plan = __esm(() => {
|
|
|
8082
8167
|
assetCapability("opencode", "cli", "workflow", "supported", ["emit-file"], "OpenCode command/workflow files."),
|
|
8083
8168
|
assetCapability("opencode", "cli", "plugin", "supported", ["emit-file"], "OpenCode local plugin modules."),
|
|
8084
8169
|
assetCapability("opencode", "cli", "custom-agent", "supported", ["emit-file"], "OpenCode custom agent files."),
|
|
8170
|
+
assetCapability("sumi", "cli", "custom-agent", "supported", ["emit-file"], "Sumi Markdown agent definitions in its explicit resolved config or project directory.", ">=0.2.22 <0.3.0"),
|
|
8085
8171
|
assetCapability("codewith", "cli", "skill", "supported", ["emit-file"], "Codewith skill bundle files."),
|
|
8086
8172
|
assetCapability("codewith", "cli", "plugin", "conditional", ["install-local", "install-marketplace"], "Codewith plugin installation requires an explicit installer."),
|
|
8087
8173
|
assetCapability("aicopilot", "cli", "skill", "supported", ["emit-file"], "AICopilot discovered skill files."),
|
|
@@ -9230,7 +9316,7 @@ function buildCursorRuleFiles(targetHome, adapter, sources) {
|
|
|
9230
9316
|
return [sourceFile, ...ruleFiles];
|
|
9231
9317
|
});
|
|
9232
9318
|
}
|
|
9233
|
-
function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig) {
|
|
9319
|
+
function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig, projectRoot) {
|
|
9234
9320
|
const surface = adapter.providerSurface ?? "legacy-dual";
|
|
9235
9321
|
const fragments = sources.flatMap((source, index) => [
|
|
9236
9322
|
makeFile(targetHome, fragmentPath(adapter, index, source), "fragment", sectionForSource(source), [source.id]),
|
|
@@ -9250,13 +9336,13 @@ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfi
|
|
|
9250
9336
|
]);
|
|
9251
9337
|
const existingConfigPath = joinTarget(targetHome, adapter.configFile);
|
|
9252
9338
|
const selectedConfig = existsSync5(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
|
|
9253
|
-
const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir));
|
|
9339
|
+
const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir, targetHome, projectRoot));
|
|
9254
9340
|
const config = {
|
|
9255
9341
|
...selectedConfig,
|
|
9256
9342
|
$schema: typeof selectedConfig["$schema"] === "string" ? selectedConfig["$schema"] : "https://opencode.ai/config.json",
|
|
9257
9343
|
instructions: [
|
|
9258
9344
|
...preservedInstructions,
|
|
9259
|
-
...fragments.map((file) => file.
|
|
9345
|
+
...fragments.map((file) => file.path)
|
|
9260
9346
|
]
|
|
9261
9347
|
};
|
|
9262
9348
|
const configSourceIds = [
|
|
@@ -9299,9 +9385,99 @@ function normalizeOpenCodeInstructions(value) {
|
|
|
9299
9385
|
}
|
|
9300
9386
|
return value;
|
|
9301
9387
|
}
|
|
9302
|
-
function pathIsManagedOpenCodeInstruction(
|
|
9303
|
-
const
|
|
9304
|
-
|
|
9388
|
+
function pathIsManagedOpenCodeInstruction(reference, managedDir, targetHome, projectRoot) {
|
|
9389
|
+
const normalizedManagedDir = posix2.normalize(managedDir.replaceAll("\\", "/"));
|
|
9390
|
+
const candidates = canonicalOpenCodeInstructionPaths(reference, targetHome, projectRoot);
|
|
9391
|
+
return candidates.some((candidate) => {
|
|
9392
|
+
const path = candidate.caseInsensitive ? candidate.path.toLowerCase() : candidate.path;
|
|
9393
|
+
const namespace = candidate.caseInsensitive ? normalizedManagedDir.toLowerCase() : normalizedManagedDir;
|
|
9394
|
+
return path === namespace || path.endsWith(`/${namespace}`) || path.includes(`/${namespace}/`);
|
|
9395
|
+
});
|
|
9396
|
+
}
|
|
9397
|
+
function canonicalOpenCodeInstructionPaths(reference, targetHome, projectRoot) {
|
|
9398
|
+
const decodedReference = decodeOpenCodePathEscapes(reference);
|
|
9399
|
+
const portable = decodedReference.replaceAll("\\", "/");
|
|
9400
|
+
if (portable.includes("\x00")) {
|
|
9401
|
+
throw new Error("OpenCode config instruction references cannot contain NUL bytes.");
|
|
9402
|
+
}
|
|
9403
|
+
if (/^[A-Za-z]:(?!\/)/.test(portable)) {
|
|
9404
|
+
throw new Error("OpenCode config instruction references cannot use Win32 drive-relative paths.");
|
|
9405
|
+
}
|
|
9406
|
+
const windowsAbsolute = /^[A-Za-z]:\//.test(portable);
|
|
9407
|
+
const uncAbsolute = portable.startsWith("//");
|
|
9408
|
+
const windowsRootRelative = /^\\(?![\\/])/.test(decodedReference);
|
|
9409
|
+
const uriScheme = windowsAbsolute || uncAbsolute || windowsRootRelative ? null : /^([A-Za-z][A-Za-z0-9+.-]*):/.exec(portable)?.[1]?.toLowerCase();
|
|
9410
|
+
if (uriScheme && uriScheme !== "file")
|
|
9411
|
+
return [];
|
|
9412
|
+
if (uriScheme === "file") {
|
|
9413
|
+
const fileReference = reference.replaceAll("\\", "/");
|
|
9414
|
+
if (!/^file:\/\//i.test(fileReference)) {
|
|
9415
|
+
throw new Error("OpenCode config contains an invalid file URL instruction reference.");
|
|
9416
|
+
}
|
|
9417
|
+
let url;
|
|
9418
|
+
try {
|
|
9419
|
+
url = new URL(fileReference);
|
|
9420
|
+
} catch {
|
|
9421
|
+
throw new Error("OpenCode config contains an invalid file URL instruction reference.");
|
|
9422
|
+
}
|
|
9423
|
+
if (url.protocol !== "file:" || url.username || url.password || url.port || url.search || url.hash) {
|
|
9424
|
+
throw new Error("OpenCode config contains an invalid file URL instruction reference.");
|
|
9425
|
+
}
|
|
9426
|
+
const pathname = decodeOpenCodePathEscapes(url.pathname).replaceAll("\\", "/");
|
|
9427
|
+
if (pathname.includes("\x00")) {
|
|
9428
|
+
throw new Error("OpenCode config instruction references cannot contain NUL bytes.");
|
|
9429
|
+
}
|
|
9430
|
+
if (/^\/[A-Za-z]:(?!\/)/.test(pathname)) {
|
|
9431
|
+
throw new Error("OpenCode config instruction references cannot use Win32 drive-relative paths.");
|
|
9432
|
+
}
|
|
9433
|
+
const host = url.hostname && url.hostname !== "localhost" ? `//${url.hostname}` : "";
|
|
9434
|
+
return [{
|
|
9435
|
+
path: normalizePortableOpenCodePath(`${host}${pathname}`, true),
|
|
9436
|
+
caseInsensitive: true
|
|
9437
|
+
}];
|
|
9438
|
+
}
|
|
9439
|
+
if (posix2.isAbsolute(portable) || windowsAbsolute || uncAbsolute || windowsRootRelative) {
|
|
9440
|
+
const windowsPathSemantics = windowsAbsolute || uncAbsolute || windowsRootRelative;
|
|
9441
|
+
return [{
|
|
9442
|
+
path: normalizePortableOpenCodePath(portable, windowsPathSemantics),
|
|
9443
|
+
caseInsensitive: windowsPathSemantics
|
|
9444
|
+
}];
|
|
9445
|
+
}
|
|
9446
|
+
const roots = [
|
|
9447
|
+
...projectRoot ? [resolveSessionPath(projectRoot)] : [],
|
|
9448
|
+
resolveSessionPath(targetHome)
|
|
9449
|
+
];
|
|
9450
|
+
const candidates = new Map;
|
|
9451
|
+
for (const root of roots) {
|
|
9452
|
+
const portableRoot = root.replaceAll("\\", "/");
|
|
9453
|
+
const caseInsensitive = /^[A-Za-z]:\//.test(portableRoot) || portableRoot.startsWith("//");
|
|
9454
|
+
const path = normalizePortableOpenCodePath(posix2.join(portableRoot, portable));
|
|
9455
|
+
candidates.set(`${caseInsensitive ? "i" : "s"}:${path}`, { path, caseInsensitive });
|
|
9456
|
+
}
|
|
9457
|
+
return [...candidates.values()];
|
|
9458
|
+
}
|
|
9459
|
+
function decodeOpenCodePathEscapes(value) {
|
|
9460
|
+
return value.replace(/(?:%[a-fA-F0-9]{2})+/g, (encoded) => {
|
|
9461
|
+
try {
|
|
9462
|
+
return decodeURIComponent(encoded);
|
|
9463
|
+
} catch {
|
|
9464
|
+
return encoded;
|
|
9465
|
+
}
|
|
9466
|
+
});
|
|
9467
|
+
}
|
|
9468
|
+
function normalizePortableOpenCodePath(value, windowsPathSemantics = false) {
|
|
9469
|
+
const portable = value.replaceAll("\\", "/");
|
|
9470
|
+
const componentNormalized = windowsPathSemantics ? portable.split("/").map(normalizeWin32PathComponent).join("/") : portable;
|
|
9471
|
+
const normalized = posix2.normalize(componentNormalized);
|
|
9472
|
+
return normalized.startsWith("//") ? `/${normalized.replace(/^\/+/u, "")}` : normalized;
|
|
9473
|
+
}
|
|
9474
|
+
function normalizeWin32PathComponent(component) {
|
|
9475
|
+
if (!component || /^[A-Za-z]:$/.test(component))
|
|
9476
|
+
return component;
|
|
9477
|
+
const withoutTrailingSpaces = component.replace(/ +$/u, "");
|
|
9478
|
+
if (withoutTrailingSpaces === "." || withoutTrailingSpaces === "..")
|
|
9479
|
+
return withoutTrailingSpaces;
|
|
9480
|
+
return component.replace(/[ .]+$/u, "");
|
|
9305
9481
|
}
|
|
9306
9482
|
function buildAntigravityRuleFiles(targetHome, adapter, sources) {
|
|
9307
9483
|
return sources.flatMap((source, index) => {
|
|
@@ -9355,7 +9531,7 @@ function makeAntigravityRuleFile(targetHome, relativePath, content, sourceIds) {
|
|
|
9355
9531
|
}
|
|
9356
9532
|
return file;
|
|
9357
9533
|
}
|
|
9358
|
-
function buildFiles(targetHome, adapter, profile, sources, providerConfig, providerVersion) {
|
|
9534
|
+
function buildFiles(targetHome, adapter, profile, sources, providerConfig, providerVersion, projectRoot) {
|
|
9359
9535
|
switch (adapter.mode) {
|
|
9360
9536
|
case "native-imports":
|
|
9361
9537
|
return buildNativeImportFiles(targetHome, adapter, profile, sources, providerVersion);
|
|
@@ -9364,7 +9540,7 @@ function buildFiles(targetHome, adapter, profile, sources, providerConfig, provi
|
|
|
9364
9540
|
case "cursor-mdc":
|
|
9365
9541
|
return buildCursorRuleFiles(targetHome, adapter, sources);
|
|
9366
9542
|
case "opencode-instructions":
|
|
9367
|
-
return buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig);
|
|
9543
|
+
return buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig, projectRoot);
|
|
9368
9544
|
case "antigravity-rules":
|
|
9369
9545
|
return buildAntigravityRuleFiles(targetHome, adapter, sources);
|
|
9370
9546
|
case "provider-rules":
|
|
@@ -9393,12 +9569,13 @@ function buildAssetFiles(input, targetHome, blocked) {
|
|
|
9393
9569
|
if (!relativePath || relativePath === ".." || relativePath.startsWith("../") || isAbsolute3(relativePath)) {
|
|
9394
9570
|
throw new Error(`Asset ${item.assetKey} is outside the session snapshot root; use a project-scoped session plan for atomic application.`);
|
|
9395
9571
|
}
|
|
9572
|
+
const renderedContent = renderNativeAgentContent(content, item.nativeAgent);
|
|
9396
9573
|
return {
|
|
9397
9574
|
path,
|
|
9398
9575
|
relativePath: assertSafeRelativePath(relativePath),
|
|
9399
9576
|
role: "asset",
|
|
9400
|
-
content,
|
|
9401
|
-
sha256: sha2566(
|
|
9577
|
+
content: renderedContent,
|
|
9578
|
+
sha256: sha2566(renderedContent),
|
|
9402
9579
|
sourceIds: [item.sourceConfigId, item.assetId]
|
|
9403
9580
|
};
|
|
9404
9581
|
});
|
|
@@ -9577,7 +9754,7 @@ function resolveSessionTargetOwnership(input, target) {
|
|
|
9577
9754
|
tool: input.tool,
|
|
9578
9755
|
profile: input.profile,
|
|
9579
9756
|
targetHome: target.targetHome,
|
|
9580
|
-
projectRoot: null,
|
|
9757
|
+
projectRoot: input.tool === "opencode" && input.projectRoot ? resolveSessionPath(input.projectRoot) : null,
|
|
9581
9758
|
ownedBy: "open-configs",
|
|
9582
9759
|
canonicalOwner: "instructions",
|
|
9583
9760
|
writer: {
|
|
@@ -9638,7 +9815,7 @@ function planSessionRender(input) {
|
|
|
9638
9815
|
if (input.providerConfig && input.tool !== "opencode") {
|
|
9639
9816
|
throw new Error("Provider base config is supported only for OpenCode session renders.");
|
|
9640
9817
|
}
|
|
9641
|
-
const baseFiles = blocked ? [] : buildFiles(targetHome, adapter, input.profile, orderedSources, input.providerConfig, input.provider_version);
|
|
9818
|
+
const baseFiles = blocked ? [] : buildFiles(targetHome, adapter, input.profile, orderedSources, input.providerConfig, input.provider_version, input.projectRoot);
|
|
9642
9819
|
const projectContext = blocked ? null : composeProjectContextSessionRender({
|
|
9643
9820
|
tool: input.tool,
|
|
9644
9821
|
adapter_mode: adapter.mode,
|
|
@@ -9739,6 +9916,7 @@ function planSessionRender(input) {
|
|
|
9739
9916
|
mutationMode: item.mutationMode,
|
|
9740
9917
|
destination: item.destination,
|
|
9741
9918
|
digest: item.source.digest,
|
|
9919
|
+
...item.nativeAgent ? { nativeAgent: item.nativeAgent } : {},
|
|
9742
9920
|
exactOnceKey: item.exactOnceKey
|
|
9743
9921
|
}))
|
|
9744
9922
|
}
|
|
@@ -13768,7 +13946,7 @@ function detectMachineContext(overrides = {}) {
|
|
|
13768
13946
|
created_at: "",
|
|
13769
13947
|
os_family: osFamily,
|
|
13770
13948
|
home_dir: homeDir6,
|
|
13771
|
-
workspace_root: overrides.workspace_root ??
|
|
13949
|
+
workspace_root: overrides.workspace_root ?? "",
|
|
13772
13950
|
bun_bin_dir: bunBinDir,
|
|
13773
13951
|
bun_path: overrides.bun_path ?? defaultBunPath,
|
|
13774
13952
|
path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join10("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
|
|
@@ -13781,7 +13959,7 @@ function machineContextToVariables(machine) {
|
|
|
13781
13959
|
OS_FAMILY: machine.os_family,
|
|
13782
13960
|
ARCH: machine.arch ?? "",
|
|
13783
13961
|
HOME_DIR: machine.home_dir,
|
|
13784
|
-
WORKSPACE_ROOT: machine.workspace_root,
|
|
13962
|
+
...machine.workspace_root ? { WORKSPACE_ROOT: machine.workspace_root } : {},
|
|
13785
13963
|
BUN_BIN_DIR: machine.bun_bin_dir,
|
|
13786
13964
|
BUN_PATH: machine.bun_path,
|
|
13787
13965
|
PATH_PREFIX: machine.path_prefix
|
|
@@ -14845,7 +15023,7 @@ init_types();
|
|
|
14845
15023
|
|
|
14846
15024
|
// src/status.ts
|
|
14847
15025
|
init_config_store();
|
|
14848
|
-
import { existsSync as
|
|
15026
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
14849
15027
|
|
|
14850
15028
|
// src/lib/apply.ts
|
|
14851
15029
|
init_types();
|
|
@@ -15395,19 +15573,9 @@ function getPackageVersion() {
|
|
|
15395
15573
|
}
|
|
15396
15574
|
|
|
15397
15575
|
// src/lib/managed-skill-runtimes.ts
|
|
15398
|
-
import {
|
|
15399
|
-
import { spawnSync as spawnSync3 } from "child_process";
|
|
15400
|
-
import {
|
|
15401
|
-
existsSync as existsSync11,
|
|
15402
|
-
lstatSync as lstatSync4,
|
|
15403
|
-
mkdirSync as mkdirSync4,
|
|
15404
|
-
readFileSync as readFileSync10,
|
|
15405
|
-
renameSync as renameSync2,
|
|
15406
|
-
rmSync as rmSync3,
|
|
15407
|
-
writeFileSync as writeFileSync3
|
|
15408
|
-
} from "fs";
|
|
15576
|
+
import { lstatSync as lstatSync4 } from "fs";
|
|
15409
15577
|
import { homedir as homedir8 } from "os";
|
|
15410
|
-
import {
|
|
15578
|
+
import { join as join14, resolve as resolve10 } from "path";
|
|
15411
15579
|
var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
|
|
15412
15580
|
var INBOX_SKILL_MARKERS = [
|
|
15413
15581
|
[".claude", "skills", "inbox", "SKILL.md"],
|
|
@@ -15416,369 +15584,67 @@ var INBOX_SKILL_MARKERS = [
|
|
|
15416
15584
|
[".config", "opencode", "skills", "inbox", "SKILL.md"],
|
|
15417
15585
|
[".cursor", "skills", "inbox", "SKILL.md"]
|
|
15418
15586
|
];
|
|
15419
|
-
|
|
15420
|
-
|
|
15421
|
-
|
|
15422
|
-
|
|
15423
|
-
function lstatOrNull(path) {
|
|
15424
|
-
try {
|
|
15425
|
-
return lstatSync4(path);
|
|
15426
|
-
} catch {
|
|
15427
|
-
return null;
|
|
15428
|
-
}
|
|
15429
|
-
}
|
|
15430
|
-
function findSymlinkedAncestor(path) {
|
|
15431
|
-
const normalized = resolve10(path);
|
|
15432
|
-
const parsed = parse4(normalized);
|
|
15433
|
-
let current = parsed.root;
|
|
15434
|
-
const rel = relative5(parsed.root, normalized);
|
|
15435
|
-
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
15587
|
+
function needsMigrationReview(homeDir6, parts) {
|
|
15588
|
+
let current = resolve10(homeDir6);
|
|
15589
|
+
const absolute = join14(current, ...parts);
|
|
15590
|
+
for (const segment of parts) {
|
|
15436
15591
|
current = join14(current, segment);
|
|
15437
|
-
|
|
15438
|
-
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
function assertNoSymlinkAncestors2(path) {
|
|
15445
|
-
const found = findSymlinkedAncestor(path);
|
|
15446
|
-
if (found !== null) {
|
|
15447
|
-
throw new Error(`managed skill path uses a symlink ancestor: ${found}`);
|
|
15448
|
-
}
|
|
15449
|
-
}
|
|
15450
|
-
function packagedInboxSkillPath(explicitPath) {
|
|
15451
|
-
if (explicitPath)
|
|
15452
|
-
return explicitPath;
|
|
15453
|
-
throw new Error("Bundled skill contracts are retired; use the Skills CLI to manage private skills");
|
|
15454
|
-
}
|
|
15455
|
-
function readCanonicalSkill(explicitPath) {
|
|
15456
|
-
const assetPath = packagedInboxSkillPath(explicitPath);
|
|
15457
|
-
const stat = lstatOrNull(assetPath);
|
|
15458
|
-
if (!stat?.isFile()) {
|
|
15459
|
-
throw new Error("packaged inbox skill contract is not a regular file");
|
|
15460
|
-
}
|
|
15461
|
-
const content = readFileSync10(assetPath, "utf8");
|
|
15462
|
-
if (!content.includes("conversations watch --from <agent> --all")) {
|
|
15463
|
-
throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
|
|
15464
|
-
}
|
|
15465
|
-
if (!content.includes("There is no separate")) {
|
|
15466
|
-
throw new Error("packaged inbox skill contract does not retire the legacy executable");
|
|
15467
|
-
}
|
|
15468
|
-
return { content, sha256: sha2568(content) };
|
|
15469
|
-
}
|
|
15470
|
-
function runProbe(command, args) {
|
|
15471
|
-
const result = spawnSync3(command, args, {
|
|
15472
|
-
encoding: "utf8",
|
|
15473
|
-
timeout: 5000,
|
|
15474
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
15475
|
-
});
|
|
15476
|
-
if (result.error || result.status !== 0) {
|
|
15477
|
-
return { ok: false, output: "" };
|
|
15478
|
-
}
|
|
15479
|
-
return {
|
|
15480
|
-
ok: true,
|
|
15481
|
-
output: `${result.stdout ?? ""}
|
|
15482
|
-
${result.stderr ?? ""}`.trim()
|
|
15483
|
-
};
|
|
15484
|
-
}
|
|
15485
|
-
function parseVersion(output) {
|
|
15486
|
-
return output.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
|
|
15487
|
-
}
|
|
15488
|
-
function compareVersions(left, right) {
|
|
15489
|
-
const a = left.split(".").map(Number);
|
|
15490
|
-
const b = right.split(".").map(Number);
|
|
15491
|
-
for (let i = 0;i < Math.max(a.length, b.length); i++) {
|
|
15492
|
-
const delta = (a[i] ?? 0) - (b[i] ?? 0);
|
|
15493
|
-
if (delta !== 0)
|
|
15494
|
-
return delta;
|
|
15495
|
-
}
|
|
15496
|
-
return 0;
|
|
15497
|
-
}
|
|
15498
|
-
function inspectSkillMarkers(homeDir6) {
|
|
15499
|
-
return INBOX_SKILL_MARKERS.map((parts) => join14(homeDir6, ...parts)).map((path) => {
|
|
15500
|
-
const stat = lstatOrNull(path);
|
|
15501
|
-
if (!stat)
|
|
15502
|
-
return null;
|
|
15503
|
-
if (!stat.isFile()) {
|
|
15504
|
-
return { path, content: null, mode: null, regular: false };
|
|
15592
|
+
let stat;
|
|
15593
|
+
try {
|
|
15594
|
+
stat = lstatSync4(current);
|
|
15595
|
+
} catch (error) {
|
|
15596
|
+
if (error.code === "ENOENT")
|
|
15597
|
+
return false;
|
|
15598
|
+
throw error;
|
|
15505
15599
|
}
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
regular: true
|
|
15511
|
-
};
|
|
15512
|
-
}).filter((snapshot) => snapshot !== null);
|
|
15600
|
+
if (stat.isSymbolicLink() || current !== absolute && !stat.isDirectory())
|
|
15601
|
+
return true;
|
|
15602
|
+
}
|
|
15603
|
+
return true;
|
|
15513
15604
|
}
|
|
15514
|
-
function
|
|
15605
|
+
function inspectLegacyInbox(options) {
|
|
15515
15606
|
const homeDir6 = options.homeDir ?? homedir8();
|
|
15516
|
-
const
|
|
15517
|
-
const
|
|
15518
|
-
const skillPresent = snapshots.length > 0;
|
|
15519
|
-
let canonicalContent = null;
|
|
15520
|
-
let canonicalSha256 = null;
|
|
15521
|
-
let assetError = null;
|
|
15522
|
-
try {
|
|
15523
|
-
const canonical = readCanonicalSkill(options.assetPath);
|
|
15524
|
-
canonicalContent = canonical.content;
|
|
15525
|
-
canonicalSha256 = canonical.sha256;
|
|
15526
|
-
} catch (error) {
|
|
15527
|
-
assetError = error instanceof Error ? error.message : String(error);
|
|
15528
|
-
}
|
|
15529
|
-
const versionProbe = skillPresent ? runProbe(runtimeCommand, ["--version"]) : { ok: false, output: "" };
|
|
15530
|
-
const helpProbe = versionProbe.ok ? runProbe(runtimeCommand, ["watch", "--help"]) : { ok: false, output: "" };
|
|
15531
|
-
const runtimeVersion = versionProbe.ok ? parseVersion(versionProbe.output) : null;
|
|
15532
|
-
const supportsFrom = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[0]);
|
|
15533
|
-
const supportsAll = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[1]);
|
|
15534
|
-
const supportsFullContent = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[2]);
|
|
15535
|
-
const packageReady = versionProbe.ok && runtimeVersion !== null && compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && helpProbe.ok && supportsFrom && supportsAll && supportsFullContent;
|
|
15536
|
-
const heartbeatProbe = skillPresent && packageReady && options.agent ? runProbe(runtimeCommand, ["agents", "heartbeat", "--from", options.agent, "--json"]) : null;
|
|
15537
|
-
const hostedHeartbeat = heartbeatProbe === null ? "unverified" : heartbeatProbe.ok ? "passed" : "failed";
|
|
15538
|
-
const deliveryVerified = hostedHeartbeat === "passed" && options.deliveryVerified === true;
|
|
15539
|
-
const staleMarkers = canonicalContent === null ? snapshots.map((snapshot) => snapshot.path) : snapshots.filter((snapshot) => !snapshot.regular || snapshot.content !== canonicalContent).map((snapshot) => snapshot.path);
|
|
15540
|
-
let reason = "skill not installed";
|
|
15541
|
-
if (skillPresent) {
|
|
15542
|
-
const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
|
|
15543
|
-
const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
|
|
15544
|
-
if (nonRegular)
|
|
15545
|
-
reason = "managed skill target is not a regular file";
|
|
15546
|
-
else if (symlinkAncestor)
|
|
15547
|
-
reason = `managed skill path uses a symlink ancestor: ${symlinkAncestor}`;
|
|
15548
|
-
else if (assetError)
|
|
15549
|
-
reason = assetError;
|
|
15550
|
-
else if (!versionProbe.ok)
|
|
15551
|
-
reason = "conversations command unavailable";
|
|
15552
|
-
else if (!runtimeVersion)
|
|
15553
|
-
reason = "conversations version is unreadable";
|
|
15554
|
-
else if (compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) < 0) {
|
|
15555
|
-
reason = `conversations ${runtimeVersion} is older than ${INBOX_CONVERSATIONS_MINIMUM_VERSION}`;
|
|
15556
|
-
} else if (!helpProbe.ok)
|
|
15557
|
-
reason = "conversations watch help is unavailable";
|
|
15558
|
-
else if (!supportsFrom || !supportsAll || !supportsFullContent) {
|
|
15559
|
-
const missing = [
|
|
15560
|
-
!supportsFrom ? "--from" : null,
|
|
15561
|
-
!supportsAll ? "--all" : null,
|
|
15562
|
-
!supportsFullContent ? "--full-content" : null
|
|
15563
|
-
].filter((flag) => flag !== null);
|
|
15564
|
-
reason = `conversations watch is missing required flags: ${missing.join(", ")}`;
|
|
15565
|
-
} else if (staleMarkers.length > 0)
|
|
15566
|
-
reason = "skill contract stale";
|
|
15567
|
-
else if (hostedHeartbeat === "failed")
|
|
15568
|
-
reason = "hosted heartbeat failed; manual fallback required";
|
|
15569
|
-
else if (hostedHeartbeat === "unverified")
|
|
15570
|
-
reason = "hosted heartbeat unverified; manual fallback required";
|
|
15571
|
-
else if (!deliveryVerified)
|
|
15572
|
-
reason = "hosted heartbeat passed; channel and DM delivery verification required";
|
|
15573
|
-
else
|
|
15574
|
-
reason = "ready";
|
|
15575
|
-
}
|
|
15607
|
+
const markers = INBOX_SKILL_MARKERS.filter((parts) => needsMigrationReview(homeDir6, parts)).map((parts) => join14(homeDir6, ...parts));
|
|
15608
|
+
const present = markers.length > 0;
|
|
15576
15609
|
return {
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15580
|
-
|
|
15581
|
-
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15586
|
-
|
|
15587
|
-
|
|
15588
|
-
|
|
15589
|
-
|
|
15590
|
-
|
|
15591
|
-
|
|
15592
|
-
|
|
15593
|
-
|
|
15594
|
-
|
|
15595
|
-
|
|
15596
|
-
reason
|
|
15597
|
-
},
|
|
15598
|
-
canonicalContent,
|
|
15599
|
-
snapshots
|
|
15610
|
+
skill: "inbox",
|
|
15611
|
+
runtime: "conversations watch",
|
|
15612
|
+
minimum_version: INBOX_CONVERSATIONS_MINIMUM_VERSION,
|
|
15613
|
+
skill_present: present,
|
|
15614
|
+
skill_markers: markers,
|
|
15615
|
+
skill_contracts_current: 0,
|
|
15616
|
+
stale_skill_markers: markers,
|
|
15617
|
+
expected_skill_sha256: null,
|
|
15618
|
+
runtime_command: options.conversationsCommand ?? "conversations",
|
|
15619
|
+
runtime_present: false,
|
|
15620
|
+
runtime_version: null,
|
|
15621
|
+
watch_supports_from: false,
|
|
15622
|
+
watch_supports_all: false,
|
|
15623
|
+
watch_supports_full_content: false,
|
|
15624
|
+
hosted_heartbeat: "unverified",
|
|
15625
|
+
delivery_verified: false,
|
|
15626
|
+
manual_fallback_ready: false,
|
|
15627
|
+
healthy: !present,
|
|
15628
|
+
reason: present ? "Native Inbox skill management is retired; use the Skills CLI to review skills migrate native, then sync and load the selected skills" : "No legacy native Inbox skill found; skill availability is managed by the Skills CLI"
|
|
15600
15629
|
};
|
|
15601
15630
|
}
|
|
15602
15631
|
function inspectManagedSkillRuntimes(options = {}) {
|
|
15603
|
-
const runtime =
|
|
15604
|
-
const installed = runtime.skill_present ? [runtime] : [];
|
|
15632
|
+
const runtime = inspectLegacyInbox(options);
|
|
15605
15633
|
return {
|
|
15606
15634
|
runtimes: [runtime],
|
|
15607
|
-
skills_present:
|
|
15608
|
-
healthy:
|
|
15609
|
-
missing:
|
|
15610
|
-
};
|
|
15611
|
-
}
|
|
15612
|
-
function runtimeReadyForWrite(status) {
|
|
15613
|
-
return status.runtime_present && status.runtime_version !== null && compareVersions(status.runtime_version, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && status.watch_supports_from && status.watch_supports_all && status.watch_supports_full_content;
|
|
15614
|
-
}
|
|
15615
|
-
function projectUpdatedStatus(status, contractCount) {
|
|
15616
|
-
const healthy = status.hosted_heartbeat === "passed" && status.delivery_verified;
|
|
15617
|
-
return {
|
|
15618
|
-
...status,
|
|
15619
|
-
skill_contracts_current: contractCount,
|
|
15620
|
-
stale_skill_markers: [],
|
|
15621
|
-
manual_fallback_ready: true,
|
|
15622
|
-
healthy,
|
|
15623
|
-
reason: healthy ? "ready" : status.hosted_heartbeat === "failed" ? "hosted heartbeat failed; manual fallback required" : status.hosted_heartbeat === "unverified" ? "hosted heartbeat unverified; manual fallback required" : "hosted heartbeat passed; channel and DM delivery verification required"
|
|
15635
|
+
skills_present: runtime.skill_present ? 1 : 0,
|
|
15636
|
+
healthy: 0,
|
|
15637
|
+
missing: runtime.skill_present ? 1 : 0
|
|
15624
15638
|
};
|
|
15625
15639
|
}
|
|
15626
|
-
function cleanup(path) {
|
|
15627
|
-
rmSync3(path, { force: true });
|
|
15628
|
-
}
|
|
15629
|
-
function writeAtomic(path, content, mode) {
|
|
15630
|
-
assertNoSymlinkAncestors2(dirname8(path));
|
|
15631
|
-
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
15632
|
-
try {
|
|
15633
|
-
mkdirSync4(dirname8(path), { recursive: true, mode: 493 });
|
|
15634
|
-
writeFileSync3(tempPath, content, { mode, flag: "wx" });
|
|
15635
|
-
renameSync2(tempPath, path);
|
|
15636
|
-
} finally {
|
|
15637
|
-
cleanup(tempPath);
|
|
15638
|
-
}
|
|
15639
|
-
}
|
|
15640
|
-
var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
|
|
15641
|
-
lstat: lstatOrNull,
|
|
15642
|
-
read: (path) => readFileSync10(path, "utf8"),
|
|
15643
|
-
write: writeAtomic
|
|
15644
|
-
};
|
|
15645
|
-
function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
|
|
15646
|
-
const written = [];
|
|
15647
|
-
try {
|
|
15648
|
-
for (const snapshot of snapshots) {
|
|
15649
|
-
const currentStat = fileOperations.lstat(snapshot.path);
|
|
15650
|
-
if (!currentStat?.isFile() || fileOperations.read(snapshot.path) !== snapshot.content) {
|
|
15651
|
-
throw new Error("managed skill changed after inspection; refusing a stale write");
|
|
15652
|
-
}
|
|
15653
|
-
fileOperations.write(snapshot.path, canonicalContent, snapshot.mode);
|
|
15654
|
-
written.push(snapshot);
|
|
15655
|
-
}
|
|
15656
|
-
return { ok: true, error: null, rollback_conflicts: [] };
|
|
15657
|
-
} catch (error) {
|
|
15658
|
-
const rollbackConflicts = [];
|
|
15659
|
-
for (const snapshot of written.reverse()) {
|
|
15660
|
-
const currentStat = fileOperations.lstat(snapshot.path);
|
|
15661
|
-
if (!currentStat?.isFile()) {
|
|
15662
|
-
rollbackConflicts.push(`${snapshot.path}: no longer a regular file`);
|
|
15663
|
-
continue;
|
|
15664
|
-
}
|
|
15665
|
-
let currentContent;
|
|
15666
|
-
try {
|
|
15667
|
-
currentContent = fileOperations.read(snapshot.path);
|
|
15668
|
-
} catch {
|
|
15669
|
-
rollbackConflicts.push(`${snapshot.path}: could not read the current file`);
|
|
15670
|
-
continue;
|
|
15671
|
-
}
|
|
15672
|
-
if (currentContent !== canonicalContent) {
|
|
15673
|
-
rollbackConflicts.push(`${snapshot.path}: changed after this reconciliation wrote it`);
|
|
15674
|
-
continue;
|
|
15675
|
-
}
|
|
15676
|
-
try {
|
|
15677
|
-
fileOperations.write(snapshot.path, snapshot.content, snapshot.mode);
|
|
15678
|
-
} catch {
|
|
15679
|
-
rollbackConflicts.push(`${snapshot.path}: still owned but could not be restored`);
|
|
15680
|
-
}
|
|
15681
|
-
}
|
|
15682
|
-
return {
|
|
15683
|
-
ok: false,
|
|
15684
|
-
error: error instanceof Error ? error.message : String(error),
|
|
15685
|
-
rollback_conflicts: rollbackConflicts
|
|
15686
|
-
};
|
|
15687
|
-
}
|
|
15688
|
-
}
|
|
15689
15640
|
async function reconcileManagedSkillRuntimes(options = {}) {
|
|
15641
|
+
const runtime = inspectLegacyInbox(options);
|
|
15690
15642
|
const dryRun = options.dryRun ?? false;
|
|
15691
|
-
const before = inspectInbox(options);
|
|
15692
|
-
const status = before.status;
|
|
15693
|
-
if (!status.skill_present) {
|
|
15694
|
-
return {
|
|
15695
|
-
runtimes: [{ ...status, action: "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
15696
|
-
changed: 0,
|
|
15697
|
-
failed: 0,
|
|
15698
|
-
dry_run: dryRun
|
|
15699
|
-
};
|
|
15700
|
-
}
|
|
15701
|
-
if (before.snapshots.some((snapshot) => !snapshot.regular)) {
|
|
15702
|
-
return {
|
|
15703
|
-
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
15704
|
-
changed: 0,
|
|
15705
|
-
failed: 1,
|
|
15706
|
-
dry_run: dryRun
|
|
15707
|
-
};
|
|
15708
|
-
}
|
|
15709
|
-
const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
|
|
15710
|
-
if (symlinkedAncestor) {
|
|
15711
|
-
return {
|
|
15712
|
-
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
15713
|
-
changed: 0,
|
|
15714
|
-
failed: 1,
|
|
15715
|
-
dry_run: dryRun
|
|
15716
|
-
};
|
|
15717
|
-
}
|
|
15718
|
-
if (!before.canonicalContent || !runtimeReadyForWrite(status)) {
|
|
15719
|
-
return {
|
|
15720
|
-
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
15721
|
-
changed: 0,
|
|
15722
|
-
failed: 1,
|
|
15723
|
-
dry_run: dryRun
|
|
15724
|
-
};
|
|
15725
|
-
}
|
|
15726
|
-
const staleSnapshots = before.snapshots.filter((snapshot) => snapshot.content !== before.canonicalContent);
|
|
15727
|
-
if (staleSnapshots.length === 0) {
|
|
15728
|
-
return {
|
|
15729
|
-
runtimes: [{ ...status, action: "unchanged", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
15730
|
-
changed: 0,
|
|
15731
|
-
failed: 0,
|
|
15732
|
-
dry_run: dryRun
|
|
15733
|
-
};
|
|
15734
|
-
}
|
|
15735
|
-
if (dryRun) {
|
|
15736
|
-
const projected = projectUpdatedStatus(status, before.snapshots.length);
|
|
15737
|
-
return {
|
|
15738
|
-
runtimes: [{
|
|
15739
|
-
...projected,
|
|
15740
|
-
action: "update",
|
|
15741
|
-
dry_run: true,
|
|
15742
|
-
skill_contracts_changed: staleSnapshots.length
|
|
15743
|
-
}],
|
|
15744
|
-
changed: 1,
|
|
15745
|
-
failed: 0,
|
|
15746
|
-
dry_run: true
|
|
15747
|
-
};
|
|
15748
|
-
}
|
|
15749
|
-
const transaction = writeSkillContractsTransactional(staleSnapshots.map((snapshot) => ({
|
|
15750
|
-
path: snapshot.path,
|
|
15751
|
-
content: snapshot.content,
|
|
15752
|
-
mode: snapshot.mode ?? 420
|
|
15753
|
-
})), before.canonicalContent);
|
|
15754
|
-
if (!transaction.ok) {
|
|
15755
|
-
const rollbackConflictReason = transaction.rollback_conflicts.length > 0 ? `; rollback conflicts: ${transaction.rollback_conflicts.join("; ")}` : "";
|
|
15756
|
-
const reason = `${transaction.error ?? "managed skill reconciliation failed"}${rollbackConflictReason}`;
|
|
15757
|
-
return {
|
|
15758
|
-
runtimes: [{
|
|
15759
|
-
...status,
|
|
15760
|
-
action: "failed",
|
|
15761
|
-
dry_run: false,
|
|
15762
|
-
skill_contracts_changed: 0,
|
|
15763
|
-
reason
|
|
15764
|
-
}],
|
|
15765
|
-
changed: 0,
|
|
15766
|
-
failed: 1,
|
|
15767
|
-
dry_run: false
|
|
15768
|
-
};
|
|
15769
|
-
}
|
|
15770
|
-
const after = inspectInbox(options).status;
|
|
15771
|
-
const accepted = after.healthy || after.manual_fallback_ready;
|
|
15772
15643
|
return {
|
|
15773
|
-
runtimes: [{
|
|
15774
|
-
|
|
15775
|
-
|
|
15776
|
-
|
|
15777
|
-
skill_contracts_changed: accepted ? staleSnapshots.length : 0
|
|
15778
|
-
}],
|
|
15779
|
-
changed: accepted ? 1 : 0,
|
|
15780
|
-
failed: accepted ? 0 : 1,
|
|
15781
|
-
dry_run: false
|
|
15644
|
+
runtimes: [{ ...runtime, action: runtime.skill_present ? "failed" : "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
15645
|
+
changed: 0,
|
|
15646
|
+
failed: runtime.skill_present ? 1 : 0,
|
|
15647
|
+
dry_run: dryRun
|
|
15782
15648
|
};
|
|
15783
15649
|
}
|
|
15784
15650
|
|
|
@@ -15842,11 +15708,11 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
15842
15708
|
continue;
|
|
15843
15709
|
knownTargets += 1;
|
|
15844
15710
|
const targetPath = expandPath(config.target_path);
|
|
15845
|
-
if (!
|
|
15711
|
+
if (!existsSync11(targetPath)) {
|
|
15846
15712
|
missingTargets += 1;
|
|
15847
15713
|
continue;
|
|
15848
15714
|
}
|
|
15849
|
-
const disk =
|
|
15715
|
+
const disk = readFileSync10(targetPath, "utf-8");
|
|
15850
15716
|
const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
|
|
15851
15717
|
if (redactedDisk !== config.content) {
|
|
15852
15718
|
driftedTargets += 1;
|
|
@@ -15937,8 +15803,8 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
15937
15803
|
};
|
|
15938
15804
|
}
|
|
15939
15805
|
// src/lib/provider-context.ts
|
|
15940
|
-
import { createHash as
|
|
15941
|
-
import { existsSync as
|
|
15806
|
+
import { createHash as createHash8 } from "crypto";
|
|
15807
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
|
|
15942
15808
|
import { join as join15 } from "path";
|
|
15943
15809
|
var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
|
|
15944
15810
|
var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
|
|
@@ -16074,8 +15940,8 @@ function renderPerEndpointFragment(entry) {
|
|
|
16074
15940
|
function renderProviderFragment(entry) {
|
|
16075
15941
|
return entry ? renderPerEndpointFragment(entry) : INVARIANT_FRAGMENT;
|
|
16076
15942
|
}
|
|
16077
|
-
function
|
|
16078
|
-
return
|
|
15943
|
+
function sha2568(content) {
|
|
15944
|
+
return createHash8("sha256").update(content).digest("hex");
|
|
16079
15945
|
}
|
|
16080
15946
|
function resolveAndRenderProviderContext(opts) {
|
|
16081
15947
|
const entry = matchProviderEndpoint(opts.origin);
|
|
@@ -16085,17 +15951,17 @@ function resolveAndRenderProviderContext(opts) {
|
|
|
16085
15951
|
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;
|
|
16086
15952
|
const content = renderProviderFragment(entry);
|
|
16087
15953
|
const dir = join15(opts.homeDir, PROVIDER_CONTEXT_DIR);
|
|
16088
|
-
if (!
|
|
16089
|
-
|
|
15954
|
+
if (!existsSync12(dir))
|
|
15955
|
+
mkdirSync4(dir, { recursive: true });
|
|
16090
15956
|
const filename = `${entry ? entry.key : "invariant"}.md`;
|
|
16091
15957
|
const fragmentPath2 = join15(dir, filename);
|
|
16092
|
-
const fragmentSha256 =
|
|
16093
|
-
|
|
15958
|
+
const fragmentSha256 = sha2568(content);
|
|
15959
|
+
writeFileSync3(fragmentPath2, content, "utf8");
|
|
16094
15960
|
const manifestPath = join15(dir, PROVIDER_CONTEXT_MANIFEST);
|
|
16095
15961
|
let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
|
|
16096
15962
|
try {
|
|
16097
|
-
if (
|
|
16098
|
-
const parsed = JSON.parse(
|
|
15963
|
+
if (existsSync12(manifestPath)) {
|
|
15964
|
+
const parsed = JSON.parse(readFileSync11(manifestPath, "utf8"));
|
|
16099
15965
|
if (parsed && typeof parsed === "object")
|
|
16100
15966
|
manifest = parsed;
|
|
16101
15967
|
}
|
|
@@ -16110,7 +15976,7 @@ function resolveAndRenderProviderContext(opts) {
|
|
|
16110
15976
|
rawModel: opts.rawModel || null
|
|
16111
15977
|
};
|
|
16112
15978
|
manifest.fragments = fragmentsObj;
|
|
16113
|
-
|
|
15979
|
+
writeFileSync3(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
16114
15980
|
return {
|
|
16115
15981
|
entry,
|
|
16116
15982
|
rawEndpoint: opts.rawEndpoint,
|
|
@@ -16210,10 +16076,10 @@ init_session_render();
|
|
|
16210
16076
|
|
|
16211
16077
|
// src/lib/station-profile.ts
|
|
16212
16078
|
init_raw_store_root();
|
|
16213
|
-
import { spawnSync as
|
|
16214
|
-
import { existsSync as
|
|
16079
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
16080
|
+
import { existsSync as existsSync13, lstatSync as lstatSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
16215
16081
|
import { arch as osArch, homedir as homedir9, hostname as osHostname3, platform as osPlatform, userInfo as osUserInfo } from "os";
|
|
16216
|
-
import { dirname as
|
|
16082
|
+
import { dirname as dirname8, join as join16 } from "path";
|
|
16217
16083
|
var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
|
|
16218
16084
|
var STATION_PROFILE_SOURCE_ID = "station-profile";
|
|
16219
16085
|
var STATION_PROFILE_LAYER = "machine";
|
|
@@ -16237,9 +16103,9 @@ function getBunGlobalModulesDir(env = process.env) {
|
|
|
16237
16103
|
}
|
|
16238
16104
|
function readMachinesManifest(path) {
|
|
16239
16105
|
try {
|
|
16240
|
-
if (!
|
|
16106
|
+
if (!existsSync13(path))
|
|
16241
16107
|
return null;
|
|
16242
|
-
const parsed = JSON.parse(
|
|
16108
|
+
const parsed = JSON.parse(readFileSync12(path, "utf8"));
|
|
16243
16109
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
16244
16110
|
return null;
|
|
16245
16111
|
const machines = parsed["machines"];
|
|
@@ -16269,7 +16135,7 @@ function metadataUser(record) {
|
|
|
16269
16135
|
}
|
|
16270
16136
|
function probeMachineStatus(machineId) {
|
|
16271
16137
|
try {
|
|
16272
|
-
const result =
|
|
16138
|
+
const result = spawnSync3("machines", ["details", "--json", "--machine", machineId], {
|
|
16273
16139
|
encoding: "utf8",
|
|
16274
16140
|
timeout: 3000,
|
|
16275
16141
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -16295,7 +16161,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
|
|
|
16295
16161
|
const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname2);
|
|
16296
16162
|
const home = homeDir6(env);
|
|
16297
16163
|
const platform = stringField(record, "platform") ?? osPlatform();
|
|
16298
|
-
const workspacePath = stringField(record, "workspacePath")
|
|
16164
|
+
const workspacePath = stringField(record, "workspacePath");
|
|
16299
16165
|
const machine = {
|
|
16300
16166
|
id: stringField(record, "id") ?? hostname2,
|
|
16301
16167
|
hostname: stringField(record, "hostname") ?? hostname2,
|
|
@@ -16314,7 +16180,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
|
|
|
16314
16180
|
function scopedPackageNames(modulesDir, scope) {
|
|
16315
16181
|
const scopeDir = join16(modulesDir, scope);
|
|
16316
16182
|
try {
|
|
16317
|
-
if (!
|
|
16183
|
+
if (!existsSync13(scopeDir))
|
|
16318
16184
|
return null;
|
|
16319
16185
|
return readdirNames(scopeDir).sort();
|
|
16320
16186
|
} catch {
|
|
@@ -16334,7 +16200,7 @@ function resolveStationProfilePackages(env = process.env) {
|
|
|
16334
16200
|
const modulesDir = getBunGlobalModulesDir(env);
|
|
16335
16201
|
let scopeDirs;
|
|
16336
16202
|
try {
|
|
16337
|
-
if (!
|
|
16203
|
+
if (!existsSync13(modulesDir))
|
|
16338
16204
|
return null;
|
|
16339
16205
|
scopeDirs = readdirNames(modulesDir).filter((name) => name.startsWith("@") && name.toLowerCase().includes("hasna"));
|
|
16340
16206
|
} catch {
|
|
@@ -16409,10 +16275,10 @@ function refreshStationProfile(options = {}) {
|
|
|
16409
16275
|
const path = getStationProfileCachePath(env);
|
|
16410
16276
|
const generatedAt = new Date().toISOString();
|
|
16411
16277
|
if (!options.dryRun) {
|
|
16412
|
-
const existing =
|
|
16278
|
+
const existing = existsSync13(path) ? readFileSync12(path, "utf8") : null;
|
|
16413
16279
|
if (existing !== content) {
|
|
16414
|
-
|
|
16415
|
-
|
|
16280
|
+
mkdirSync5(dirname8(path), { recursive: true });
|
|
16281
|
+
writeFileSync4(path, content, "utf8");
|
|
16416
16282
|
}
|
|
16417
16283
|
}
|
|
16418
16284
|
return {
|
|
@@ -16428,9 +16294,9 @@ function refreshStationProfile(options = {}) {
|
|
|
16428
16294
|
function readStationProfile(env = process.env) {
|
|
16429
16295
|
const path = getStationProfileCachePath(env);
|
|
16430
16296
|
try {
|
|
16431
|
-
if (!
|
|
16297
|
+
if (!existsSync13(path))
|
|
16432
16298
|
return null;
|
|
16433
|
-
return
|
|
16299
|
+
return readFileSync12(path, "utf8");
|
|
16434
16300
|
} catch {
|
|
16435
16301
|
return null;
|
|
16436
16302
|
}
|
|
@@ -16454,22 +16320,172 @@ function stationProfileSource(env = process.env) {
|
|
|
16454
16320
|
init_instruction_graph();
|
|
16455
16321
|
init_asset_plan();
|
|
16456
16322
|
|
|
16323
|
+
// src/lib/harness-discovery.ts
|
|
16324
|
+
import { accessSync, constants as constants2, lstatSync as lstatSync6, realpathSync as realpathSync4, statSync as statSync5 } from "fs";
|
|
16325
|
+
import { homedir as homedir10 } from "os";
|
|
16326
|
+
import { delimiter, dirname as dirname9, isAbsolute as isAbsolute7, join as join17, resolve as resolve11 } from "path";
|
|
16327
|
+
var HARNESS_DISCOVERY_SCHEMA = "hasna.instructions.harness-discovery/v1";
|
|
16328
|
+
var DISCOVERABLE_HARNESSES = ["claude", "codex", "opencode", "sumi"];
|
|
16329
|
+
function pathInput(value, home) {
|
|
16330
|
+
if (!value || value !== value.trim() || /[\x00-\x1f\x7f]/.test(value))
|
|
16331
|
+
throw new Error("Harness paths must be nonempty and contain no surrounding whitespace or control characters.");
|
|
16332
|
+
if (value.split("/").some((segment) => segment === "." || segment === ".."))
|
|
16333
|
+
throw new Error("Harness paths must not contain dot segments; normalizing them can change a symlink destination.");
|
|
16334
|
+
let path = value;
|
|
16335
|
+
if (home) {
|
|
16336
|
+
for (const prefix of ["~", "{{HOME}}", "{{HOME_DIR}}", "${HOME}"]) {
|
|
16337
|
+
if (path === prefix)
|
|
16338
|
+
path = home;
|
|
16339
|
+
else if (path.startsWith(`${prefix}/`))
|
|
16340
|
+
path = join17(home, path.slice(prefix.length + 1));
|
|
16341
|
+
}
|
|
16342
|
+
}
|
|
16343
|
+
if (!isAbsolute7(path))
|
|
16344
|
+
throw new Error("Harness paths must be absolute or explicitly relative to the owner home (~/ or {{HOME_DIR}}/).");
|
|
16345
|
+
return resolve11(path);
|
|
16346
|
+
}
|
|
16347
|
+
function observe(path) {
|
|
16348
|
+
let symlink = false;
|
|
16349
|
+
let viaSymlink = false;
|
|
16350
|
+
for (let parent = dirname9(path);parent !== dirname9(parent); parent = dirname9(parent)) {
|
|
16351
|
+
try {
|
|
16352
|
+
if (lstatSync6(parent).isSymbolicLink()) {
|
|
16353
|
+
viaSymlink = true;
|
|
16354
|
+
break;
|
|
16355
|
+
}
|
|
16356
|
+
} catch {}
|
|
16357
|
+
}
|
|
16358
|
+
try {
|
|
16359
|
+
symlink = lstatSync6(path).isSymbolicLink();
|
|
16360
|
+
const stat = statSync5(path);
|
|
16361
|
+
return { path, state: "present", realPath: realpathSync4(path), symlink, viaSymlink: viaSymlink || symlink, type: stat.isFile() ? "file" : stat.isDirectory() ? "directory" : "other" };
|
|
16362
|
+
} catch (error) {
|
|
16363
|
+
const missing = error.code === "ENOENT";
|
|
16364
|
+
return { path, state: missing ? symlink ? "dangling-symlink" : "missing" : "unreadable", realPath: null, symlink, viaSymlink: viaSymlink || symlink, type: null };
|
|
16365
|
+
}
|
|
16366
|
+
}
|
|
16367
|
+
function executableAt(path) {
|
|
16368
|
+
const observation = observe(path);
|
|
16369
|
+
if (observation.type !== "file")
|
|
16370
|
+
return null;
|
|
16371
|
+
try {
|
|
16372
|
+
accessSync(path, constants2.X_OK);
|
|
16373
|
+
return observation;
|
|
16374
|
+
} catch {
|
|
16375
|
+
return null;
|
|
16376
|
+
}
|
|
16377
|
+
}
|
|
16378
|
+
function configSelection(tool, home, env, override) {
|
|
16379
|
+
if (override !== undefined)
|
|
16380
|
+
return { path: pathInput(override, home), source: "override" };
|
|
16381
|
+
const key = { claude: "CLAUDE_CONFIG_DIR", codex: "CODEX_HOME", opencode: "OPENCODE_CONFIG_DIR", sumi: "SUMI_CONFIG_DIR" }[tool];
|
|
16382
|
+
const nativePath = (value, selector) => {
|
|
16383
|
+
try {
|
|
16384
|
+
return pathInput(value);
|
|
16385
|
+
} catch {
|
|
16386
|
+
throw new Error(`${selector} is present but is not an unambiguous absolute native path; supply the reviewed runtime path explicitly.`);
|
|
16387
|
+
}
|
|
16388
|
+
};
|
|
16389
|
+
if (env[key] !== undefined && !(tool === "codex" && env[key] === ""))
|
|
16390
|
+
return { path: nativePath(env[key], key), source: key };
|
|
16391
|
+
if (tool === "claude" || tool === "codex")
|
|
16392
|
+
return { path: join17(home, `.${tool}`), source: "native-default" };
|
|
16393
|
+
if (env.XDG_CONFIG_HOME !== undefined)
|
|
16394
|
+
return { path: join17(nativePath(env.XDG_CONFIG_HOME, "XDG_CONFIG_HOME"), tool), source: "XDG_CONFIG_HOME" };
|
|
16395
|
+
if (tool === "sumi") {
|
|
16396
|
+
if (env.SUMI_HOME !== undefined)
|
|
16397
|
+
return { path: join17(nativePath(env.SUMI_HOME, "SUMI_HOME"), "config"), source: "SUMI_HOME" };
|
|
16398
|
+
return null;
|
|
16399
|
+
}
|
|
16400
|
+
return { path: join17(home, ".config", tool), source: "native-default" };
|
|
16401
|
+
}
|
|
16402
|
+
function discoverHarnesses(options = {}) {
|
|
16403
|
+
const env = options.env ?? process.env;
|
|
16404
|
+
const home = pathInput(options.ownerHome ?? env.HOME ?? env.USERPROFILE ?? homedir10());
|
|
16405
|
+
const project = options.projectRoot === undefined ? null : observe(pathInput(options.projectRoot, home));
|
|
16406
|
+
const variables = { HOME_DIR: home };
|
|
16407
|
+
if (project)
|
|
16408
|
+
variables.PROJECT_ROOT = project.path;
|
|
16409
|
+
const tools = DISCOVERABLE_HARNESSES.map((tool) => {
|
|
16410
|
+
const diagnostics = [];
|
|
16411
|
+
const override = options.overrides?.[tool];
|
|
16412
|
+
let executable = null;
|
|
16413
|
+
let executableSource = null;
|
|
16414
|
+
if (override?.executable !== undefined) {
|
|
16415
|
+
executable = executableAt(pathInput(override.executable, home));
|
|
16416
|
+
executableSource = "override";
|
|
16417
|
+
if (!executable)
|
|
16418
|
+
diagnostics.push("Explicit executable is missing, unreadable, not a file, or not executable; PATH fallback is disabled.");
|
|
16419
|
+
} else {
|
|
16420
|
+
for (const directory of (env.PATH ?? "").split(delimiter)) {
|
|
16421
|
+
if (!isAbsolute7(directory)) {
|
|
16422
|
+
const warning = "Relative or empty PATH entries were ignored; this is an absolute-path candidate inventory, not proof of the launcher's selected executable.";
|
|
16423
|
+
if (!diagnostics.includes(warning))
|
|
16424
|
+
diagnostics.push(warning);
|
|
16425
|
+
continue;
|
|
16426
|
+
}
|
|
16427
|
+
let validated;
|
|
16428
|
+
try {
|
|
16429
|
+
validated = pathInput(directory);
|
|
16430
|
+
} catch {
|
|
16431
|
+
diagnostics.push("An ambiguous PATH directory was ignored; use an explicit executable override after reviewing its native path.");
|
|
16432
|
+
continue;
|
|
16433
|
+
}
|
|
16434
|
+
executable = executableAt(join17(validated, tool));
|
|
16435
|
+
if (executable) {
|
|
16436
|
+
executableSource = "PATH";
|
|
16437
|
+
break;
|
|
16438
|
+
}
|
|
16439
|
+
}
|
|
16440
|
+
}
|
|
16441
|
+
let selected = null;
|
|
16442
|
+
let configError = false;
|
|
16443
|
+
try {
|
|
16444
|
+
selected = configSelection(tool, home, env, override?.configDir);
|
|
16445
|
+
} catch (error) {
|
|
16446
|
+
if (override?.configDir !== undefined)
|
|
16447
|
+
throw error;
|
|
16448
|
+
configError = true;
|
|
16449
|
+
diagnostics.push(`Config root unresolved: ${error.message}`);
|
|
16450
|
+
}
|
|
16451
|
+
const config = selected ? { ...observe(selected.path), source: selected.source } : null;
|
|
16452
|
+
const filename = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
16453
|
+
const globalPrompt = config ? observe(join17(config.path, filename)) : null;
|
|
16454
|
+
const promptOverrides = tool === "codex" ? [...new Set([config?.path, project?.path].filter((path) => !!path))].map((path) => observe(join17(path, "AGENTS.override.md"))) : [];
|
|
16455
|
+
if (!config && !configError)
|
|
16456
|
+
diagnostics.push("Config root unresolved: supply the actual `sumi debug paths config` result as a configDir override; no runtime or migration was invoked.");
|
|
16457
|
+
if (executable) {
|
|
16458
|
+
variables[`${tool.toUpperCase()}_EXECUTABLE`] = executable.path;
|
|
16459
|
+
if (config)
|
|
16460
|
+
variables[`${tool.toUpperCase()}_CONFIG_DIR`] = config.path;
|
|
16461
|
+
}
|
|
16462
|
+
const linkedGlobal = !!globalPrompt?.viaSymlink || !!config?.viaSymlink;
|
|
16463
|
+
const hasOverride = promptOverrides.some((path) => path.state !== "missing" || path.viaSymlink);
|
|
16464
|
+
const scopeReviewRequired = linkedGlobal || hasOverride;
|
|
16465
|
+
if (linkedGlobal)
|
|
16466
|
+
diagnostics.push("Global config or prompt resolves through a symlink; preserve its lexical path and review the destination scope before planning writes.");
|
|
16467
|
+
if (hasOverride)
|
|
16468
|
+
diagnostics.push("A Codex AGENTS.override.md candidate may take precedence; review its native loader behavior and scope before applying AGENTS.md.");
|
|
16469
|
+
return { tool, status: executable ? "executable-found" : "absent", executable, executableSource, versionEvidence: "not-probed", config, globalPrompt, projectPrompt: project ? observe(join17(project.path, filename)) : null, promptOverrides, scopeReviewRequired, diagnostics };
|
|
16470
|
+
});
|
|
16471
|
+
return { schema: HARNESS_DISCOVERY_SCHEMA, ownerHome: observe(home), projectRoot: project, tools, templateVariables: variables };
|
|
16472
|
+
}
|
|
16457
16473
|
// src/lib/session-apply.ts
|
|
16458
16474
|
init_project_context();
|
|
16459
16475
|
init_session_render_state();
|
|
16460
16476
|
init_session_render();
|
|
16461
16477
|
init_cursor_authority();
|
|
16462
16478
|
init_session_authority();
|
|
16463
|
-
import { createHash as
|
|
16479
|
+
import { createHash as createHash9, randomUUID as randomUUID4 } from "crypto";
|
|
16464
16480
|
import {
|
|
16465
|
-
existsSync as
|
|
16466
|
-
lstatSync as
|
|
16467
|
-
mkdirSync as
|
|
16468
|
-
readFileSync as
|
|
16481
|
+
existsSync as existsSync14,
|
|
16482
|
+
lstatSync as lstatSync7,
|
|
16483
|
+
mkdirSync as mkdirSync6,
|
|
16484
|
+
readFileSync as readFileSync13,
|
|
16469
16485
|
readdirSync as readdirSync3,
|
|
16470
|
-
statSync as
|
|
16486
|
+
statSync as statSync6
|
|
16471
16487
|
} from "fs";
|
|
16472
|
-
import { dirname as dirname10, isAbsolute as
|
|
16488
|
+
import { dirname as dirname10, isAbsolute as isAbsolute8, join as join18, parse as parse4, posix as posix3, relative as relative5, resolve as resolve12 } from "path";
|
|
16473
16489
|
class SessionApplyError extends Error {
|
|
16474
16490
|
constructor(message) {
|
|
16475
16491
|
super(message);
|
|
@@ -16499,13 +16515,15 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16499
16515
|
const adoptedHashes = new Map(adoptions.map((entry) => [entry.relativePath, entry.preimageSha256]));
|
|
16500
16516
|
const reconciliations = validateFileReconciliations(plan, targetHome, previousManifest, options);
|
|
16501
16517
|
const reconciledHashes = new Map(reconciliations.map((entry) => [entry.relativePath, entry.preimageSha256]));
|
|
16502
|
-
const
|
|
16518
|
+
const retirements = validateFileRetirements(plan, targetHome, previousManifest, options);
|
|
16519
|
+
const retiredHashes = new Map(retirements.map((entry) => [entry.relativePath, entry.preimageSha256]));
|
|
16520
|
+
const manifestFile = manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations, retirements);
|
|
16503
16521
|
const files = [...payloadFiles, manifestFile];
|
|
16504
16522
|
const currentRelativePaths = new Set(files.map((file) => file.relativePath));
|
|
16505
16523
|
const drift = checkSessionRenderDrift(targetHome, manifestPath);
|
|
16506
16524
|
const results = [
|
|
16507
16525
|
...files.map((file) => planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes)),
|
|
16508
|
-
...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options)
|
|
16526
|
+
...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes)
|
|
16509
16527
|
];
|
|
16510
16528
|
assertNotSilentManagedWipeout(plan, results);
|
|
16511
16529
|
const conflicts = results.filter((result) => result.action === "conflict");
|
|
@@ -16529,7 +16547,8 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16529
16547
|
conflicts,
|
|
16530
16548
|
drift,
|
|
16531
16549
|
adoptions,
|
|
16532
|
-
reconciliations
|
|
16550
|
+
reconciliations,
|
|
16551
|
+
retirements
|
|
16533
16552
|
};
|
|
16534
16553
|
}
|
|
16535
16554
|
let snapshotPath = null;
|
|
@@ -16544,10 +16563,14 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16544
16563
|
const forcePortableFileOps = options.test_hooks?.force_portable_file_ops ?? false;
|
|
16545
16564
|
assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
|
|
16546
16565
|
ensureSessionTargetHome(targetHome);
|
|
16547
|
-
rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations);
|
|
16566
|
+
rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements);
|
|
16548
16567
|
snapshotPath = rollback.snapshotPath;
|
|
16549
16568
|
options.test_hooks?.before_apply_writes?.({ plan, results });
|
|
16550
16569
|
assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
|
|
16570
|
+
for (const result of results) {
|
|
16571
|
+
assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
|
|
16572
|
+
coordination?.assert_held();
|
|
16573
|
+
}
|
|
16551
16574
|
const resultsByPath = new Map(results.map((result) => [result.path, result]));
|
|
16552
16575
|
for (const file of payloadFiles) {
|
|
16553
16576
|
applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
|
|
@@ -16583,7 +16606,8 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16583
16606
|
conflicts,
|
|
16584
16607
|
drift,
|
|
16585
16608
|
adoptions,
|
|
16586
|
-
reconciliations
|
|
16609
|
+
reconciliations,
|
|
16610
|
+
retirements
|
|
16587
16611
|
};
|
|
16588
16612
|
}
|
|
16589
16613
|
function assertManifestPrecondition(path, targetHome, expected) {
|
|
@@ -16612,7 +16636,7 @@ function validateFileAdoptions(plan, targetHome, previousHashes, options) {
|
|
|
16612
16636
|
throw new SessionApplyError(`Duplicate file adoption target: ${request.relativePath}`);
|
|
16613
16637
|
}
|
|
16614
16638
|
seen.add(request.relativePath);
|
|
16615
|
-
const candidates = plan.
|
|
16639
|
+
const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
|
|
16616
16640
|
if (candidates.length !== 1 || candidates[0].role === "manifest") {
|
|
16617
16641
|
throw new SessionApplyError(`File adoption target is not a unique planned instruction output: ${request.relativePath}`);
|
|
16618
16642
|
}
|
|
@@ -16631,15 +16655,19 @@ function validateFileAdoptions(plan, targetHome, previousHashes, options) {
|
|
|
16631
16655
|
};
|
|
16632
16656
|
});
|
|
16633
16657
|
}
|
|
16658
|
+
function exactPreimageCandidates(plan) {
|
|
16659
|
+
const customAgentPaths = new Set((plan.assetPlan?.assets ?? []).filter((asset) => asset.kind === "custom-agent" && asset.support === "supported" && asset.action === "write" && asset.destination.strategy === "emit-file").map((asset) => posix3.normalize(asset.destination.relativePath)));
|
|
16660
|
+
return [...plan.files, ...(plan.assetFiles ?? []).filter((file) => customAgentPaths.has(file.relativePath))];
|
|
16661
|
+
}
|
|
16634
16662
|
function readExactFilePreimage(path, targetHome, request, operation) {
|
|
16635
16663
|
if (currentSessionFileHash(path, targetHome) === null) {
|
|
16636
16664
|
throw new SessionApplyError(`File ${operation} target does not exist: ${request.relativePath}`);
|
|
16637
16665
|
}
|
|
16638
|
-
const bytes =
|
|
16666
|
+
const bytes = readFileSync13(path);
|
|
16639
16667
|
if (!bytes.equals(Buffer.from(bytes.toString("utf8"), "utf8"))) {
|
|
16640
16668
|
throw new SessionApplyError(`File ${operation} target must contain losslessly restorable UTF-8: ${request.relativePath}`);
|
|
16641
16669
|
}
|
|
16642
|
-
const observedSha256 =
|
|
16670
|
+
const observedSha256 = createHash9("sha256").update(bytes).digest("hex");
|
|
16643
16671
|
if (observedSha256 !== request.sha256) {
|
|
16644
16672
|
throw new SessionApplyError(`File ${operation} SHA-256 precondition failed: ${request.relativePath}`);
|
|
16645
16673
|
}
|
|
@@ -16668,7 +16696,7 @@ function validateFileReconciliations(plan, targetHome, previousManifest, options
|
|
|
16668
16696
|
throw new SessionApplyError(`Duplicate file reconciliation target: ${request.relativePath}`);
|
|
16669
16697
|
}
|
|
16670
16698
|
seen.add(request.relativePath);
|
|
16671
|
-
const candidates = plan.
|
|
16699
|
+
const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
|
|
16672
16700
|
if (candidates.length !== 1 || candidates[0].role === "manifest") {
|
|
16673
16701
|
throw new SessionApplyError(`File reconciliation target is not a unique planned instruction output: ${request.relativePath}`);
|
|
16674
16702
|
}
|
|
@@ -16695,8 +16723,47 @@ function validateFileReconciliations(plan, targetHome, previousManifest, options
|
|
|
16695
16723
|
};
|
|
16696
16724
|
});
|
|
16697
16725
|
}
|
|
16698
|
-
function
|
|
16699
|
-
|
|
16726
|
+
function validateFileRetirements(plan, targetHome, previousManifest, options) {
|
|
16727
|
+
const requests = options.retireFiles ?? [];
|
|
16728
|
+
if (!Array.isArray(requests))
|
|
16729
|
+
throw new SessionApplyError("Session file retirements must be an array.");
|
|
16730
|
+
if (requests.length === 0)
|
|
16731
|
+
return [];
|
|
16732
|
+
if (options.force)
|
|
16733
|
+
throw new SessionApplyError("Exact file retirement cannot be combined with force.");
|
|
16734
|
+
if (options.expectedManifestSha256 === undefined) {
|
|
16735
|
+
throw new SessionApplyError("Exact file retirement requires an expected manifest SHA-256 precondition.");
|
|
16736
|
+
}
|
|
16737
|
+
if (!previousManifest || previousManifest.targetOwner?.writer?.id !== SESSION_RENDERER_OWNER_ID || previousManifest.tool !== plan.tool || previousManifest.targetHome !== targetHome) {
|
|
16738
|
+
throw new SessionApplyError("File retirement requires this renderer's manifest for the same tool and target home.");
|
|
16739
|
+
}
|
|
16740
|
+
const seen = new Set;
|
|
16741
|
+
return requests.map((request) => {
|
|
16742
|
+
if (!request || typeof request.relativePath !== "string" || typeof request.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(request.sha256)) {
|
|
16743
|
+
throw new SessionApplyError("Each file retirement requires a plan-relative path and a 64-character lowercase SHA-256.");
|
|
16744
|
+
}
|
|
16745
|
+
if (seen.has(request.relativePath))
|
|
16746
|
+
throw new SessionApplyError(`Duplicate file retirement target: ${request.relativePath}`);
|
|
16747
|
+
seen.add(request.relativePath);
|
|
16748
|
+
if (plan.allFiles.some((file) => file.relativePath === request.relativePath)) {
|
|
16749
|
+
throw new SessionApplyError(`File retirement target is retained by the new plan: ${request.relativePath}`);
|
|
16750
|
+
}
|
|
16751
|
+
const owned = previousManifest.files.filter((entry) => entry.relativePath === request.relativePath);
|
|
16752
|
+
const path = resolveManifestRelativePath(request.relativePath, targetHome);
|
|
16753
|
+
if (owned.length !== 1 || owned[0].path !== path || !/^[a-f0-9]{64}$/.test(owned[0].sha256) || !Array.isArray(owned[0].sourceIds) || owned[0].sourceIds.some((id) => typeof id !== "string") || !isPlanManagedFile(plan, request.relativePath, owned[0].role) && owned[0].role !== "asset") {
|
|
16754
|
+
throw new SessionApplyError(`File retirement target is not an obsolete managed fragment, rule, or asset: ${request.relativePath}`);
|
|
16755
|
+
}
|
|
16756
|
+
return {
|
|
16757
|
+
path,
|
|
16758
|
+
relativePath: request.relativePath,
|
|
16759
|
+
preimageSha256: readExactFilePreimage(path, targetHome, request, "retirement"),
|
|
16760
|
+
previousManagedSha256: owned[0].sha256,
|
|
16761
|
+
sourceIds: [...owned[0].sourceIds]
|
|
16762
|
+
};
|
|
16763
|
+
});
|
|
16764
|
+
}
|
|
16765
|
+
function manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations, retirements) {
|
|
16766
|
+
if (adoptions.length === 0 && previousManifest?.adoptions === undefined && reconciliations.length === 0 && previousManifest?.reconciliations === undefined && retirements.length === 0 && previousManifest?.retirements === undefined)
|
|
16700
16767
|
return plan.manifestFile;
|
|
16701
16768
|
if (previousManifest?.adoptions !== undefined && !Array.isArray(previousManifest.adoptions)) {
|
|
16702
16769
|
throw new SessionApplyError("Previous session manifest adoption provenance is invalid.");
|
|
@@ -16737,14 +16804,32 @@ function manifestWithFileProvenance(plan, previousManifest, adoptions, reconcili
|
|
|
16737
16804
|
};
|
|
16738
16805
|
reconciledEntries.set(JSON.stringify(canonical), canonical);
|
|
16739
16806
|
}
|
|
16807
|
+
if (previousManifest?.retirements !== undefined && !Array.isArray(previousManifest.retirements)) {
|
|
16808
|
+
throw new SessionApplyError("Previous session manifest retirement provenance is invalid.");
|
|
16809
|
+
}
|
|
16810
|
+
const retiredEntries = new Map;
|
|
16811
|
+
for (const entry of [...previousManifest?.retirements ?? [], ...retirements.map(({ path: _path, ...receipt }) => receipt)]) {
|
|
16812
|
+
if (!entry || typeof entry.relativePath !== "string" || !/^[a-f0-9]{64}$/.test(entry.preimageSha256) || !/^[a-f0-9]{64}$/.test(entry.previousManagedSha256) || !Array.isArray(entry.sourceIds) || entry.sourceIds.some((id) => typeof id !== "string")) {
|
|
16813
|
+
throw new SessionApplyError("Previous session manifest retirement provenance is invalid.");
|
|
16814
|
+
}
|
|
16815
|
+
resolveManifestRelativePath(entry.relativePath, plan.targetHome);
|
|
16816
|
+
const canonical = {
|
|
16817
|
+
relativePath: entry.relativePath,
|
|
16818
|
+
preimageSha256: entry.preimageSha256,
|
|
16819
|
+
previousManagedSha256: entry.previousManagedSha256,
|
|
16820
|
+
sourceIds: [...entry.sourceIds]
|
|
16821
|
+
};
|
|
16822
|
+
retiredEntries.set(JSON.stringify(canonical), canonical);
|
|
16823
|
+
}
|
|
16740
16824
|
const manifest = {
|
|
16741
16825
|
...plan.manifest,
|
|
16742
16826
|
...adoptions.length > 0 || previousManifest?.adoptions !== undefined ? { adoptions: [...entries.values()] } : {},
|
|
16743
|
-
...reconciliations.length > 0 || previousManifest?.reconciliations !== undefined ? { reconciliations: [...reconciledEntries.values()] } : {}
|
|
16827
|
+
...reconciliations.length > 0 || previousManifest?.reconciliations !== undefined ? { reconciliations: [...reconciledEntries.values()] } : {},
|
|
16828
|
+
...retirements.length > 0 || previousManifest?.retirements !== undefined ? { retirements: [...retiredEntries.values()] } : {}
|
|
16744
16829
|
};
|
|
16745
16830
|
const content = `${JSON.stringify(manifest, null, 2)}
|
|
16746
16831
|
`;
|
|
16747
|
-
return { ...plan.manifestFile, content, sha256:
|
|
16832
|
+
return { ...plan.manifestFile, content, sha256: sha2569(content) };
|
|
16748
16833
|
}
|
|
16749
16834
|
function assertCursorAuthorityUnchanged(plan) {
|
|
16750
16835
|
if (plan.tool !== "cursor" || plan.targetKind === "blocked")
|
|
@@ -16772,13 +16857,13 @@ function assertClaudeAuthorityStillClear(plan, targetHome, ownedClaudeAuthoritie
|
|
|
16772
16857
|
throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
|
|
16773
16858
|
}
|
|
16774
16859
|
function ensureSessionTargetHome(targetHome) {
|
|
16775
|
-
if (!
|
|
16776
|
-
|
|
16860
|
+
if (!existsSync14(targetHome))
|
|
16861
|
+
mkdirSync6(targetHome, { recursive: true, mode: 448 });
|
|
16777
16862
|
assertSafeTargetHome(targetHome);
|
|
16778
16863
|
}
|
|
16779
16864
|
function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
16780
16865
|
const safeTargetHome = assertSafeTargetHome(targetHome);
|
|
16781
|
-
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(
|
|
16866
|
+
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve12(manifestPath)), safeTargetHome) : resolve12(safeTargetHome, ".hasna", "session-render-manifest.json");
|
|
16782
16867
|
const checkedAt = new Date().toISOString();
|
|
16783
16868
|
const previousManifest = readPreviousManifest(resolvedManifestPath);
|
|
16784
16869
|
if (!previousManifest) {
|
|
@@ -16795,7 +16880,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
16795
16880
|
const drifted = [];
|
|
16796
16881
|
for (const file of previousManifest.files) {
|
|
16797
16882
|
const target = resolveManifestRelativePath(file.relativePath, safeTargetHome);
|
|
16798
|
-
if (!
|
|
16883
|
+
if (!existsSync14(target)) {
|
|
16799
16884
|
missing.push({
|
|
16800
16885
|
path: target,
|
|
16801
16886
|
relativePath: file.relativePath,
|
|
@@ -16805,7 +16890,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
16805
16890
|
});
|
|
16806
16891
|
continue;
|
|
16807
16892
|
}
|
|
16808
|
-
const actualSha256 =
|
|
16893
|
+
const actualSha256 = sha2569(readFileSync13(target, "utf-8"));
|
|
16809
16894
|
if (actualSha256 !== file.sha256) {
|
|
16810
16895
|
drifted.push({
|
|
16811
16896
|
path: target,
|
|
@@ -16828,13 +16913,13 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
16828
16913
|
function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
|
|
16829
16914
|
const snapshot = readSessionRenderSnapshot(snapshotPath);
|
|
16830
16915
|
const targetHome = assertSafeTargetHome(snapshot.targetHome);
|
|
16831
|
-
const resolvedSnapshotPath =
|
|
16916
|
+
const resolvedSnapshotPath = resolve12(snapshotPath);
|
|
16832
16917
|
const snapshotDir = getSessionRenderSnapshotDir(targetHome);
|
|
16833
|
-
const snapshotDirRelative =
|
|
16834
|
-
const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !
|
|
16918
|
+
const snapshotDirRelative = relative5(snapshotDir, resolvedSnapshotPath);
|
|
16919
|
+
const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute8(snapshotDirRelative);
|
|
16835
16920
|
if (!insideSnapshotDir) {
|
|
16836
|
-
const snapshotRelativePath =
|
|
16837
|
-
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") ||
|
|
16921
|
+
const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
|
|
16922
|
+
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute8(snapshotRelativePath)) {
|
|
16838
16923
|
throw new SessionApplyError("Session snapshot must be stored inside its session-render snapshot location.");
|
|
16839
16924
|
}
|
|
16840
16925
|
}
|
|
@@ -16951,19 +17036,19 @@ function requiredRestoreHash(file) {
|
|
|
16951
17036
|
return file.previousSha256;
|
|
16952
17037
|
}
|
|
16953
17038
|
function readSessionRenderSnapshot(snapshotPath) {
|
|
16954
|
-
const resolved =
|
|
16955
|
-
if (!
|
|
17039
|
+
const resolved = resolve12(snapshotPath);
|
|
17040
|
+
if (!existsSync14(resolved))
|
|
16956
17041
|
throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
|
|
16957
|
-
const stat =
|
|
17042
|
+
const stat = lstatSync7(resolved);
|
|
16958
17043
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
16959
17044
|
throw new SessionApplyError(`Session snapshot is not a regular file: ${snapshotPath}`);
|
|
16960
17045
|
}
|
|
16961
|
-
if (
|
|
17046
|
+
if (statSync6(resolved).size > 32 * 1024 * 1024) {
|
|
16962
17047
|
throw new SessionApplyError(`Session snapshot exceeds the 32 MiB restore limit: ${snapshotPath}`);
|
|
16963
17048
|
}
|
|
16964
17049
|
let parsed;
|
|
16965
17050
|
try {
|
|
16966
|
-
parsed = JSON.parse(
|
|
17051
|
+
parsed = JSON.parse(readFileSync13(resolved, "utf8"));
|
|
16967
17052
|
} catch {
|
|
16968
17053
|
throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
|
|
16969
17054
|
}
|
|
@@ -16985,7 +17070,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
16985
17070
|
const previousManifest = snapshot.previousManifest;
|
|
16986
17071
|
const previousFiles = new Map;
|
|
16987
17072
|
for (const file of snapshot.files) {
|
|
16988
|
-
if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" ||
|
|
17073
|
+
if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" || sha2569(file.content) !== file.sha256) {
|
|
16989
17074
|
throw new SessionApplyError(`Session snapshot previous file metadata is invalid: ${snapshotPath}`);
|
|
16990
17075
|
}
|
|
16991
17076
|
resolveSnapshotFilePath(file.relativePath, file.path, targetHome);
|
|
@@ -17032,8 +17117,8 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
17032
17117
|
}
|
|
17033
17118
|
function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
|
|
17034
17119
|
assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
|
|
17035
|
-
const manifestPath =
|
|
17036
|
-
const manifestRelativePath =
|
|
17120
|
+
const manifestPath = resolve12(snapshot.manifestPath);
|
|
17121
|
+
const manifestRelativePath = relative5(targetHome, manifestPath).replaceAll("\\", "/");
|
|
17037
17122
|
resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
|
|
17038
17123
|
const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
|
|
17039
17124
|
if (manifestSha256 === null) {
|
|
@@ -17041,7 +17126,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
17041
17126
|
}
|
|
17042
17127
|
let parsedManifest;
|
|
17043
17128
|
try {
|
|
17044
|
-
parsedManifest = JSON.parse(
|
|
17129
|
+
parsedManifest = JSON.parse(readFileSync13(manifestPath, "utf8"));
|
|
17045
17130
|
} catch {
|
|
17046
17131
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
|
|
17047
17132
|
}
|
|
@@ -17049,7 +17134,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
17049
17134
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
|
|
17050
17135
|
}
|
|
17051
17136
|
const appliedManifest = parsedManifest;
|
|
17052
|
-
if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" ||
|
|
17137
|
+
if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve12(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
|
|
17053
17138
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
|
|
17054
17139
|
}
|
|
17055
17140
|
const afterFiles = [];
|
|
@@ -17134,16 +17219,16 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
|
|
|
17134
17219
|
throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
|
|
17135
17220
|
}
|
|
17136
17221
|
for (const entry of readdirSync3(dirname10(snapshotPath))) {
|
|
17137
|
-
const candidatePath =
|
|
17138
|
-
if (candidatePath ===
|
|
17222
|
+
const candidatePath = resolve12(dirname10(snapshotPath), entry);
|
|
17223
|
+
if (candidatePath === resolve12(snapshotPath) || !entry.endsWith(".json"))
|
|
17139
17224
|
continue;
|
|
17140
|
-
const candidateStat =
|
|
17225
|
+
const candidateStat = lstatSync7(candidatePath);
|
|
17141
17226
|
if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
|
|
17142
17227
|
continue;
|
|
17143
17228
|
try {
|
|
17144
|
-
const candidate = JSON.parse(
|
|
17229
|
+
const candidate = JSON.parse(readFileSync13(candidatePath, "utf8"));
|
|
17145
17230
|
const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
|
|
17146
|
-
if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" &&
|
|
17231
|
+
if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve12(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
|
|
17147
17232
|
throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
|
|
17148
17233
|
}
|
|
17149
17234
|
} catch (error) {
|
|
@@ -17201,7 +17286,7 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
17201
17286
|
return "create";
|
|
17202
17287
|
}
|
|
17203
17288
|
if (file.role === "manifest" && previousManifest) {
|
|
17204
|
-
const previousManifestSha256 =
|
|
17289
|
+
const previousManifestSha256 = sha2569(`${JSON.stringify(previousManifest, null, 2)}
|
|
17205
17290
|
`);
|
|
17206
17291
|
if (previousManifestSha256 !== file.sha256) {
|
|
17207
17292
|
throw new SessionApplyError(`Cannot infer legacy v1 manifest action without a before-image: ${file.relativePath}`);
|
|
@@ -17212,15 +17297,15 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
17212
17297
|
}
|
|
17213
17298
|
function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
|
|
17214
17299
|
const path = resolveManifestRelativePath(relativePath, targetHome);
|
|
17215
|
-
if (
|
|
17300
|
+
if (resolve12(recordedPath) !== path) {
|
|
17216
17301
|
throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
|
|
17217
17302
|
}
|
|
17218
17303
|
return path;
|
|
17219
17304
|
}
|
|
17220
17305
|
function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes) {
|
|
17221
17306
|
const target = resolvePlannedFilePath(plan, file, targetHome);
|
|
17222
|
-
const previousContent =
|
|
17223
|
-
const previousSha256 = previousContent === null ? null :
|
|
17307
|
+
const previousContent = existsSync14(target) ? readFileSync13(target, "utf-8") : null;
|
|
17308
|
+
const previousSha256 = previousContent === null ? null : sha2569(previousContent);
|
|
17224
17309
|
const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
|
|
17225
17310
|
const changed = previousContent !== file.content;
|
|
17226
17311
|
const exactSha256 = adoptedHashes.get(file.relativePath) ?? reconciledHashes.get(file.relativePath);
|
|
@@ -17328,17 +17413,55 @@ function isPlanManagedFile(plan, relativePath, role) {
|
|
|
17328
17413
|
return true;
|
|
17329
17414
|
return plan.tool === "claude" && role === "rule" && relativePath.startsWith("rules/");
|
|
17330
17415
|
}
|
|
17331
|
-
function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options) {
|
|
17416
|
+
function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes) {
|
|
17332
17417
|
if (!previousManifest)
|
|
17333
17418
|
return [];
|
|
17334
|
-
return previousManifest.files.filter((file) => !currentRelativePaths.has(file.relativePath)).filter((file) => isPlanManagedFile(plan, file.relativePath, file.role)).map((file) => planStaleFileResult(file, targetHome, options)).filter((result) => result !== null);
|
|
17419
|
+
return previousManifest.files.filter((file) => !currentRelativePaths.has(file.relativePath)).filter((file) => isPlanManagedFile(plan, file.relativePath, file.role) || file.role === "asset" || retiredHashes.has(file.relativePath)).map((file) => planStaleFileResult(file, targetHome, options, retiredHashes.get(file.relativePath))).filter((result) => result !== null);
|
|
17335
17420
|
}
|
|
17336
|
-
function planStaleFileResult(file, targetHome, options) {
|
|
17421
|
+
function planStaleFileResult(file, targetHome, options, retiredHash) {
|
|
17337
17422
|
const target = resolveManifestRelativePath(file.relativePath, targetHome);
|
|
17338
|
-
if (!
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17423
|
+
if (!existsSync14(target)) {
|
|
17424
|
+
if (file.role !== "asset")
|
|
17425
|
+
return null;
|
|
17426
|
+
return {
|
|
17427
|
+
path: target,
|
|
17428
|
+
relativePath: file.relativePath,
|
|
17429
|
+
role: file.role,
|
|
17430
|
+
action: "conflict",
|
|
17431
|
+
changed: true,
|
|
17432
|
+
previousSha256: null,
|
|
17433
|
+
newSha256: "",
|
|
17434
|
+
reason: "obsolete managed asset is missing; preserve manifest ownership until its reviewed preimage is restored and retired exactly"
|
|
17435
|
+
};
|
|
17436
|
+
}
|
|
17437
|
+
const previousContent = readFileSync13(target, "utf-8");
|
|
17438
|
+
const previousSha256 = sha2569(previousContent);
|
|
17439
|
+
if (retiredHash !== undefined) {
|
|
17440
|
+
if (previousSha256 !== retiredHash)
|
|
17441
|
+
throw new SessionApplyError(`File retirement preimage changed after validation: ${file.relativePath}`);
|
|
17442
|
+
return {
|
|
17443
|
+
path: target,
|
|
17444
|
+
relativePath: file.relativePath,
|
|
17445
|
+
role: file.role,
|
|
17446
|
+
action: "delete",
|
|
17447
|
+
changed: true,
|
|
17448
|
+
previousSha256,
|
|
17449
|
+
newSha256: "",
|
|
17450
|
+
reason: "exact reviewed obsolete managed file retired"
|
|
17451
|
+
};
|
|
17452
|
+
}
|
|
17453
|
+
if (file.role === "asset") {
|
|
17454
|
+
return {
|
|
17455
|
+
path: target,
|
|
17456
|
+
relativePath: file.relativePath,
|
|
17457
|
+
role: file.role,
|
|
17458
|
+
action: "conflict",
|
|
17459
|
+
changed: true,
|
|
17460
|
+
previousSha256,
|
|
17461
|
+
newSha256: "",
|
|
17462
|
+
reason: "obsolete managed asset requires exact retirement; preserve ownership until --retire-file and --expected-manifest-sha256 are supplied"
|
|
17463
|
+
};
|
|
17464
|
+
}
|
|
17342
17465
|
if (!options.force && previousSha256 !== file.sha256) {
|
|
17343
17466
|
return {
|
|
17344
17467
|
path: target,
|
|
@@ -17382,31 +17505,31 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
|
|
|
17382
17505
|
return previousHashes.get(file.relativePath) === previousSha256;
|
|
17383
17506
|
}
|
|
17384
17507
|
function resolvePlannedFilePath(plan, file, targetHome) {
|
|
17385
|
-
const target =
|
|
17386
|
-
const rel =
|
|
17387
|
-
if (rel === "" || rel === ".." || rel.startsWith("../") ||
|
|
17508
|
+
const target = resolve12(targetHome, ...file.relativePath.split("/"));
|
|
17509
|
+
const rel = relative5(targetHome, target);
|
|
17510
|
+
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute8(rel)) {
|
|
17388
17511
|
throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
|
|
17389
17512
|
}
|
|
17390
|
-
if (
|
|
17513
|
+
if (resolve12(file.path) !== target) {
|
|
17391
17514
|
throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
|
|
17392
17515
|
}
|
|
17393
17516
|
assertNoSymlinkSegments2(targetHome, target);
|
|
17394
17517
|
return target;
|
|
17395
17518
|
}
|
|
17396
17519
|
function resolveManifestRelativePath(relativePath, targetHome) {
|
|
17397
|
-
const target =
|
|
17398
|
-
const rel =
|
|
17399
|
-
if (rel === "" || rel === ".." || rel.startsWith("../") ||
|
|
17520
|
+
const target = resolve12(targetHome, ...relativePath.split(/[\\/]+/));
|
|
17521
|
+
const rel = relative5(targetHome, target);
|
|
17522
|
+
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute8(rel)) {
|
|
17400
17523
|
throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
|
|
17401
17524
|
}
|
|
17402
17525
|
assertNoSymlinkSegments2(targetHome, target);
|
|
17403
17526
|
return target;
|
|
17404
17527
|
}
|
|
17405
17528
|
function readPreviousManifest(path) {
|
|
17406
|
-
if (!
|
|
17529
|
+
if (!existsSync14(path))
|
|
17407
17530
|
return null;
|
|
17408
17531
|
try {
|
|
17409
|
-
const parsed = JSON.parse(
|
|
17532
|
+
const parsed = JSON.parse(readFileSync13(path, "utf-8"));
|
|
17410
17533
|
if (parsed.schema !== SESSION_RENDER_SCHEMA)
|
|
17411
17534
|
return null;
|
|
17412
17535
|
if (!Array.isArray(parsed.files))
|
|
@@ -17440,18 +17563,18 @@ function applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, a
|
|
|
17440
17563
|
function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
|
|
17441
17564
|
const actualHash = currentSessionFileHash(path, targetHome);
|
|
17442
17565
|
if (actualHash !== expectedHash) {
|
|
17443
|
-
throw new SessionApplyError(`Session apply path changed after planning: ${
|
|
17566
|
+
throw new SessionApplyError(`Session apply path changed after planning: ${relative5(targetHome, path)}`);
|
|
17444
17567
|
}
|
|
17445
17568
|
}
|
|
17446
17569
|
function currentSessionFileHash(path, targetHome) {
|
|
17447
17570
|
assertNoSymlinkSegments2(targetHome, path);
|
|
17448
|
-
if (!
|
|
17571
|
+
if (!existsSync14(path))
|
|
17449
17572
|
return null;
|
|
17450
|
-
const stat =
|
|
17573
|
+
const stat = lstatSync7(path);
|
|
17451
17574
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
17452
17575
|
throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
|
|
17453
17576
|
}
|
|
17454
|
-
return
|
|
17577
|
+
return sha2569(readFileSync13(path, "utf-8"));
|
|
17455
17578
|
}
|
|
17456
17579
|
function requiredPreviousHash(result) {
|
|
17457
17580
|
if (result.previousSha256 === null) {
|
|
@@ -17459,18 +17582,18 @@ function requiredPreviousHash(result) {
|
|
|
17459
17582
|
}
|
|
17460
17583
|
return result.previousSha256;
|
|
17461
17584
|
}
|
|
17462
|
-
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations) {
|
|
17585
|
+
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements) {
|
|
17463
17586
|
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").map((result) => {
|
|
17464
17587
|
assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
|
|
17465
|
-
const content =
|
|
17466
|
-
if (
|
|
17588
|
+
const content = readFileSync13(result.path, "utf-8");
|
|
17589
|
+
if (sha2569(content) !== result.previousSha256) {
|
|
17467
17590
|
throw new SessionApplyError(`Session snapshot preimage changed after planning: ${result.relativePath}`);
|
|
17468
17591
|
}
|
|
17469
17592
|
return {
|
|
17470
17593
|
path: result.path,
|
|
17471
17594
|
relativePath: result.relativePath,
|
|
17472
17595
|
role: result.role,
|
|
17473
|
-
sha256:
|
|
17596
|
+
sha256: sha2569(content),
|
|
17474
17597
|
content
|
|
17475
17598
|
};
|
|
17476
17599
|
});
|
|
@@ -17483,7 +17606,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
17483
17606
|
};
|
|
17484
17607
|
}
|
|
17485
17608
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
17486
|
-
const snapshotPath =
|
|
17609
|
+
const snapshotPath = join18(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID4()}.json`);
|
|
17487
17610
|
const afterFiles = results.map((result) => {
|
|
17488
17611
|
if (result.action === "conflict") {
|
|
17489
17612
|
throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
|
|
@@ -17508,11 +17631,12 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
17508
17631
|
files: existingFiles,
|
|
17509
17632
|
afterFiles,
|
|
17510
17633
|
...adoptions.length > 0 ? { adoptions } : {},
|
|
17511
|
-
...reconciliations.length > 0 ? { reconciliations } : {}
|
|
17634
|
+
...reconciliations.length > 0 ? { reconciliations } : {},
|
|
17635
|
+
...retirements.length > 0 ? { retirements } : {}
|
|
17512
17636
|
};
|
|
17513
17637
|
const snapshotContent = `${JSON.stringify(snapshot, null, 2)}
|
|
17514
17638
|
`;
|
|
17515
|
-
if ((adoptions.length > 0 || reconciliations.length > 0) && Buffer.byteLength(snapshotContent, "utf8") > 32 * 1024 * 1024) {
|
|
17639
|
+
if ((adoptions.length > 0 || reconciliations.length > 0 || retirements.length > 0) && Buffer.byteLength(snapshotContent, "utf8") > 32 * 1024 * 1024) {
|
|
17516
17640
|
throw new SessionApplyError("Exact file preimage snapshot exceeds the 32 MiB restore limit.");
|
|
17517
17641
|
}
|
|
17518
17642
|
coordination?.assert_held();
|
|
@@ -17535,51 +17659,51 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
17535
17659
|
};
|
|
17536
17660
|
}
|
|
17537
17661
|
function assertSafeTargetHome(targetHome) {
|
|
17538
|
-
if (!
|
|
17662
|
+
if (!isAbsolute8(targetHome))
|
|
17539
17663
|
throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
|
|
17540
|
-
const normalized =
|
|
17541
|
-
if (normalized ===
|
|
17664
|
+
const normalized = resolve12(targetHome);
|
|
17665
|
+
if (normalized === parse4(normalized).root) {
|
|
17542
17666
|
throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
|
|
17543
17667
|
}
|
|
17544
|
-
|
|
17545
|
-
if (
|
|
17668
|
+
assertNoSymlinkAncestors2(normalized);
|
|
17669
|
+
if (existsSync14(normalized) && lstatSync7(normalized).isSymbolicLink()) {
|
|
17546
17670
|
throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
|
|
17547
17671
|
}
|
|
17548
17672
|
return normalized;
|
|
17549
17673
|
}
|
|
17550
17674
|
function assertNoSymlinkSegments2(root, target) {
|
|
17551
|
-
|
|
17552
|
-
const rel =
|
|
17675
|
+
assertNoSymlinkAncestors2(root);
|
|
17676
|
+
const rel = relative5(root, target);
|
|
17553
17677
|
let current = root;
|
|
17554
17678
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17555
|
-
current =
|
|
17556
|
-
if (
|
|
17679
|
+
current = join18(current, segment);
|
|
17680
|
+
if (existsSync14(current) && lstatSync7(current).isSymbolicLink()) {
|
|
17557
17681
|
throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
|
|
17558
17682
|
}
|
|
17559
17683
|
}
|
|
17560
17684
|
}
|
|
17561
|
-
function
|
|
17562
|
-
const normalized =
|
|
17563
|
-
const parsed =
|
|
17685
|
+
function assertNoSymlinkAncestors2(path) {
|
|
17686
|
+
const normalized = resolve12(path);
|
|
17687
|
+
const parsed = parse4(normalized);
|
|
17564
17688
|
let current = parsed.root;
|
|
17565
|
-
const rel =
|
|
17689
|
+
const rel = relative5(parsed.root, normalized);
|
|
17566
17690
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17567
|
-
current =
|
|
17568
|
-
if (!
|
|
17691
|
+
current = join18(current, segment);
|
|
17692
|
+
if (!existsSync14(current))
|
|
17569
17693
|
return;
|
|
17570
|
-
if (
|
|
17694
|
+
if (lstatSync7(current).isSymbolicLink()) {
|
|
17571
17695
|
throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
|
|
17572
17696
|
}
|
|
17573
17697
|
}
|
|
17574
17698
|
}
|
|
17575
|
-
function
|
|
17576
|
-
return
|
|
17699
|
+
function sha2569(content) {
|
|
17700
|
+
return createHash9("sha256").update(content).digest("hex");
|
|
17577
17701
|
}
|
|
17578
17702
|
// src/lib/session-refresh.ts
|
|
17579
17703
|
init_zod();
|
|
17580
|
-
import { createHash as
|
|
17581
|
-
import { closeSync as closeSync4, constants as
|
|
17582
|
-
import { dirname as dirname11, join as
|
|
17704
|
+
import { createHash as createHash10 } from "crypto";
|
|
17705
|
+
import { closeSync as closeSync4, constants as constants3, fstatSync as fstatSync4, lstatSync as lstatSync8, openSync as openSync4, readFileSync as readFileSync14 } from "fs";
|
|
17706
|
+
import { basename as basename6, dirname as dirname11, isAbsolute as isAbsolute9, join as join19, parse as parse5, resolve as resolve13 } from "path";
|
|
17583
17707
|
|
|
17584
17708
|
// src/lib/global-source-coverage.ts
|
|
17585
17709
|
var GLOBAL_SOURCE_SLUG_PREFIX = "global-";
|
|
@@ -17612,7 +17736,6 @@ function computeGlobalSourceCoverage(registryConfigs, configuredSlugs) {
|
|
|
17612
17736
|
init_instruction_graph();
|
|
17613
17737
|
init_project_context();
|
|
17614
17738
|
init_session_render();
|
|
17615
|
-
import { basename as basename6 } from "path";
|
|
17616
17739
|
var nonempty = exports_external.string().min(1).max(4096);
|
|
17617
17740
|
var selectorSchema = exports_external.object({
|
|
17618
17741
|
schema: exports_external.literal("hasna.instructions.hosted-profile-selector/v1"),
|
|
@@ -17663,10 +17786,10 @@ function assertHostedProfileBindings(profileId, configs, bindings) {
|
|
|
17663
17786
|
}
|
|
17664
17787
|
}
|
|
17665
17788
|
function readManagedManifest(targetHome) {
|
|
17666
|
-
const path =
|
|
17789
|
+
const path = join19(targetHome, SESSION_RENDER_MANIFEST_RELATIVE_PATH);
|
|
17667
17790
|
let ancestor = dirname11(path);
|
|
17668
17791
|
while (true) {
|
|
17669
|
-
const stat =
|
|
17792
|
+
const stat = lstatSync8(ancestor);
|
|
17670
17793
|
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
17671
17794
|
throw new Error("SESSION_REFRESH_PATH_INVALID: managed target uses a symlink or non-directory ancestor.");
|
|
17672
17795
|
const parent = dirname11(ancestor);
|
|
@@ -17674,13 +17797,13 @@ function readManagedManifest(targetHome) {
|
|
|
17674
17797
|
break;
|
|
17675
17798
|
ancestor = parent;
|
|
17676
17799
|
}
|
|
17677
|
-
const fd = openSync4(path,
|
|
17800
|
+
const fd = openSync4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
17678
17801
|
let raw;
|
|
17679
17802
|
try {
|
|
17680
17803
|
const stat = fstatSync4(fd);
|
|
17681
17804
|
if (!stat.isFile() || stat.size > SESSION_MANAGED_INPUT_MAX_BYTES)
|
|
17682
17805
|
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: expected a bounded regular manifest file.");
|
|
17683
|
-
raw =
|
|
17806
|
+
raw = readFileSync14(fd);
|
|
17684
17807
|
if (raw.byteLength > SESSION_MANAGED_INPUT_MAX_BYTES)
|
|
17685
17808
|
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: manifest grew beyond the read limit.");
|
|
17686
17809
|
} finally {
|
|
@@ -17690,15 +17813,28 @@ function readManagedManifest(targetHome) {
|
|
|
17690
17813
|
if (manifest.schema !== SESSION_RENDER_SCHEMA || !SESSION_RENDER_TOOLS.includes(manifest.tool) || manifest.targetHome !== targetHome || !manifest.profile?.trim() || !["session-home", "project-root"].includes(manifest.targetKind) || !Array.isArray(manifest.files) || !manifest.refreshSelector || manifest.targetOwner?.writer?.id !== SESSION_RENDERER_OWNER_ID || manifest.targetOwner?.targetHome !== targetHome) {
|
|
17691
17814
|
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: target is not an owned hosted profile render; apply a hosted --compile-profile first.");
|
|
17692
17815
|
}
|
|
17816
|
+
const recordedProjectRoot = manifest.targetOwner.projectRoot;
|
|
17817
|
+
if (recordedProjectRoot !== null && recordedProjectRoot !== undefined) {
|
|
17818
|
+
if (typeof recordedProjectRoot !== "string" || !isAbsolute9(recordedProjectRoot)) {
|
|
17819
|
+
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: recorded project root must be an absolute path.");
|
|
17820
|
+
}
|
|
17821
|
+
const normalizedProjectRoot = resolve13(recordedProjectRoot);
|
|
17822
|
+
if (normalizedProjectRoot === parse5(normalizedProjectRoot).root || manifest.targetKind === "project-root" && normalizedProjectRoot !== targetHome || manifest.targetKind === "session-home" && manifest.tool !== "opencode") {
|
|
17823
|
+
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: recorded project root does not match the managed target.");
|
|
17824
|
+
}
|
|
17825
|
+
manifest.targetOwner.projectRoot = normalizedProjectRoot;
|
|
17826
|
+
} else if (manifest.targetKind === "project-root") {
|
|
17827
|
+
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: project-scoped render is missing its recorded project root.");
|
|
17828
|
+
}
|
|
17693
17829
|
manifest.refreshSelector = normalizeSessionHostedProfileSelector(manifest.refreshSelector);
|
|
17694
|
-
return { manifest, sha256:
|
|
17830
|
+
return { manifest, sha256: createHash10("sha256").update(raw).digest("hex") };
|
|
17695
17831
|
}
|
|
17696
17832
|
async function refreshSessionRender(input) {
|
|
17697
17833
|
const { store } = input;
|
|
17698
17834
|
if (store.mode !== "api" || !store.v1BaseUrl)
|
|
17699
17835
|
throw new Error("SESSION_REFRESH_HOSTED_REQUIRED: refresh requires the configured hosted Instructions API.");
|
|
17700
|
-
const targetHome =
|
|
17701
|
-
if (targetHome ===
|
|
17836
|
+
const targetHome = resolve13(input.targetHome);
|
|
17837
|
+
if (targetHome === parse5(targetHome).root)
|
|
17702
17838
|
throw new Error("SESSION_REFRESH_PATH_INVALID: filesystem root is not a managed target.");
|
|
17703
17839
|
const previous = readManagedManifest(targetHome);
|
|
17704
17840
|
const selector = previous.manifest.refreshSelector;
|
|
@@ -17732,7 +17868,7 @@ async function refreshSessionRender(input) {
|
|
|
17732
17868
|
profile_id: profile.id,
|
|
17733
17869
|
provider_version: selector.providerVersion,
|
|
17734
17870
|
targetHome,
|
|
17735
|
-
...previous.manifest.targetKind === "project-root" ? { projectRoot: targetHome } : {},
|
|
17871
|
+
...previous.manifest.targetKind === "project-root" ? { projectRoot: targetHome } : previous.manifest.targetOwner.projectRoot ? { projectRoot: previous.manifest.targetOwner.projectRoot } : {},
|
|
17736
17872
|
sessionId: previous.manifest.sessionId ?? undefined,
|
|
17737
17873
|
refreshSelector: selector,
|
|
17738
17874
|
codewithNativeImports: selector.codewithNativeImports,
|
|
@@ -17741,7 +17877,7 @@ async function refreshSessionRender(input) {
|
|
|
17741
17877
|
bindings,
|
|
17742
17878
|
asset_configs: assetConfigs,
|
|
17743
17879
|
asset_bindings: assetBindings,
|
|
17744
|
-
asset_plan_mode:
|
|
17880
|
+
asset_plan_mode: "apply",
|
|
17745
17881
|
asset_scope: selector.assetScope,
|
|
17746
17882
|
asset_surface: selector.assetSurface,
|
|
17747
17883
|
extra_sources: station ? [station] : undefined,
|
|
@@ -18117,8 +18253,8 @@ init_codewith_shared_todos_storage_standard();
|
|
|
18117
18253
|
|
|
18118
18254
|
// src/lib/sync.ts
|
|
18119
18255
|
init_config_store();
|
|
18120
|
-
import { existsSync as
|
|
18121
|
-
import { basename as basename7, extname as extname3, join as
|
|
18256
|
+
import { existsSync as existsSync16, readdirSync as readdirSync5, readFileSync as readFileSync16 } from "fs";
|
|
18257
|
+
import { basename as basename7, extname as extname3, join as join21 } from "path";
|
|
18122
18258
|
init_config_agents();
|
|
18123
18259
|
init_redact();
|
|
18124
18260
|
init_machine();
|
|
@@ -18126,8 +18262,8 @@ init_transforms();
|
|
|
18126
18262
|
|
|
18127
18263
|
// src/lib/sync-dir.ts
|
|
18128
18264
|
init_config_store();
|
|
18129
|
-
import { existsSync as
|
|
18130
|
-
import { join as
|
|
18265
|
+
import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync15, statSync as statSync7 } from "fs";
|
|
18266
|
+
import { join as join20, relative as relative6 } from "path";
|
|
18131
18267
|
init_redact();
|
|
18132
18268
|
var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
|
|
18133
18269
|
function shouldSkip(p) {
|
|
@@ -18136,9 +18272,9 @@ function shouldSkip(p) {
|
|
|
18136
18272
|
async function syncFromDir(dir, opts = {}) {
|
|
18137
18273
|
const store = opts.store ?? resolveConfigStore();
|
|
18138
18274
|
const absDir = expandPath(dir);
|
|
18139
|
-
if (!
|
|
18275
|
+
if (!existsSync15(absDir))
|
|
18140
18276
|
return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
|
|
18141
|
-
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync4(absDir).map((f) =>
|
|
18277
|
+
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync4(absDir).map((f) => join20(absDir, f)).filter((f) => statSync7(f).isFile());
|
|
18142
18278
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
18143
18279
|
const allConfigs = await store.listConfigs();
|
|
18144
18280
|
for (const file of files) {
|
|
@@ -18147,7 +18283,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
18147
18283
|
continue;
|
|
18148
18284
|
}
|
|
18149
18285
|
try {
|
|
18150
|
-
const content =
|
|
18286
|
+
const content = readFileSync15(file, "utf-8");
|
|
18151
18287
|
if (content.length > 500000) {
|
|
18152
18288
|
result.skipped.push(file + " (too large)");
|
|
18153
18289
|
continue;
|
|
@@ -18157,7 +18293,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
18157
18293
|
const existing = allConfigs.find((c) => c.target_path === targetPath);
|
|
18158
18294
|
if (!existing) {
|
|
18159
18295
|
if (!opts.dryRun)
|
|
18160
|
-
await store.createConfig({ name:
|
|
18296
|
+
await store.createConfig({ name: relative6(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content: redacted.content });
|
|
18161
18297
|
result.added++;
|
|
18162
18298
|
} else if (existing.content !== redacted.content) {
|
|
18163
18299
|
if (!opts.dryRun)
|
|
@@ -18197,7 +18333,7 @@ async function syncToDir(dir, opts = {}) {
|
|
|
18197
18333
|
}
|
|
18198
18334
|
function walkDir(dir, files = []) {
|
|
18199
18335
|
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
18200
|
-
const full =
|
|
18336
|
+
const full = join20(dir, entry.name);
|
|
18201
18337
|
if (shouldSkip(full))
|
|
18202
18338
|
continue;
|
|
18203
18339
|
if (entry.isDirectory())
|
|
@@ -18258,7 +18394,7 @@ function isGeneratedOutputTarget2(config, owners) {
|
|
|
18258
18394
|
return !!ownerIds && !ownerIds.has(config.id);
|
|
18259
18395
|
}
|
|
18260
18396
|
function hasClaudePromptSource() {
|
|
18261
|
-
return
|
|
18397
|
+
return existsSync16(expandPath("~/.claude/CLAUDE.md"));
|
|
18262
18398
|
}
|
|
18263
18399
|
function hasClaudeRuleSourceForCursorTarget(targetPath) {
|
|
18264
18400
|
const absoluteTargetPath = expandPath(targetPath);
|
|
@@ -18266,7 +18402,7 @@ function hasClaudeRuleSourceForCursorTarget(targetPath) {
|
|
|
18266
18402
|
if (!absoluteTargetPath.startsWith(`${absolutePrefix}/`) || !absoluteTargetPath.endsWith(".mdc"))
|
|
18267
18403
|
return false;
|
|
18268
18404
|
const stem = basename7(absoluteTargetPath, ".mdc");
|
|
18269
|
-
return
|
|
18405
|
+
return existsSync16(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync16(expandPath(`~/.claude/rules/${stem}.mdc`));
|
|
18270
18406
|
}
|
|
18271
18407
|
function isKnownGeneratedTargetPath(targetPath) {
|
|
18272
18408
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
@@ -18331,11 +18467,11 @@ async function syncProject(opts) {
|
|
|
18331
18467
|
const allConfigs = await store.listConfigs();
|
|
18332
18468
|
const machine = detectMachineContext();
|
|
18333
18469
|
for (const pf of PROJECT_CONFIG_FILES) {
|
|
18334
|
-
const abs =
|
|
18335
|
-
if (!
|
|
18470
|
+
const abs = join21(absDir, pf.file);
|
|
18471
|
+
if (!existsSync16(abs))
|
|
18336
18472
|
continue;
|
|
18337
18473
|
try {
|
|
18338
|
-
const rawContent =
|
|
18474
|
+
const rawContent = readFileSync16(abs, "utf-8");
|
|
18339
18475
|
if (rawContent.length > 500000) {
|
|
18340
18476
|
result.skipped.push(pf.file);
|
|
18341
18477
|
continue;
|
|
@@ -18364,20 +18500,20 @@ async function syncProject(opts) {
|
|
|
18364
18500
|
}
|
|
18365
18501
|
}
|
|
18366
18502
|
for (const ruleDir of [
|
|
18367
|
-
{ dir:
|
|
18368
|
-
{ dir:
|
|
18369
|
-
{ dir:
|
|
18370
|
-
{ dir:
|
|
18371
|
-
{ dir:
|
|
18372
|
-
{ dir:
|
|
18373
|
-
{ dir:
|
|
18503
|
+
{ dir: join21(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
|
|
18504
|
+
{ dir: join21(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
|
|
18505
|
+
{ dir: join21(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
|
|
18506
|
+
{ dir: join21(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
|
|
18507
|
+
{ dir: join21(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
|
|
18508
|
+
{ dir: join21(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
|
|
18509
|
+
{ dir: join21(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
|
|
18374
18510
|
]) {
|
|
18375
|
-
if (!
|
|
18511
|
+
if (!existsSync16(ruleDir.dir))
|
|
18376
18512
|
continue;
|
|
18377
18513
|
const mdFiles = readdirSync5(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
18378
18514
|
for (const f of mdFiles) {
|
|
18379
|
-
const abs =
|
|
18380
|
-
const raw =
|
|
18515
|
+
const abs = join21(ruleDir.dir, f);
|
|
18516
|
+
const raw = readFileSync16(abs, "utf-8");
|
|
18381
18517
|
const redacted = redactContent(raw, "markdown");
|
|
18382
18518
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
18383
18519
|
const content = machineAware.content;
|
|
@@ -18416,20 +18552,20 @@ async function syncKnown(opts = {}) {
|
|
|
18416
18552
|
for (const known of targets) {
|
|
18417
18553
|
if (known.rulesDir) {
|
|
18418
18554
|
const absDir = expandPath(known.rulesDir);
|
|
18419
|
-
if (!
|
|
18555
|
+
if (!existsSync16(absDir)) {
|
|
18420
18556
|
result.skipped.push(known.rulesDir);
|
|
18421
18557
|
continue;
|
|
18422
18558
|
}
|
|
18423
18559
|
const extensions = known.rulesExtensions ?? [".md", ".mdc"];
|
|
18424
18560
|
const ruleFiles = readdirSync5(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
|
|
18425
18561
|
for (const f of ruleFiles) {
|
|
18426
|
-
const abs2 =
|
|
18562
|
+
const abs2 = join21(absDir, f);
|
|
18427
18563
|
const targetPath = abs2.replace(home, "~");
|
|
18428
18564
|
if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
|
|
18429
18565
|
result.skipped.push(`${targetPath} (generated output)`);
|
|
18430
18566
|
continue;
|
|
18431
18567
|
}
|
|
18432
|
-
const raw =
|
|
18568
|
+
const raw = readFileSync16(abs2, "utf-8");
|
|
18433
18569
|
const redacted = redactContent(raw, "markdown");
|
|
18434
18570
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
18435
18571
|
const content = machineAware.content;
|
|
@@ -18457,12 +18593,12 @@ async function syncKnown(opts = {}) {
|
|
|
18457
18593
|
continue;
|
|
18458
18594
|
}
|
|
18459
18595
|
const abs = expandPath(known.path);
|
|
18460
|
-
if (!
|
|
18596
|
+
if (!existsSync16(abs)) {
|
|
18461
18597
|
result.skipped.push(known.path);
|
|
18462
18598
|
continue;
|
|
18463
18599
|
}
|
|
18464
18600
|
try {
|
|
18465
|
-
const rawContent = normalizeKnownConfigSource(known,
|
|
18601
|
+
const rawContent = normalizeKnownConfigSource(known, readFileSync16(abs, "utf-8"));
|
|
18466
18602
|
if (rawContent.length > 500000) {
|
|
18467
18603
|
result.skipped.push(known.path + " (too large)");
|
|
18468
18604
|
continue;
|
|
@@ -18565,9 +18701,9 @@ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
|
|
|
18565
18701
|
}
|
|
18566
18702
|
function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
|
|
18567
18703
|
const path = expandPath(targetPath);
|
|
18568
|
-
if (!
|
|
18704
|
+
if (!existsSync16(path))
|
|
18569
18705
|
return `(file not found on disk: ${path})`;
|
|
18570
|
-
const diskContent =
|
|
18706
|
+
const diskContent = readFileSync16(path, "utf-8");
|
|
18571
18707
|
if (diskContent === expectedContent)
|
|
18572
18708
|
return "(no diff \u2014 identical)";
|
|
18573
18709
|
const format = redactFormatForTarget(targetPath, storedFormat);
|
|
@@ -18727,9 +18863,9 @@ function detectFormat(filePath) {
|
|
|
18727
18863
|
// src/lib/export.ts
|
|
18728
18864
|
init_types();
|
|
18729
18865
|
init_config_store();
|
|
18730
|
-
import { createHash as
|
|
18731
|
-
import { existsSync as
|
|
18732
|
-
import { dirname as dirname12, join as
|
|
18866
|
+
import { createHash as createHash11 } from "crypto";
|
|
18867
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync7, mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
18868
|
+
import { dirname as dirname12, join as join22, resolve as resolve14 } from "path";
|
|
18733
18869
|
import { tmpdir } from "os";
|
|
18734
18870
|
var ARCHIVE_EXCLUSIONS = [
|
|
18735
18871
|
{
|
|
@@ -18751,8 +18887,8 @@ var ARCHIVE_EXCLUSIONS = [
|
|
|
18751
18887
|
function compareText(left, right) {
|
|
18752
18888
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
18753
18889
|
}
|
|
18754
|
-
function
|
|
18755
|
-
return
|
|
18890
|
+
function sha25610(value) {
|
|
18891
|
+
return createHash11("sha256").update(value).digest("hex");
|
|
18756
18892
|
}
|
|
18757
18893
|
function canonicalize(value) {
|
|
18758
18894
|
if (Array.isArray(value))
|
|
@@ -18876,19 +19012,19 @@ function computeIntegrity(domain, options) {
|
|
|
18876
19012
|
machines: collections.machines.length
|
|
18877
19013
|
};
|
|
18878
19014
|
const hashes = {
|
|
18879
|
-
configs:
|
|
18880
|
-
config_snapshots:
|
|
18881
|
-
profiles:
|
|
18882
|
-
profile_config_bindings:
|
|
18883
|
-
profile_asset_bindings:
|
|
18884
|
-
machines:
|
|
19015
|
+
configs: sha25610(canonicalDomainJson(collections.configs)),
|
|
19016
|
+
config_snapshots: sha25610(canonicalDomainJson(collections.config_snapshots)),
|
|
19017
|
+
profiles: sha25610(canonicalDomainJson(collections.profiles)),
|
|
19018
|
+
profile_config_bindings: sha25610(canonicalDomainJson(collections.profile_config_bindings)),
|
|
19019
|
+
profile_asset_bindings: sha25610(canonicalDomainJson(collections.profile_asset_bindings)),
|
|
19020
|
+
machines: sha25610(canonicalDomainJson(collections.machines))
|
|
18885
19021
|
};
|
|
18886
19022
|
return {
|
|
18887
19023
|
algorithm: "sha256",
|
|
18888
19024
|
canonicalization: options.canonicalization,
|
|
18889
19025
|
counts,
|
|
18890
19026
|
hashes,
|
|
18891
|
-
domain_sha256:
|
|
19027
|
+
domain_sha256: sha25610(canonicalDomainJson({ counts, hashes }))
|
|
18892
19028
|
};
|
|
18893
19029
|
}
|
|
18894
19030
|
function computeDomainIntegrity(domain) {
|
|
@@ -19059,19 +19195,19 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
19059
19195
|
exported_at: new Date().toISOString(),
|
|
19060
19196
|
payload: {
|
|
19061
19197
|
path: "domain.json",
|
|
19062
|
-
sha256:
|
|
19198
|
+
sha256: sha25610(payload),
|
|
19063
19199
|
size_bytes: Buffer.byteLength(payload)
|
|
19064
19200
|
},
|
|
19065
19201
|
integrity,
|
|
19066
19202
|
exclusions: ARCHIVE_EXCLUSIONS
|
|
19067
19203
|
};
|
|
19068
|
-
const absOutput =
|
|
19069
|
-
|
|
19070
|
-
const stagingDir = mkdtempSync(
|
|
19204
|
+
const absOutput = resolve14(outputPath);
|
|
19205
|
+
mkdirSync7(dirname12(absOutput), { recursive: true });
|
|
19206
|
+
const stagingDir = mkdtempSync(join22(tmpdir(), "instructions-domain-export-"));
|
|
19071
19207
|
try {
|
|
19072
|
-
|
|
19208
|
+
writeFileSync5(join22(stagingDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
19073
19209
|
`, "utf-8");
|
|
19074
|
-
|
|
19210
|
+
writeFileSync5(join22(stagingDir, "domain.json"), payload, "utf-8");
|
|
19075
19211
|
const proc = Bun.spawn(["tar", "czf", absOutput, "-C", stagingDir, "manifest.json", "domain.json"], {
|
|
19076
19212
|
stdout: "pipe",
|
|
19077
19213
|
stderr: "pipe"
|
|
@@ -19083,15 +19219,15 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
19083
19219
|
}
|
|
19084
19220
|
return { path: absOutput, count: domain.configs.length, counts: integrity.counts, integrity };
|
|
19085
19221
|
} finally {
|
|
19086
|
-
if (
|
|
19087
|
-
|
|
19222
|
+
if (existsSync17(stagingDir))
|
|
19223
|
+
rmSync3(stagingDir, { recursive: true, force: true });
|
|
19088
19224
|
}
|
|
19089
19225
|
}
|
|
19090
19226
|
// src/lib/import.ts
|
|
19091
19227
|
init_types();
|
|
19092
19228
|
init_config_store();
|
|
19093
|
-
import { createHash as
|
|
19094
|
-
import { resolve as
|
|
19229
|
+
import { createHash as createHash12 } from "crypto";
|
|
19230
|
+
import { resolve as resolve15 } from "path";
|
|
19095
19231
|
function compareText2(left, right) {
|
|
19096
19232
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
19097
19233
|
}
|
|
@@ -19115,8 +19251,8 @@ function emptyResult() {
|
|
|
19115
19251
|
integrity: null
|
|
19116
19252
|
};
|
|
19117
19253
|
}
|
|
19118
|
-
function
|
|
19119
|
-
return
|
|
19254
|
+
function sha25611(value) {
|
|
19255
|
+
return createHash12("sha256").update(value).digest("hex");
|
|
19120
19256
|
}
|
|
19121
19257
|
function normalizeMemberName(name) {
|
|
19122
19258
|
let normalized = name;
|
|
@@ -19186,7 +19322,7 @@ async function readV2(reader, manifest) {
|
|
|
19186
19322
|
throw new Error("Invalid v2 archive manifest");
|
|
19187
19323
|
}
|
|
19188
19324
|
const payload = await reader.read("domain.json");
|
|
19189
|
-
if (Buffer.byteLength(payload) !== manifest.payload.size_bytes ||
|
|
19325
|
+
if (Buffer.byteLength(payload) !== manifest.payload.size_bytes || sha25611(payload) !== manifest.payload.sha256) {
|
|
19190
19326
|
throw new Error("Archive integrity failure: domain payload hash or size does not match the manifest");
|
|
19191
19327
|
}
|
|
19192
19328
|
const domain = validateInstructionsDomainArchive(JSON.parse(payload));
|
|
@@ -19379,7 +19515,7 @@ async function importV1(reader, manifest, store, conflict) {
|
|
|
19379
19515
|
async function importConfigs(bundlePath, opts = {}) {
|
|
19380
19516
|
const store = opts.store ?? resolveConfigStore();
|
|
19381
19517
|
const conflict = opts.conflict ?? "skip";
|
|
19382
|
-
const reader = await openArchive(
|
|
19518
|
+
const reader = await openArchive(resolve15(bundlePath));
|
|
19383
19519
|
const manifestText = await reader.read("manifest.json");
|
|
19384
19520
|
let manifest;
|
|
19385
19521
|
try {
|
|
@@ -19407,9 +19543,9 @@ init_redact();
|
|
|
19407
19543
|
|
|
19408
19544
|
// src/lib/package-manager-guard.ts
|
|
19409
19545
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
19410
|
-
import { existsSync as
|
|
19411
|
-
import { homedir as
|
|
19412
|
-
import { basename as basename8, dirname as dirname13, isAbsolute as
|
|
19546
|
+
import { existsSync as existsSync18, lstatSync as lstatSync9, readdirSync as readdirSync6, readFileSync as readFileSync17 } from "fs";
|
|
19547
|
+
import { homedir as homedir11 } from "os";
|
|
19548
|
+
import { basename as basename8, dirname as dirname13, isAbsolute as isAbsolute10, join as join23, relative as relative7, resolve as resolve16 } from "path";
|
|
19413
19549
|
var SKIP_DIRS = new Set([
|
|
19414
19550
|
".git",
|
|
19415
19551
|
"node_modules",
|
|
@@ -19446,14 +19582,14 @@ var TOKEN_VALUE_PATTERNS = [
|
|
|
19446
19582
|
{ re: /xoxb-[0-9]+-[A-Za-z0-9-]+/, rule: "literal-slack-token", detail: "literal Slack token-like value" }
|
|
19447
19583
|
];
|
|
19448
19584
|
function scanPackageManagerSecrets(options = {}) {
|
|
19449
|
-
const cwd = options.cwd ?
|
|
19450
|
-
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) =>
|
|
19585
|
+
const cwd = options.cwd ? resolve16(options.cwd) : process.cwd();
|
|
19586
|
+
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve16(cwd, root));
|
|
19451
19587
|
const findings = [];
|
|
19452
19588
|
let scannedFiles = 0;
|
|
19453
19589
|
for (const root of roots) {
|
|
19454
|
-
if (!
|
|
19590
|
+
if (!existsSync18(root))
|
|
19455
19591
|
continue;
|
|
19456
|
-
const stat =
|
|
19592
|
+
const stat = lstatSync9(root);
|
|
19457
19593
|
if (stat.isFile()) {
|
|
19458
19594
|
if (!shouldScanRepoFile(root))
|
|
19459
19595
|
continue;
|
|
@@ -19468,7 +19604,7 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
19468
19604
|
continue;
|
|
19469
19605
|
const tracked = trackedFiles(root);
|
|
19470
19606
|
for (const file of collectRepoFiles(root)) {
|
|
19471
|
-
const rel = toPosix(
|
|
19607
|
+
const rel = toPosix(relative7(root, file));
|
|
19472
19608
|
const isTracked = tracked.has(rel);
|
|
19473
19609
|
const text = readTextFile(file);
|
|
19474
19610
|
if (text === null)
|
|
@@ -19478,10 +19614,10 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
19478
19614
|
}
|
|
19479
19615
|
}
|
|
19480
19616
|
if (options.includeHome) {
|
|
19481
|
-
const home =
|
|
19617
|
+
const home = homedir11();
|
|
19482
19618
|
for (const name of HOME_FILES) {
|
|
19483
|
-
const file =
|
|
19484
|
-
if (!
|
|
19619
|
+
const file = join23(home, name);
|
|
19620
|
+
if (!existsSync18(file))
|
|
19485
19621
|
continue;
|
|
19486
19622
|
const text = readTextFile(file);
|
|
19487
19623
|
if (text === null)
|
|
@@ -19505,12 +19641,12 @@ function collectRepoFiles(root) {
|
|
|
19505
19641
|
if (entry.isDirectory()) {
|
|
19506
19642
|
if (SKIP_DIRS.has(entry.name))
|
|
19507
19643
|
continue;
|
|
19508
|
-
visit(
|
|
19644
|
+
visit(join23(dir, entry.name));
|
|
19509
19645
|
continue;
|
|
19510
19646
|
}
|
|
19511
19647
|
if (!entry.isFile())
|
|
19512
19648
|
continue;
|
|
19513
|
-
const file =
|
|
19649
|
+
const file = join23(dir, entry.name);
|
|
19514
19650
|
if (shouldScanRepoFile(file))
|
|
19515
19651
|
out.push(file);
|
|
19516
19652
|
}
|
|
@@ -19545,10 +19681,10 @@ function isNpmrcName(name) {
|
|
|
19545
19681
|
}
|
|
19546
19682
|
function readTextFile(file) {
|
|
19547
19683
|
try {
|
|
19548
|
-
const stat =
|
|
19684
|
+
const stat = lstatSync9(file);
|
|
19549
19685
|
if (!stat.isFile() || stat.size > 5000000)
|
|
19550
19686
|
return null;
|
|
19551
|
-
const buf =
|
|
19687
|
+
const buf = readFileSync17(file);
|
|
19552
19688
|
if (buf.includes(0))
|
|
19553
19689
|
return null;
|
|
19554
19690
|
return buf.toString("utf-8");
|
|
@@ -19752,7 +19888,7 @@ function isTrackedFile(file) {
|
|
|
19752
19888
|
encoding: "utf-8",
|
|
19753
19889
|
stdio: ["ignore", "pipe", "ignore"]
|
|
19754
19890
|
}).trim();
|
|
19755
|
-
const rel = toPosix(
|
|
19891
|
+
const rel = toPosix(relative7(repoRoot, file));
|
|
19756
19892
|
execFileSync2("git", ["-C", repoRoot, "ls-files", "--error-unmatch", "--", rel], {
|
|
19757
19893
|
stdio: ["ignore", "ignore", "ignore"]
|
|
19758
19894
|
});
|
|
@@ -19778,13 +19914,13 @@ function stripInlineComment(value) {
|
|
|
19778
19914
|
return value.replace(/\s[#;].*$/, "").trim();
|
|
19779
19915
|
}
|
|
19780
19916
|
function displayPath(file, root) {
|
|
19781
|
-
const home =
|
|
19917
|
+
const home = homedir11();
|
|
19782
19918
|
if (root === home && (file === home || file.startsWith(home + "/")))
|
|
19783
|
-
return "~/" + toPosix(
|
|
19784
|
-
if (
|
|
19785
|
-
return toPosix(
|
|
19919
|
+
return "~/" + toPosix(relative7(home, file));
|
|
19920
|
+
if (isAbsolute10(root) && file.startsWith(root + "/"))
|
|
19921
|
+
return toPosix(relative7(root, file));
|
|
19786
19922
|
if (file === home || file.startsWith(home + "/"))
|
|
19787
|
-
return "~/" + toPosix(
|
|
19923
|
+
return "~/" + toPosix(relative7(home, file));
|
|
19788
19924
|
return file;
|
|
19789
19925
|
}
|
|
19790
19926
|
function toPosix(path) {
|
|
@@ -19824,6 +19960,7 @@ export {
|
|
|
19824
19960
|
renderTemplatePreview,
|
|
19825
19961
|
renderTemplate,
|
|
19826
19962
|
renderProviderFragment,
|
|
19963
|
+
renderNativeAgentContent,
|
|
19827
19964
|
renderMachineAwareContentPreview,
|
|
19828
19965
|
renderMachineAwareContent,
|
|
19829
19966
|
refreshStationProfile,
|
|
@@ -19872,6 +20009,7 @@ export {
|
|
|
19872
20009
|
ensureGlobalAgentRulesStandardConfig,
|
|
19873
20010
|
ensureDangerousOperationGuardStandardConfig,
|
|
19874
20011
|
ensureCodewithSharedTodosStorageStandardConfig,
|
|
20012
|
+
discoverHarnesses,
|
|
19875
20013
|
diffConfig,
|
|
19876
20014
|
detectMachineContext,
|
|
19877
20015
|
detectFormat,
|
|
@@ -19957,8 +20095,10 @@ export {
|
|
|
19957
20095
|
INSTRUCTIONS_LOCAL_OPT_IN_ENV,
|
|
19958
20096
|
INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA,
|
|
19959
20097
|
INBOX_CONVERSATIONS_MINIMUM_VERSION,
|
|
20098
|
+
HARNESS_DISCOVERY_SCHEMA,
|
|
19960
20099
|
GLOBAL_AGENT_RULES_STANDARD_SLUG,
|
|
19961
20100
|
GLOBAL_AGENT_RULES_STANDARD_CONTENT,
|
|
20101
|
+
DISCOVERABLE_HARNESSES,
|
|
19962
20102
|
DANGEROUS_OPERATION_GUARD_STANDARD_SLUG,
|
|
19963
20103
|
DANGEROUS_OPERATION_GUARD_STANDARD_CONTENT,
|
|
19964
20104
|
ConfigVersionConflictError,
|