@hasna/instructions 0.7.2 → 0.7.4
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-ps6jjt6k.js → apply-1gyr8r0x.js} +2 -2
- package/dist/chunks/{apply-wj7wh5km.js → apply-fyfzqc7s.js} +2 -2
- package/dist/chunks/{index-y8410ve0.js → index-6sbmg1vc.js} +1 -1
- package/dist/chunks/{index-18j7x3xw.js → index-axs3yftz.js} +2 -2
- package/dist/chunks/{index-pq18fdbj.js → index-dwagz53p.js} +64 -3
- package/dist/chunks/{index-n6nh6f5r.js → index-enh4yv85.js} +156 -15
- package/dist/chunks/{index-ape24zc0.js → index-n9arv47w.js} +1 -1
- package/dist/chunks/{index-ek15201v.js → index-z27xp7fh.js} +2 -2
- package/dist/chunks/{local-1c1qxzvx.js → local-5dsm856v.js} +1 -1
- package/dist/chunks/{local-hc67bynj.js → local-yxsbs8x3.js} +1 -1
- package/dist/chunks/{sync-azd9t4p8.js → sync-6r1v8hd3.js} +3 -3
- package/dist/chunks/{sync-12m3vs6g.js → sync-k3pab4s6.js} +3 -3
- package/dist/cli/index.js +526 -555
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +690 -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,62 @@ 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) => candidate === normalizedManagedDir || candidate.endsWith(`/${normalizedManagedDir}`) || candidate.includes(`/${normalizedManagedDir}/`));
|
|
9392
|
+
}
|
|
9393
|
+
function canonicalOpenCodeInstructionPaths(reference, targetHome, projectRoot) {
|
|
9394
|
+
const portable = decodeOpenCodePathEscapes(reference.replaceAll("\\", "/")).replaceAll("\\", "/");
|
|
9395
|
+
if (portable.includes("\x00")) {
|
|
9396
|
+
throw new Error("OpenCode config instruction references cannot contain NUL bytes.");
|
|
9397
|
+
}
|
|
9398
|
+
const windowsAbsolute = /^[A-Za-z]:\//.test(portable);
|
|
9399
|
+
const uriScheme = windowsAbsolute ? null : /^([A-Za-z][A-Za-z0-9+.-]*):/.exec(portable)?.[1]?.toLowerCase();
|
|
9400
|
+
if (uriScheme && uriScheme !== "file")
|
|
9401
|
+
return [];
|
|
9402
|
+
if (uriScheme === "file") {
|
|
9403
|
+
const fileReference = reference.replaceAll("\\", "/");
|
|
9404
|
+
if (!/^file:\/\//i.test(fileReference)) {
|
|
9405
|
+
throw new Error("OpenCode config contains an invalid file URL instruction reference.");
|
|
9406
|
+
}
|
|
9407
|
+
let url;
|
|
9408
|
+
try {
|
|
9409
|
+
url = new URL(fileReference);
|
|
9410
|
+
} catch {
|
|
9411
|
+
throw new Error("OpenCode config contains an invalid file URL instruction reference.");
|
|
9412
|
+
}
|
|
9413
|
+
if (url.protocol !== "file:" || url.username || url.password || url.port || url.search || url.hash) {
|
|
9414
|
+
throw new Error("OpenCode config contains an invalid file URL instruction reference.");
|
|
9415
|
+
}
|
|
9416
|
+
const pathname = decodeOpenCodePathEscapes(url.pathname);
|
|
9417
|
+
if (pathname.includes("\x00")) {
|
|
9418
|
+
throw new Error("OpenCode config instruction references cannot contain NUL bytes.");
|
|
9419
|
+
}
|
|
9420
|
+
const host = url.hostname && url.hostname !== "localhost" ? `//${url.hostname}` : "";
|
|
9421
|
+
return [normalizePortableOpenCodePath(`${host}${pathname}`)];
|
|
9422
|
+
}
|
|
9423
|
+
if (posix2.isAbsolute(portable) || windowsAbsolute || portable.startsWith("//")) {
|
|
9424
|
+
return [normalizePortableOpenCodePath(portable)];
|
|
9425
|
+
}
|
|
9426
|
+
const roots = [
|
|
9427
|
+
...projectRoot ? [resolveSessionPath(projectRoot)] : [],
|
|
9428
|
+
resolveSessionPath(targetHome)
|
|
9429
|
+
];
|
|
9430
|
+
return [...new Set(roots.map((root) => normalizePortableOpenCodePath(posix2.join(root.replaceAll("\\", "/"), portable))))];
|
|
9431
|
+
}
|
|
9432
|
+
function decodeOpenCodePathEscapes(value) {
|
|
9433
|
+
return value.replace(/(?:%[a-fA-F0-9]{2})+/g, (encoded) => {
|
|
9434
|
+
try {
|
|
9435
|
+
return decodeURIComponent(encoded);
|
|
9436
|
+
} catch {
|
|
9437
|
+
return encoded;
|
|
9438
|
+
}
|
|
9439
|
+
});
|
|
9440
|
+
}
|
|
9441
|
+
function normalizePortableOpenCodePath(value) {
|
|
9442
|
+
const normalized = posix2.normalize(value.replaceAll("\\", "/"));
|
|
9443
|
+
return normalized.startsWith("//") ? `/${normalized.replace(/^\/+/u, "")}` : normalized;
|
|
9305
9444
|
}
|
|
9306
9445
|
function buildAntigravityRuleFiles(targetHome, adapter, sources) {
|
|
9307
9446
|
return sources.flatMap((source, index) => {
|
|
@@ -9355,7 +9494,7 @@ function makeAntigravityRuleFile(targetHome, relativePath, content, sourceIds) {
|
|
|
9355
9494
|
}
|
|
9356
9495
|
return file;
|
|
9357
9496
|
}
|
|
9358
|
-
function buildFiles(targetHome, adapter, profile, sources, providerConfig, providerVersion) {
|
|
9497
|
+
function buildFiles(targetHome, adapter, profile, sources, providerConfig, providerVersion, projectRoot) {
|
|
9359
9498
|
switch (adapter.mode) {
|
|
9360
9499
|
case "native-imports":
|
|
9361
9500
|
return buildNativeImportFiles(targetHome, adapter, profile, sources, providerVersion);
|
|
@@ -9364,7 +9503,7 @@ function buildFiles(targetHome, adapter, profile, sources, providerConfig, provi
|
|
|
9364
9503
|
case "cursor-mdc":
|
|
9365
9504
|
return buildCursorRuleFiles(targetHome, adapter, sources);
|
|
9366
9505
|
case "opencode-instructions":
|
|
9367
|
-
return buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig);
|
|
9506
|
+
return buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig, projectRoot);
|
|
9368
9507
|
case "antigravity-rules":
|
|
9369
9508
|
return buildAntigravityRuleFiles(targetHome, adapter, sources);
|
|
9370
9509
|
case "provider-rules":
|
|
@@ -9393,12 +9532,13 @@ function buildAssetFiles(input, targetHome, blocked) {
|
|
|
9393
9532
|
if (!relativePath || relativePath === ".." || relativePath.startsWith("../") || isAbsolute3(relativePath)) {
|
|
9394
9533
|
throw new Error(`Asset ${item.assetKey} is outside the session snapshot root; use a project-scoped session plan for atomic application.`);
|
|
9395
9534
|
}
|
|
9535
|
+
const renderedContent = renderNativeAgentContent(content, item.nativeAgent);
|
|
9396
9536
|
return {
|
|
9397
9537
|
path,
|
|
9398
9538
|
relativePath: assertSafeRelativePath(relativePath),
|
|
9399
9539
|
role: "asset",
|
|
9400
|
-
content,
|
|
9401
|
-
sha256: sha2566(
|
|
9540
|
+
content: renderedContent,
|
|
9541
|
+
sha256: sha2566(renderedContent),
|
|
9402
9542
|
sourceIds: [item.sourceConfigId, item.assetId]
|
|
9403
9543
|
};
|
|
9404
9544
|
});
|
|
@@ -9577,7 +9717,7 @@ function resolveSessionTargetOwnership(input, target) {
|
|
|
9577
9717
|
tool: input.tool,
|
|
9578
9718
|
profile: input.profile,
|
|
9579
9719
|
targetHome: target.targetHome,
|
|
9580
|
-
projectRoot: null,
|
|
9720
|
+
projectRoot: input.tool === "opencode" && input.projectRoot ? resolveSessionPath(input.projectRoot) : null,
|
|
9581
9721
|
ownedBy: "open-configs",
|
|
9582
9722
|
canonicalOwner: "instructions",
|
|
9583
9723
|
writer: {
|
|
@@ -9638,7 +9778,7 @@ function planSessionRender(input) {
|
|
|
9638
9778
|
if (input.providerConfig && input.tool !== "opencode") {
|
|
9639
9779
|
throw new Error("Provider base config is supported only for OpenCode session renders.");
|
|
9640
9780
|
}
|
|
9641
|
-
const baseFiles = blocked ? [] : buildFiles(targetHome, adapter, input.profile, orderedSources, input.providerConfig, input.provider_version);
|
|
9781
|
+
const baseFiles = blocked ? [] : buildFiles(targetHome, adapter, input.profile, orderedSources, input.providerConfig, input.provider_version, input.projectRoot);
|
|
9642
9782
|
const projectContext = blocked ? null : composeProjectContextSessionRender({
|
|
9643
9783
|
tool: input.tool,
|
|
9644
9784
|
adapter_mode: adapter.mode,
|
|
@@ -9739,6 +9879,7 @@ function planSessionRender(input) {
|
|
|
9739
9879
|
mutationMode: item.mutationMode,
|
|
9740
9880
|
destination: item.destination,
|
|
9741
9881
|
digest: item.source.digest,
|
|
9882
|
+
...item.nativeAgent ? { nativeAgent: item.nativeAgent } : {},
|
|
9742
9883
|
exactOnceKey: item.exactOnceKey
|
|
9743
9884
|
}))
|
|
9744
9885
|
}
|
|
@@ -13768,7 +13909,7 @@ function detectMachineContext(overrides = {}) {
|
|
|
13768
13909
|
created_at: "",
|
|
13769
13910
|
os_family: osFamily,
|
|
13770
13911
|
home_dir: homeDir6,
|
|
13771
|
-
workspace_root: overrides.workspace_root ??
|
|
13912
|
+
workspace_root: overrides.workspace_root ?? "",
|
|
13772
13913
|
bun_bin_dir: bunBinDir,
|
|
13773
13914
|
bun_path: overrides.bun_path ?? defaultBunPath,
|
|
13774
13915
|
path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join10("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
|
|
@@ -13781,7 +13922,7 @@ function machineContextToVariables(machine) {
|
|
|
13781
13922
|
OS_FAMILY: machine.os_family,
|
|
13782
13923
|
ARCH: machine.arch ?? "",
|
|
13783
13924
|
HOME_DIR: machine.home_dir,
|
|
13784
|
-
WORKSPACE_ROOT: machine.workspace_root,
|
|
13925
|
+
...machine.workspace_root ? { WORKSPACE_ROOT: machine.workspace_root } : {},
|
|
13785
13926
|
BUN_BIN_DIR: machine.bun_bin_dir,
|
|
13786
13927
|
BUN_PATH: machine.bun_path,
|
|
13787
13928
|
PATH_PREFIX: machine.path_prefix
|
|
@@ -14845,7 +14986,7 @@ init_types();
|
|
|
14845
14986
|
|
|
14846
14987
|
// src/status.ts
|
|
14847
14988
|
init_config_store();
|
|
14848
|
-
import { existsSync as
|
|
14989
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
14849
14990
|
|
|
14850
14991
|
// src/lib/apply.ts
|
|
14851
14992
|
init_types();
|
|
@@ -15395,19 +15536,9 @@ function getPackageVersion() {
|
|
|
15395
15536
|
}
|
|
15396
15537
|
|
|
15397
15538
|
// 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";
|
|
15539
|
+
import { lstatSync as lstatSync4 } from "fs";
|
|
15409
15540
|
import { homedir as homedir8 } from "os";
|
|
15410
|
-
import {
|
|
15541
|
+
import { join as join14, resolve as resolve10 } from "path";
|
|
15411
15542
|
var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
|
|
15412
15543
|
var INBOX_SKILL_MARKERS = [
|
|
15413
15544
|
[".claude", "skills", "inbox", "SKILL.md"],
|
|
@@ -15416,369 +15547,67 @@ var INBOX_SKILL_MARKERS = [
|
|
|
15416
15547
|
[".config", "opencode", "skills", "inbox", "SKILL.md"],
|
|
15417
15548
|
[".cursor", "skills", "inbox", "SKILL.md"]
|
|
15418
15549
|
];
|
|
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)) {
|
|
15550
|
+
function needsMigrationReview(homeDir6, parts) {
|
|
15551
|
+
let current = resolve10(homeDir6);
|
|
15552
|
+
const absolute = join14(current, ...parts);
|
|
15553
|
+
for (const segment of parts) {
|
|
15436
15554
|
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 };
|
|
15555
|
+
let stat;
|
|
15556
|
+
try {
|
|
15557
|
+
stat = lstatSync4(current);
|
|
15558
|
+
} catch (error) {
|
|
15559
|
+
if (error.code === "ENOENT")
|
|
15560
|
+
return false;
|
|
15561
|
+
throw error;
|
|
15505
15562
|
}
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
regular: true
|
|
15511
|
-
};
|
|
15512
|
-
}).filter((snapshot) => snapshot !== null);
|
|
15563
|
+
if (stat.isSymbolicLink() || current !== absolute && !stat.isDirectory())
|
|
15564
|
+
return true;
|
|
15565
|
+
}
|
|
15566
|
+
return true;
|
|
15513
15567
|
}
|
|
15514
|
-
function
|
|
15568
|
+
function inspectLegacyInbox(options) {
|
|
15515
15569
|
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
|
-
}
|
|
15570
|
+
const markers = INBOX_SKILL_MARKERS.filter((parts) => needsMigrationReview(homeDir6, parts)).map((parts) => join14(homeDir6, ...parts));
|
|
15571
|
+
const present = markers.length > 0;
|
|
15576
15572
|
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
|
|
15573
|
+
skill: "inbox",
|
|
15574
|
+
runtime: "conversations watch",
|
|
15575
|
+
minimum_version: INBOX_CONVERSATIONS_MINIMUM_VERSION,
|
|
15576
|
+
skill_present: present,
|
|
15577
|
+
skill_markers: markers,
|
|
15578
|
+
skill_contracts_current: 0,
|
|
15579
|
+
stale_skill_markers: markers,
|
|
15580
|
+
expected_skill_sha256: null,
|
|
15581
|
+
runtime_command: options.conversationsCommand ?? "conversations",
|
|
15582
|
+
runtime_present: false,
|
|
15583
|
+
runtime_version: null,
|
|
15584
|
+
watch_supports_from: false,
|
|
15585
|
+
watch_supports_all: false,
|
|
15586
|
+
watch_supports_full_content: false,
|
|
15587
|
+
hosted_heartbeat: "unverified",
|
|
15588
|
+
delivery_verified: false,
|
|
15589
|
+
manual_fallback_ready: false,
|
|
15590
|
+
healthy: !present,
|
|
15591
|
+
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
15592
|
};
|
|
15601
15593
|
}
|
|
15602
15594
|
function inspectManagedSkillRuntimes(options = {}) {
|
|
15603
|
-
const runtime =
|
|
15604
|
-
const installed = runtime.skill_present ? [runtime] : [];
|
|
15595
|
+
const runtime = inspectLegacyInbox(options);
|
|
15605
15596
|
return {
|
|
15606
15597
|
runtimes: [runtime],
|
|
15607
|
-
skills_present:
|
|
15608
|
-
healthy:
|
|
15609
|
-
missing:
|
|
15598
|
+
skills_present: runtime.skill_present ? 1 : 0,
|
|
15599
|
+
healthy: 0,
|
|
15600
|
+
missing: runtime.skill_present ? 1 : 0
|
|
15610
15601
|
};
|
|
15611
15602
|
}
|
|
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"
|
|
15624
|
-
};
|
|
15625
|
-
}
|
|
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
15603
|
async function reconcileManagedSkillRuntimes(options = {}) {
|
|
15604
|
+
const runtime = inspectLegacyInbox(options);
|
|
15690
15605
|
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
15606
|
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
|
|
15607
|
+
runtimes: [{ ...runtime, action: runtime.skill_present ? "failed" : "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
15608
|
+
changed: 0,
|
|
15609
|
+
failed: runtime.skill_present ? 1 : 0,
|
|
15610
|
+
dry_run: dryRun
|
|
15782
15611
|
};
|
|
15783
15612
|
}
|
|
15784
15613
|
|
|
@@ -15842,11 +15671,11 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
15842
15671
|
continue;
|
|
15843
15672
|
knownTargets += 1;
|
|
15844
15673
|
const targetPath = expandPath(config.target_path);
|
|
15845
|
-
if (!
|
|
15674
|
+
if (!existsSync11(targetPath)) {
|
|
15846
15675
|
missingTargets += 1;
|
|
15847
15676
|
continue;
|
|
15848
15677
|
}
|
|
15849
|
-
const disk =
|
|
15678
|
+
const disk = readFileSync10(targetPath, "utf-8");
|
|
15850
15679
|
const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
|
|
15851
15680
|
if (redactedDisk !== config.content) {
|
|
15852
15681
|
driftedTargets += 1;
|
|
@@ -15937,8 +15766,8 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
15937
15766
|
};
|
|
15938
15767
|
}
|
|
15939
15768
|
// src/lib/provider-context.ts
|
|
15940
|
-
import { createHash as
|
|
15941
|
-
import { existsSync as
|
|
15769
|
+
import { createHash as createHash8 } from "crypto";
|
|
15770
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
|
|
15942
15771
|
import { join as join15 } from "path";
|
|
15943
15772
|
var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
|
|
15944
15773
|
var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
|
|
@@ -16074,8 +15903,8 @@ function renderPerEndpointFragment(entry) {
|
|
|
16074
15903
|
function renderProviderFragment(entry) {
|
|
16075
15904
|
return entry ? renderPerEndpointFragment(entry) : INVARIANT_FRAGMENT;
|
|
16076
15905
|
}
|
|
16077
|
-
function
|
|
16078
|
-
return
|
|
15906
|
+
function sha2568(content) {
|
|
15907
|
+
return createHash8("sha256").update(content).digest("hex");
|
|
16079
15908
|
}
|
|
16080
15909
|
function resolveAndRenderProviderContext(opts) {
|
|
16081
15910
|
const entry = matchProviderEndpoint(opts.origin);
|
|
@@ -16085,17 +15914,17 @@ function resolveAndRenderProviderContext(opts) {
|
|
|
16085
15914
|
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
15915
|
const content = renderProviderFragment(entry);
|
|
16087
15916
|
const dir = join15(opts.homeDir, PROVIDER_CONTEXT_DIR);
|
|
16088
|
-
if (!
|
|
16089
|
-
|
|
15917
|
+
if (!existsSync12(dir))
|
|
15918
|
+
mkdirSync4(dir, { recursive: true });
|
|
16090
15919
|
const filename = `${entry ? entry.key : "invariant"}.md`;
|
|
16091
15920
|
const fragmentPath2 = join15(dir, filename);
|
|
16092
|
-
const fragmentSha256 =
|
|
16093
|
-
|
|
15921
|
+
const fragmentSha256 = sha2568(content);
|
|
15922
|
+
writeFileSync3(fragmentPath2, content, "utf8");
|
|
16094
15923
|
const manifestPath = join15(dir, PROVIDER_CONTEXT_MANIFEST);
|
|
16095
15924
|
let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
|
|
16096
15925
|
try {
|
|
16097
|
-
if (
|
|
16098
|
-
const parsed = JSON.parse(
|
|
15926
|
+
if (existsSync12(manifestPath)) {
|
|
15927
|
+
const parsed = JSON.parse(readFileSync11(manifestPath, "utf8"));
|
|
16099
15928
|
if (parsed && typeof parsed === "object")
|
|
16100
15929
|
manifest = parsed;
|
|
16101
15930
|
}
|
|
@@ -16110,7 +15939,7 @@ function resolveAndRenderProviderContext(opts) {
|
|
|
16110
15939
|
rawModel: opts.rawModel || null
|
|
16111
15940
|
};
|
|
16112
15941
|
manifest.fragments = fragmentsObj;
|
|
16113
|
-
|
|
15942
|
+
writeFileSync3(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
16114
15943
|
return {
|
|
16115
15944
|
entry,
|
|
16116
15945
|
rawEndpoint: opts.rawEndpoint,
|
|
@@ -16210,10 +16039,10 @@ init_session_render();
|
|
|
16210
16039
|
|
|
16211
16040
|
// src/lib/station-profile.ts
|
|
16212
16041
|
init_raw_store_root();
|
|
16213
|
-
import { spawnSync as
|
|
16214
|
-
import { existsSync as
|
|
16042
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
16043
|
+
import { existsSync as existsSync13, lstatSync as lstatSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
16215
16044
|
import { arch as osArch, homedir as homedir9, hostname as osHostname3, platform as osPlatform, userInfo as osUserInfo } from "os";
|
|
16216
|
-
import { dirname as
|
|
16045
|
+
import { dirname as dirname8, join as join16 } from "path";
|
|
16217
16046
|
var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
|
|
16218
16047
|
var STATION_PROFILE_SOURCE_ID = "station-profile";
|
|
16219
16048
|
var STATION_PROFILE_LAYER = "machine";
|
|
@@ -16237,9 +16066,9 @@ function getBunGlobalModulesDir(env = process.env) {
|
|
|
16237
16066
|
}
|
|
16238
16067
|
function readMachinesManifest(path) {
|
|
16239
16068
|
try {
|
|
16240
|
-
if (!
|
|
16069
|
+
if (!existsSync13(path))
|
|
16241
16070
|
return null;
|
|
16242
|
-
const parsed = JSON.parse(
|
|
16071
|
+
const parsed = JSON.parse(readFileSync12(path, "utf8"));
|
|
16243
16072
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
16244
16073
|
return null;
|
|
16245
16074
|
const machines = parsed["machines"];
|
|
@@ -16269,7 +16098,7 @@ function metadataUser(record) {
|
|
|
16269
16098
|
}
|
|
16270
16099
|
function probeMachineStatus(machineId) {
|
|
16271
16100
|
try {
|
|
16272
|
-
const result =
|
|
16101
|
+
const result = spawnSync3("machines", ["details", "--json", "--machine", machineId], {
|
|
16273
16102
|
encoding: "utf8",
|
|
16274
16103
|
timeout: 3000,
|
|
16275
16104
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -16295,7 +16124,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
|
|
|
16295
16124
|
const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname2);
|
|
16296
16125
|
const home = homeDir6(env);
|
|
16297
16126
|
const platform = stringField(record, "platform") ?? osPlatform();
|
|
16298
|
-
const workspacePath = stringField(record, "workspacePath")
|
|
16127
|
+
const workspacePath = stringField(record, "workspacePath");
|
|
16299
16128
|
const machine = {
|
|
16300
16129
|
id: stringField(record, "id") ?? hostname2,
|
|
16301
16130
|
hostname: stringField(record, "hostname") ?? hostname2,
|
|
@@ -16314,7 +16143,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
|
|
|
16314
16143
|
function scopedPackageNames(modulesDir, scope) {
|
|
16315
16144
|
const scopeDir = join16(modulesDir, scope);
|
|
16316
16145
|
try {
|
|
16317
|
-
if (!
|
|
16146
|
+
if (!existsSync13(scopeDir))
|
|
16318
16147
|
return null;
|
|
16319
16148
|
return readdirNames(scopeDir).sort();
|
|
16320
16149
|
} catch {
|
|
@@ -16334,7 +16163,7 @@ function resolveStationProfilePackages(env = process.env) {
|
|
|
16334
16163
|
const modulesDir = getBunGlobalModulesDir(env);
|
|
16335
16164
|
let scopeDirs;
|
|
16336
16165
|
try {
|
|
16337
|
-
if (!
|
|
16166
|
+
if (!existsSync13(modulesDir))
|
|
16338
16167
|
return null;
|
|
16339
16168
|
scopeDirs = readdirNames(modulesDir).filter((name) => name.startsWith("@") && name.toLowerCase().includes("hasna"));
|
|
16340
16169
|
} catch {
|
|
@@ -16409,10 +16238,10 @@ function refreshStationProfile(options = {}) {
|
|
|
16409
16238
|
const path = getStationProfileCachePath(env);
|
|
16410
16239
|
const generatedAt = new Date().toISOString();
|
|
16411
16240
|
if (!options.dryRun) {
|
|
16412
|
-
const existing =
|
|
16241
|
+
const existing = existsSync13(path) ? readFileSync12(path, "utf8") : null;
|
|
16413
16242
|
if (existing !== content) {
|
|
16414
|
-
|
|
16415
|
-
|
|
16243
|
+
mkdirSync5(dirname8(path), { recursive: true });
|
|
16244
|
+
writeFileSync4(path, content, "utf8");
|
|
16416
16245
|
}
|
|
16417
16246
|
}
|
|
16418
16247
|
return {
|
|
@@ -16428,9 +16257,9 @@ function refreshStationProfile(options = {}) {
|
|
|
16428
16257
|
function readStationProfile(env = process.env) {
|
|
16429
16258
|
const path = getStationProfileCachePath(env);
|
|
16430
16259
|
try {
|
|
16431
|
-
if (!
|
|
16260
|
+
if (!existsSync13(path))
|
|
16432
16261
|
return null;
|
|
16433
|
-
return
|
|
16262
|
+
return readFileSync12(path, "utf8");
|
|
16434
16263
|
} catch {
|
|
16435
16264
|
return null;
|
|
16436
16265
|
}
|
|
@@ -16454,22 +16283,172 @@ function stationProfileSource(env = process.env) {
|
|
|
16454
16283
|
init_instruction_graph();
|
|
16455
16284
|
init_asset_plan();
|
|
16456
16285
|
|
|
16286
|
+
// src/lib/harness-discovery.ts
|
|
16287
|
+
import { accessSync, constants as constants2, lstatSync as lstatSync6, realpathSync as realpathSync4, statSync as statSync5 } from "fs";
|
|
16288
|
+
import { homedir as homedir10 } from "os";
|
|
16289
|
+
import { delimiter, dirname as dirname9, isAbsolute as isAbsolute7, join as join17, resolve as resolve11 } from "path";
|
|
16290
|
+
var HARNESS_DISCOVERY_SCHEMA = "hasna.instructions.harness-discovery/v1";
|
|
16291
|
+
var DISCOVERABLE_HARNESSES = ["claude", "codex", "opencode", "sumi"];
|
|
16292
|
+
function pathInput(value, home) {
|
|
16293
|
+
if (!value || value !== value.trim() || /[\x00-\x1f\x7f]/.test(value))
|
|
16294
|
+
throw new Error("Harness paths must be nonempty and contain no surrounding whitespace or control characters.");
|
|
16295
|
+
if (value.split("/").some((segment) => segment === "." || segment === ".."))
|
|
16296
|
+
throw new Error("Harness paths must not contain dot segments; normalizing them can change a symlink destination.");
|
|
16297
|
+
let path = value;
|
|
16298
|
+
if (home) {
|
|
16299
|
+
for (const prefix of ["~", "{{HOME}}", "{{HOME_DIR}}", "${HOME}"]) {
|
|
16300
|
+
if (path === prefix)
|
|
16301
|
+
path = home;
|
|
16302
|
+
else if (path.startsWith(`${prefix}/`))
|
|
16303
|
+
path = join17(home, path.slice(prefix.length + 1));
|
|
16304
|
+
}
|
|
16305
|
+
}
|
|
16306
|
+
if (!isAbsolute7(path))
|
|
16307
|
+
throw new Error("Harness paths must be absolute or explicitly relative to the owner home (~/ or {{HOME_DIR}}/).");
|
|
16308
|
+
return resolve11(path);
|
|
16309
|
+
}
|
|
16310
|
+
function observe(path) {
|
|
16311
|
+
let symlink = false;
|
|
16312
|
+
let viaSymlink = false;
|
|
16313
|
+
for (let parent = dirname9(path);parent !== dirname9(parent); parent = dirname9(parent)) {
|
|
16314
|
+
try {
|
|
16315
|
+
if (lstatSync6(parent).isSymbolicLink()) {
|
|
16316
|
+
viaSymlink = true;
|
|
16317
|
+
break;
|
|
16318
|
+
}
|
|
16319
|
+
} catch {}
|
|
16320
|
+
}
|
|
16321
|
+
try {
|
|
16322
|
+
symlink = lstatSync6(path).isSymbolicLink();
|
|
16323
|
+
const stat = statSync5(path);
|
|
16324
|
+
return { path, state: "present", realPath: realpathSync4(path), symlink, viaSymlink: viaSymlink || symlink, type: stat.isFile() ? "file" : stat.isDirectory() ? "directory" : "other" };
|
|
16325
|
+
} catch (error) {
|
|
16326
|
+
const missing = error.code === "ENOENT";
|
|
16327
|
+
return { path, state: missing ? symlink ? "dangling-symlink" : "missing" : "unreadable", realPath: null, symlink, viaSymlink: viaSymlink || symlink, type: null };
|
|
16328
|
+
}
|
|
16329
|
+
}
|
|
16330
|
+
function executableAt(path) {
|
|
16331
|
+
const observation = observe(path);
|
|
16332
|
+
if (observation.type !== "file")
|
|
16333
|
+
return null;
|
|
16334
|
+
try {
|
|
16335
|
+
accessSync(path, constants2.X_OK);
|
|
16336
|
+
return observation;
|
|
16337
|
+
} catch {
|
|
16338
|
+
return null;
|
|
16339
|
+
}
|
|
16340
|
+
}
|
|
16341
|
+
function configSelection(tool, home, env, override) {
|
|
16342
|
+
if (override !== undefined)
|
|
16343
|
+
return { path: pathInput(override, home), source: "override" };
|
|
16344
|
+
const key = { claude: "CLAUDE_CONFIG_DIR", codex: "CODEX_HOME", opencode: "OPENCODE_CONFIG_DIR", sumi: "SUMI_CONFIG_DIR" }[tool];
|
|
16345
|
+
const nativePath = (value, selector) => {
|
|
16346
|
+
try {
|
|
16347
|
+
return pathInput(value);
|
|
16348
|
+
} catch {
|
|
16349
|
+
throw new Error(`${selector} is present but is not an unambiguous absolute native path; supply the reviewed runtime path explicitly.`);
|
|
16350
|
+
}
|
|
16351
|
+
};
|
|
16352
|
+
if (env[key] !== undefined && !(tool === "codex" && env[key] === ""))
|
|
16353
|
+
return { path: nativePath(env[key], key), source: key };
|
|
16354
|
+
if (tool === "claude" || tool === "codex")
|
|
16355
|
+
return { path: join17(home, `.${tool}`), source: "native-default" };
|
|
16356
|
+
if (env.XDG_CONFIG_HOME !== undefined)
|
|
16357
|
+
return { path: join17(nativePath(env.XDG_CONFIG_HOME, "XDG_CONFIG_HOME"), tool), source: "XDG_CONFIG_HOME" };
|
|
16358
|
+
if (tool === "sumi") {
|
|
16359
|
+
if (env.SUMI_HOME !== undefined)
|
|
16360
|
+
return { path: join17(nativePath(env.SUMI_HOME, "SUMI_HOME"), "config"), source: "SUMI_HOME" };
|
|
16361
|
+
return null;
|
|
16362
|
+
}
|
|
16363
|
+
return { path: join17(home, ".config", tool), source: "native-default" };
|
|
16364
|
+
}
|
|
16365
|
+
function discoverHarnesses(options = {}) {
|
|
16366
|
+
const env = options.env ?? process.env;
|
|
16367
|
+
const home = pathInput(options.ownerHome ?? env.HOME ?? env.USERPROFILE ?? homedir10());
|
|
16368
|
+
const project = options.projectRoot === undefined ? null : observe(pathInput(options.projectRoot, home));
|
|
16369
|
+
const variables = { HOME_DIR: home };
|
|
16370
|
+
if (project)
|
|
16371
|
+
variables.PROJECT_ROOT = project.path;
|
|
16372
|
+
const tools = DISCOVERABLE_HARNESSES.map((tool) => {
|
|
16373
|
+
const diagnostics = [];
|
|
16374
|
+
const override = options.overrides?.[tool];
|
|
16375
|
+
let executable = null;
|
|
16376
|
+
let executableSource = null;
|
|
16377
|
+
if (override?.executable !== undefined) {
|
|
16378
|
+
executable = executableAt(pathInput(override.executable, home));
|
|
16379
|
+
executableSource = "override";
|
|
16380
|
+
if (!executable)
|
|
16381
|
+
diagnostics.push("Explicit executable is missing, unreadable, not a file, or not executable; PATH fallback is disabled.");
|
|
16382
|
+
} else {
|
|
16383
|
+
for (const directory of (env.PATH ?? "").split(delimiter)) {
|
|
16384
|
+
if (!isAbsolute7(directory)) {
|
|
16385
|
+
const warning = "Relative or empty PATH entries were ignored; this is an absolute-path candidate inventory, not proof of the launcher's selected executable.";
|
|
16386
|
+
if (!diagnostics.includes(warning))
|
|
16387
|
+
diagnostics.push(warning);
|
|
16388
|
+
continue;
|
|
16389
|
+
}
|
|
16390
|
+
let validated;
|
|
16391
|
+
try {
|
|
16392
|
+
validated = pathInput(directory);
|
|
16393
|
+
} catch {
|
|
16394
|
+
diagnostics.push("An ambiguous PATH directory was ignored; use an explicit executable override after reviewing its native path.");
|
|
16395
|
+
continue;
|
|
16396
|
+
}
|
|
16397
|
+
executable = executableAt(join17(validated, tool));
|
|
16398
|
+
if (executable) {
|
|
16399
|
+
executableSource = "PATH";
|
|
16400
|
+
break;
|
|
16401
|
+
}
|
|
16402
|
+
}
|
|
16403
|
+
}
|
|
16404
|
+
let selected = null;
|
|
16405
|
+
let configError = false;
|
|
16406
|
+
try {
|
|
16407
|
+
selected = configSelection(tool, home, env, override?.configDir);
|
|
16408
|
+
} catch (error) {
|
|
16409
|
+
if (override?.configDir !== undefined)
|
|
16410
|
+
throw error;
|
|
16411
|
+
configError = true;
|
|
16412
|
+
diagnostics.push(`Config root unresolved: ${error.message}`);
|
|
16413
|
+
}
|
|
16414
|
+
const config = selected ? { ...observe(selected.path), source: selected.source } : null;
|
|
16415
|
+
const filename = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
16416
|
+
const globalPrompt = config ? observe(join17(config.path, filename)) : null;
|
|
16417
|
+
const promptOverrides = tool === "codex" ? [...new Set([config?.path, project?.path].filter((path) => !!path))].map((path) => observe(join17(path, "AGENTS.override.md"))) : [];
|
|
16418
|
+
if (!config && !configError)
|
|
16419
|
+
diagnostics.push("Config root unresolved: supply the actual `sumi debug paths config` result as a configDir override; no runtime or migration was invoked.");
|
|
16420
|
+
if (executable) {
|
|
16421
|
+
variables[`${tool.toUpperCase()}_EXECUTABLE`] = executable.path;
|
|
16422
|
+
if (config)
|
|
16423
|
+
variables[`${tool.toUpperCase()}_CONFIG_DIR`] = config.path;
|
|
16424
|
+
}
|
|
16425
|
+
const linkedGlobal = !!globalPrompt?.viaSymlink || !!config?.viaSymlink;
|
|
16426
|
+
const hasOverride = promptOverrides.some((path) => path.state !== "missing" || path.viaSymlink);
|
|
16427
|
+
const scopeReviewRequired = linkedGlobal || hasOverride;
|
|
16428
|
+
if (linkedGlobal)
|
|
16429
|
+
diagnostics.push("Global config or prompt resolves through a symlink; preserve its lexical path and review the destination scope before planning writes.");
|
|
16430
|
+
if (hasOverride)
|
|
16431
|
+
diagnostics.push("A Codex AGENTS.override.md candidate may take precedence; review its native loader behavior and scope before applying AGENTS.md.");
|
|
16432
|
+
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 };
|
|
16433
|
+
});
|
|
16434
|
+
return { schema: HARNESS_DISCOVERY_SCHEMA, ownerHome: observe(home), projectRoot: project, tools, templateVariables: variables };
|
|
16435
|
+
}
|
|
16457
16436
|
// src/lib/session-apply.ts
|
|
16458
16437
|
init_project_context();
|
|
16459
16438
|
init_session_render_state();
|
|
16460
16439
|
init_session_render();
|
|
16461
16440
|
init_cursor_authority();
|
|
16462
16441
|
init_session_authority();
|
|
16463
|
-
import { createHash as
|
|
16442
|
+
import { createHash as createHash9, randomUUID as randomUUID4 } from "crypto";
|
|
16464
16443
|
import {
|
|
16465
|
-
existsSync as
|
|
16466
|
-
lstatSync as
|
|
16467
|
-
mkdirSync as
|
|
16468
|
-
readFileSync as
|
|
16444
|
+
existsSync as existsSync14,
|
|
16445
|
+
lstatSync as lstatSync7,
|
|
16446
|
+
mkdirSync as mkdirSync6,
|
|
16447
|
+
readFileSync as readFileSync13,
|
|
16469
16448
|
readdirSync as readdirSync3,
|
|
16470
|
-
statSync as
|
|
16449
|
+
statSync as statSync6
|
|
16471
16450
|
} from "fs";
|
|
16472
|
-
import { dirname as dirname10, isAbsolute as
|
|
16451
|
+
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
16452
|
class SessionApplyError extends Error {
|
|
16474
16453
|
constructor(message) {
|
|
16475
16454
|
super(message);
|
|
@@ -16499,13 +16478,15 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16499
16478
|
const adoptedHashes = new Map(adoptions.map((entry) => [entry.relativePath, entry.preimageSha256]));
|
|
16500
16479
|
const reconciliations = validateFileReconciliations(plan, targetHome, previousManifest, options);
|
|
16501
16480
|
const reconciledHashes = new Map(reconciliations.map((entry) => [entry.relativePath, entry.preimageSha256]));
|
|
16502
|
-
const
|
|
16481
|
+
const retirements = validateFileRetirements(plan, targetHome, previousManifest, options);
|
|
16482
|
+
const retiredHashes = new Map(retirements.map((entry) => [entry.relativePath, entry.preimageSha256]));
|
|
16483
|
+
const manifestFile = manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations, retirements);
|
|
16503
16484
|
const files = [...payloadFiles, manifestFile];
|
|
16504
16485
|
const currentRelativePaths = new Set(files.map((file) => file.relativePath));
|
|
16505
16486
|
const drift = checkSessionRenderDrift(targetHome, manifestPath);
|
|
16506
16487
|
const results = [
|
|
16507
16488
|
...files.map((file) => planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes)),
|
|
16508
|
-
...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options)
|
|
16489
|
+
...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes)
|
|
16509
16490
|
];
|
|
16510
16491
|
assertNotSilentManagedWipeout(plan, results);
|
|
16511
16492
|
const conflicts = results.filter((result) => result.action === "conflict");
|
|
@@ -16529,7 +16510,8 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16529
16510
|
conflicts,
|
|
16530
16511
|
drift,
|
|
16531
16512
|
adoptions,
|
|
16532
|
-
reconciliations
|
|
16513
|
+
reconciliations,
|
|
16514
|
+
retirements
|
|
16533
16515
|
};
|
|
16534
16516
|
}
|
|
16535
16517
|
let snapshotPath = null;
|
|
@@ -16544,10 +16526,14 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16544
16526
|
const forcePortableFileOps = options.test_hooks?.force_portable_file_ops ?? false;
|
|
16545
16527
|
assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
|
|
16546
16528
|
ensureSessionTargetHome(targetHome);
|
|
16547
|
-
rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations);
|
|
16529
|
+
rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements);
|
|
16548
16530
|
snapshotPath = rollback.snapshotPath;
|
|
16549
16531
|
options.test_hooks?.before_apply_writes?.({ plan, results });
|
|
16550
16532
|
assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
|
|
16533
|
+
for (const result of results) {
|
|
16534
|
+
assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
|
|
16535
|
+
coordination?.assert_held();
|
|
16536
|
+
}
|
|
16551
16537
|
const resultsByPath = new Map(results.map((result) => [result.path, result]));
|
|
16552
16538
|
for (const file of payloadFiles) {
|
|
16553
16539
|
applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
|
|
@@ -16583,7 +16569,8 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16583
16569
|
conflicts,
|
|
16584
16570
|
drift,
|
|
16585
16571
|
adoptions,
|
|
16586
|
-
reconciliations
|
|
16572
|
+
reconciliations,
|
|
16573
|
+
retirements
|
|
16587
16574
|
};
|
|
16588
16575
|
}
|
|
16589
16576
|
function assertManifestPrecondition(path, targetHome, expected) {
|
|
@@ -16612,7 +16599,7 @@ function validateFileAdoptions(plan, targetHome, previousHashes, options) {
|
|
|
16612
16599
|
throw new SessionApplyError(`Duplicate file adoption target: ${request.relativePath}`);
|
|
16613
16600
|
}
|
|
16614
16601
|
seen.add(request.relativePath);
|
|
16615
|
-
const candidates = plan.
|
|
16602
|
+
const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
|
|
16616
16603
|
if (candidates.length !== 1 || candidates[0].role === "manifest") {
|
|
16617
16604
|
throw new SessionApplyError(`File adoption target is not a unique planned instruction output: ${request.relativePath}`);
|
|
16618
16605
|
}
|
|
@@ -16631,15 +16618,19 @@ function validateFileAdoptions(plan, targetHome, previousHashes, options) {
|
|
|
16631
16618
|
};
|
|
16632
16619
|
});
|
|
16633
16620
|
}
|
|
16621
|
+
function exactPreimageCandidates(plan) {
|
|
16622
|
+
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)));
|
|
16623
|
+
return [...plan.files, ...(plan.assetFiles ?? []).filter((file) => customAgentPaths.has(file.relativePath))];
|
|
16624
|
+
}
|
|
16634
16625
|
function readExactFilePreimage(path, targetHome, request, operation) {
|
|
16635
16626
|
if (currentSessionFileHash(path, targetHome) === null) {
|
|
16636
16627
|
throw new SessionApplyError(`File ${operation} target does not exist: ${request.relativePath}`);
|
|
16637
16628
|
}
|
|
16638
|
-
const bytes =
|
|
16629
|
+
const bytes = readFileSync13(path);
|
|
16639
16630
|
if (!bytes.equals(Buffer.from(bytes.toString("utf8"), "utf8"))) {
|
|
16640
16631
|
throw new SessionApplyError(`File ${operation} target must contain losslessly restorable UTF-8: ${request.relativePath}`);
|
|
16641
16632
|
}
|
|
16642
|
-
const observedSha256 =
|
|
16633
|
+
const observedSha256 = createHash9("sha256").update(bytes).digest("hex");
|
|
16643
16634
|
if (observedSha256 !== request.sha256) {
|
|
16644
16635
|
throw new SessionApplyError(`File ${operation} SHA-256 precondition failed: ${request.relativePath}`);
|
|
16645
16636
|
}
|
|
@@ -16668,7 +16659,7 @@ function validateFileReconciliations(plan, targetHome, previousManifest, options
|
|
|
16668
16659
|
throw new SessionApplyError(`Duplicate file reconciliation target: ${request.relativePath}`);
|
|
16669
16660
|
}
|
|
16670
16661
|
seen.add(request.relativePath);
|
|
16671
|
-
const candidates = plan.
|
|
16662
|
+
const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
|
|
16672
16663
|
if (candidates.length !== 1 || candidates[0].role === "manifest") {
|
|
16673
16664
|
throw new SessionApplyError(`File reconciliation target is not a unique planned instruction output: ${request.relativePath}`);
|
|
16674
16665
|
}
|
|
@@ -16695,8 +16686,47 @@ function validateFileReconciliations(plan, targetHome, previousManifest, options
|
|
|
16695
16686
|
};
|
|
16696
16687
|
});
|
|
16697
16688
|
}
|
|
16698
|
-
function
|
|
16699
|
-
|
|
16689
|
+
function validateFileRetirements(plan, targetHome, previousManifest, options) {
|
|
16690
|
+
const requests = options.retireFiles ?? [];
|
|
16691
|
+
if (!Array.isArray(requests))
|
|
16692
|
+
throw new SessionApplyError("Session file retirements must be an array.");
|
|
16693
|
+
if (requests.length === 0)
|
|
16694
|
+
return [];
|
|
16695
|
+
if (options.force)
|
|
16696
|
+
throw new SessionApplyError("Exact file retirement cannot be combined with force.");
|
|
16697
|
+
if (options.expectedManifestSha256 === undefined) {
|
|
16698
|
+
throw new SessionApplyError("Exact file retirement requires an expected manifest SHA-256 precondition.");
|
|
16699
|
+
}
|
|
16700
|
+
if (!previousManifest || previousManifest.targetOwner?.writer?.id !== SESSION_RENDERER_OWNER_ID || previousManifest.tool !== plan.tool || previousManifest.targetHome !== targetHome) {
|
|
16701
|
+
throw new SessionApplyError("File retirement requires this renderer's manifest for the same tool and target home.");
|
|
16702
|
+
}
|
|
16703
|
+
const seen = new Set;
|
|
16704
|
+
return requests.map((request) => {
|
|
16705
|
+
if (!request || typeof request.relativePath !== "string" || typeof request.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(request.sha256)) {
|
|
16706
|
+
throw new SessionApplyError("Each file retirement requires a plan-relative path and a 64-character lowercase SHA-256.");
|
|
16707
|
+
}
|
|
16708
|
+
if (seen.has(request.relativePath))
|
|
16709
|
+
throw new SessionApplyError(`Duplicate file retirement target: ${request.relativePath}`);
|
|
16710
|
+
seen.add(request.relativePath);
|
|
16711
|
+
if (plan.allFiles.some((file) => file.relativePath === request.relativePath)) {
|
|
16712
|
+
throw new SessionApplyError(`File retirement target is retained by the new plan: ${request.relativePath}`);
|
|
16713
|
+
}
|
|
16714
|
+
const owned = previousManifest.files.filter((entry) => entry.relativePath === request.relativePath);
|
|
16715
|
+
const path = resolveManifestRelativePath(request.relativePath, targetHome);
|
|
16716
|
+
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") {
|
|
16717
|
+
throw new SessionApplyError(`File retirement target is not an obsolete managed fragment, rule, or asset: ${request.relativePath}`);
|
|
16718
|
+
}
|
|
16719
|
+
return {
|
|
16720
|
+
path,
|
|
16721
|
+
relativePath: request.relativePath,
|
|
16722
|
+
preimageSha256: readExactFilePreimage(path, targetHome, request, "retirement"),
|
|
16723
|
+
previousManagedSha256: owned[0].sha256,
|
|
16724
|
+
sourceIds: [...owned[0].sourceIds]
|
|
16725
|
+
};
|
|
16726
|
+
});
|
|
16727
|
+
}
|
|
16728
|
+
function manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations, retirements) {
|
|
16729
|
+
if (adoptions.length === 0 && previousManifest?.adoptions === undefined && reconciliations.length === 0 && previousManifest?.reconciliations === undefined && retirements.length === 0 && previousManifest?.retirements === undefined)
|
|
16700
16730
|
return plan.manifestFile;
|
|
16701
16731
|
if (previousManifest?.adoptions !== undefined && !Array.isArray(previousManifest.adoptions)) {
|
|
16702
16732
|
throw new SessionApplyError("Previous session manifest adoption provenance is invalid.");
|
|
@@ -16737,14 +16767,32 @@ function manifestWithFileProvenance(plan, previousManifest, adoptions, reconcili
|
|
|
16737
16767
|
};
|
|
16738
16768
|
reconciledEntries.set(JSON.stringify(canonical), canonical);
|
|
16739
16769
|
}
|
|
16770
|
+
if (previousManifest?.retirements !== undefined && !Array.isArray(previousManifest.retirements)) {
|
|
16771
|
+
throw new SessionApplyError("Previous session manifest retirement provenance is invalid.");
|
|
16772
|
+
}
|
|
16773
|
+
const retiredEntries = new Map;
|
|
16774
|
+
for (const entry of [...previousManifest?.retirements ?? [], ...retirements.map(({ path: _path, ...receipt }) => receipt)]) {
|
|
16775
|
+
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")) {
|
|
16776
|
+
throw new SessionApplyError("Previous session manifest retirement provenance is invalid.");
|
|
16777
|
+
}
|
|
16778
|
+
resolveManifestRelativePath(entry.relativePath, plan.targetHome);
|
|
16779
|
+
const canonical = {
|
|
16780
|
+
relativePath: entry.relativePath,
|
|
16781
|
+
preimageSha256: entry.preimageSha256,
|
|
16782
|
+
previousManagedSha256: entry.previousManagedSha256,
|
|
16783
|
+
sourceIds: [...entry.sourceIds]
|
|
16784
|
+
};
|
|
16785
|
+
retiredEntries.set(JSON.stringify(canonical), canonical);
|
|
16786
|
+
}
|
|
16740
16787
|
const manifest = {
|
|
16741
16788
|
...plan.manifest,
|
|
16742
16789
|
...adoptions.length > 0 || previousManifest?.adoptions !== undefined ? { adoptions: [...entries.values()] } : {},
|
|
16743
|
-
...reconciliations.length > 0 || previousManifest?.reconciliations !== undefined ? { reconciliations: [...reconciledEntries.values()] } : {}
|
|
16790
|
+
...reconciliations.length > 0 || previousManifest?.reconciliations !== undefined ? { reconciliations: [...reconciledEntries.values()] } : {},
|
|
16791
|
+
...retirements.length > 0 || previousManifest?.retirements !== undefined ? { retirements: [...retiredEntries.values()] } : {}
|
|
16744
16792
|
};
|
|
16745
16793
|
const content = `${JSON.stringify(manifest, null, 2)}
|
|
16746
16794
|
`;
|
|
16747
|
-
return { ...plan.manifestFile, content, sha256:
|
|
16795
|
+
return { ...plan.manifestFile, content, sha256: sha2569(content) };
|
|
16748
16796
|
}
|
|
16749
16797
|
function assertCursorAuthorityUnchanged(plan) {
|
|
16750
16798
|
if (plan.tool !== "cursor" || plan.targetKind === "blocked")
|
|
@@ -16772,13 +16820,13 @@ function assertClaudeAuthorityStillClear(plan, targetHome, ownedClaudeAuthoritie
|
|
|
16772
16820
|
throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
|
|
16773
16821
|
}
|
|
16774
16822
|
function ensureSessionTargetHome(targetHome) {
|
|
16775
|
-
if (!
|
|
16776
|
-
|
|
16823
|
+
if (!existsSync14(targetHome))
|
|
16824
|
+
mkdirSync6(targetHome, { recursive: true, mode: 448 });
|
|
16777
16825
|
assertSafeTargetHome(targetHome);
|
|
16778
16826
|
}
|
|
16779
16827
|
function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
16780
16828
|
const safeTargetHome = assertSafeTargetHome(targetHome);
|
|
16781
|
-
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(
|
|
16829
|
+
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve12(manifestPath)), safeTargetHome) : resolve12(safeTargetHome, ".hasna", "session-render-manifest.json");
|
|
16782
16830
|
const checkedAt = new Date().toISOString();
|
|
16783
16831
|
const previousManifest = readPreviousManifest(resolvedManifestPath);
|
|
16784
16832
|
if (!previousManifest) {
|
|
@@ -16795,7 +16843,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
16795
16843
|
const drifted = [];
|
|
16796
16844
|
for (const file of previousManifest.files) {
|
|
16797
16845
|
const target = resolveManifestRelativePath(file.relativePath, safeTargetHome);
|
|
16798
|
-
if (!
|
|
16846
|
+
if (!existsSync14(target)) {
|
|
16799
16847
|
missing.push({
|
|
16800
16848
|
path: target,
|
|
16801
16849
|
relativePath: file.relativePath,
|
|
@@ -16805,7 +16853,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
16805
16853
|
});
|
|
16806
16854
|
continue;
|
|
16807
16855
|
}
|
|
16808
|
-
const actualSha256 =
|
|
16856
|
+
const actualSha256 = sha2569(readFileSync13(target, "utf-8"));
|
|
16809
16857
|
if (actualSha256 !== file.sha256) {
|
|
16810
16858
|
drifted.push({
|
|
16811
16859
|
path: target,
|
|
@@ -16828,13 +16876,13 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
16828
16876
|
function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
|
|
16829
16877
|
const snapshot = readSessionRenderSnapshot(snapshotPath);
|
|
16830
16878
|
const targetHome = assertSafeTargetHome(snapshot.targetHome);
|
|
16831
|
-
const resolvedSnapshotPath =
|
|
16879
|
+
const resolvedSnapshotPath = resolve12(snapshotPath);
|
|
16832
16880
|
const snapshotDir = getSessionRenderSnapshotDir(targetHome);
|
|
16833
|
-
const snapshotDirRelative =
|
|
16834
|
-
const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !
|
|
16881
|
+
const snapshotDirRelative = relative5(snapshotDir, resolvedSnapshotPath);
|
|
16882
|
+
const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute8(snapshotDirRelative);
|
|
16835
16883
|
if (!insideSnapshotDir) {
|
|
16836
|
-
const snapshotRelativePath =
|
|
16837
|
-
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") ||
|
|
16884
|
+
const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
|
|
16885
|
+
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute8(snapshotRelativePath)) {
|
|
16838
16886
|
throw new SessionApplyError("Session snapshot must be stored inside its session-render snapshot location.");
|
|
16839
16887
|
}
|
|
16840
16888
|
}
|
|
@@ -16951,19 +16999,19 @@ function requiredRestoreHash(file) {
|
|
|
16951
16999
|
return file.previousSha256;
|
|
16952
17000
|
}
|
|
16953
17001
|
function readSessionRenderSnapshot(snapshotPath) {
|
|
16954
|
-
const resolved =
|
|
16955
|
-
if (!
|
|
17002
|
+
const resolved = resolve12(snapshotPath);
|
|
17003
|
+
if (!existsSync14(resolved))
|
|
16956
17004
|
throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
|
|
16957
|
-
const stat =
|
|
17005
|
+
const stat = lstatSync7(resolved);
|
|
16958
17006
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
16959
17007
|
throw new SessionApplyError(`Session snapshot is not a regular file: ${snapshotPath}`);
|
|
16960
17008
|
}
|
|
16961
|
-
if (
|
|
17009
|
+
if (statSync6(resolved).size > 32 * 1024 * 1024) {
|
|
16962
17010
|
throw new SessionApplyError(`Session snapshot exceeds the 32 MiB restore limit: ${snapshotPath}`);
|
|
16963
17011
|
}
|
|
16964
17012
|
let parsed;
|
|
16965
17013
|
try {
|
|
16966
|
-
parsed = JSON.parse(
|
|
17014
|
+
parsed = JSON.parse(readFileSync13(resolved, "utf8"));
|
|
16967
17015
|
} catch {
|
|
16968
17016
|
throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
|
|
16969
17017
|
}
|
|
@@ -16985,7 +17033,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
16985
17033
|
const previousManifest = snapshot.previousManifest;
|
|
16986
17034
|
const previousFiles = new Map;
|
|
16987
17035
|
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" ||
|
|
17036
|
+
if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" || sha2569(file.content) !== file.sha256) {
|
|
16989
17037
|
throw new SessionApplyError(`Session snapshot previous file metadata is invalid: ${snapshotPath}`);
|
|
16990
17038
|
}
|
|
16991
17039
|
resolveSnapshotFilePath(file.relativePath, file.path, targetHome);
|
|
@@ -17032,8 +17080,8 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
17032
17080
|
}
|
|
17033
17081
|
function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
|
|
17034
17082
|
assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
|
|
17035
|
-
const manifestPath =
|
|
17036
|
-
const manifestRelativePath =
|
|
17083
|
+
const manifestPath = resolve12(snapshot.manifestPath);
|
|
17084
|
+
const manifestRelativePath = relative5(targetHome, manifestPath).replaceAll("\\", "/");
|
|
17037
17085
|
resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
|
|
17038
17086
|
const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
|
|
17039
17087
|
if (manifestSha256 === null) {
|
|
@@ -17041,7 +17089,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
17041
17089
|
}
|
|
17042
17090
|
let parsedManifest;
|
|
17043
17091
|
try {
|
|
17044
|
-
parsedManifest = JSON.parse(
|
|
17092
|
+
parsedManifest = JSON.parse(readFileSync13(manifestPath, "utf8"));
|
|
17045
17093
|
} catch {
|
|
17046
17094
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
|
|
17047
17095
|
}
|
|
@@ -17049,7 +17097,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
17049
17097
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
|
|
17050
17098
|
}
|
|
17051
17099
|
const appliedManifest = parsedManifest;
|
|
17052
|
-
if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" ||
|
|
17100
|
+
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
17101
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
|
|
17054
17102
|
}
|
|
17055
17103
|
const afterFiles = [];
|
|
@@ -17134,16 +17182,16 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
|
|
|
17134
17182
|
throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
|
|
17135
17183
|
}
|
|
17136
17184
|
for (const entry of readdirSync3(dirname10(snapshotPath))) {
|
|
17137
|
-
const candidatePath =
|
|
17138
|
-
if (candidatePath ===
|
|
17185
|
+
const candidatePath = resolve12(dirname10(snapshotPath), entry);
|
|
17186
|
+
if (candidatePath === resolve12(snapshotPath) || !entry.endsWith(".json"))
|
|
17139
17187
|
continue;
|
|
17140
|
-
const candidateStat =
|
|
17188
|
+
const candidateStat = lstatSync7(candidatePath);
|
|
17141
17189
|
if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
|
|
17142
17190
|
continue;
|
|
17143
17191
|
try {
|
|
17144
|
-
const candidate = JSON.parse(
|
|
17192
|
+
const candidate = JSON.parse(readFileSync13(candidatePath, "utf8"));
|
|
17145
17193
|
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" &&
|
|
17194
|
+
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
17195
|
throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
|
|
17148
17196
|
}
|
|
17149
17197
|
} catch (error) {
|
|
@@ -17201,7 +17249,7 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
17201
17249
|
return "create";
|
|
17202
17250
|
}
|
|
17203
17251
|
if (file.role === "manifest" && previousManifest) {
|
|
17204
|
-
const previousManifestSha256 =
|
|
17252
|
+
const previousManifestSha256 = sha2569(`${JSON.stringify(previousManifest, null, 2)}
|
|
17205
17253
|
`);
|
|
17206
17254
|
if (previousManifestSha256 !== file.sha256) {
|
|
17207
17255
|
throw new SessionApplyError(`Cannot infer legacy v1 manifest action without a before-image: ${file.relativePath}`);
|
|
@@ -17212,15 +17260,15 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
17212
17260
|
}
|
|
17213
17261
|
function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
|
|
17214
17262
|
const path = resolveManifestRelativePath(relativePath, targetHome);
|
|
17215
|
-
if (
|
|
17263
|
+
if (resolve12(recordedPath) !== path) {
|
|
17216
17264
|
throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
|
|
17217
17265
|
}
|
|
17218
17266
|
return path;
|
|
17219
17267
|
}
|
|
17220
17268
|
function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes) {
|
|
17221
17269
|
const target = resolvePlannedFilePath(plan, file, targetHome);
|
|
17222
|
-
const previousContent =
|
|
17223
|
-
const previousSha256 = previousContent === null ? null :
|
|
17270
|
+
const previousContent = existsSync14(target) ? readFileSync13(target, "utf-8") : null;
|
|
17271
|
+
const previousSha256 = previousContent === null ? null : sha2569(previousContent);
|
|
17224
17272
|
const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
|
|
17225
17273
|
const changed = previousContent !== file.content;
|
|
17226
17274
|
const exactSha256 = adoptedHashes.get(file.relativePath) ?? reconciledHashes.get(file.relativePath);
|
|
@@ -17328,17 +17376,55 @@ function isPlanManagedFile(plan, relativePath, role) {
|
|
|
17328
17376
|
return true;
|
|
17329
17377
|
return plan.tool === "claude" && role === "rule" && relativePath.startsWith("rules/");
|
|
17330
17378
|
}
|
|
17331
|
-
function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options) {
|
|
17379
|
+
function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes) {
|
|
17332
17380
|
if (!previousManifest)
|
|
17333
17381
|
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);
|
|
17382
|
+
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
17383
|
}
|
|
17336
|
-
function planStaleFileResult(file, targetHome, options) {
|
|
17384
|
+
function planStaleFileResult(file, targetHome, options, retiredHash) {
|
|
17337
17385
|
const target = resolveManifestRelativePath(file.relativePath, targetHome);
|
|
17338
|
-
if (!
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17386
|
+
if (!existsSync14(target)) {
|
|
17387
|
+
if (file.role !== "asset")
|
|
17388
|
+
return null;
|
|
17389
|
+
return {
|
|
17390
|
+
path: target,
|
|
17391
|
+
relativePath: file.relativePath,
|
|
17392
|
+
role: file.role,
|
|
17393
|
+
action: "conflict",
|
|
17394
|
+
changed: true,
|
|
17395
|
+
previousSha256: null,
|
|
17396
|
+
newSha256: "",
|
|
17397
|
+
reason: "obsolete managed asset is missing; preserve manifest ownership until its reviewed preimage is restored and retired exactly"
|
|
17398
|
+
};
|
|
17399
|
+
}
|
|
17400
|
+
const previousContent = readFileSync13(target, "utf-8");
|
|
17401
|
+
const previousSha256 = sha2569(previousContent);
|
|
17402
|
+
if (retiredHash !== undefined) {
|
|
17403
|
+
if (previousSha256 !== retiredHash)
|
|
17404
|
+
throw new SessionApplyError(`File retirement preimage changed after validation: ${file.relativePath}`);
|
|
17405
|
+
return {
|
|
17406
|
+
path: target,
|
|
17407
|
+
relativePath: file.relativePath,
|
|
17408
|
+
role: file.role,
|
|
17409
|
+
action: "delete",
|
|
17410
|
+
changed: true,
|
|
17411
|
+
previousSha256,
|
|
17412
|
+
newSha256: "",
|
|
17413
|
+
reason: "exact reviewed obsolete managed file retired"
|
|
17414
|
+
};
|
|
17415
|
+
}
|
|
17416
|
+
if (file.role === "asset") {
|
|
17417
|
+
return {
|
|
17418
|
+
path: target,
|
|
17419
|
+
relativePath: file.relativePath,
|
|
17420
|
+
role: file.role,
|
|
17421
|
+
action: "conflict",
|
|
17422
|
+
changed: true,
|
|
17423
|
+
previousSha256,
|
|
17424
|
+
newSha256: "",
|
|
17425
|
+
reason: "obsolete managed asset requires exact retirement; preserve ownership until --retire-file and --expected-manifest-sha256 are supplied"
|
|
17426
|
+
};
|
|
17427
|
+
}
|
|
17342
17428
|
if (!options.force && previousSha256 !== file.sha256) {
|
|
17343
17429
|
return {
|
|
17344
17430
|
path: target,
|
|
@@ -17382,31 +17468,31 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
|
|
|
17382
17468
|
return previousHashes.get(file.relativePath) === previousSha256;
|
|
17383
17469
|
}
|
|
17384
17470
|
function resolvePlannedFilePath(plan, file, targetHome) {
|
|
17385
|
-
const target =
|
|
17386
|
-
const rel =
|
|
17387
|
-
if (rel === "" || rel === ".." || rel.startsWith("../") ||
|
|
17471
|
+
const target = resolve12(targetHome, ...file.relativePath.split("/"));
|
|
17472
|
+
const rel = relative5(targetHome, target);
|
|
17473
|
+
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute8(rel)) {
|
|
17388
17474
|
throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
|
|
17389
17475
|
}
|
|
17390
|
-
if (
|
|
17476
|
+
if (resolve12(file.path) !== target) {
|
|
17391
17477
|
throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
|
|
17392
17478
|
}
|
|
17393
17479
|
assertNoSymlinkSegments2(targetHome, target);
|
|
17394
17480
|
return target;
|
|
17395
17481
|
}
|
|
17396
17482
|
function resolveManifestRelativePath(relativePath, targetHome) {
|
|
17397
|
-
const target =
|
|
17398
|
-
const rel =
|
|
17399
|
-
if (rel === "" || rel === ".." || rel.startsWith("../") ||
|
|
17483
|
+
const target = resolve12(targetHome, ...relativePath.split(/[\\/]+/));
|
|
17484
|
+
const rel = relative5(targetHome, target);
|
|
17485
|
+
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute8(rel)) {
|
|
17400
17486
|
throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
|
|
17401
17487
|
}
|
|
17402
17488
|
assertNoSymlinkSegments2(targetHome, target);
|
|
17403
17489
|
return target;
|
|
17404
17490
|
}
|
|
17405
17491
|
function readPreviousManifest(path) {
|
|
17406
|
-
if (!
|
|
17492
|
+
if (!existsSync14(path))
|
|
17407
17493
|
return null;
|
|
17408
17494
|
try {
|
|
17409
|
-
const parsed = JSON.parse(
|
|
17495
|
+
const parsed = JSON.parse(readFileSync13(path, "utf-8"));
|
|
17410
17496
|
if (parsed.schema !== SESSION_RENDER_SCHEMA)
|
|
17411
17497
|
return null;
|
|
17412
17498
|
if (!Array.isArray(parsed.files))
|
|
@@ -17440,18 +17526,18 @@ function applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, a
|
|
|
17440
17526
|
function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
|
|
17441
17527
|
const actualHash = currentSessionFileHash(path, targetHome);
|
|
17442
17528
|
if (actualHash !== expectedHash) {
|
|
17443
|
-
throw new SessionApplyError(`Session apply path changed after planning: ${
|
|
17529
|
+
throw new SessionApplyError(`Session apply path changed after planning: ${relative5(targetHome, path)}`);
|
|
17444
17530
|
}
|
|
17445
17531
|
}
|
|
17446
17532
|
function currentSessionFileHash(path, targetHome) {
|
|
17447
17533
|
assertNoSymlinkSegments2(targetHome, path);
|
|
17448
|
-
if (!
|
|
17534
|
+
if (!existsSync14(path))
|
|
17449
17535
|
return null;
|
|
17450
|
-
const stat =
|
|
17536
|
+
const stat = lstatSync7(path);
|
|
17451
17537
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
17452
17538
|
throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
|
|
17453
17539
|
}
|
|
17454
|
-
return
|
|
17540
|
+
return sha2569(readFileSync13(path, "utf-8"));
|
|
17455
17541
|
}
|
|
17456
17542
|
function requiredPreviousHash(result) {
|
|
17457
17543
|
if (result.previousSha256 === null) {
|
|
@@ -17459,18 +17545,18 @@ function requiredPreviousHash(result) {
|
|
|
17459
17545
|
}
|
|
17460
17546
|
return result.previousSha256;
|
|
17461
17547
|
}
|
|
17462
|
-
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations) {
|
|
17548
|
+
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements) {
|
|
17463
17549
|
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").map((result) => {
|
|
17464
17550
|
assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
|
|
17465
|
-
const content =
|
|
17466
|
-
if (
|
|
17551
|
+
const content = readFileSync13(result.path, "utf-8");
|
|
17552
|
+
if (sha2569(content) !== result.previousSha256) {
|
|
17467
17553
|
throw new SessionApplyError(`Session snapshot preimage changed after planning: ${result.relativePath}`);
|
|
17468
17554
|
}
|
|
17469
17555
|
return {
|
|
17470
17556
|
path: result.path,
|
|
17471
17557
|
relativePath: result.relativePath,
|
|
17472
17558
|
role: result.role,
|
|
17473
|
-
sha256:
|
|
17559
|
+
sha256: sha2569(content),
|
|
17474
17560
|
content
|
|
17475
17561
|
};
|
|
17476
17562
|
});
|
|
@@ -17483,7 +17569,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
17483
17569
|
};
|
|
17484
17570
|
}
|
|
17485
17571
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
17486
|
-
const snapshotPath =
|
|
17572
|
+
const snapshotPath = join18(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID4()}.json`);
|
|
17487
17573
|
const afterFiles = results.map((result) => {
|
|
17488
17574
|
if (result.action === "conflict") {
|
|
17489
17575
|
throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
|
|
@@ -17508,11 +17594,12 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
17508
17594
|
files: existingFiles,
|
|
17509
17595
|
afterFiles,
|
|
17510
17596
|
...adoptions.length > 0 ? { adoptions } : {},
|
|
17511
|
-
...reconciliations.length > 0 ? { reconciliations } : {}
|
|
17597
|
+
...reconciliations.length > 0 ? { reconciliations } : {},
|
|
17598
|
+
...retirements.length > 0 ? { retirements } : {}
|
|
17512
17599
|
};
|
|
17513
17600
|
const snapshotContent = `${JSON.stringify(snapshot, null, 2)}
|
|
17514
17601
|
`;
|
|
17515
|
-
if ((adoptions.length > 0 || reconciliations.length > 0) && Buffer.byteLength(snapshotContent, "utf8") > 32 * 1024 * 1024) {
|
|
17602
|
+
if ((adoptions.length > 0 || reconciliations.length > 0 || retirements.length > 0) && Buffer.byteLength(snapshotContent, "utf8") > 32 * 1024 * 1024) {
|
|
17516
17603
|
throw new SessionApplyError("Exact file preimage snapshot exceeds the 32 MiB restore limit.");
|
|
17517
17604
|
}
|
|
17518
17605
|
coordination?.assert_held();
|
|
@@ -17535,51 +17622,51 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
17535
17622
|
};
|
|
17536
17623
|
}
|
|
17537
17624
|
function assertSafeTargetHome(targetHome) {
|
|
17538
|
-
if (!
|
|
17625
|
+
if (!isAbsolute8(targetHome))
|
|
17539
17626
|
throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
|
|
17540
|
-
const normalized =
|
|
17541
|
-
if (normalized ===
|
|
17627
|
+
const normalized = resolve12(targetHome);
|
|
17628
|
+
if (normalized === parse4(normalized).root) {
|
|
17542
17629
|
throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
|
|
17543
17630
|
}
|
|
17544
|
-
|
|
17545
|
-
if (
|
|
17631
|
+
assertNoSymlinkAncestors2(normalized);
|
|
17632
|
+
if (existsSync14(normalized) && lstatSync7(normalized).isSymbolicLink()) {
|
|
17546
17633
|
throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
|
|
17547
17634
|
}
|
|
17548
17635
|
return normalized;
|
|
17549
17636
|
}
|
|
17550
17637
|
function assertNoSymlinkSegments2(root, target) {
|
|
17551
|
-
|
|
17552
|
-
const rel =
|
|
17638
|
+
assertNoSymlinkAncestors2(root);
|
|
17639
|
+
const rel = relative5(root, target);
|
|
17553
17640
|
let current = root;
|
|
17554
17641
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17555
|
-
current =
|
|
17556
|
-
if (
|
|
17642
|
+
current = join18(current, segment);
|
|
17643
|
+
if (existsSync14(current) && lstatSync7(current).isSymbolicLink()) {
|
|
17557
17644
|
throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
|
|
17558
17645
|
}
|
|
17559
17646
|
}
|
|
17560
17647
|
}
|
|
17561
|
-
function
|
|
17562
|
-
const normalized =
|
|
17563
|
-
const parsed =
|
|
17648
|
+
function assertNoSymlinkAncestors2(path) {
|
|
17649
|
+
const normalized = resolve12(path);
|
|
17650
|
+
const parsed = parse4(normalized);
|
|
17564
17651
|
let current = parsed.root;
|
|
17565
|
-
const rel =
|
|
17652
|
+
const rel = relative5(parsed.root, normalized);
|
|
17566
17653
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17567
|
-
current =
|
|
17568
|
-
if (!
|
|
17654
|
+
current = join18(current, segment);
|
|
17655
|
+
if (!existsSync14(current))
|
|
17569
17656
|
return;
|
|
17570
|
-
if (
|
|
17657
|
+
if (lstatSync7(current).isSymbolicLink()) {
|
|
17571
17658
|
throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
|
|
17572
17659
|
}
|
|
17573
17660
|
}
|
|
17574
17661
|
}
|
|
17575
|
-
function
|
|
17576
|
-
return
|
|
17662
|
+
function sha2569(content) {
|
|
17663
|
+
return createHash9("sha256").update(content).digest("hex");
|
|
17577
17664
|
}
|
|
17578
17665
|
// src/lib/session-refresh.ts
|
|
17579
17666
|
init_zod();
|
|
17580
|
-
import { createHash as
|
|
17581
|
-
import { closeSync as closeSync4, constants as
|
|
17582
|
-
import { dirname as dirname11, join as
|
|
17667
|
+
import { createHash as createHash10 } from "crypto";
|
|
17668
|
+
import { closeSync as closeSync4, constants as constants3, fstatSync as fstatSync4, lstatSync as lstatSync8, openSync as openSync4, readFileSync as readFileSync14 } from "fs";
|
|
17669
|
+
import { basename as basename6, dirname as dirname11, isAbsolute as isAbsolute9, join as join19, parse as parse5, resolve as resolve13 } from "path";
|
|
17583
17670
|
|
|
17584
17671
|
// src/lib/global-source-coverage.ts
|
|
17585
17672
|
var GLOBAL_SOURCE_SLUG_PREFIX = "global-";
|
|
@@ -17612,7 +17699,6 @@ function computeGlobalSourceCoverage(registryConfigs, configuredSlugs) {
|
|
|
17612
17699
|
init_instruction_graph();
|
|
17613
17700
|
init_project_context();
|
|
17614
17701
|
init_session_render();
|
|
17615
|
-
import { basename as basename6 } from "path";
|
|
17616
17702
|
var nonempty = exports_external.string().min(1).max(4096);
|
|
17617
17703
|
var selectorSchema = exports_external.object({
|
|
17618
17704
|
schema: exports_external.literal("hasna.instructions.hosted-profile-selector/v1"),
|
|
@@ -17663,10 +17749,10 @@ function assertHostedProfileBindings(profileId, configs, bindings) {
|
|
|
17663
17749
|
}
|
|
17664
17750
|
}
|
|
17665
17751
|
function readManagedManifest(targetHome) {
|
|
17666
|
-
const path =
|
|
17752
|
+
const path = join19(targetHome, SESSION_RENDER_MANIFEST_RELATIVE_PATH);
|
|
17667
17753
|
let ancestor = dirname11(path);
|
|
17668
17754
|
while (true) {
|
|
17669
|
-
const stat =
|
|
17755
|
+
const stat = lstatSync8(ancestor);
|
|
17670
17756
|
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
17671
17757
|
throw new Error("SESSION_REFRESH_PATH_INVALID: managed target uses a symlink or non-directory ancestor.");
|
|
17672
17758
|
const parent = dirname11(ancestor);
|
|
@@ -17674,13 +17760,13 @@ function readManagedManifest(targetHome) {
|
|
|
17674
17760
|
break;
|
|
17675
17761
|
ancestor = parent;
|
|
17676
17762
|
}
|
|
17677
|
-
const fd = openSync4(path,
|
|
17763
|
+
const fd = openSync4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
17678
17764
|
let raw;
|
|
17679
17765
|
try {
|
|
17680
17766
|
const stat = fstatSync4(fd);
|
|
17681
17767
|
if (!stat.isFile() || stat.size > SESSION_MANAGED_INPUT_MAX_BYTES)
|
|
17682
17768
|
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: expected a bounded regular manifest file.");
|
|
17683
|
-
raw =
|
|
17769
|
+
raw = readFileSync14(fd);
|
|
17684
17770
|
if (raw.byteLength > SESSION_MANAGED_INPUT_MAX_BYTES)
|
|
17685
17771
|
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: manifest grew beyond the read limit.");
|
|
17686
17772
|
} finally {
|
|
@@ -17690,15 +17776,28 @@ function readManagedManifest(targetHome) {
|
|
|
17690
17776
|
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
17777
|
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: target is not an owned hosted profile render; apply a hosted --compile-profile first.");
|
|
17692
17778
|
}
|
|
17779
|
+
const recordedProjectRoot = manifest.targetOwner.projectRoot;
|
|
17780
|
+
if (recordedProjectRoot !== null && recordedProjectRoot !== undefined) {
|
|
17781
|
+
if (typeof recordedProjectRoot !== "string" || !isAbsolute9(recordedProjectRoot)) {
|
|
17782
|
+
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: recorded project root must be an absolute path.");
|
|
17783
|
+
}
|
|
17784
|
+
const normalizedProjectRoot = resolve13(recordedProjectRoot);
|
|
17785
|
+
if (normalizedProjectRoot === parse5(normalizedProjectRoot).root || manifest.targetKind === "project-root" && normalizedProjectRoot !== targetHome || manifest.targetKind === "session-home" && manifest.tool !== "opencode") {
|
|
17786
|
+
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: recorded project root does not match the managed target.");
|
|
17787
|
+
}
|
|
17788
|
+
manifest.targetOwner.projectRoot = normalizedProjectRoot;
|
|
17789
|
+
} else if (manifest.targetKind === "project-root") {
|
|
17790
|
+
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: project-scoped render is missing its recorded project root.");
|
|
17791
|
+
}
|
|
17693
17792
|
manifest.refreshSelector = normalizeSessionHostedProfileSelector(manifest.refreshSelector);
|
|
17694
|
-
return { manifest, sha256:
|
|
17793
|
+
return { manifest, sha256: createHash10("sha256").update(raw).digest("hex") };
|
|
17695
17794
|
}
|
|
17696
17795
|
async function refreshSessionRender(input) {
|
|
17697
17796
|
const { store } = input;
|
|
17698
17797
|
if (store.mode !== "api" || !store.v1BaseUrl)
|
|
17699
17798
|
throw new Error("SESSION_REFRESH_HOSTED_REQUIRED: refresh requires the configured hosted Instructions API.");
|
|
17700
|
-
const targetHome =
|
|
17701
|
-
if (targetHome ===
|
|
17799
|
+
const targetHome = resolve13(input.targetHome);
|
|
17800
|
+
if (targetHome === parse5(targetHome).root)
|
|
17702
17801
|
throw new Error("SESSION_REFRESH_PATH_INVALID: filesystem root is not a managed target.");
|
|
17703
17802
|
const previous = readManagedManifest(targetHome);
|
|
17704
17803
|
const selector = previous.manifest.refreshSelector;
|
|
@@ -17732,7 +17831,7 @@ async function refreshSessionRender(input) {
|
|
|
17732
17831
|
profile_id: profile.id,
|
|
17733
17832
|
provider_version: selector.providerVersion,
|
|
17734
17833
|
targetHome,
|
|
17735
|
-
...previous.manifest.targetKind === "project-root" ? { projectRoot: targetHome } : {},
|
|
17834
|
+
...previous.manifest.targetKind === "project-root" ? { projectRoot: targetHome } : previous.manifest.targetOwner.projectRoot ? { projectRoot: previous.manifest.targetOwner.projectRoot } : {},
|
|
17736
17835
|
sessionId: previous.manifest.sessionId ?? undefined,
|
|
17737
17836
|
refreshSelector: selector,
|
|
17738
17837
|
codewithNativeImports: selector.codewithNativeImports,
|
|
@@ -17741,7 +17840,7 @@ async function refreshSessionRender(input) {
|
|
|
17741
17840
|
bindings,
|
|
17742
17841
|
asset_configs: assetConfigs,
|
|
17743
17842
|
asset_bindings: assetBindings,
|
|
17744
|
-
asset_plan_mode:
|
|
17843
|
+
asset_plan_mode: "apply",
|
|
17745
17844
|
asset_scope: selector.assetScope,
|
|
17746
17845
|
asset_surface: selector.assetSurface,
|
|
17747
17846
|
extra_sources: station ? [station] : undefined,
|
|
@@ -18117,8 +18216,8 @@ init_codewith_shared_todos_storage_standard();
|
|
|
18117
18216
|
|
|
18118
18217
|
// src/lib/sync.ts
|
|
18119
18218
|
init_config_store();
|
|
18120
|
-
import { existsSync as
|
|
18121
|
-
import { basename as basename7, extname as extname3, join as
|
|
18219
|
+
import { existsSync as existsSync16, readdirSync as readdirSync5, readFileSync as readFileSync16 } from "fs";
|
|
18220
|
+
import { basename as basename7, extname as extname3, join as join21 } from "path";
|
|
18122
18221
|
init_config_agents();
|
|
18123
18222
|
init_redact();
|
|
18124
18223
|
init_machine();
|
|
@@ -18126,8 +18225,8 @@ init_transforms();
|
|
|
18126
18225
|
|
|
18127
18226
|
// src/lib/sync-dir.ts
|
|
18128
18227
|
init_config_store();
|
|
18129
|
-
import { existsSync as
|
|
18130
|
-
import { join as
|
|
18228
|
+
import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync15, statSync as statSync7 } from "fs";
|
|
18229
|
+
import { join as join20, relative as relative6 } from "path";
|
|
18131
18230
|
init_redact();
|
|
18132
18231
|
var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
|
|
18133
18232
|
function shouldSkip(p) {
|
|
@@ -18136,9 +18235,9 @@ function shouldSkip(p) {
|
|
|
18136
18235
|
async function syncFromDir(dir, opts = {}) {
|
|
18137
18236
|
const store = opts.store ?? resolveConfigStore();
|
|
18138
18237
|
const absDir = expandPath(dir);
|
|
18139
|
-
if (!
|
|
18238
|
+
if (!existsSync15(absDir))
|
|
18140
18239
|
return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
|
|
18141
|
-
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync4(absDir).map((f) =>
|
|
18240
|
+
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync4(absDir).map((f) => join20(absDir, f)).filter((f) => statSync7(f).isFile());
|
|
18142
18241
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
18143
18242
|
const allConfigs = await store.listConfigs();
|
|
18144
18243
|
for (const file of files) {
|
|
@@ -18147,7 +18246,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
18147
18246
|
continue;
|
|
18148
18247
|
}
|
|
18149
18248
|
try {
|
|
18150
|
-
const content =
|
|
18249
|
+
const content = readFileSync15(file, "utf-8");
|
|
18151
18250
|
if (content.length > 500000) {
|
|
18152
18251
|
result.skipped.push(file + " (too large)");
|
|
18153
18252
|
continue;
|
|
@@ -18157,7 +18256,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
18157
18256
|
const existing = allConfigs.find((c) => c.target_path === targetPath);
|
|
18158
18257
|
if (!existing) {
|
|
18159
18258
|
if (!opts.dryRun)
|
|
18160
|
-
await store.createConfig({ name:
|
|
18259
|
+
await store.createConfig({ name: relative6(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content: redacted.content });
|
|
18161
18260
|
result.added++;
|
|
18162
18261
|
} else if (existing.content !== redacted.content) {
|
|
18163
18262
|
if (!opts.dryRun)
|
|
@@ -18197,7 +18296,7 @@ async function syncToDir(dir, opts = {}) {
|
|
|
18197
18296
|
}
|
|
18198
18297
|
function walkDir(dir, files = []) {
|
|
18199
18298
|
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
18200
|
-
const full =
|
|
18299
|
+
const full = join20(dir, entry.name);
|
|
18201
18300
|
if (shouldSkip(full))
|
|
18202
18301
|
continue;
|
|
18203
18302
|
if (entry.isDirectory())
|
|
@@ -18258,7 +18357,7 @@ function isGeneratedOutputTarget2(config, owners) {
|
|
|
18258
18357
|
return !!ownerIds && !ownerIds.has(config.id);
|
|
18259
18358
|
}
|
|
18260
18359
|
function hasClaudePromptSource() {
|
|
18261
|
-
return
|
|
18360
|
+
return existsSync16(expandPath("~/.claude/CLAUDE.md"));
|
|
18262
18361
|
}
|
|
18263
18362
|
function hasClaudeRuleSourceForCursorTarget(targetPath) {
|
|
18264
18363
|
const absoluteTargetPath = expandPath(targetPath);
|
|
@@ -18266,7 +18365,7 @@ function hasClaudeRuleSourceForCursorTarget(targetPath) {
|
|
|
18266
18365
|
if (!absoluteTargetPath.startsWith(`${absolutePrefix}/`) || !absoluteTargetPath.endsWith(".mdc"))
|
|
18267
18366
|
return false;
|
|
18268
18367
|
const stem = basename7(absoluteTargetPath, ".mdc");
|
|
18269
|
-
return
|
|
18368
|
+
return existsSync16(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync16(expandPath(`~/.claude/rules/${stem}.mdc`));
|
|
18270
18369
|
}
|
|
18271
18370
|
function isKnownGeneratedTargetPath(targetPath) {
|
|
18272
18371
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
@@ -18331,11 +18430,11 @@ async function syncProject(opts) {
|
|
|
18331
18430
|
const allConfigs = await store.listConfigs();
|
|
18332
18431
|
const machine = detectMachineContext();
|
|
18333
18432
|
for (const pf of PROJECT_CONFIG_FILES) {
|
|
18334
|
-
const abs =
|
|
18335
|
-
if (!
|
|
18433
|
+
const abs = join21(absDir, pf.file);
|
|
18434
|
+
if (!existsSync16(abs))
|
|
18336
18435
|
continue;
|
|
18337
18436
|
try {
|
|
18338
|
-
const rawContent =
|
|
18437
|
+
const rawContent = readFileSync16(abs, "utf-8");
|
|
18339
18438
|
if (rawContent.length > 500000) {
|
|
18340
18439
|
result.skipped.push(pf.file);
|
|
18341
18440
|
continue;
|
|
@@ -18364,20 +18463,20 @@ async function syncProject(opts) {
|
|
|
18364
18463
|
}
|
|
18365
18464
|
}
|
|
18366
18465
|
for (const ruleDir of [
|
|
18367
|
-
{ dir:
|
|
18368
|
-
{ dir:
|
|
18369
|
-
{ dir:
|
|
18370
|
-
{ dir:
|
|
18371
|
-
{ dir:
|
|
18372
|
-
{ dir:
|
|
18373
|
-
{ dir:
|
|
18466
|
+
{ dir: join21(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
|
|
18467
|
+
{ dir: join21(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
|
|
18468
|
+
{ dir: join21(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
|
|
18469
|
+
{ dir: join21(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
|
|
18470
|
+
{ dir: join21(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
|
|
18471
|
+
{ dir: join21(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
|
|
18472
|
+
{ dir: join21(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
|
|
18374
18473
|
]) {
|
|
18375
|
-
if (!
|
|
18474
|
+
if (!existsSync16(ruleDir.dir))
|
|
18376
18475
|
continue;
|
|
18377
18476
|
const mdFiles = readdirSync5(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
18378
18477
|
for (const f of mdFiles) {
|
|
18379
|
-
const abs =
|
|
18380
|
-
const raw =
|
|
18478
|
+
const abs = join21(ruleDir.dir, f);
|
|
18479
|
+
const raw = readFileSync16(abs, "utf-8");
|
|
18381
18480
|
const redacted = redactContent(raw, "markdown");
|
|
18382
18481
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
18383
18482
|
const content = machineAware.content;
|
|
@@ -18416,20 +18515,20 @@ async function syncKnown(opts = {}) {
|
|
|
18416
18515
|
for (const known of targets) {
|
|
18417
18516
|
if (known.rulesDir) {
|
|
18418
18517
|
const absDir = expandPath(known.rulesDir);
|
|
18419
|
-
if (!
|
|
18518
|
+
if (!existsSync16(absDir)) {
|
|
18420
18519
|
result.skipped.push(known.rulesDir);
|
|
18421
18520
|
continue;
|
|
18422
18521
|
}
|
|
18423
18522
|
const extensions = known.rulesExtensions ?? [".md", ".mdc"];
|
|
18424
18523
|
const ruleFiles = readdirSync5(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
|
|
18425
18524
|
for (const f of ruleFiles) {
|
|
18426
|
-
const abs2 =
|
|
18525
|
+
const abs2 = join21(absDir, f);
|
|
18427
18526
|
const targetPath = abs2.replace(home, "~");
|
|
18428
18527
|
if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
|
|
18429
18528
|
result.skipped.push(`${targetPath} (generated output)`);
|
|
18430
18529
|
continue;
|
|
18431
18530
|
}
|
|
18432
|
-
const raw =
|
|
18531
|
+
const raw = readFileSync16(abs2, "utf-8");
|
|
18433
18532
|
const redacted = redactContent(raw, "markdown");
|
|
18434
18533
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
18435
18534
|
const content = machineAware.content;
|
|
@@ -18457,12 +18556,12 @@ async function syncKnown(opts = {}) {
|
|
|
18457
18556
|
continue;
|
|
18458
18557
|
}
|
|
18459
18558
|
const abs = expandPath(known.path);
|
|
18460
|
-
if (!
|
|
18559
|
+
if (!existsSync16(abs)) {
|
|
18461
18560
|
result.skipped.push(known.path);
|
|
18462
18561
|
continue;
|
|
18463
18562
|
}
|
|
18464
18563
|
try {
|
|
18465
|
-
const rawContent = normalizeKnownConfigSource(known,
|
|
18564
|
+
const rawContent = normalizeKnownConfigSource(known, readFileSync16(abs, "utf-8"));
|
|
18466
18565
|
if (rawContent.length > 500000) {
|
|
18467
18566
|
result.skipped.push(known.path + " (too large)");
|
|
18468
18567
|
continue;
|
|
@@ -18565,9 +18664,9 @@ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
|
|
|
18565
18664
|
}
|
|
18566
18665
|
function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
|
|
18567
18666
|
const path = expandPath(targetPath);
|
|
18568
|
-
if (!
|
|
18667
|
+
if (!existsSync16(path))
|
|
18569
18668
|
return `(file not found on disk: ${path})`;
|
|
18570
|
-
const diskContent =
|
|
18669
|
+
const diskContent = readFileSync16(path, "utf-8");
|
|
18571
18670
|
if (diskContent === expectedContent)
|
|
18572
18671
|
return "(no diff \u2014 identical)";
|
|
18573
18672
|
const format = redactFormatForTarget(targetPath, storedFormat);
|
|
@@ -18727,9 +18826,9 @@ function detectFormat(filePath) {
|
|
|
18727
18826
|
// src/lib/export.ts
|
|
18728
18827
|
init_types();
|
|
18729
18828
|
init_config_store();
|
|
18730
|
-
import { createHash as
|
|
18731
|
-
import { existsSync as
|
|
18732
|
-
import { dirname as dirname12, join as
|
|
18829
|
+
import { createHash as createHash11 } from "crypto";
|
|
18830
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync7, mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
18831
|
+
import { dirname as dirname12, join as join22, resolve as resolve14 } from "path";
|
|
18733
18832
|
import { tmpdir } from "os";
|
|
18734
18833
|
var ARCHIVE_EXCLUSIONS = [
|
|
18735
18834
|
{
|
|
@@ -18751,8 +18850,8 @@ var ARCHIVE_EXCLUSIONS = [
|
|
|
18751
18850
|
function compareText(left, right) {
|
|
18752
18851
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
18753
18852
|
}
|
|
18754
|
-
function
|
|
18755
|
-
return
|
|
18853
|
+
function sha25610(value) {
|
|
18854
|
+
return createHash11("sha256").update(value).digest("hex");
|
|
18756
18855
|
}
|
|
18757
18856
|
function canonicalize(value) {
|
|
18758
18857
|
if (Array.isArray(value))
|
|
@@ -18876,19 +18975,19 @@ function computeIntegrity(domain, options) {
|
|
|
18876
18975
|
machines: collections.machines.length
|
|
18877
18976
|
};
|
|
18878
18977
|
const hashes = {
|
|
18879
|
-
configs:
|
|
18880
|
-
config_snapshots:
|
|
18881
|
-
profiles:
|
|
18882
|
-
profile_config_bindings:
|
|
18883
|
-
profile_asset_bindings:
|
|
18884
|
-
machines:
|
|
18978
|
+
configs: sha25610(canonicalDomainJson(collections.configs)),
|
|
18979
|
+
config_snapshots: sha25610(canonicalDomainJson(collections.config_snapshots)),
|
|
18980
|
+
profiles: sha25610(canonicalDomainJson(collections.profiles)),
|
|
18981
|
+
profile_config_bindings: sha25610(canonicalDomainJson(collections.profile_config_bindings)),
|
|
18982
|
+
profile_asset_bindings: sha25610(canonicalDomainJson(collections.profile_asset_bindings)),
|
|
18983
|
+
machines: sha25610(canonicalDomainJson(collections.machines))
|
|
18885
18984
|
};
|
|
18886
18985
|
return {
|
|
18887
18986
|
algorithm: "sha256",
|
|
18888
18987
|
canonicalization: options.canonicalization,
|
|
18889
18988
|
counts,
|
|
18890
18989
|
hashes,
|
|
18891
|
-
domain_sha256:
|
|
18990
|
+
domain_sha256: sha25610(canonicalDomainJson({ counts, hashes }))
|
|
18892
18991
|
};
|
|
18893
18992
|
}
|
|
18894
18993
|
function computeDomainIntegrity(domain) {
|
|
@@ -19059,19 +19158,19 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
19059
19158
|
exported_at: new Date().toISOString(),
|
|
19060
19159
|
payload: {
|
|
19061
19160
|
path: "domain.json",
|
|
19062
|
-
sha256:
|
|
19161
|
+
sha256: sha25610(payload),
|
|
19063
19162
|
size_bytes: Buffer.byteLength(payload)
|
|
19064
19163
|
},
|
|
19065
19164
|
integrity,
|
|
19066
19165
|
exclusions: ARCHIVE_EXCLUSIONS
|
|
19067
19166
|
};
|
|
19068
|
-
const absOutput =
|
|
19069
|
-
|
|
19070
|
-
const stagingDir = mkdtempSync(
|
|
19167
|
+
const absOutput = resolve14(outputPath);
|
|
19168
|
+
mkdirSync7(dirname12(absOutput), { recursive: true });
|
|
19169
|
+
const stagingDir = mkdtempSync(join22(tmpdir(), "instructions-domain-export-"));
|
|
19071
19170
|
try {
|
|
19072
|
-
|
|
19171
|
+
writeFileSync5(join22(stagingDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
19073
19172
|
`, "utf-8");
|
|
19074
|
-
|
|
19173
|
+
writeFileSync5(join22(stagingDir, "domain.json"), payload, "utf-8");
|
|
19075
19174
|
const proc = Bun.spawn(["tar", "czf", absOutput, "-C", stagingDir, "manifest.json", "domain.json"], {
|
|
19076
19175
|
stdout: "pipe",
|
|
19077
19176
|
stderr: "pipe"
|
|
@@ -19083,15 +19182,15 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
19083
19182
|
}
|
|
19084
19183
|
return { path: absOutput, count: domain.configs.length, counts: integrity.counts, integrity };
|
|
19085
19184
|
} finally {
|
|
19086
|
-
if (
|
|
19087
|
-
|
|
19185
|
+
if (existsSync17(stagingDir))
|
|
19186
|
+
rmSync3(stagingDir, { recursive: true, force: true });
|
|
19088
19187
|
}
|
|
19089
19188
|
}
|
|
19090
19189
|
// src/lib/import.ts
|
|
19091
19190
|
init_types();
|
|
19092
19191
|
init_config_store();
|
|
19093
|
-
import { createHash as
|
|
19094
|
-
import { resolve as
|
|
19192
|
+
import { createHash as createHash12 } from "crypto";
|
|
19193
|
+
import { resolve as resolve15 } from "path";
|
|
19095
19194
|
function compareText2(left, right) {
|
|
19096
19195
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
19097
19196
|
}
|
|
@@ -19115,8 +19214,8 @@ function emptyResult() {
|
|
|
19115
19214
|
integrity: null
|
|
19116
19215
|
};
|
|
19117
19216
|
}
|
|
19118
|
-
function
|
|
19119
|
-
return
|
|
19217
|
+
function sha25611(value) {
|
|
19218
|
+
return createHash12("sha256").update(value).digest("hex");
|
|
19120
19219
|
}
|
|
19121
19220
|
function normalizeMemberName(name) {
|
|
19122
19221
|
let normalized = name;
|
|
@@ -19186,7 +19285,7 @@ async function readV2(reader, manifest) {
|
|
|
19186
19285
|
throw new Error("Invalid v2 archive manifest");
|
|
19187
19286
|
}
|
|
19188
19287
|
const payload = await reader.read("domain.json");
|
|
19189
|
-
if (Buffer.byteLength(payload) !== manifest.payload.size_bytes ||
|
|
19288
|
+
if (Buffer.byteLength(payload) !== manifest.payload.size_bytes || sha25611(payload) !== manifest.payload.sha256) {
|
|
19190
19289
|
throw new Error("Archive integrity failure: domain payload hash or size does not match the manifest");
|
|
19191
19290
|
}
|
|
19192
19291
|
const domain = validateInstructionsDomainArchive(JSON.parse(payload));
|
|
@@ -19379,7 +19478,7 @@ async function importV1(reader, manifest, store, conflict) {
|
|
|
19379
19478
|
async function importConfigs(bundlePath, opts = {}) {
|
|
19380
19479
|
const store = opts.store ?? resolveConfigStore();
|
|
19381
19480
|
const conflict = opts.conflict ?? "skip";
|
|
19382
|
-
const reader = await openArchive(
|
|
19481
|
+
const reader = await openArchive(resolve15(bundlePath));
|
|
19383
19482
|
const manifestText = await reader.read("manifest.json");
|
|
19384
19483
|
let manifest;
|
|
19385
19484
|
try {
|
|
@@ -19407,9 +19506,9 @@ init_redact();
|
|
|
19407
19506
|
|
|
19408
19507
|
// src/lib/package-manager-guard.ts
|
|
19409
19508
|
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
|
|
19509
|
+
import { existsSync as existsSync18, lstatSync as lstatSync9, readdirSync as readdirSync6, readFileSync as readFileSync17 } from "fs";
|
|
19510
|
+
import { homedir as homedir11 } from "os";
|
|
19511
|
+
import { basename as basename8, dirname as dirname13, isAbsolute as isAbsolute10, join as join23, relative as relative7, resolve as resolve16 } from "path";
|
|
19413
19512
|
var SKIP_DIRS = new Set([
|
|
19414
19513
|
".git",
|
|
19415
19514
|
"node_modules",
|
|
@@ -19446,14 +19545,14 @@ var TOKEN_VALUE_PATTERNS = [
|
|
|
19446
19545
|
{ re: /xoxb-[0-9]+-[A-Za-z0-9-]+/, rule: "literal-slack-token", detail: "literal Slack token-like value" }
|
|
19447
19546
|
];
|
|
19448
19547
|
function scanPackageManagerSecrets(options = {}) {
|
|
19449
|
-
const cwd = options.cwd ?
|
|
19450
|
-
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) =>
|
|
19548
|
+
const cwd = options.cwd ? resolve16(options.cwd) : process.cwd();
|
|
19549
|
+
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve16(cwd, root));
|
|
19451
19550
|
const findings = [];
|
|
19452
19551
|
let scannedFiles = 0;
|
|
19453
19552
|
for (const root of roots) {
|
|
19454
|
-
if (!
|
|
19553
|
+
if (!existsSync18(root))
|
|
19455
19554
|
continue;
|
|
19456
|
-
const stat =
|
|
19555
|
+
const stat = lstatSync9(root);
|
|
19457
19556
|
if (stat.isFile()) {
|
|
19458
19557
|
if (!shouldScanRepoFile(root))
|
|
19459
19558
|
continue;
|
|
@@ -19468,7 +19567,7 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
19468
19567
|
continue;
|
|
19469
19568
|
const tracked = trackedFiles(root);
|
|
19470
19569
|
for (const file of collectRepoFiles(root)) {
|
|
19471
|
-
const rel = toPosix(
|
|
19570
|
+
const rel = toPosix(relative7(root, file));
|
|
19472
19571
|
const isTracked = tracked.has(rel);
|
|
19473
19572
|
const text = readTextFile(file);
|
|
19474
19573
|
if (text === null)
|
|
@@ -19478,10 +19577,10 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
19478
19577
|
}
|
|
19479
19578
|
}
|
|
19480
19579
|
if (options.includeHome) {
|
|
19481
|
-
const home =
|
|
19580
|
+
const home = homedir11();
|
|
19482
19581
|
for (const name of HOME_FILES) {
|
|
19483
|
-
const file =
|
|
19484
|
-
if (!
|
|
19582
|
+
const file = join23(home, name);
|
|
19583
|
+
if (!existsSync18(file))
|
|
19485
19584
|
continue;
|
|
19486
19585
|
const text = readTextFile(file);
|
|
19487
19586
|
if (text === null)
|
|
@@ -19505,12 +19604,12 @@ function collectRepoFiles(root) {
|
|
|
19505
19604
|
if (entry.isDirectory()) {
|
|
19506
19605
|
if (SKIP_DIRS.has(entry.name))
|
|
19507
19606
|
continue;
|
|
19508
|
-
visit(
|
|
19607
|
+
visit(join23(dir, entry.name));
|
|
19509
19608
|
continue;
|
|
19510
19609
|
}
|
|
19511
19610
|
if (!entry.isFile())
|
|
19512
19611
|
continue;
|
|
19513
|
-
const file =
|
|
19612
|
+
const file = join23(dir, entry.name);
|
|
19514
19613
|
if (shouldScanRepoFile(file))
|
|
19515
19614
|
out.push(file);
|
|
19516
19615
|
}
|
|
@@ -19545,10 +19644,10 @@ function isNpmrcName(name) {
|
|
|
19545
19644
|
}
|
|
19546
19645
|
function readTextFile(file) {
|
|
19547
19646
|
try {
|
|
19548
|
-
const stat =
|
|
19647
|
+
const stat = lstatSync9(file);
|
|
19549
19648
|
if (!stat.isFile() || stat.size > 5000000)
|
|
19550
19649
|
return null;
|
|
19551
|
-
const buf =
|
|
19650
|
+
const buf = readFileSync17(file);
|
|
19552
19651
|
if (buf.includes(0))
|
|
19553
19652
|
return null;
|
|
19554
19653
|
return buf.toString("utf-8");
|
|
@@ -19752,7 +19851,7 @@ function isTrackedFile(file) {
|
|
|
19752
19851
|
encoding: "utf-8",
|
|
19753
19852
|
stdio: ["ignore", "pipe", "ignore"]
|
|
19754
19853
|
}).trim();
|
|
19755
|
-
const rel = toPosix(
|
|
19854
|
+
const rel = toPosix(relative7(repoRoot, file));
|
|
19756
19855
|
execFileSync2("git", ["-C", repoRoot, "ls-files", "--error-unmatch", "--", rel], {
|
|
19757
19856
|
stdio: ["ignore", "ignore", "ignore"]
|
|
19758
19857
|
});
|
|
@@ -19778,13 +19877,13 @@ function stripInlineComment(value) {
|
|
|
19778
19877
|
return value.replace(/\s[#;].*$/, "").trim();
|
|
19779
19878
|
}
|
|
19780
19879
|
function displayPath(file, root) {
|
|
19781
|
-
const home =
|
|
19880
|
+
const home = homedir11();
|
|
19782
19881
|
if (root === home && (file === home || file.startsWith(home + "/")))
|
|
19783
|
-
return "~/" + toPosix(
|
|
19784
|
-
if (
|
|
19785
|
-
return toPosix(
|
|
19882
|
+
return "~/" + toPosix(relative7(home, file));
|
|
19883
|
+
if (isAbsolute10(root) && file.startsWith(root + "/"))
|
|
19884
|
+
return toPosix(relative7(root, file));
|
|
19786
19885
|
if (file === home || file.startsWith(home + "/"))
|
|
19787
|
-
return "~/" + toPosix(
|
|
19886
|
+
return "~/" + toPosix(relative7(home, file));
|
|
19788
19887
|
return file;
|
|
19789
19888
|
}
|
|
19790
19889
|
function toPosix(path) {
|
|
@@ -19824,6 +19923,7 @@ export {
|
|
|
19824
19923
|
renderTemplatePreview,
|
|
19825
19924
|
renderTemplate,
|
|
19826
19925
|
renderProviderFragment,
|
|
19926
|
+
renderNativeAgentContent,
|
|
19827
19927
|
renderMachineAwareContentPreview,
|
|
19828
19928
|
renderMachineAwareContent,
|
|
19829
19929
|
refreshStationProfile,
|
|
@@ -19872,6 +19972,7 @@ export {
|
|
|
19872
19972
|
ensureGlobalAgentRulesStandardConfig,
|
|
19873
19973
|
ensureDangerousOperationGuardStandardConfig,
|
|
19874
19974
|
ensureCodewithSharedTodosStorageStandardConfig,
|
|
19975
|
+
discoverHarnesses,
|
|
19875
19976
|
diffConfig,
|
|
19876
19977
|
detectMachineContext,
|
|
19877
19978
|
detectFormat,
|
|
@@ -19957,8 +20058,10 @@ export {
|
|
|
19957
20058
|
INSTRUCTIONS_LOCAL_OPT_IN_ENV,
|
|
19958
20059
|
INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA,
|
|
19959
20060
|
INBOX_CONVERSATIONS_MINIMUM_VERSION,
|
|
20061
|
+
HARNESS_DISCOVERY_SCHEMA,
|
|
19960
20062
|
GLOBAL_AGENT_RULES_STANDARD_SLUG,
|
|
19961
20063
|
GLOBAL_AGENT_RULES_STANDARD_CONTENT,
|
|
20064
|
+
DISCOVERABLE_HARNESSES,
|
|
19962
20065
|
DANGEROUS_OPERATION_GUARD_STANDARD_SLUG,
|
|
19963
20066
|
DANGEROUS_OPERATION_GUARD_STANDARD_CONTENT,
|
|
19964
20067
|
ConfigVersionConflictError,
|