@hasna/instructions 0.7.1 → 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 +47 -7
- package/dist/chunks/{apply-c3sd4agz.js → apply-1gyr8r0x.js} +3 -3
- package/dist/chunks/{apply-pnn7a3y7.js → apply-fyfzqc7s.js} +3 -3
- package/dist/chunks/{index-3djthcm6.js → index-27az1qb1.js} +28 -1
- package/dist/chunks/{index-r7nfef2g.js → index-6sbmg1vc.js} +3 -3
- package/dist/chunks/{index-pdpg8a44.js → index-axs3yftz.js} +2 -2
- package/dist/chunks/{index-aj66f6xr.js → index-dwagz53p.js} +92 -8
- package/dist/chunks/{index-6jncnnw7.js → index-enh4yv85.js} +274 -31
- package/dist/chunks/{index-ypcef3a2.js → index-n9arv47w.js} +2 -2
- package/dist/chunks/{index-n3fjspr0.js → index-preyvj8n.js} +28 -1
- package/dist/chunks/{index-jg8ptcwh.js → index-z27xp7fh.js} +2 -2
- package/dist/chunks/{local-0a9gja58.js → local-5dsm856v.js} +15 -4
- package/dist/chunks/{local-969ye538.js → local-yxsbs8x3.js} +15 -4
- package/dist/chunks/{sync-xtkbm5br.js → sync-6r1v8hd3.js} +4 -4
- package/dist/chunks/{sync-q5hvwq6e.js → sync-k3pab4s6.js} +4 -4
- package/dist/chunks/{template-wgnzdn7h.js → template-e4qrxs8p.js} +1 -1
- package/dist/chunks/{template-fsfxpe4p.js → template-nhn124zd.js} +1 -1
- package/dist/cli/index.js +1018 -584
- package/dist/data/config-store.d.ts +12 -4
- package/dist/data/config-store.d.ts.map +1 -1
- package/dist/db/configs.d.ts.map +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1264 -601
- package/dist/lib/asset-plan.d.ts +4 -1
- package/dist/lib/asset-plan.d.ts.map +1 -1
- package/dist/lib/codewith-shared-todos-storage-standard.d.ts.map +1 -1
- package/dist/lib/dangerous-operation-guard-standard.d.ts.map +1 -1
- package/dist/lib/global-agent-rules-standard.d.ts.map +1 -1
- package/dist/lib/global-source-coverage.d.ts +12 -7
- package/dist/lib/global-source-coverage.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/instruction-graph.d.ts +1 -1
- package/dist/lib/instruction-graph.d.ts.map +1 -1
- package/dist/lib/instruction-source-policy.d.ts +6 -0
- package/dist/lib/instruction-source-policy.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/platform-profiles.d.ts.map +1 -1
- package/dist/lib/project-dashboard-standard.d.ts.map +1 -1
- package/dist/lib/session-apply.d.ts +35 -0
- package/dist/lib/session-apply.d.ts.map +1 -1
- package/dist/lib/session-refresh.d.ts +30 -0
- package/dist/lib/session-refresh.d.ts.map +1 -0
- package/dist/lib/session-render.d.ts +41 -1
- package/dist/lib/session-render.d.ts.map +1 -1
- package/dist/lib/transforms.d.ts +1 -0
- package/dist/lib/transforms.d.ts.map +1 -1
- package/dist/mcp/index.js +13 -12
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +172 -0
- package/dist/sdk/v1-client.d.ts +7 -1
- package/dist/sdk/v1-client.d.ts.map +1 -1
- package/dist/sdk/v1.generated.d.ts +26 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/index.js +199 -8
- package/dist/server/openapi.d.ts +150 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/cloud-store.d.ts.map +1 -1
- package/dist/types/index.d.ts +24 -2
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,7 +16,12 @@ var __export = (target, all) => {
|
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
|
|
18
18
|
// src/types/index.ts
|
|
19
|
-
|
|
19
|
+
function validateExpectedConfigVersion(value) {
|
|
20
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)) {
|
|
21
|
+
throw new InvalidExpectedVersionError;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
var CONFIG_KINDS, CONFIG_CATEGORIES, CONFIG_AGENTS, CONFIG_TRANSFORMS, CONFIG_FORMATS, PROFILE_CONFIG_BINDING_SCHEMA = "hasna.instructions.profile-config-binding/v1", INSTRUCTION_ACTIVATION_MODES, INSTRUCTION_FALLBACKS, PROFILE_ASSET_BINDING_SCHEMA = "hasna.instructions.profile-asset-binding/v1", ASSET_KINDS, ASSET_DESTINATION_STRATEGIES, ASSET_SCOPES, ASSET_UNINSTALL_POLICIES, ASSET_ROLLBACK_POLICIES, INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA = "hasna.instructions.domain-archive/v2", InvalidExpectedVersionError, ConfigVersionConflictError, ConfigNotFoundError, ProfileNotFoundError, ConfigApplyError, TemplateRenderError;
|
|
20
25
|
var init_types = __esm(() => {
|
|
21
26
|
CONFIG_KINDS = ["file", "reference"];
|
|
22
27
|
CONFIG_CATEGORIES = [
|
|
@@ -33,6 +38,7 @@ var init_types = __esm(() => {
|
|
|
33
38
|
"claude",
|
|
34
39
|
"codex",
|
|
35
40
|
"opencode",
|
|
41
|
+
"sumi",
|
|
36
42
|
"cursor",
|
|
37
43
|
"codewith",
|
|
38
44
|
"aicopilot",
|
|
@@ -53,6 +59,7 @@ var init_types = __esm(() => {
|
|
|
53
59
|
"claude-passthrough",
|
|
54
60
|
"codex-flat",
|
|
55
61
|
"opencode-flat",
|
|
62
|
+
"sumi-flat",
|
|
56
63
|
"cursor-mdc",
|
|
57
64
|
"skill-neutral"
|
|
58
65
|
];
|
|
@@ -71,6 +78,25 @@ var init_types = __esm(() => {
|
|
|
71
78
|
ASSET_SCOPES = ["global", "project", "session"];
|
|
72
79
|
ASSET_UNINSTALL_POLICIES = ["remove-managed", "retain"];
|
|
73
80
|
ASSET_ROLLBACK_POLICIES = ["snapshot", "installer-receipt", "none"];
|
|
81
|
+
InvalidExpectedVersionError = class InvalidExpectedVersionError extends Error {
|
|
82
|
+
code = "INVALID_EXPECTED_VERSION";
|
|
83
|
+
constructor() {
|
|
84
|
+
super("expected_version must be a positive safe integer");
|
|
85
|
+
this.name = "InvalidExpectedVersionError";
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
ConfigVersionConflictError = class ConfigVersionConflictError extends Error {
|
|
89
|
+
config_id;
|
|
90
|
+
expected_version;
|
|
91
|
+
code = "CONFIG_VERSION_CONFLICT";
|
|
92
|
+
status = 409;
|
|
93
|
+
constructor(config_id, expected_version) {
|
|
94
|
+
super(`Config version conflict: ${config_id} no longer has expected version ${expected_version}`);
|
|
95
|
+
this.config_id = config_id;
|
|
96
|
+
this.expected_version = expected_version;
|
|
97
|
+
this.name = "ConfigVersionConflictError";
|
|
98
|
+
}
|
|
99
|
+
};
|
|
74
100
|
ConfigNotFoundError = class ConfigNotFoundError extends Error {
|
|
75
101
|
constructor(id) {
|
|
76
102
|
super(`Config not found: ${id}`);
|
|
@@ -143,6 +169,31 @@ function boundedReadPage(items, total, options = {}) {
|
|
|
143
169
|
}
|
|
144
170
|
var init_bounded_read = () => {};
|
|
145
171
|
|
|
172
|
+
// src/lib/instruction-source-policy.ts
|
|
173
|
+
function isRetiredInstructionSource(config) {
|
|
174
|
+
return config.tags.some((tag) => tag === "retired-global-source" || tag === "retired-instruction-source");
|
|
175
|
+
}
|
|
176
|
+
function instructionSourceRejection(config) {
|
|
177
|
+
if (isRetiredInstructionSource(config) || config.tags.includes("config-only")) {
|
|
178
|
+
return "source is retired or explicitly configuration-only";
|
|
179
|
+
}
|
|
180
|
+
if (config.category !== "rules")
|
|
181
|
+
return `category ${config.category} is not an instruction source; classify reviewed prose as rules`;
|
|
182
|
+
if (config.format !== "markdown" && config.format !== "text")
|
|
183
|
+
return `format ${config.format} is not instruction prose`;
|
|
184
|
+
if (config.is_template)
|
|
185
|
+
return "unresolved templates must be rendered and reviewed before prompt injection";
|
|
186
|
+
if (!config.content.trim())
|
|
187
|
+
return "instruction source is empty";
|
|
188
|
+
if (config.content.includes("\x00"))
|
|
189
|
+
return "instruction source contains binary content";
|
|
190
|
+
const target = config.target_path?.split(/[\\/]/).pop() ?? "";
|
|
191
|
+
if (/\.(?:json|toml|yaml|yml|ini|sh|bash|zsh|fish|js|ts|py|rules)$/i.test(target)) {
|
|
192
|
+
return "target is an executable, provider policy, or settings file rather than a prompt";
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
146
197
|
// src/lib/global-agent-rules-standard.ts
|
|
147
198
|
import { createHash } from "crypto";
|
|
148
199
|
function parseAgentOperatingRulesVersion(content) {
|
|
@@ -246,9 +297,13 @@ async function ensureGlobalAgentRulesStandardConfig(store = resolveConfigStore()
|
|
|
246
297
|
let existing;
|
|
247
298
|
try {
|
|
248
299
|
existing = await store.getConfig(GLOBAL_AGENT_RULES_STANDARD_SLUG);
|
|
249
|
-
} catch {
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (!(error instanceof ConfigNotFoundError))
|
|
302
|
+
throw error;
|
|
250
303
|
return await store.createConfig(standardConfigInput(resolveAgentOperatingRulesPayload(null)));
|
|
251
304
|
}
|
|
305
|
+
if (isRetiredInstructionSource(existing))
|
|
306
|
+
return existing;
|
|
252
307
|
const payload = resolveAgentOperatingRulesPayload(existing.content);
|
|
253
308
|
const input = standardConfigInput(payload);
|
|
254
309
|
if (existing.content !== input.content || existing.description !== input.description || existing.category !== input.category || existing.agent !== input.agent || existing.format !== input.format || existing.kind !== input.kind || JSON.stringify(existing.tags) !== JSON.stringify(input.tags)) {
|
|
@@ -258,6 +313,7 @@ async function ensureGlobalAgentRulesStandardConfig(store = resolveConfigStore()
|
|
|
258
313
|
}
|
|
259
314
|
var GLOBAL_AGENT_RULES_STANDARD_SLUG = "global-agent-rules-standard", AGENT_OPERATING_RULES_SOURCE_SET_ID = "hasna-global-agent-rules-standard", AGENT_OPERATING_RULES_SOURCE_ID = "hasna-agent-operating-rules", AGENT_OPERATING_RULES_ROLE = "agent-operating-rules", AGENT_OPERATING_RULES_VERSION = "1.1.26", AGENT_OPERATING_RULES_SOURCE_SET_VERSION = "2026-08-11", AGENT_OPERATING_RULES_SEMANTIC_POLICY_KEY = "hasna:agent-operating-rules", AGENT_OPERATING_RULES_SENTINEL_PATTERN, AGENT_OPERATING_RULES_HEADING_PATTERN, AGENT_OPERATING_RULES_PAYLOAD_SHA256 = "486844a3d869e3dabc2f33fd66479d4e2fb0a0d1864a3d8a3dd05669eb3f1b77", AGENT_OPERATING_RULES_UPSTREAM_FILE_SHA256 = "b8e89cdb49e207e5b497ac51384d67022b94fe5645cc9273db60384eb2c2fb32", SCOPED_OPERATIONAL_CONTROL_POLICY_REFERENCE = "hasna-agent-operating-rules/scoped-operational-control/v1", AGENT_OPERATING_RULES_UPSTREAM, SCOPED_OPERATIONAL_CONTROL_POLICY, AGENT_OPERATING_RULES_PROVENANCE, AGENT_OPERATING_RULES_METADATA, GLOBAL_AGENT_RULES_STANDARD_CONTENT;
|
|
260
315
|
var init_global_agent_rules_standard = __esm(() => {
|
|
316
|
+
init_types();
|
|
261
317
|
init_config_store();
|
|
262
318
|
AGENT_OPERATING_RULES_SENTINEL_PATTERN = /<!--\s*hasna:agent-operating-rules\s+v=([0-9]+\.[0-9]+\.[0-9]+)\s*-->/i;
|
|
263
319
|
AGENT_OPERATING_RULES_HEADING_PATTERN = /^#\s*Hasna Agent Operating Rules\s+\u2014\s+v[0-9]+\.[0-9]+\.[0-9]+\s+\(([0-9]{4}-[0-9]{2}-[0-9]{2})\)/;
|
|
@@ -384,21 +440,23 @@ async function ensureCodewithSharedTodosStorageStandardConfig(store = resolveCon
|
|
|
384
440
|
let config;
|
|
385
441
|
try {
|
|
386
442
|
const existing = await store.getConfig(CODEWITH_SHARED_TODOS_STORAGE_STANDARD_SLUG);
|
|
443
|
+
if (isRetiredInstructionSource(existing))
|
|
444
|
+
return existing;
|
|
387
445
|
if (existing.content !== input.content || existing.description !== input.description || existing.category !== input.category || existing.agent !== input.agent || existing.format !== input.format || existing.kind !== input.kind || JSON.stringify(existing.tags) !== JSON.stringify(input.tags)) {
|
|
388
446
|
config = await store.updateConfig(existing.id, input);
|
|
389
447
|
} else {
|
|
390
448
|
config = existing;
|
|
391
449
|
}
|
|
392
|
-
} catch {
|
|
450
|
+
} catch (error) {
|
|
451
|
+
if (!(error instanceof ConfigNotFoundError))
|
|
452
|
+
throw error;
|
|
393
453
|
config = await store.createConfig(input);
|
|
394
454
|
}
|
|
395
|
-
for (const profile of await store.listProfiles()) {
|
|
396
|
-
await store.addConfigToProfile(profile.id, config.id);
|
|
397
|
-
}
|
|
398
455
|
return config;
|
|
399
456
|
}
|
|
400
457
|
var CODEWITH_SHARED_TODOS_STORAGE_STANDARD_SLUG = "codewith-shared-todos-storage-standard", CODEWITH_SHARED_TODOS_STORAGE_POLICY_REFERENCE = "codewith/shared-todos-storage/v1", CODEWITH_SHARED_TODOS_STORAGE_STANDARD_CONTENT;
|
|
401
458
|
var init_codewith_shared_todos_storage_standard = __esm(() => {
|
|
459
|
+
init_types();
|
|
402
460
|
init_config_store();
|
|
403
461
|
CODEWITH_SHARED_TODOS_STORAGE_STANDARD_CONTENT = `# Codewith Shared Todos Storage Standard
|
|
404
462
|
|
|
@@ -7292,7 +7350,7 @@ function sha2562(content) {
|
|
|
7292
7350
|
function isRecord(value) {
|
|
7293
7351
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
7294
7352
|
}
|
|
7295
|
-
var PROJECT_CONTEXT_SCHEMA = "hasna.projects.project_context_bundle.v1", PROJECT_CONTEXT_SCHEMA_V2 = "hasna.projects.project_context_bundle.v2", PROJECT_CONTEXT_SUPPORTED_SCHEMAS, projectContextSchema, PROJECT_CONTEXT_MAX_INPUT_BYTES, PROJECT_CONTEXT_MAX_RENDERED_BYTES, PROJECT_CONTEXT_MAX_APPROX_TOKENS = 1000, PROJECT_CONTEXT_MAX_COMMANDS = 6, PROJECT_CONTEXT_MAX_WARNINGS = 3, PROJECT_CONTEXT_FRAGMENT_PATH = ".hasna/instructions/project-context.md", PROJECT_CONTEXT_MANIFEST_PATH = ".hasna/projects/project-context-manifest.json", PROJECT_CONTEXT_CACHE_PATH = ".hasna/projects/project-context-cache.json", PROJECT_CONTEXT_LOCK_PATH = ".hasna/projects/project-context.lock", PROJECT_CONTEXT_SNAPSHOT_DIR = ".hasna/projects/project-context-snapshots", PROJECT_CONTEXT_CACHE_SCHEMA = "hasna.instructions.project-context-cache/v1", PROJECT_CONTEXT_MANAGED_COMMENT = "Managed by @hasna/configs project context", SESSION_COMPATIBILITY_MANIFEST_MAX_BYTES, FOREIGN_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_PATHS, SESSION_MANAGED_OUTPUT_WARN_BYTES, PROJECT_CONTEXT_LOCK_STALE_MS, LEGACY_CONFIGS_PACKAGE = "@hasna/configs", LEGACY_CONFIGS_COMPAT_VERSION = "0.2.45", LEGACY_CONFIGS_EXECUTABLE = "configs", PROJECT_KINDS, PROJECT_STATUSES, LINK_STATES, RESOLUTION_SOURCES, safeId, nullableId, producerSlug, producerName, safeOptionalDisplay, isoTimestamp, revisionSchema, hashSchema, absolutePath, commandArg, financeText, financeLegalEntity, financeProjectMetadataSchema, commandSchema, projectContextBundleSchema, storedManifestProjectContextSchema, storedManifestFileSchema, storedManifestObservationSchema, projectContextMetadataSnapshotSchema, projectContextCacheSchema, ProjectContextError, ProjectContextHashRace, anchoredFsOps, atomicExchange, atomicExchangeLibraries;
|
|
7353
|
+
var PROJECT_CONTEXT_SCHEMA = "hasna.projects.project_context_bundle.v1", PROJECT_CONTEXT_SCHEMA_V2 = "hasna.projects.project_context_bundle.v2", PROJECT_CONTEXT_SUPPORTED_SCHEMAS, projectContextSchema, PROJECT_CONTEXT_MAX_INPUT_BYTES, PROJECT_CONTEXT_MAX_RENDERED_BYTES, PROJECT_CONTEXT_MAX_APPROX_TOKENS = 1000, PROJECT_CONTEXT_MAX_COMMANDS = 6, PROJECT_CONTEXT_MAX_WARNINGS = 3, PROJECT_CONTEXT_FRAGMENT_PATH = ".hasna/instructions/project-context.md", PROJECT_CONTEXT_MANIFEST_PATH = ".hasna/projects/project-context-manifest.json", PROJECT_CONTEXT_CACHE_PATH = ".hasna/projects/project-context-cache.json", PROJECT_CONTEXT_LOCK_PATH = ".hasna/projects/project-context.lock", PROJECT_CONTEXT_SNAPSHOT_DIR = ".hasna/projects/project-context-snapshots", PROJECT_CONTEXT_CACHE_SCHEMA = "hasna.instructions.project-context-cache/v1", PROJECT_CONTEXT_MANAGED_COMMENT = "Managed by @hasna/configs project context", SESSION_COMPATIBILITY_MANIFEST_MAX_BYTES, FOREIGN_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_MAX_BYTES, SESSION_MANAGED_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_PATHS, SESSION_MANAGED_OUTPUT_WARN_BYTES, PROJECT_CONTEXT_LOCK_STALE_MS, LEGACY_CONFIGS_PACKAGE = "@hasna/configs", LEGACY_CONFIGS_COMPAT_VERSION = "0.2.45", LEGACY_CONFIGS_EXECUTABLE = "configs", PROJECT_KINDS, PROJECT_STATUSES, LINK_STATES, RESOLUTION_SOURCES, safeId, nullableId, producerSlug, producerName, safeOptionalDisplay, isoTimestamp, revisionSchema, hashSchema, absolutePath, commandArg, financeText, financeLegalEntity, financeProjectMetadataSchema, commandSchema, projectContextBundleSchema, storedManifestProjectContextSchema, storedManifestFileSchema, storedManifestObservationSchema, projectContextMetadataSnapshotSchema, projectContextCacheSchema, ProjectContextError, ProjectContextHashRace, anchoredFsOps, atomicExchange, atomicExchangeLibraries;
|
|
7296
7354
|
var init_project_context = __esm(() => {
|
|
7297
7355
|
init_zod();
|
|
7298
7356
|
init_redact();
|
|
@@ -7305,6 +7363,7 @@ var init_project_context = __esm(() => {
|
|
|
7305
7363
|
SESSION_COMPATIBILITY_MANIFEST_MAX_BYTES = 8 * 1024 * 1024;
|
|
7306
7364
|
FOREIGN_INPUT_MAX_BYTES = 256 * 1024;
|
|
7307
7365
|
SESSION_MANAGED_OUTPUT_MAX_BYTES = SESSION_COMPATIBILITY_MANIFEST_MAX_BYTES;
|
|
7366
|
+
SESSION_MANAGED_INPUT_MAX_BYTES = SESSION_MANAGED_OUTPUT_MAX_BYTES;
|
|
7308
7367
|
SESSION_MANAGED_OUTPUT_PATHS = [
|
|
7309
7368
|
".hasna/session-render-manifest.json",
|
|
7310
7369
|
".codewith/.hasna/session-render-manifest.json",
|
|
@@ -7604,6 +7663,9 @@ function buildCodexAgentsMd(source, context = {}) {
|
|
|
7604
7663
|
function buildOpenCodeAgentsMd(source, context = {}) {
|
|
7605
7664
|
return flattenWithRules(source, context);
|
|
7606
7665
|
}
|
|
7666
|
+
function buildSumiAgentsMd(source, context = {}) {
|
|
7667
|
+
return flattenWithRules(source, context);
|
|
7668
|
+
}
|
|
7607
7669
|
function buildCursorMdc(source) {
|
|
7608
7670
|
const stem = source.target_path ? basename2(source.target_path, extname(source.target_path)) : source.slug;
|
|
7609
7671
|
const description = source.name || stem;
|
|
@@ -7632,6 +7694,8 @@ function applyTransform(source, output, context = {}) {
|
|
|
7632
7694
|
return buildCodexAgentsMd(source, context);
|
|
7633
7695
|
case "opencode-flat":
|
|
7634
7696
|
return buildOpenCodeAgentsMd(source, context);
|
|
7697
|
+
case "sumi-flat":
|
|
7698
|
+
return buildSumiAgentsMd(source, context);
|
|
7635
7699
|
case "cursor-mdc":
|
|
7636
7700
|
return buildCursorMdc(source);
|
|
7637
7701
|
case "skill-neutral":
|
|
@@ -7759,6 +7823,10 @@ function normalizeProfileAssetBinding(value) {
|
|
|
7759
7823
|
const rollback = record["rollback"];
|
|
7760
7824
|
if (!ASSET_ROLLBACK_POLICIES.includes(rollback))
|
|
7761
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
|
+
}
|
|
7762
7830
|
return {
|
|
7763
7831
|
schema: PROFILE_ASSET_BINDING_SCHEMA,
|
|
7764
7832
|
assetKey,
|
|
@@ -7778,6 +7846,7 @@ function normalizeProfileAssetBinding(value) {
|
|
|
7778
7846
|
root,
|
|
7779
7847
|
relativePath: nonEmptyString(destinationRecord["relativePath"], "destination.relativePath")
|
|
7780
7848
|
},
|
|
7849
|
+
...nativeAgent ? { nativeAgent } : {},
|
|
7781
7850
|
uninstall,
|
|
7782
7851
|
rollback
|
|
7783
7852
|
};
|
|
@@ -7853,6 +7922,7 @@ function compileAssetPlan(input) {
|
|
|
7853
7922
|
allowed: binding.source.allowed
|
|
7854
7923
|
},
|
|
7855
7924
|
destination: binding.destination,
|
|
7925
|
+
...binding.nativeAgent ? { nativeAgent: binding.nativeAgent } : {},
|
|
7856
7926
|
support,
|
|
7857
7927
|
action,
|
|
7858
7928
|
mutationMode: input.mode,
|
|
@@ -7908,6 +7978,18 @@ function validateAssetSourceAndDestination(bundle, binding, diagnostics) {
|
|
|
7908
7978
|
pathSafe = false;
|
|
7909
7979
|
diagnostics.push(diagnostic("error", "ASSET_DESTINATION_UNSAFE", binding.assetKey, error.message));
|
|
7910
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
|
+
}
|
|
7911
7993
|
const expectedLocator = bundle.locator;
|
|
7912
7994
|
if (!binding.source.immutable || binding.source.locator !== expectedLocator) {
|
|
7913
7995
|
diagnostics.push(diagnostic("error", "ASSET_SOURCE_MUTABLE_OR_UNPINNED", binding.assetKey, `Asset source must be pinned to ${expectedLocator}.`));
|
|
@@ -7963,6 +8045,72 @@ function safeRelativePath(value) {
|
|
|
7963
8045
|
}
|
|
7964
8046
|
return normalized;
|
|
7965
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
|
+
}
|
|
7966
8114
|
function diagnostic(severity, code, assetKey, message) {
|
|
7967
8115
|
return { severity, code, assetKey, message };
|
|
7968
8116
|
}
|
|
@@ -8008,6 +8156,7 @@ var init_asset_plan = __esm(() => {
|
|
|
8008
8156
|
ASSET_CAPABILITY_DESCRIPTORS = Object.freeze([
|
|
8009
8157
|
assetCapability("claude", "code", "skill", "supported", ["emit-file"], "Claude Code project or profile skill files."),
|
|
8010
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"),
|
|
8011
8160
|
assetCapability("claude", "code", "hook", "supported", ["emit-file"], "Claude Code hook configuration fragments."),
|
|
8012
8161
|
assetCapability("claude", "code", "plugin", "conditional", ["install-marketplace"], "Claude marketplace installation requires an explicit installer."),
|
|
8013
8162
|
assetCapability("codex", "cli", "skill", "supported", ["emit-file"], "Codex skill bundle files."),
|
|
@@ -8018,6 +8167,7 @@ var init_asset_plan = __esm(() => {
|
|
|
8018
8167
|
assetCapability("opencode", "cli", "workflow", "supported", ["emit-file"], "OpenCode command/workflow files."),
|
|
8019
8168
|
assetCapability("opencode", "cli", "plugin", "supported", ["emit-file"], "OpenCode local plugin modules."),
|
|
8020
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"),
|
|
8021
8171
|
assetCapability("codewith", "cli", "skill", "supported", ["emit-file"], "Codewith skill bundle files."),
|
|
8022
8172
|
assetCapability("codewith", "cli", "plugin", "conditional", ["install-local", "install-marketplace"], "Codewith plugin installation requires an explicit installer."),
|
|
8023
8173
|
assetCapability("aicopilot", "cli", "skill", "supported", ["emit-file"], "AICopilot discovered skill files."),
|
|
@@ -8050,6 +8200,7 @@ var init_asset_plan = __esm(() => {
|
|
|
8050
8200
|
codex: "cli",
|
|
8051
8201
|
cursor: "ide",
|
|
8052
8202
|
opencode: "cli",
|
|
8203
|
+
sumi: "cli",
|
|
8053
8204
|
codewith: "cli",
|
|
8054
8205
|
qwen: "cli",
|
|
8055
8206
|
aicopilot: "cli",
|
|
@@ -8698,9 +8849,19 @@ function skippedSource(source, reason) {
|
|
|
8698
8849
|
};
|
|
8699
8850
|
}
|
|
8700
8851
|
function normalizeSources(sources, tool, allowEmptySources) {
|
|
8701
|
-
const
|
|
8852
|
+
const providerSkipped = [];
|
|
8853
|
+
const normalized = sources.flatMap((source, index) => {
|
|
8702
8854
|
if (!source.id.trim())
|
|
8703
8855
|
throw new Error("Session instruction source id is required.");
|
|
8856
|
+
if (source.targetProviders?.length && !providerTargetsTool(source.targetProviders, tool)) {
|
|
8857
|
+
providerSkipped.push({
|
|
8858
|
+
id: source.id,
|
|
8859
|
+
label: source.label ?? source.id,
|
|
8860
|
+
targetProviders: source.targetProviders,
|
|
8861
|
+
reason: "instruction source targets a different provider"
|
|
8862
|
+
});
|
|
8863
|
+
return [];
|
|
8864
|
+
}
|
|
8704
8865
|
const floored = applyAgentOperatingRulesFloor(source, source.content ?? "");
|
|
8705
8866
|
const content = filterProviderOnlyBlocks(floored.content, tool);
|
|
8706
8867
|
const normalized2 = {
|
|
@@ -8719,7 +8880,7 @@ function normalizeSources(sources, tool, allowEmptySources) {
|
|
|
8719
8880
|
if (!allowEmptySources && !normalized2.content.trim() && normalized2.resolvedRules.length === 0 && !hasPathReferences) {
|
|
8720
8881
|
throw new Error(`Session instruction source "${source.id}" is empty. Pass --allow-empty-sources only for explicit empty renders.`);
|
|
8721
8882
|
}
|
|
8722
|
-
return normalized2;
|
|
8883
|
+
return [normalized2];
|
|
8723
8884
|
});
|
|
8724
8885
|
const originalOrder = [...normalized].sort(compareSessionInstructionSources);
|
|
8725
8886
|
const deduplicated = deduplicateSemanticPolicySources(normalized);
|
|
@@ -8727,7 +8888,7 @@ function normalizeSources(sources, tool, allowEmptySources) {
|
|
|
8727
8888
|
validateTargetedReplacementSources(originalOrder, ordered, deduplicated.skipped, tool);
|
|
8728
8889
|
rejectDuplicateSourceSlugs(ordered);
|
|
8729
8890
|
rejectDuplicateRulePaths(ordered);
|
|
8730
|
-
return { sources: ordered, skipped: deduplicated.skipped };
|
|
8891
|
+
return { sources: ordered, skipped: [...providerSkipped, ...deduplicated.skipped] };
|
|
8731
8892
|
}
|
|
8732
8893
|
function compareSessionInstructionSources(a, b) {
|
|
8733
8894
|
return SESSION_LAYER_RANK[a.resolvedLayer] - SESSION_LAYER_RANK[b.resolvedLayer] || a.resolvedOrder - b.resolvedOrder || a.id.localeCompare(b.id);
|
|
@@ -9155,7 +9316,7 @@ function buildCursorRuleFiles(targetHome, adapter, sources) {
|
|
|
9155
9316
|
return [sourceFile, ...ruleFiles];
|
|
9156
9317
|
});
|
|
9157
9318
|
}
|
|
9158
|
-
function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig) {
|
|
9319
|
+
function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig, projectRoot) {
|
|
9159
9320
|
const surface = adapter.providerSurface ?? "legacy-dual";
|
|
9160
9321
|
const fragments = sources.flatMap((source, index) => [
|
|
9161
9322
|
makeFile(targetHome, fragmentPath(adapter, index, source), "fragment", sectionForSource(source), [source.id]),
|
|
@@ -9175,13 +9336,13 @@ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfi
|
|
|
9175
9336
|
]);
|
|
9176
9337
|
const existingConfigPath = joinTarget(targetHome, adapter.configFile);
|
|
9177
9338
|
const selectedConfig = existsSync5(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
|
|
9178
|
-
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));
|
|
9179
9340
|
const config = {
|
|
9180
9341
|
...selectedConfig,
|
|
9181
9342
|
$schema: typeof selectedConfig["$schema"] === "string" ? selectedConfig["$schema"] : "https://opencode.ai/config.json",
|
|
9182
9343
|
instructions: [
|
|
9183
9344
|
...preservedInstructions,
|
|
9184
|
-
...fragments.map((file) => file.
|
|
9345
|
+
...fragments.map((file) => file.path)
|
|
9185
9346
|
]
|
|
9186
9347
|
};
|
|
9187
9348
|
const configSourceIds = [
|
|
@@ -9224,9 +9385,62 @@ function normalizeOpenCodeInstructions(value) {
|
|
|
9224
9385
|
}
|
|
9225
9386
|
return value;
|
|
9226
9387
|
}
|
|
9227
|
-
function pathIsManagedOpenCodeInstruction(
|
|
9228
|
-
const
|
|
9229
|
-
|
|
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;
|
|
9230
9444
|
}
|
|
9231
9445
|
function buildAntigravityRuleFiles(targetHome, adapter, sources) {
|
|
9232
9446
|
return sources.flatMap((source, index) => {
|
|
@@ -9280,7 +9494,7 @@ function makeAntigravityRuleFile(targetHome, relativePath, content, sourceIds) {
|
|
|
9280
9494
|
}
|
|
9281
9495
|
return file;
|
|
9282
9496
|
}
|
|
9283
|
-
function buildFiles(targetHome, adapter, profile, sources, providerConfig, providerVersion) {
|
|
9497
|
+
function buildFiles(targetHome, adapter, profile, sources, providerConfig, providerVersion, projectRoot) {
|
|
9284
9498
|
switch (adapter.mode) {
|
|
9285
9499
|
case "native-imports":
|
|
9286
9500
|
return buildNativeImportFiles(targetHome, adapter, profile, sources, providerVersion);
|
|
@@ -9289,7 +9503,7 @@ function buildFiles(targetHome, adapter, profile, sources, providerConfig, provi
|
|
|
9289
9503
|
case "cursor-mdc":
|
|
9290
9504
|
return buildCursorRuleFiles(targetHome, adapter, sources);
|
|
9291
9505
|
case "opencode-instructions":
|
|
9292
|
-
return buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig);
|
|
9506
|
+
return buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig, projectRoot);
|
|
9293
9507
|
case "antigravity-rules":
|
|
9294
9508
|
return buildAntigravityRuleFiles(targetHome, adapter, sources);
|
|
9295
9509
|
case "provider-rules":
|
|
@@ -9318,12 +9532,13 @@ function buildAssetFiles(input, targetHome, blocked) {
|
|
|
9318
9532
|
if (!relativePath || relativePath === ".." || relativePath.startsWith("../") || isAbsolute3(relativePath)) {
|
|
9319
9533
|
throw new Error(`Asset ${item.assetKey} is outside the session snapshot root; use a project-scoped session plan for atomic application.`);
|
|
9320
9534
|
}
|
|
9535
|
+
const renderedContent = renderNativeAgentContent(content, item.nativeAgent);
|
|
9321
9536
|
return {
|
|
9322
9537
|
path,
|
|
9323
9538
|
relativePath: assertSafeRelativePath(relativePath),
|
|
9324
9539
|
role: "asset",
|
|
9325
|
-
content,
|
|
9326
|
-
sha256: sha2566(
|
|
9540
|
+
content: renderedContent,
|
|
9541
|
+
sha256: sha2566(renderedContent),
|
|
9327
9542
|
sourceIds: [item.sourceConfigId, item.assetId]
|
|
9328
9543
|
};
|
|
9329
9544
|
});
|
|
@@ -9332,6 +9547,11 @@ function isNativeProfileSessionTarget(input) {
|
|
|
9332
9547
|
return (input.tool === "grok" || input.tool === "devin") && Boolean(input.targetHome?.trim()) && !input.projectRoot;
|
|
9333
9548
|
}
|
|
9334
9549
|
function adapterFor(input) {
|
|
9550
|
+
if (input.tool === "sumi" && input.projectRoot) {
|
|
9551
|
+
if (input.providerSurface)
|
|
9552
|
+
throw new Error(`Provider surface ${input.providerSurface} is not valid for Sumi.`);
|
|
9553
|
+
return Object.freeze({ ...SESSION_TOOL_ADAPTERS.sumi, projectScoped: true, envVar: undefined });
|
|
9554
|
+
}
|
|
9335
9555
|
if (input.tool === "opencode" && input.providerSurface) {
|
|
9336
9556
|
if (input.providerSurface !== "opencode-config-instructions" && input.providerSurface !== "opencode-agents-md") {
|
|
9337
9557
|
throw new Error(`Provider surface ${input.providerSurface} is not valid for OpenCode.`);
|
|
@@ -9373,7 +9593,7 @@ function adapterFor(input) {
|
|
|
9373
9593
|
}
|
|
9374
9594
|
if (input.tool !== "codewith")
|
|
9375
9595
|
return SESSION_TOOL_ADAPTERS[input.tool];
|
|
9376
|
-
const gatedNativeImports = input.codewithNativeImports
|
|
9596
|
+
const gatedNativeImports = input.codewithNativeImports ?? (process.env[CODEWITH_NATIVE_IMPORTS_ENV] === "1" || process.env[CODEWITH_NATIVE_IMPORTS_ENV] === "true");
|
|
9377
9597
|
return gatedNativeImports ? CODEWITH_NATIVE_ADAPTER : CODEWITH_FLATTENED_ADAPTER;
|
|
9378
9598
|
}
|
|
9379
9599
|
function getHomeDir() {
|
|
@@ -9424,6 +9644,13 @@ function assertSafeTargetRoot(targetHome) {
|
|
|
9424
9644
|
return normalized;
|
|
9425
9645
|
}
|
|
9426
9646
|
function resolveRenderTarget(input, adapter) {
|
|
9647
|
+
if (input.tool === "sumi" && !input.projectRoot && !input.targetHome?.trim()) {
|
|
9648
|
+
return {
|
|
9649
|
+
targetHome: defaultTargetHome(input.tool, input.profile, input.sessionId),
|
|
9650
|
+
targetKind: "blocked",
|
|
9651
|
+
blockers: ["Sumi requires --target-home from `sumi debug paths config`, or --project-root for repository AGENTS.md."]
|
|
9652
|
+
};
|
|
9653
|
+
}
|
|
9427
9654
|
if (adapter.projectScoped) {
|
|
9428
9655
|
if (!input.projectRoot) {
|
|
9429
9656
|
const label = input.tool === "cursor" ? "Cursor rules" : input.tool === "antigravity" ? "Antigravity rules" : `${adapter.description.split(".")[0]} files`;
|
|
@@ -9490,7 +9717,7 @@ function resolveSessionTargetOwnership(input, target) {
|
|
|
9490
9717
|
tool: input.tool,
|
|
9491
9718
|
profile: input.profile,
|
|
9492
9719
|
targetHome: target.targetHome,
|
|
9493
|
-
projectRoot: null,
|
|
9720
|
+
projectRoot: input.tool === "opencode" && input.projectRoot ? resolveSessionPath(input.projectRoot) : null,
|
|
9494
9721
|
ownedBy: "open-configs",
|
|
9495
9722
|
canonicalOwner: "instructions",
|
|
9496
9723
|
writer: {
|
|
@@ -9525,6 +9752,14 @@ function planSessionRender(input) {
|
|
|
9525
9752
|
const normalized = normalizeSources(input.sources, input.tool, allowEmptySources);
|
|
9526
9753
|
const composed = composeSources(normalized.sources, input.tool);
|
|
9527
9754
|
const orderedSources = composed.sources;
|
|
9755
|
+
if (input.tool === "sumi") {
|
|
9756
|
+
for (const source of orderedSources) {
|
|
9757
|
+
const activation = source.metadata?.["activation"];
|
|
9758
|
+
if (source.globs?.length || source.resolvedRules.some((rule) => rule.globs?.length) || activation?.mode && activation.mode !== "always") {
|
|
9759
|
+
throw new Error(`Sumi AGENTS.md cannot preserve conditional activation for source ${source.id}; compile a profile with an explicit supported fallback.`);
|
|
9760
|
+
}
|
|
9761
|
+
}
|
|
9762
|
+
}
|
|
9528
9763
|
const skippedSources = [
|
|
9529
9764
|
...input.skippedSources ?? [],
|
|
9530
9765
|
...normalized.skipped,
|
|
@@ -9543,7 +9778,7 @@ function planSessionRender(input) {
|
|
|
9543
9778
|
if (input.providerConfig && input.tool !== "opencode") {
|
|
9544
9779
|
throw new Error("Provider base config is supported only for OpenCode session renders.");
|
|
9545
9780
|
}
|
|
9546
|
-
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);
|
|
9547
9782
|
const projectContext = blocked ? null : composeProjectContextSessionRender({
|
|
9548
9783
|
tool: input.tool,
|
|
9549
9784
|
adapter_mode: adapter.mode,
|
|
@@ -9568,6 +9803,7 @@ function planSessionRender(input) {
|
|
|
9568
9803
|
adapterMode: adapter.mode,
|
|
9569
9804
|
profile: input.profile,
|
|
9570
9805
|
sessionId: input.sessionId ?? null,
|
|
9806
|
+
...input.refreshSelector ? { refreshSelector: input.refreshSelector } : {},
|
|
9571
9807
|
targetHome,
|
|
9572
9808
|
targetKind,
|
|
9573
9809
|
targetOwner,
|
|
@@ -9643,6 +9879,7 @@ function planSessionRender(input) {
|
|
|
9643
9879
|
mutationMode: item.mutationMode,
|
|
9644
9880
|
destination: item.destination,
|
|
9645
9881
|
digest: item.source.digest,
|
|
9882
|
+
...item.nativeAgent ? { nativeAgent: item.nativeAgent } : {},
|
|
9646
9883
|
exactOnceKey: item.exactOnceKey
|
|
9647
9884
|
}))
|
|
9648
9885
|
}
|
|
@@ -9741,8 +9978,9 @@ function selectProfileConfigsForSessionRender(configs, tool) {
|
|
|
9741
9978
|
skippedSources.push(skippedProfileConfig(config, ["opencode"], "provider settings belong to OpenCode"));
|
|
9742
9979
|
continue;
|
|
9743
9980
|
}
|
|
9744
|
-
|
|
9745
|
-
|
|
9981
|
+
const sourceRejection = instructionSourceRejection(config);
|
|
9982
|
+
if (sourceRejection) {
|
|
9983
|
+
skippedSources.push(skippedProfileConfig(config, [], sourceRejection));
|
|
9746
9984
|
continue;
|
|
9747
9985
|
}
|
|
9748
9986
|
const output = config.outputs.find((candidate) => candidate.agent === tool);
|
|
@@ -9750,7 +9988,7 @@ function selectProfileConfigsForSessionRender(configs, tool) {
|
|
|
9750
9988
|
skippedSources.push(skippedProfileConfig(config, [config.agent], "rule targets a different provider"));
|
|
9751
9989
|
continue;
|
|
9752
9990
|
}
|
|
9753
|
-
const selectedContent = output ? applyTransform(config, output, { configs }) : config.content;
|
|
9991
|
+
const selectedContent = output ? applyTransform(config, output, { configs: [config] }) : config.content;
|
|
9754
9992
|
sources.push({
|
|
9755
9993
|
config,
|
|
9756
9994
|
source: sourceFromConfig({ ...config, content: selectedContent }, sources.length)
|
|
@@ -10159,6 +10397,7 @@ var init_session_render = __esm(() => {
|
|
|
10159
10397
|
"codex",
|
|
10160
10398
|
"cursor",
|
|
10161
10399
|
"opencode",
|
|
10400
|
+
"sumi",
|
|
10162
10401
|
"codewith",
|
|
10163
10402
|
"qwen",
|
|
10164
10403
|
"aicopilot",
|
|
@@ -10247,6 +10486,15 @@ var init_session_render = __esm(() => {
|
|
|
10247
10486
|
description: "OpenCode AGENTS.md plus opencode.json instructions pointing at managed fragments.",
|
|
10248
10487
|
providerSurface: "legacy-dual"
|
|
10249
10488
|
},
|
|
10489
|
+
sumi: {
|
|
10490
|
+
tool: "sumi",
|
|
10491
|
+
mode: "flattened-markdown",
|
|
10492
|
+
indexFile: "AGENTS.md",
|
|
10493
|
+
managedDir: SESSION_RENDER_INSTRUCTIONS_MANAGED_DIR,
|
|
10494
|
+
envVar: "SUMI_CONFIG_DIR",
|
|
10495
|
+
nativeImports: false,
|
|
10496
|
+
description: "Sumi native AGENTS.md in the explicit resolved config directory or project root."
|
|
10497
|
+
},
|
|
10250
10498
|
aicopilot: {
|
|
10251
10499
|
tool: "aicopilot",
|
|
10252
10500
|
mode: "flattened-markdown",
|
|
@@ -10535,12 +10783,22 @@ function compileInstructionGraph(input) {
|
|
|
10535
10783
|
const selector = binding.providers?.find((entry) => entry.provider === input.context.provider || entry.provider === "global");
|
|
10536
10784
|
if (binding.providers?.length && !selector)
|
|
10537
10785
|
continue;
|
|
10786
|
+
const output = config.outputs.find((entry) => entry.agent === input.context.provider);
|
|
10787
|
+
if (!binding.providers?.length && config.agent !== "global" && config.agent !== input.context.provider && !output) {
|
|
10788
|
+
diagnostics.push({ severity: "info", code: "SOURCE_PROVIDER_NOT_SELECTED", config_id: configId, message: `${config.slug} targets ${config.agent}, not ${input.context.provider}.` });
|
|
10789
|
+
continue;
|
|
10790
|
+
}
|
|
10538
10791
|
if (selector?.version_range && !providerVersionSatisfies(input.context.provider_version, selector.version_range)) {
|
|
10539
10792
|
diagnostics.push({ severity: binding.required ? "error" : "warning", code: "PROVIDER_SELECTOR_VERSION_MISMATCH", config_id: configId, message: `${config.slug} requires ${input.context.provider} ${selector.version_range}; current version is ${input.context.provider_version}.` });
|
|
10540
10793
|
continue;
|
|
10541
10794
|
}
|
|
10542
10795
|
if (!activationMatches(binding.activation, config, input.context))
|
|
10543
10796
|
continue;
|
|
10797
|
+
const rejection = instructionSourceRejection(config);
|
|
10798
|
+
if (rejection) {
|
|
10799
|
+
diagnostics.push({ severity: !binding.required && binding.fallback === "omit" ? "warning" : "error", code: "SOURCE_NOT_INSTRUCTION", config_id: configId, message: `${config.slug}: ${rejection}.` });
|
|
10800
|
+
continue;
|
|
10801
|
+
}
|
|
10544
10802
|
if (capability2.activation_modes.includes(binding.activation.mode)) {
|
|
10545
10803
|
selected.add(configId);
|
|
10546
10804
|
effective.set(configId, binding.activation);
|
|
@@ -10615,7 +10873,9 @@ function compileInstructionGraph(input) {
|
|
|
10615
10873
|
assertExactOnce(units, artifacts);
|
|
10616
10874
|
const sources = units.map((unit, index) => {
|
|
10617
10875
|
const config = configs.get(unit.config_id);
|
|
10618
|
-
|
|
10876
|
+
const output = config.outputs.find((entry) => entry.agent === input.context.provider);
|
|
10877
|
+
const content = output ? applyTransform(config, output, { configs: [config] }) : config.content;
|
|
10878
|
+
return sourceForUnit({ ...config, content }, unit, index);
|
|
10619
10879
|
});
|
|
10620
10880
|
const planWithoutHash = {
|
|
10621
10881
|
schema: INSTRUCTION_GRAPH_PLAN_SCHEMA,
|
|
@@ -10843,6 +11103,7 @@ var init_instruction_graph = __esm(() => {
|
|
|
10843
11103
|
init_types();
|
|
10844
11104
|
init_session_render();
|
|
10845
11105
|
init_asset_plan();
|
|
11106
|
+
init_transforms();
|
|
10846
11107
|
COMMON_FALLBACKS = ["fail", "flatten", "promote-always", "omit"];
|
|
10847
11108
|
DEFAULT_PROVIDER_CAPABILITIES = Object.freeze({
|
|
10848
11109
|
claude: capability("claude", ">=1.0.0", "native-import", "CLAUDE.md @ imports plus path-gated rules", {
|
|
@@ -10854,6 +11115,7 @@ var init_instruction_graph = __esm(() => {
|
|
|
10854
11115
|
codex: capability("codex", ">=0.1.0", "flattened", "AGENTS.md", { asset_surface: "cli" }),
|
|
10855
11116
|
cursor: capability("cursor", ">=1.0.0", "cursor-rule", ".cursor/rules/*.mdc", { activation_modes: ["always", "glob"], conditional_artifacts: true, asset_surface: "ide" }),
|
|
10856
11117
|
opencode: capability("opencode", ">=1.0.0", "managed-fragment", "opencode.json instructions", { provider_variant: "v1-instructions", session_surface: "opencode-config-instructions" }),
|
|
11118
|
+
sumi: capability("sumi", ">=0.2.22 <0.3.0", "flattened", "AGENTS.md"),
|
|
10857
11119
|
codewith: capability("codewith", ">=0.1.0", "flattened", "CODEWITH.md"),
|
|
10858
11120
|
qwen: capability("qwen", ">=0.1.0", "flattened", "QWEN.md"),
|
|
10859
11121
|
aicopilot: capability("aicopilot", ">=0.1.0", "flattened", "AICOPILOT.md"),
|
|
@@ -13464,6 +13726,7 @@ function listConfigSummariesPage(filter = {}, options = {}, db) {
|
|
|
13464
13726
|
})), total, normalized);
|
|
13465
13727
|
}
|
|
13466
13728
|
function updateConfig(idOrSlug, input, db) {
|
|
13729
|
+
validateExpectedConfigVersion(input.expected_version);
|
|
13467
13730
|
const d = db || getDatabase();
|
|
13468
13731
|
const existing = getConfig(idOrSlug, d);
|
|
13469
13732
|
const ts = now();
|
|
@@ -13519,7 +13782,15 @@ function updateConfig(idOrSlug, input, db) {
|
|
|
13519
13782
|
}
|
|
13520
13783
|
return d.transaction(() => {
|
|
13521
13784
|
params.push(existing.id);
|
|
13522
|
-
|
|
13785
|
+
const condition = input.expected_version === undefined ? "" : " AND version = ?";
|
|
13786
|
+
if (input.expected_version !== undefined)
|
|
13787
|
+
params.push(input.expected_version);
|
|
13788
|
+
const result = d.run(`UPDATE configs SET ${updates.join(", ")} WHERE id = ?${condition}`, params);
|
|
13789
|
+
if (result.changes !== 1) {
|
|
13790
|
+
if (input.expected_version !== undefined)
|
|
13791
|
+
throw new ConfigVersionConflictError(existing.id, input.expected_version);
|
|
13792
|
+
throw new ConfigNotFoundError(existing.id);
|
|
13793
|
+
}
|
|
13523
13794
|
const updated = getConfigById(existing.id, d);
|
|
13524
13795
|
createSnapshot(updated.id, updated.content, updated.version, d);
|
|
13525
13796
|
return updated;
|
|
@@ -13638,7 +13909,7 @@ function detectMachineContext(overrides = {}) {
|
|
|
13638
13909
|
created_at: "",
|
|
13639
13910
|
os_family: osFamily,
|
|
13640
13911
|
home_dir: homeDir6,
|
|
13641
|
-
workspace_root: overrides.workspace_root ??
|
|
13912
|
+
workspace_root: overrides.workspace_root ?? "",
|
|
13642
13913
|
bun_bin_dir: bunBinDir,
|
|
13643
13914
|
bun_path: overrides.bun_path ?? defaultBunPath,
|
|
13644
13915
|
path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join10("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
|
|
@@ -13651,7 +13922,7 @@ function machineContextToVariables(machine) {
|
|
|
13651
13922
|
OS_FAMILY: machine.os_family,
|
|
13652
13923
|
ARCH: machine.arch ?? "",
|
|
13653
13924
|
HOME_DIR: machine.home_dir,
|
|
13654
|
-
WORKSPACE_ROOT: machine.workspace_root,
|
|
13925
|
+
...machine.workspace_root ? { WORKSPACE_ROOT: machine.workspace_root } : {},
|
|
13655
13926
|
BUN_BIN_DIR: machine.bun_bin_dir,
|
|
13656
13927
|
BUN_PATH: machine.bun_path,
|
|
13657
13928
|
PATH_PREFIX: machine.path_prefix
|
|
@@ -14418,7 +14689,9 @@ class CloudConfigStore {
|
|
|
14418
14689
|
return data.config;
|
|
14419
14690
|
}
|
|
14420
14691
|
async updateConfig(idOrSlug, input) {
|
|
14421
|
-
|
|
14692
|
+
validateExpectedConfigVersion(input.expected_version);
|
|
14693
|
+
const conditional = input.expected_version !== undefined;
|
|
14694
|
+
const { data } = await this.request(conditional ? "POST" : "PATCH", `/configs/${encodeURIComponent(idOrSlug)}${conditional ? "/conditional-update" : ""}`, input);
|
|
14422
14695
|
return data.config;
|
|
14423
14696
|
}
|
|
14424
14697
|
async deleteConfig(idOrSlug) {
|
|
@@ -14495,7 +14768,7 @@ class CloudConfigStore {
|
|
|
14495
14768
|
throw new ProfileNotFoundError(idOrSlug);
|
|
14496
14769
|
return parseBoundedOrLegacyPage(data.configs, data.profile.configs, normalized, "profile membership");
|
|
14497
14770
|
}
|
|
14498
|
-
async getProfileConfigBindings(idOrSlug) {
|
|
14771
|
+
async getProfileConfigBindings(idOrSlug, options = {}) {
|
|
14499
14772
|
let routeMissing = false;
|
|
14500
14773
|
const bindings = await aggregateBoundedCollection("profile config bindings", async (cursor) => {
|
|
14501
14774
|
const normalized = normalizeBoundedReadOptions({ limit: 100, cursor });
|
|
@@ -14508,6 +14781,8 @@ class CloudConfigStore {
|
|
|
14508
14781
|
}, (binding) => `${binding.profile_id}\x00${binding.config_id}`);
|
|
14509
14782
|
if (!routeMissing)
|
|
14510
14783
|
return bindings;
|
|
14784
|
+
if (options.requireExplicit)
|
|
14785
|
+
throw new Error("HOSTED_BINDINGS_REQUIRED: profile config binding route is unavailable; refusing legacy activation fallback.");
|
|
14511
14786
|
const profile = await this.getProfile(idOrSlug);
|
|
14512
14787
|
const configs = await this.getProfileConfigs(idOrSlug);
|
|
14513
14788
|
return configs.map((config, sort_order) => ({
|
|
@@ -14572,7 +14847,7 @@ class CloudConfigStore {
|
|
|
14572
14847
|
async removeConfigFromProfile(profileIdOrSlug, configId) {
|
|
14573
14848
|
await this.request("DELETE", `/profiles/${encodeURIComponent(profileIdOrSlug)}/configs/${encodeURIComponent(configId)}`, undefined, { allow404: true });
|
|
14574
14849
|
}
|
|
14575
|
-
async getProfileAssetBindings(profileIdOrSlug) {
|
|
14850
|
+
async getProfileAssetBindings(profileIdOrSlug, options = {}) {
|
|
14576
14851
|
let routeMissing = false;
|
|
14577
14852
|
const assets = await aggregateBoundedCollection("profile asset bindings", async (cursor) => {
|
|
14578
14853
|
const normalized = normalizeBoundedReadOptions({ limit: 100, cursor });
|
|
@@ -14585,6 +14860,8 @@ class CloudConfigStore {
|
|
|
14585
14860
|
}, (asset) => `${asset.profile_id}\x00${asset.binding.assetKey}`);
|
|
14586
14861
|
if (!routeMissing)
|
|
14587
14862
|
return assets;
|
|
14863
|
+
if (options.requireExplicit)
|
|
14864
|
+
throw new Error("HOSTED_ASSETS_REQUIRED: profile asset binding route is unavailable; refusing an empty asset fallback.");
|
|
14588
14865
|
await this.getProfile(profileIdOrSlug);
|
|
14589
14866
|
return [];
|
|
14590
14867
|
}
|
|
@@ -14709,7 +14986,7 @@ init_types();
|
|
|
14709
14986
|
|
|
14710
14987
|
// src/status.ts
|
|
14711
14988
|
init_config_store();
|
|
14712
|
-
import { existsSync as
|
|
14989
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
14713
14990
|
|
|
14714
14991
|
// src/lib/apply.ts
|
|
14715
14992
|
init_types();
|
|
@@ -15259,19 +15536,9 @@ function getPackageVersion() {
|
|
|
15259
15536
|
}
|
|
15260
15537
|
|
|
15261
15538
|
// src/lib/managed-skill-runtimes.ts
|
|
15262
|
-
import {
|
|
15263
|
-
import { spawnSync as spawnSync3 } from "child_process";
|
|
15264
|
-
import {
|
|
15265
|
-
existsSync as existsSync11,
|
|
15266
|
-
lstatSync as lstatSync4,
|
|
15267
|
-
mkdirSync as mkdirSync4,
|
|
15268
|
-
readFileSync as readFileSync10,
|
|
15269
|
-
renameSync as renameSync2,
|
|
15270
|
-
rmSync as rmSync3,
|
|
15271
|
-
writeFileSync as writeFileSync3
|
|
15272
|
-
} from "fs";
|
|
15539
|
+
import { lstatSync as lstatSync4 } from "fs";
|
|
15273
15540
|
import { homedir as homedir8 } from "os";
|
|
15274
|
-
import {
|
|
15541
|
+
import { join as join14, resolve as resolve10 } from "path";
|
|
15275
15542
|
var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
|
|
15276
15543
|
var INBOX_SKILL_MARKERS = [
|
|
15277
15544
|
[".claude", "skills", "inbox", "SKILL.md"],
|
|
@@ -15280,369 +15547,67 @@ var INBOX_SKILL_MARKERS = [
|
|
|
15280
15547
|
[".config", "opencode", "skills", "inbox", "SKILL.md"],
|
|
15281
15548
|
[".cursor", "skills", "inbox", "SKILL.md"]
|
|
15282
15549
|
];
|
|
15283
|
-
|
|
15284
|
-
|
|
15285
|
-
|
|
15286
|
-
|
|
15287
|
-
function lstatOrNull(path) {
|
|
15288
|
-
try {
|
|
15289
|
-
return lstatSync4(path);
|
|
15290
|
-
} catch {
|
|
15291
|
-
return null;
|
|
15292
|
-
}
|
|
15293
|
-
}
|
|
15294
|
-
function findSymlinkedAncestor(path) {
|
|
15295
|
-
const normalized = resolve10(path);
|
|
15296
|
-
const parsed = parse4(normalized);
|
|
15297
|
-
let current = parsed.root;
|
|
15298
|
-
const rel = relative5(parsed.root, normalized);
|
|
15299
|
-
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) {
|
|
15300
15554
|
current = join14(current, segment);
|
|
15301
|
-
|
|
15302
|
-
|
|
15303
|
-
|
|
15304
|
-
|
|
15305
|
-
|
|
15306
|
-
|
|
15307
|
-
|
|
15308
|
-
function assertNoSymlinkAncestors2(path) {
|
|
15309
|
-
const found = findSymlinkedAncestor(path);
|
|
15310
|
-
if (found !== null) {
|
|
15311
|
-
throw new Error(`managed skill path uses a symlink ancestor: ${found}`);
|
|
15312
|
-
}
|
|
15313
|
-
}
|
|
15314
|
-
function packagedInboxSkillPath(explicitPath) {
|
|
15315
|
-
if (explicitPath)
|
|
15316
|
-
return explicitPath;
|
|
15317
|
-
throw new Error("Bundled skill contracts are retired; use the Skills CLI to manage private skills");
|
|
15318
|
-
}
|
|
15319
|
-
function readCanonicalSkill(explicitPath) {
|
|
15320
|
-
const assetPath = packagedInboxSkillPath(explicitPath);
|
|
15321
|
-
const stat = lstatOrNull(assetPath);
|
|
15322
|
-
if (!stat?.isFile()) {
|
|
15323
|
-
throw new Error("packaged inbox skill contract is not a regular file");
|
|
15324
|
-
}
|
|
15325
|
-
const content = readFileSync10(assetPath, "utf8");
|
|
15326
|
-
if (!content.includes("conversations watch --from <agent> --all")) {
|
|
15327
|
-
throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
|
|
15328
|
-
}
|
|
15329
|
-
if (!content.includes("There is no separate")) {
|
|
15330
|
-
throw new Error("packaged inbox skill contract does not retire the legacy executable");
|
|
15331
|
-
}
|
|
15332
|
-
return { content, sha256: sha2568(content) };
|
|
15333
|
-
}
|
|
15334
|
-
function runProbe(command, args) {
|
|
15335
|
-
const result = spawnSync3(command, args, {
|
|
15336
|
-
encoding: "utf8",
|
|
15337
|
-
timeout: 5000,
|
|
15338
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
15339
|
-
});
|
|
15340
|
-
if (result.error || result.status !== 0) {
|
|
15341
|
-
return { ok: false, output: "" };
|
|
15342
|
-
}
|
|
15343
|
-
return {
|
|
15344
|
-
ok: true,
|
|
15345
|
-
output: `${result.stdout ?? ""}
|
|
15346
|
-
${result.stderr ?? ""}`.trim()
|
|
15347
|
-
};
|
|
15348
|
-
}
|
|
15349
|
-
function parseVersion(output) {
|
|
15350
|
-
return output.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
|
|
15351
|
-
}
|
|
15352
|
-
function compareVersions(left, right) {
|
|
15353
|
-
const a = left.split(".").map(Number);
|
|
15354
|
-
const b = right.split(".").map(Number);
|
|
15355
|
-
for (let i = 0;i < Math.max(a.length, b.length); i++) {
|
|
15356
|
-
const delta = (a[i] ?? 0) - (b[i] ?? 0);
|
|
15357
|
-
if (delta !== 0)
|
|
15358
|
-
return delta;
|
|
15359
|
-
}
|
|
15360
|
-
return 0;
|
|
15361
|
-
}
|
|
15362
|
-
function inspectSkillMarkers(homeDir6) {
|
|
15363
|
-
return INBOX_SKILL_MARKERS.map((parts) => join14(homeDir6, ...parts)).map((path) => {
|
|
15364
|
-
const stat = lstatOrNull(path);
|
|
15365
|
-
if (!stat)
|
|
15366
|
-
return null;
|
|
15367
|
-
if (!stat.isFile()) {
|
|
15368
|
-
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;
|
|
15369
15562
|
}
|
|
15370
|
-
|
|
15371
|
-
|
|
15372
|
-
|
|
15373
|
-
|
|
15374
|
-
regular: true
|
|
15375
|
-
};
|
|
15376
|
-
}).filter((snapshot) => snapshot !== null);
|
|
15563
|
+
if (stat.isSymbolicLink() || current !== absolute && !stat.isDirectory())
|
|
15564
|
+
return true;
|
|
15565
|
+
}
|
|
15566
|
+
return true;
|
|
15377
15567
|
}
|
|
15378
|
-
function
|
|
15568
|
+
function inspectLegacyInbox(options) {
|
|
15379
15569
|
const homeDir6 = options.homeDir ?? homedir8();
|
|
15380
|
-
const
|
|
15381
|
-
const
|
|
15382
|
-
const skillPresent = snapshots.length > 0;
|
|
15383
|
-
let canonicalContent = null;
|
|
15384
|
-
let canonicalSha256 = null;
|
|
15385
|
-
let assetError = null;
|
|
15386
|
-
try {
|
|
15387
|
-
const canonical = readCanonicalSkill(options.assetPath);
|
|
15388
|
-
canonicalContent = canonical.content;
|
|
15389
|
-
canonicalSha256 = canonical.sha256;
|
|
15390
|
-
} catch (error) {
|
|
15391
|
-
assetError = error instanceof Error ? error.message : String(error);
|
|
15392
|
-
}
|
|
15393
|
-
const versionProbe = skillPresent ? runProbe(runtimeCommand, ["--version"]) : { ok: false, output: "" };
|
|
15394
|
-
const helpProbe = versionProbe.ok ? runProbe(runtimeCommand, ["watch", "--help"]) : { ok: false, output: "" };
|
|
15395
|
-
const runtimeVersion = versionProbe.ok ? parseVersion(versionProbe.output) : null;
|
|
15396
|
-
const supportsFrom = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[0]);
|
|
15397
|
-
const supportsAll = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[1]);
|
|
15398
|
-
const supportsFullContent = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[2]);
|
|
15399
|
-
const packageReady = versionProbe.ok && runtimeVersion !== null && compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && helpProbe.ok && supportsFrom && supportsAll && supportsFullContent;
|
|
15400
|
-
const heartbeatProbe = skillPresent && packageReady && options.agent ? runProbe(runtimeCommand, ["agents", "heartbeat", "--from", options.agent, "--json"]) : null;
|
|
15401
|
-
const hostedHeartbeat = heartbeatProbe === null ? "unverified" : heartbeatProbe.ok ? "passed" : "failed";
|
|
15402
|
-
const deliveryVerified = hostedHeartbeat === "passed" && options.deliveryVerified === true;
|
|
15403
|
-
const staleMarkers = canonicalContent === null ? snapshots.map((snapshot) => snapshot.path) : snapshots.filter((snapshot) => !snapshot.regular || snapshot.content !== canonicalContent).map((snapshot) => snapshot.path);
|
|
15404
|
-
let reason = "skill not installed";
|
|
15405
|
-
if (skillPresent) {
|
|
15406
|
-
const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
|
|
15407
|
-
const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
|
|
15408
|
-
if (nonRegular)
|
|
15409
|
-
reason = "managed skill target is not a regular file";
|
|
15410
|
-
else if (symlinkAncestor)
|
|
15411
|
-
reason = `managed skill path uses a symlink ancestor: ${symlinkAncestor}`;
|
|
15412
|
-
else if (assetError)
|
|
15413
|
-
reason = assetError;
|
|
15414
|
-
else if (!versionProbe.ok)
|
|
15415
|
-
reason = "conversations command unavailable";
|
|
15416
|
-
else if (!runtimeVersion)
|
|
15417
|
-
reason = "conversations version is unreadable";
|
|
15418
|
-
else if (compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) < 0) {
|
|
15419
|
-
reason = `conversations ${runtimeVersion} is older than ${INBOX_CONVERSATIONS_MINIMUM_VERSION}`;
|
|
15420
|
-
} else if (!helpProbe.ok)
|
|
15421
|
-
reason = "conversations watch help is unavailable";
|
|
15422
|
-
else if (!supportsFrom || !supportsAll || !supportsFullContent) {
|
|
15423
|
-
const missing = [
|
|
15424
|
-
!supportsFrom ? "--from" : null,
|
|
15425
|
-
!supportsAll ? "--all" : null,
|
|
15426
|
-
!supportsFullContent ? "--full-content" : null
|
|
15427
|
-
].filter((flag) => flag !== null);
|
|
15428
|
-
reason = `conversations watch is missing required flags: ${missing.join(", ")}`;
|
|
15429
|
-
} else if (staleMarkers.length > 0)
|
|
15430
|
-
reason = "skill contract stale";
|
|
15431
|
-
else if (hostedHeartbeat === "failed")
|
|
15432
|
-
reason = "hosted heartbeat failed; manual fallback required";
|
|
15433
|
-
else if (hostedHeartbeat === "unverified")
|
|
15434
|
-
reason = "hosted heartbeat unverified; manual fallback required";
|
|
15435
|
-
else if (!deliveryVerified)
|
|
15436
|
-
reason = "hosted heartbeat passed; channel and DM delivery verification required";
|
|
15437
|
-
else
|
|
15438
|
-
reason = "ready";
|
|
15439
|
-
}
|
|
15570
|
+
const markers = INBOX_SKILL_MARKERS.filter((parts) => needsMigrationReview(homeDir6, parts)).map((parts) => join14(homeDir6, ...parts));
|
|
15571
|
+
const present = markers.length > 0;
|
|
15440
15572
|
return {
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15451
|
-
|
|
15452
|
-
|
|
15453
|
-
|
|
15454
|
-
|
|
15455
|
-
|
|
15456
|
-
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
reason
|
|
15461
|
-
},
|
|
15462
|
-
canonicalContent,
|
|
15463
|
-
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"
|
|
15464
15592
|
};
|
|
15465
15593
|
}
|
|
15466
15594
|
function inspectManagedSkillRuntimes(options = {}) {
|
|
15467
|
-
const runtime =
|
|
15468
|
-
const installed = runtime.skill_present ? [runtime] : [];
|
|
15595
|
+
const runtime = inspectLegacyInbox(options);
|
|
15469
15596
|
return {
|
|
15470
15597
|
runtimes: [runtime],
|
|
15471
|
-
skills_present:
|
|
15472
|
-
healthy:
|
|
15473
|
-
missing:
|
|
15474
|
-
};
|
|
15475
|
-
}
|
|
15476
|
-
function runtimeReadyForWrite(status) {
|
|
15477
|
-
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;
|
|
15478
|
-
}
|
|
15479
|
-
function projectUpdatedStatus(status, contractCount) {
|
|
15480
|
-
const healthy = status.hosted_heartbeat === "passed" && status.delivery_verified;
|
|
15481
|
-
return {
|
|
15482
|
-
...status,
|
|
15483
|
-
skill_contracts_current: contractCount,
|
|
15484
|
-
stale_skill_markers: [],
|
|
15485
|
-
manual_fallback_ready: true,
|
|
15486
|
-
healthy,
|
|
15487
|
-
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"
|
|
15598
|
+
skills_present: runtime.skill_present ? 1 : 0,
|
|
15599
|
+
healthy: 0,
|
|
15600
|
+
missing: runtime.skill_present ? 1 : 0
|
|
15488
15601
|
};
|
|
15489
15602
|
}
|
|
15490
|
-
function cleanup(path) {
|
|
15491
|
-
rmSync3(path, { force: true });
|
|
15492
|
-
}
|
|
15493
|
-
function writeAtomic(path, content, mode) {
|
|
15494
|
-
assertNoSymlinkAncestors2(dirname8(path));
|
|
15495
|
-
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
15496
|
-
try {
|
|
15497
|
-
mkdirSync4(dirname8(path), { recursive: true, mode: 493 });
|
|
15498
|
-
writeFileSync3(tempPath, content, { mode, flag: "wx" });
|
|
15499
|
-
renameSync2(tempPath, path);
|
|
15500
|
-
} finally {
|
|
15501
|
-
cleanup(tempPath);
|
|
15502
|
-
}
|
|
15503
|
-
}
|
|
15504
|
-
var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
|
|
15505
|
-
lstat: lstatOrNull,
|
|
15506
|
-
read: (path) => readFileSync10(path, "utf8"),
|
|
15507
|
-
write: writeAtomic
|
|
15508
|
-
};
|
|
15509
|
-
function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
|
|
15510
|
-
const written = [];
|
|
15511
|
-
try {
|
|
15512
|
-
for (const snapshot of snapshots) {
|
|
15513
|
-
const currentStat = fileOperations.lstat(snapshot.path);
|
|
15514
|
-
if (!currentStat?.isFile() || fileOperations.read(snapshot.path) !== snapshot.content) {
|
|
15515
|
-
throw new Error("managed skill changed after inspection; refusing a stale write");
|
|
15516
|
-
}
|
|
15517
|
-
fileOperations.write(snapshot.path, canonicalContent, snapshot.mode);
|
|
15518
|
-
written.push(snapshot);
|
|
15519
|
-
}
|
|
15520
|
-
return { ok: true, error: null, rollback_conflicts: [] };
|
|
15521
|
-
} catch (error) {
|
|
15522
|
-
const rollbackConflicts = [];
|
|
15523
|
-
for (const snapshot of written.reverse()) {
|
|
15524
|
-
const currentStat = fileOperations.lstat(snapshot.path);
|
|
15525
|
-
if (!currentStat?.isFile()) {
|
|
15526
|
-
rollbackConflicts.push(`${snapshot.path}: no longer a regular file`);
|
|
15527
|
-
continue;
|
|
15528
|
-
}
|
|
15529
|
-
let currentContent;
|
|
15530
|
-
try {
|
|
15531
|
-
currentContent = fileOperations.read(snapshot.path);
|
|
15532
|
-
} catch {
|
|
15533
|
-
rollbackConflicts.push(`${snapshot.path}: could not read the current file`);
|
|
15534
|
-
continue;
|
|
15535
|
-
}
|
|
15536
|
-
if (currentContent !== canonicalContent) {
|
|
15537
|
-
rollbackConflicts.push(`${snapshot.path}: changed after this reconciliation wrote it`);
|
|
15538
|
-
continue;
|
|
15539
|
-
}
|
|
15540
|
-
try {
|
|
15541
|
-
fileOperations.write(snapshot.path, snapshot.content, snapshot.mode);
|
|
15542
|
-
} catch {
|
|
15543
|
-
rollbackConflicts.push(`${snapshot.path}: still owned but could not be restored`);
|
|
15544
|
-
}
|
|
15545
|
-
}
|
|
15546
|
-
return {
|
|
15547
|
-
ok: false,
|
|
15548
|
-
error: error instanceof Error ? error.message : String(error),
|
|
15549
|
-
rollback_conflicts: rollbackConflicts
|
|
15550
|
-
};
|
|
15551
|
-
}
|
|
15552
|
-
}
|
|
15553
15603
|
async function reconcileManagedSkillRuntimes(options = {}) {
|
|
15604
|
+
const runtime = inspectLegacyInbox(options);
|
|
15554
15605
|
const dryRun = options.dryRun ?? false;
|
|
15555
|
-
const before = inspectInbox(options);
|
|
15556
|
-
const status = before.status;
|
|
15557
|
-
if (!status.skill_present) {
|
|
15558
|
-
return {
|
|
15559
|
-
runtimes: [{ ...status, action: "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
15560
|
-
changed: 0,
|
|
15561
|
-
failed: 0,
|
|
15562
|
-
dry_run: dryRun
|
|
15563
|
-
};
|
|
15564
|
-
}
|
|
15565
|
-
if (before.snapshots.some((snapshot) => !snapshot.regular)) {
|
|
15566
|
-
return {
|
|
15567
|
-
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
15568
|
-
changed: 0,
|
|
15569
|
-
failed: 1,
|
|
15570
|
-
dry_run: dryRun
|
|
15571
|
-
};
|
|
15572
|
-
}
|
|
15573
|
-
const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
|
|
15574
|
-
if (symlinkedAncestor) {
|
|
15575
|
-
return {
|
|
15576
|
-
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
15577
|
-
changed: 0,
|
|
15578
|
-
failed: 1,
|
|
15579
|
-
dry_run: dryRun
|
|
15580
|
-
};
|
|
15581
|
-
}
|
|
15582
|
-
if (!before.canonicalContent || !runtimeReadyForWrite(status)) {
|
|
15583
|
-
return {
|
|
15584
|
-
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
15585
|
-
changed: 0,
|
|
15586
|
-
failed: 1,
|
|
15587
|
-
dry_run: dryRun
|
|
15588
|
-
};
|
|
15589
|
-
}
|
|
15590
|
-
const staleSnapshots = before.snapshots.filter((snapshot) => snapshot.content !== before.canonicalContent);
|
|
15591
|
-
if (staleSnapshots.length === 0) {
|
|
15592
|
-
return {
|
|
15593
|
-
runtimes: [{ ...status, action: "unchanged", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
15594
|
-
changed: 0,
|
|
15595
|
-
failed: 0,
|
|
15596
|
-
dry_run: dryRun
|
|
15597
|
-
};
|
|
15598
|
-
}
|
|
15599
|
-
if (dryRun) {
|
|
15600
|
-
const projected = projectUpdatedStatus(status, before.snapshots.length);
|
|
15601
|
-
return {
|
|
15602
|
-
runtimes: [{
|
|
15603
|
-
...projected,
|
|
15604
|
-
action: "update",
|
|
15605
|
-
dry_run: true,
|
|
15606
|
-
skill_contracts_changed: staleSnapshots.length
|
|
15607
|
-
}],
|
|
15608
|
-
changed: 1,
|
|
15609
|
-
failed: 0,
|
|
15610
|
-
dry_run: true
|
|
15611
|
-
};
|
|
15612
|
-
}
|
|
15613
|
-
const transaction = writeSkillContractsTransactional(staleSnapshots.map((snapshot) => ({
|
|
15614
|
-
path: snapshot.path,
|
|
15615
|
-
content: snapshot.content,
|
|
15616
|
-
mode: snapshot.mode ?? 420
|
|
15617
|
-
})), before.canonicalContent);
|
|
15618
|
-
if (!transaction.ok) {
|
|
15619
|
-
const rollbackConflictReason = transaction.rollback_conflicts.length > 0 ? `; rollback conflicts: ${transaction.rollback_conflicts.join("; ")}` : "";
|
|
15620
|
-
const reason = `${transaction.error ?? "managed skill reconciliation failed"}${rollbackConflictReason}`;
|
|
15621
|
-
return {
|
|
15622
|
-
runtimes: [{
|
|
15623
|
-
...status,
|
|
15624
|
-
action: "failed",
|
|
15625
|
-
dry_run: false,
|
|
15626
|
-
skill_contracts_changed: 0,
|
|
15627
|
-
reason
|
|
15628
|
-
}],
|
|
15629
|
-
changed: 0,
|
|
15630
|
-
failed: 1,
|
|
15631
|
-
dry_run: false
|
|
15632
|
-
};
|
|
15633
|
-
}
|
|
15634
|
-
const after = inspectInbox(options).status;
|
|
15635
|
-
const accepted = after.healthy || after.manual_fallback_ready;
|
|
15636
15606
|
return {
|
|
15637
|
-
runtimes: [{
|
|
15638
|
-
|
|
15639
|
-
|
|
15640
|
-
|
|
15641
|
-
skill_contracts_changed: accepted ? staleSnapshots.length : 0
|
|
15642
|
-
}],
|
|
15643
|
-
changed: accepted ? 1 : 0,
|
|
15644
|
-
failed: accepted ? 0 : 1,
|
|
15645
|
-
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
|
|
15646
15611
|
};
|
|
15647
15612
|
}
|
|
15648
15613
|
|
|
@@ -15706,11 +15671,11 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
15706
15671
|
continue;
|
|
15707
15672
|
knownTargets += 1;
|
|
15708
15673
|
const targetPath = expandPath(config.target_path);
|
|
15709
|
-
if (!
|
|
15674
|
+
if (!existsSync11(targetPath)) {
|
|
15710
15675
|
missingTargets += 1;
|
|
15711
15676
|
continue;
|
|
15712
15677
|
}
|
|
15713
|
-
const disk =
|
|
15678
|
+
const disk = readFileSync10(targetPath, "utf-8");
|
|
15714
15679
|
const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
|
|
15715
15680
|
if (redactedDisk !== config.content) {
|
|
15716
15681
|
driftedTargets += 1;
|
|
@@ -15801,8 +15766,8 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
15801
15766
|
};
|
|
15802
15767
|
}
|
|
15803
15768
|
// src/lib/provider-context.ts
|
|
15804
|
-
import { createHash as
|
|
15805
|
-
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";
|
|
15806
15771
|
import { join as join15 } from "path";
|
|
15807
15772
|
var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
|
|
15808
15773
|
var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
|
|
@@ -15938,8 +15903,8 @@ function renderPerEndpointFragment(entry) {
|
|
|
15938
15903
|
function renderProviderFragment(entry) {
|
|
15939
15904
|
return entry ? renderPerEndpointFragment(entry) : INVARIANT_FRAGMENT;
|
|
15940
15905
|
}
|
|
15941
|
-
function
|
|
15942
|
-
return
|
|
15906
|
+
function sha2568(content) {
|
|
15907
|
+
return createHash8("sha256").update(content).digest("hex");
|
|
15943
15908
|
}
|
|
15944
15909
|
function resolveAndRenderProviderContext(opts) {
|
|
15945
15910
|
const entry = matchProviderEndpoint(opts.origin);
|
|
@@ -15949,17 +15914,17 @@ function resolveAndRenderProviderContext(opts) {
|
|
|
15949
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;
|
|
15950
15915
|
const content = renderProviderFragment(entry);
|
|
15951
15916
|
const dir = join15(opts.homeDir, PROVIDER_CONTEXT_DIR);
|
|
15952
|
-
if (!
|
|
15953
|
-
|
|
15917
|
+
if (!existsSync12(dir))
|
|
15918
|
+
mkdirSync4(dir, { recursive: true });
|
|
15954
15919
|
const filename = `${entry ? entry.key : "invariant"}.md`;
|
|
15955
15920
|
const fragmentPath2 = join15(dir, filename);
|
|
15956
|
-
const fragmentSha256 =
|
|
15957
|
-
|
|
15921
|
+
const fragmentSha256 = sha2568(content);
|
|
15922
|
+
writeFileSync3(fragmentPath2, content, "utf8");
|
|
15958
15923
|
const manifestPath = join15(dir, PROVIDER_CONTEXT_MANIFEST);
|
|
15959
15924
|
let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
|
|
15960
15925
|
try {
|
|
15961
|
-
if (
|
|
15962
|
-
const parsed = JSON.parse(
|
|
15926
|
+
if (existsSync12(manifestPath)) {
|
|
15927
|
+
const parsed = JSON.parse(readFileSync11(manifestPath, "utf8"));
|
|
15963
15928
|
if (parsed && typeof parsed === "object")
|
|
15964
15929
|
manifest = parsed;
|
|
15965
15930
|
}
|
|
@@ -15974,7 +15939,7 @@ function resolveAndRenderProviderContext(opts) {
|
|
|
15974
15939
|
rawModel: opts.rawModel || null
|
|
15975
15940
|
};
|
|
15976
15941
|
manifest.fragments = fragmentsObj;
|
|
15977
|
-
|
|
15942
|
+
writeFileSync3(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
15978
15943
|
return {
|
|
15979
15944
|
entry,
|
|
15980
15945
|
rawEndpoint: opts.rawEndpoint,
|
|
@@ -16074,10 +16039,10 @@ init_session_render();
|
|
|
16074
16039
|
|
|
16075
16040
|
// src/lib/station-profile.ts
|
|
16076
16041
|
init_raw_store_root();
|
|
16077
|
-
import { spawnSync as
|
|
16078
|
-
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";
|
|
16079
16044
|
import { arch as osArch, homedir as homedir9, hostname as osHostname3, platform as osPlatform, userInfo as osUserInfo } from "os";
|
|
16080
|
-
import { dirname as
|
|
16045
|
+
import { dirname as dirname8, join as join16 } from "path";
|
|
16081
16046
|
var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
|
|
16082
16047
|
var STATION_PROFILE_SOURCE_ID = "station-profile";
|
|
16083
16048
|
var STATION_PROFILE_LAYER = "machine";
|
|
@@ -16101,9 +16066,9 @@ function getBunGlobalModulesDir(env = process.env) {
|
|
|
16101
16066
|
}
|
|
16102
16067
|
function readMachinesManifest(path) {
|
|
16103
16068
|
try {
|
|
16104
|
-
if (!
|
|
16069
|
+
if (!existsSync13(path))
|
|
16105
16070
|
return null;
|
|
16106
|
-
const parsed = JSON.parse(
|
|
16071
|
+
const parsed = JSON.parse(readFileSync12(path, "utf8"));
|
|
16107
16072
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
16108
16073
|
return null;
|
|
16109
16074
|
const machines = parsed["machines"];
|
|
@@ -16133,7 +16098,7 @@ function metadataUser(record) {
|
|
|
16133
16098
|
}
|
|
16134
16099
|
function probeMachineStatus(machineId) {
|
|
16135
16100
|
try {
|
|
16136
|
-
const result =
|
|
16101
|
+
const result = spawnSync3("machines", ["details", "--json", "--machine", machineId], {
|
|
16137
16102
|
encoding: "utf8",
|
|
16138
16103
|
timeout: 3000,
|
|
16139
16104
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -16159,7 +16124,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
|
|
|
16159
16124
|
const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname2);
|
|
16160
16125
|
const home = homeDir6(env);
|
|
16161
16126
|
const platform = stringField(record, "platform") ?? osPlatform();
|
|
16162
|
-
const workspacePath = stringField(record, "workspacePath")
|
|
16127
|
+
const workspacePath = stringField(record, "workspacePath");
|
|
16163
16128
|
const machine = {
|
|
16164
16129
|
id: stringField(record, "id") ?? hostname2,
|
|
16165
16130
|
hostname: stringField(record, "hostname") ?? hostname2,
|
|
@@ -16178,7 +16143,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
|
|
|
16178
16143
|
function scopedPackageNames(modulesDir, scope) {
|
|
16179
16144
|
const scopeDir = join16(modulesDir, scope);
|
|
16180
16145
|
try {
|
|
16181
|
-
if (!
|
|
16146
|
+
if (!existsSync13(scopeDir))
|
|
16182
16147
|
return null;
|
|
16183
16148
|
return readdirNames(scopeDir).sort();
|
|
16184
16149
|
} catch {
|
|
@@ -16198,7 +16163,7 @@ function resolveStationProfilePackages(env = process.env) {
|
|
|
16198
16163
|
const modulesDir = getBunGlobalModulesDir(env);
|
|
16199
16164
|
let scopeDirs;
|
|
16200
16165
|
try {
|
|
16201
|
-
if (!
|
|
16166
|
+
if (!existsSync13(modulesDir))
|
|
16202
16167
|
return null;
|
|
16203
16168
|
scopeDirs = readdirNames(modulesDir).filter((name) => name.startsWith("@") && name.toLowerCase().includes("hasna"));
|
|
16204
16169
|
} catch {
|
|
@@ -16273,10 +16238,10 @@ function refreshStationProfile(options = {}) {
|
|
|
16273
16238
|
const path = getStationProfileCachePath(env);
|
|
16274
16239
|
const generatedAt = new Date().toISOString();
|
|
16275
16240
|
if (!options.dryRun) {
|
|
16276
|
-
const existing =
|
|
16241
|
+
const existing = existsSync13(path) ? readFileSync12(path, "utf8") : null;
|
|
16277
16242
|
if (existing !== content) {
|
|
16278
|
-
|
|
16279
|
-
|
|
16243
|
+
mkdirSync5(dirname8(path), { recursive: true });
|
|
16244
|
+
writeFileSync4(path, content, "utf8");
|
|
16280
16245
|
}
|
|
16281
16246
|
}
|
|
16282
16247
|
return {
|
|
@@ -16292,9 +16257,9 @@ function refreshStationProfile(options = {}) {
|
|
|
16292
16257
|
function readStationProfile(env = process.env) {
|
|
16293
16258
|
const path = getStationProfileCachePath(env);
|
|
16294
16259
|
try {
|
|
16295
|
-
if (!
|
|
16260
|
+
if (!existsSync13(path))
|
|
16296
16261
|
return null;
|
|
16297
|
-
return
|
|
16262
|
+
return readFileSync12(path, "utf8");
|
|
16298
16263
|
} catch {
|
|
16299
16264
|
return null;
|
|
16300
16265
|
}
|
|
@@ -16318,23 +16283,172 @@ function stationProfileSource(env = process.env) {
|
|
|
16318
16283
|
init_instruction_graph();
|
|
16319
16284
|
init_asset_plan();
|
|
16320
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
|
+
}
|
|
16321
16436
|
// src/lib/session-apply.ts
|
|
16322
16437
|
init_project_context();
|
|
16323
16438
|
init_session_render_state();
|
|
16324
16439
|
init_session_render();
|
|
16325
16440
|
init_cursor_authority();
|
|
16326
16441
|
init_session_authority();
|
|
16327
|
-
import { createHash as
|
|
16442
|
+
import { createHash as createHash9, randomUUID as randomUUID4 } from "crypto";
|
|
16328
16443
|
import {
|
|
16329
|
-
existsSync as
|
|
16330
|
-
lstatSync as
|
|
16331
|
-
mkdirSync as
|
|
16332
|
-
readFileSync as
|
|
16444
|
+
existsSync as existsSync14,
|
|
16445
|
+
lstatSync as lstatSync7,
|
|
16446
|
+
mkdirSync as mkdirSync6,
|
|
16447
|
+
readFileSync as readFileSync13,
|
|
16333
16448
|
readdirSync as readdirSync3,
|
|
16334
|
-
statSync as
|
|
16449
|
+
statSync as statSync6
|
|
16335
16450
|
} from "fs";
|
|
16336
|
-
import { dirname as dirname10, isAbsolute as
|
|
16337
|
-
|
|
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";
|
|
16338
16452
|
class SessionApplyError extends Error {
|
|
16339
16453
|
constructor(message) {
|
|
16340
16454
|
super(message);
|
|
@@ -16356,15 +16470,23 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16356
16470
|
const targetHome = assertSafeTargetHome(plan.targetHome);
|
|
16357
16471
|
assertClaudeAuthorityStillClear(plan, targetHome, options.ownedClaudeAuthorities);
|
|
16358
16472
|
const payloadFiles = [...plan.files, ...plan.assetFiles ?? []];
|
|
16359
|
-
const files = [...payloadFiles, plan.manifestFile];
|
|
16360
16473
|
const manifestPath = resolvePlannedFilePath(plan, plan.manifestFile, targetHome);
|
|
16474
|
+
assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
|
|
16361
16475
|
const previousManifest = readPreviousManifest(manifestPath);
|
|
16362
16476
|
const previousHashes = previousManifest ? new Map(previousManifest.files.map((file) => [file.relativePath, file.sha256])) : new Map;
|
|
16477
|
+
const adoptions = validateFileAdoptions(plan, targetHome, previousHashes, options);
|
|
16478
|
+
const adoptedHashes = new Map(adoptions.map((entry) => [entry.relativePath, entry.preimageSha256]));
|
|
16479
|
+
const reconciliations = validateFileReconciliations(plan, targetHome, previousManifest, options);
|
|
16480
|
+
const reconciledHashes = new Map(reconciliations.map((entry) => [entry.relativePath, entry.preimageSha256]));
|
|
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);
|
|
16484
|
+
const files = [...payloadFiles, manifestFile];
|
|
16363
16485
|
const currentRelativePaths = new Set(files.map((file) => file.relativePath));
|
|
16364
16486
|
const drift = checkSessionRenderDrift(targetHome, manifestPath);
|
|
16365
16487
|
const results = [
|
|
16366
|
-
...files.map((file) => planFileResult(plan, file, targetHome, previousHashes, previousManifest, options)),
|
|
16367
|
-
...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options)
|
|
16488
|
+
...files.map((file) => planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes)),
|
|
16489
|
+
...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes)
|
|
16368
16490
|
];
|
|
16369
16491
|
assertNotSilentManagedWipeout(plan, results);
|
|
16370
16492
|
const conflicts = results.filter((result) => result.action === "conflict");
|
|
@@ -16386,7 +16508,10 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16386
16508
|
skippedSources: plan.manifest.skippedSources,
|
|
16387
16509
|
files: results,
|
|
16388
16510
|
conflicts,
|
|
16389
|
-
drift
|
|
16511
|
+
drift,
|
|
16512
|
+
adoptions,
|
|
16513
|
+
reconciliations,
|
|
16514
|
+
retirements
|
|
16390
16515
|
};
|
|
16391
16516
|
}
|
|
16392
16517
|
let snapshotPath = null;
|
|
@@ -16399,10 +16524,16 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16399
16524
|
if (!options.dryRun) {
|
|
16400
16525
|
const allowPortableFallback = coordination === null;
|
|
16401
16526
|
const forcePortableFileOps = options.test_hooks?.force_portable_file_ops ?? false;
|
|
16527
|
+
assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
|
|
16402
16528
|
ensureSessionTargetHome(targetHome);
|
|
16403
|
-
rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps);
|
|
16529
|
+
rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements);
|
|
16404
16530
|
snapshotPath = rollback.snapshotPath;
|
|
16405
16531
|
options.test_hooks?.before_apply_writes?.({ plan, results });
|
|
16532
|
+
assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
|
|
16533
|
+
for (const result of results) {
|
|
16534
|
+
assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
|
|
16535
|
+
coordination?.assert_held();
|
|
16536
|
+
}
|
|
16406
16537
|
const resultsByPath = new Map(results.map((result) => [result.path, result]));
|
|
16407
16538
|
for (const file of payloadFiles) {
|
|
16408
16539
|
applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
|
|
@@ -16422,7 +16553,7 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16422
16553
|
});
|
|
16423
16554
|
coordination?.assert_held();
|
|
16424
16555
|
}
|
|
16425
|
-
applyPlannedFile(plan,
|
|
16556
|
+
applyPlannedFile(plan, manifestFile, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
|
|
16426
16557
|
}
|
|
16427
16558
|
return {
|
|
16428
16559
|
dryRun: options.dryRun ?? false,
|
|
@@ -16436,9 +16567,233 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16436
16567
|
skippedSources: plan.manifest.skippedSources,
|
|
16437
16568
|
files: results,
|
|
16438
16569
|
conflicts,
|
|
16439
|
-
drift
|
|
16570
|
+
drift,
|
|
16571
|
+
adoptions,
|
|
16572
|
+
reconciliations,
|
|
16573
|
+
retirements
|
|
16440
16574
|
};
|
|
16441
16575
|
}
|
|
16576
|
+
function assertManifestPrecondition(path, targetHome, expected) {
|
|
16577
|
+
if (expected === undefined)
|
|
16578
|
+
return;
|
|
16579
|
+
if (!/^[a-f0-9]{64}$/.test(expected)) {
|
|
16580
|
+
throw new SessionApplyError("Expected session manifest SHA-256 must contain 64 lowercase hexadecimal characters.");
|
|
16581
|
+
}
|
|
16582
|
+
if (currentSessionFileHash(path, targetHome) !== expected) {
|
|
16583
|
+
throw new SessionApplyError("Session manifest SHA-256 precondition failed; reload the manifest and resolve a new plan.");
|
|
16584
|
+
}
|
|
16585
|
+
}
|
|
16586
|
+
function validateFileAdoptions(plan, targetHome, previousHashes, options) {
|
|
16587
|
+
const requests = options.adoptFiles ?? [];
|
|
16588
|
+
if (!Array.isArray(requests))
|
|
16589
|
+
throw new SessionApplyError("Session file adoptions must be an array.");
|
|
16590
|
+
if (requests.length > 0 && options.force) {
|
|
16591
|
+
throw new SessionApplyError("Exact file adoption cannot be combined with force.");
|
|
16592
|
+
}
|
|
16593
|
+
const seen = new Set;
|
|
16594
|
+
return requests.map((request) => {
|
|
16595
|
+
if (!request || typeof request.relativePath !== "string" || typeof request.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(request.sha256)) {
|
|
16596
|
+
throw new SessionApplyError("Each file adoption requires a plan-relative path and a 64-character lowercase SHA-256.");
|
|
16597
|
+
}
|
|
16598
|
+
if (seen.has(request.relativePath)) {
|
|
16599
|
+
throw new SessionApplyError(`Duplicate file adoption target: ${request.relativePath}`);
|
|
16600
|
+
}
|
|
16601
|
+
seen.add(request.relativePath);
|
|
16602
|
+
const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
|
|
16603
|
+
if (candidates.length !== 1 || candidates[0].role === "manifest") {
|
|
16604
|
+
throw new SessionApplyError(`File adoption target is not a unique planned instruction output: ${request.relativePath}`);
|
|
16605
|
+
}
|
|
16606
|
+
const file = candidates[0];
|
|
16607
|
+
const path = resolvePlannedFilePath(plan, file, targetHome);
|
|
16608
|
+
if (previousHashes.has(file.relativePath) || sessionRenderOwnsPath(path)) {
|
|
16609
|
+
throw new SessionApplyError(`File adoption target is already managed: ${request.relativePath}`);
|
|
16610
|
+
}
|
|
16611
|
+
const observedSha256 = readExactFilePreimage(path, targetHome, request, "adoption");
|
|
16612
|
+
return {
|
|
16613
|
+
path,
|
|
16614
|
+
relativePath: file.relativePath,
|
|
16615
|
+
preimageSha256: observedSha256,
|
|
16616
|
+
renderedSha256: file.sha256,
|
|
16617
|
+
sourceIds: [...file.sourceIds]
|
|
16618
|
+
};
|
|
16619
|
+
});
|
|
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
|
+
}
|
|
16625
|
+
function readExactFilePreimage(path, targetHome, request, operation) {
|
|
16626
|
+
if (currentSessionFileHash(path, targetHome) === null) {
|
|
16627
|
+
throw new SessionApplyError(`File ${operation} target does not exist: ${request.relativePath}`);
|
|
16628
|
+
}
|
|
16629
|
+
const bytes = readFileSync13(path);
|
|
16630
|
+
if (!bytes.equals(Buffer.from(bytes.toString("utf8"), "utf8"))) {
|
|
16631
|
+
throw new SessionApplyError(`File ${operation} target must contain losslessly restorable UTF-8: ${request.relativePath}`);
|
|
16632
|
+
}
|
|
16633
|
+
const observedSha256 = createHash9("sha256").update(bytes).digest("hex");
|
|
16634
|
+
if (observedSha256 !== request.sha256) {
|
|
16635
|
+
throw new SessionApplyError(`File ${operation} SHA-256 precondition failed: ${request.relativePath}`);
|
|
16636
|
+
}
|
|
16637
|
+
return observedSha256;
|
|
16638
|
+
}
|
|
16639
|
+
function validateFileReconciliations(plan, targetHome, previousManifest, options) {
|
|
16640
|
+
const requests = options.reconcileFiles ?? [];
|
|
16641
|
+
if (!Array.isArray(requests))
|
|
16642
|
+
throw new SessionApplyError("Session file reconciliations must be an array.");
|
|
16643
|
+
if (requests.length === 0)
|
|
16644
|
+
return [];
|
|
16645
|
+
if (options.force)
|
|
16646
|
+
throw new SessionApplyError("Exact file reconciliation cannot be combined with force.");
|
|
16647
|
+
if (options.expectedManifestSha256 === undefined) {
|
|
16648
|
+
throw new SessionApplyError("Exact file reconciliation requires an expected manifest SHA-256 precondition.");
|
|
16649
|
+
}
|
|
16650
|
+
if (!previousManifest || previousManifest.targetOwner?.writer?.id !== SESSION_RENDERER_OWNER_ID || previousManifest.tool !== plan.tool || previousManifest.targetHome !== targetHome) {
|
|
16651
|
+
throw new SessionApplyError("File reconciliation requires this renderer's manifest for the same tool and target home.");
|
|
16652
|
+
}
|
|
16653
|
+
const seen = new Set;
|
|
16654
|
+
return requests.map((request) => {
|
|
16655
|
+
if (!request || typeof request.relativePath !== "string" || typeof request.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(request.sha256)) {
|
|
16656
|
+
throw new SessionApplyError("Each file reconciliation requires a plan-relative path and a 64-character lowercase SHA-256.");
|
|
16657
|
+
}
|
|
16658
|
+
if (seen.has(request.relativePath)) {
|
|
16659
|
+
throw new SessionApplyError(`Duplicate file reconciliation target: ${request.relativePath}`);
|
|
16660
|
+
}
|
|
16661
|
+
seen.add(request.relativePath);
|
|
16662
|
+
const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
|
|
16663
|
+
if (candidates.length !== 1 || candidates[0].role === "manifest") {
|
|
16664
|
+
throw new SessionApplyError(`File reconciliation target is not a unique planned instruction output: ${request.relativePath}`);
|
|
16665
|
+
}
|
|
16666
|
+
const file = candidates[0];
|
|
16667
|
+
const path = resolvePlannedFilePath(plan, file, targetHome);
|
|
16668
|
+
const owned = previousManifest.files.filter((entry) => entry.relativePath === file.relativePath);
|
|
16669
|
+
if (owned.length !== 1 || owned[0].path !== path || owned[0].role !== file.role || !/^[a-f0-9]{64}$/.test(owned[0].sha256)) {
|
|
16670
|
+
throw new SessionApplyError(`File reconciliation target is not owned by the observed manifest: ${request.relativePath}`);
|
|
16671
|
+
}
|
|
16672
|
+
const observedSha256 = readExactFilePreimage(path, targetHome, request, "reconciliation");
|
|
16673
|
+
if (observedSha256 === owned[0].sha256) {
|
|
16674
|
+
throw new SessionApplyError(`File reconciliation target has no managed drift: ${request.relativePath}`);
|
|
16675
|
+
}
|
|
16676
|
+
if (observedSha256 === file.sha256) {
|
|
16677
|
+
throw new SessionApplyError(`File reconciliation target already matches the planned output: ${request.relativePath}`);
|
|
16678
|
+
}
|
|
16679
|
+
return {
|
|
16680
|
+
path,
|
|
16681
|
+
relativePath: file.relativePath,
|
|
16682
|
+
preimageSha256: observedSha256,
|
|
16683
|
+
renderedSha256: file.sha256,
|
|
16684
|
+
previousManagedSha256: owned[0].sha256,
|
|
16685
|
+
sourceIds: [...file.sourceIds]
|
|
16686
|
+
};
|
|
16687
|
+
});
|
|
16688
|
+
}
|
|
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)
|
|
16730
|
+
return plan.manifestFile;
|
|
16731
|
+
if (previousManifest?.adoptions !== undefined && !Array.isArray(previousManifest.adoptions)) {
|
|
16732
|
+
throw new SessionApplyError("Previous session manifest adoption provenance is invalid.");
|
|
16733
|
+
}
|
|
16734
|
+
const entries = new Map;
|
|
16735
|
+
for (const entry of [
|
|
16736
|
+
...previousManifest?.adoptions ?? [],
|
|
16737
|
+
...adoptions.map(({ path: _path, ...receipt }) => receipt)
|
|
16738
|
+
]) {
|
|
16739
|
+
if (!entry || typeof entry.relativePath !== "string" || !/^[a-f0-9]{64}$/.test(entry.preimageSha256) || !/^[a-f0-9]{64}$/.test(entry.renderedSha256) || !Array.isArray(entry.sourceIds) || entry.sourceIds.some((id) => typeof id !== "string")) {
|
|
16740
|
+
throw new SessionApplyError("Previous session manifest adoption provenance is invalid.");
|
|
16741
|
+
}
|
|
16742
|
+
resolveManifestRelativePath(entry.relativePath, plan.targetHome);
|
|
16743
|
+
const previous = entries.get(entry.relativePath);
|
|
16744
|
+
if (previous && JSON.stringify(previous) !== JSON.stringify(entry)) {
|
|
16745
|
+
throw new SessionApplyError(`Conflicting session file adoption provenance: ${entry.relativePath}`);
|
|
16746
|
+
}
|
|
16747
|
+
entries.set(entry.relativePath, entry);
|
|
16748
|
+
}
|
|
16749
|
+
if (previousManifest?.reconciliations !== undefined && !Array.isArray(previousManifest.reconciliations)) {
|
|
16750
|
+
throw new SessionApplyError("Previous session manifest reconciliation provenance is invalid.");
|
|
16751
|
+
}
|
|
16752
|
+
const reconciledEntries = new Map;
|
|
16753
|
+
for (const entry of [
|
|
16754
|
+
...previousManifest?.reconciliations ?? [],
|
|
16755
|
+
...reconciliations.map(({ path: _path, ...receipt }) => receipt)
|
|
16756
|
+
]) {
|
|
16757
|
+
if (!entry || typeof entry.relativePath !== "string" || !/^[a-f0-9]{64}$/.test(entry.preimageSha256) || !/^[a-f0-9]{64}$/.test(entry.renderedSha256) || !/^[a-f0-9]{64}$/.test(entry.previousManagedSha256) || !Array.isArray(entry.sourceIds) || entry.sourceIds.some((id) => typeof id !== "string")) {
|
|
16758
|
+
throw new SessionApplyError("Previous session manifest reconciliation provenance is invalid.");
|
|
16759
|
+
}
|
|
16760
|
+
resolveManifestRelativePath(entry.relativePath, plan.targetHome);
|
|
16761
|
+
const canonical = {
|
|
16762
|
+
relativePath: entry.relativePath,
|
|
16763
|
+
preimageSha256: entry.preimageSha256,
|
|
16764
|
+
renderedSha256: entry.renderedSha256,
|
|
16765
|
+
previousManagedSha256: entry.previousManagedSha256,
|
|
16766
|
+
sourceIds: [...entry.sourceIds]
|
|
16767
|
+
};
|
|
16768
|
+
reconciledEntries.set(JSON.stringify(canonical), canonical);
|
|
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
|
+
}
|
|
16787
|
+
const manifest = {
|
|
16788
|
+
...plan.manifest,
|
|
16789
|
+
...adoptions.length > 0 || previousManifest?.adoptions !== undefined ? { adoptions: [...entries.values()] } : {},
|
|
16790
|
+
...reconciliations.length > 0 || previousManifest?.reconciliations !== undefined ? { reconciliations: [...reconciledEntries.values()] } : {},
|
|
16791
|
+
...retirements.length > 0 || previousManifest?.retirements !== undefined ? { retirements: [...retiredEntries.values()] } : {}
|
|
16792
|
+
};
|
|
16793
|
+
const content = `${JSON.stringify(manifest, null, 2)}
|
|
16794
|
+
`;
|
|
16795
|
+
return { ...plan.manifestFile, content, sha256: sha2569(content) };
|
|
16796
|
+
}
|
|
16442
16797
|
function assertCursorAuthorityUnchanged(plan) {
|
|
16443
16798
|
if (plan.tool !== "cursor" || plan.targetKind === "blocked")
|
|
16444
16799
|
return;
|
|
@@ -16465,13 +16820,13 @@ function assertClaudeAuthorityStillClear(plan, targetHome, ownedClaudeAuthoritie
|
|
|
16465
16820
|
throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
|
|
16466
16821
|
}
|
|
16467
16822
|
function ensureSessionTargetHome(targetHome) {
|
|
16468
|
-
if (!
|
|
16469
|
-
|
|
16823
|
+
if (!existsSync14(targetHome))
|
|
16824
|
+
mkdirSync6(targetHome, { recursive: true, mode: 448 });
|
|
16470
16825
|
assertSafeTargetHome(targetHome);
|
|
16471
16826
|
}
|
|
16472
16827
|
function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
16473
16828
|
const safeTargetHome = assertSafeTargetHome(targetHome);
|
|
16474
|
-
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(
|
|
16829
|
+
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve12(manifestPath)), safeTargetHome) : resolve12(safeTargetHome, ".hasna", "session-render-manifest.json");
|
|
16475
16830
|
const checkedAt = new Date().toISOString();
|
|
16476
16831
|
const previousManifest = readPreviousManifest(resolvedManifestPath);
|
|
16477
16832
|
if (!previousManifest) {
|
|
@@ -16488,7 +16843,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
16488
16843
|
const drifted = [];
|
|
16489
16844
|
for (const file of previousManifest.files) {
|
|
16490
16845
|
const target = resolveManifestRelativePath(file.relativePath, safeTargetHome);
|
|
16491
|
-
if (!
|
|
16846
|
+
if (!existsSync14(target)) {
|
|
16492
16847
|
missing.push({
|
|
16493
16848
|
path: target,
|
|
16494
16849
|
relativePath: file.relativePath,
|
|
@@ -16498,7 +16853,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
16498
16853
|
});
|
|
16499
16854
|
continue;
|
|
16500
16855
|
}
|
|
16501
|
-
const actualSha256 =
|
|
16856
|
+
const actualSha256 = sha2569(readFileSync13(target, "utf-8"));
|
|
16502
16857
|
if (actualSha256 !== file.sha256) {
|
|
16503
16858
|
drifted.push({
|
|
16504
16859
|
path: target,
|
|
@@ -16521,13 +16876,13 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
16521
16876
|
function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
|
|
16522
16877
|
const snapshot = readSessionRenderSnapshot(snapshotPath);
|
|
16523
16878
|
const targetHome = assertSafeTargetHome(snapshot.targetHome);
|
|
16524
|
-
const resolvedSnapshotPath =
|
|
16879
|
+
const resolvedSnapshotPath = resolve12(snapshotPath);
|
|
16525
16880
|
const snapshotDir = getSessionRenderSnapshotDir(targetHome);
|
|
16526
|
-
const snapshotDirRelative =
|
|
16527
|
-
const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !
|
|
16881
|
+
const snapshotDirRelative = relative5(snapshotDir, resolvedSnapshotPath);
|
|
16882
|
+
const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute8(snapshotDirRelative);
|
|
16528
16883
|
if (!insideSnapshotDir) {
|
|
16529
|
-
const snapshotRelativePath =
|
|
16530
|
-
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") ||
|
|
16884
|
+
const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
|
|
16885
|
+
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute8(snapshotRelativePath)) {
|
|
16531
16886
|
throw new SessionApplyError("Session snapshot must be stored inside its session-render snapshot location.");
|
|
16532
16887
|
}
|
|
16533
16888
|
}
|
|
@@ -16644,19 +16999,19 @@ function requiredRestoreHash(file) {
|
|
|
16644
16999
|
return file.previousSha256;
|
|
16645
17000
|
}
|
|
16646
17001
|
function readSessionRenderSnapshot(snapshotPath) {
|
|
16647
|
-
const resolved =
|
|
16648
|
-
if (!
|
|
17002
|
+
const resolved = resolve12(snapshotPath);
|
|
17003
|
+
if (!existsSync14(resolved))
|
|
16649
17004
|
throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
|
|
16650
|
-
const stat =
|
|
17005
|
+
const stat = lstatSync7(resolved);
|
|
16651
17006
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
16652
17007
|
throw new SessionApplyError(`Session snapshot is not a regular file: ${snapshotPath}`);
|
|
16653
17008
|
}
|
|
16654
|
-
if (
|
|
17009
|
+
if (statSync6(resolved).size > 32 * 1024 * 1024) {
|
|
16655
17010
|
throw new SessionApplyError(`Session snapshot exceeds the 32 MiB restore limit: ${snapshotPath}`);
|
|
16656
17011
|
}
|
|
16657
17012
|
let parsed;
|
|
16658
17013
|
try {
|
|
16659
|
-
parsed = JSON.parse(
|
|
17014
|
+
parsed = JSON.parse(readFileSync13(resolved, "utf8"));
|
|
16660
17015
|
} catch {
|
|
16661
17016
|
throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
|
|
16662
17017
|
}
|
|
@@ -16678,7 +17033,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
16678
17033
|
const previousManifest = snapshot.previousManifest;
|
|
16679
17034
|
const previousFiles = new Map;
|
|
16680
17035
|
for (const file of snapshot.files) {
|
|
16681
|
-
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) {
|
|
16682
17037
|
throw new SessionApplyError(`Session snapshot previous file metadata is invalid: ${snapshotPath}`);
|
|
16683
17038
|
}
|
|
16684
17039
|
resolveSnapshotFilePath(file.relativePath, file.path, targetHome);
|
|
@@ -16725,8 +17080,8 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
16725
17080
|
}
|
|
16726
17081
|
function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
|
|
16727
17082
|
assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
|
|
16728
|
-
const manifestPath =
|
|
16729
|
-
const manifestRelativePath =
|
|
17083
|
+
const manifestPath = resolve12(snapshot.manifestPath);
|
|
17084
|
+
const manifestRelativePath = relative5(targetHome, manifestPath).replaceAll("\\", "/");
|
|
16730
17085
|
resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
|
|
16731
17086
|
const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
|
|
16732
17087
|
if (manifestSha256 === null) {
|
|
@@ -16734,7 +17089,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
16734
17089
|
}
|
|
16735
17090
|
let parsedManifest;
|
|
16736
17091
|
try {
|
|
16737
|
-
parsedManifest = JSON.parse(
|
|
17092
|
+
parsedManifest = JSON.parse(readFileSync13(manifestPath, "utf8"));
|
|
16738
17093
|
} catch {
|
|
16739
17094
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
|
|
16740
17095
|
}
|
|
@@ -16742,7 +17097,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
16742
17097
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
|
|
16743
17098
|
}
|
|
16744
17099
|
const appliedManifest = parsedManifest;
|
|
16745
|
-
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)) {
|
|
16746
17101
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
|
|
16747
17102
|
}
|
|
16748
17103
|
const afterFiles = [];
|
|
@@ -16827,16 +17182,16 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
|
|
|
16827
17182
|
throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
|
|
16828
17183
|
}
|
|
16829
17184
|
for (const entry of readdirSync3(dirname10(snapshotPath))) {
|
|
16830
|
-
const candidatePath =
|
|
16831
|
-
if (candidatePath ===
|
|
17185
|
+
const candidatePath = resolve12(dirname10(snapshotPath), entry);
|
|
17186
|
+
if (candidatePath === resolve12(snapshotPath) || !entry.endsWith(".json"))
|
|
16832
17187
|
continue;
|
|
16833
|
-
const candidateStat =
|
|
17188
|
+
const candidateStat = lstatSync7(candidatePath);
|
|
16834
17189
|
if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
|
|
16835
17190
|
continue;
|
|
16836
17191
|
try {
|
|
16837
|
-
const candidate = JSON.parse(
|
|
17192
|
+
const candidate = JSON.parse(readFileSync13(candidatePath, "utf8"));
|
|
16838
17193
|
const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
|
|
16839
|
-
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) {
|
|
16840
17195
|
throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
|
|
16841
17196
|
}
|
|
16842
17197
|
} catch (error) {
|
|
@@ -16894,7 +17249,7 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
16894
17249
|
return "create";
|
|
16895
17250
|
}
|
|
16896
17251
|
if (file.role === "manifest" && previousManifest) {
|
|
16897
|
-
const previousManifestSha256 =
|
|
17252
|
+
const previousManifestSha256 = sha2569(`${JSON.stringify(previousManifest, null, 2)}
|
|
16898
17253
|
`);
|
|
16899
17254
|
if (previousManifestSha256 !== file.sha256) {
|
|
16900
17255
|
throw new SessionApplyError(`Cannot infer legacy v1 manifest action without a before-image: ${file.relativePath}`);
|
|
@@ -16905,17 +17260,34 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
16905
17260
|
}
|
|
16906
17261
|
function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
|
|
16907
17262
|
const path = resolveManifestRelativePath(relativePath, targetHome);
|
|
16908
|
-
if (
|
|
17263
|
+
if (resolve12(recordedPath) !== path) {
|
|
16909
17264
|
throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
|
|
16910
17265
|
}
|
|
16911
17266
|
return path;
|
|
16912
17267
|
}
|
|
16913
|
-
function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
|
|
17268
|
+
function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes) {
|
|
16914
17269
|
const target = resolvePlannedFilePath(plan, file, targetHome);
|
|
16915
|
-
const previousContent =
|
|
16916
|
-
const previousSha256 = previousContent === null ? null :
|
|
17270
|
+
const previousContent = existsSync14(target) ? readFileSync13(target, "utf-8") : null;
|
|
17271
|
+
const previousSha256 = previousContent === null ? null : sha2569(previousContent);
|
|
16917
17272
|
const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
|
|
16918
17273
|
const changed = previousContent !== file.content;
|
|
17274
|
+
const exactSha256 = adoptedHashes.get(file.relativePath) ?? reconciledHashes.get(file.relativePath);
|
|
17275
|
+
if (exactSha256 !== undefined) {
|
|
17276
|
+
const operation = adoptedHashes.has(file.relativePath) ? "adoption" : "reconciliation";
|
|
17277
|
+
if (previousSha256 !== exactSha256) {
|
|
17278
|
+
throw new SessionApplyError(`File ${operation} SHA-256 precondition failed: ${file.relativePath}`);
|
|
17279
|
+
}
|
|
17280
|
+
return {
|
|
17281
|
+
path: target,
|
|
17282
|
+
relativePath: file.relativePath,
|
|
17283
|
+
role: file.role,
|
|
17284
|
+
action: "update",
|
|
17285
|
+
changed,
|
|
17286
|
+
previousSha256,
|
|
17287
|
+
newSha256: file.sha256,
|
|
17288
|
+
reason: `exact observed SHA-256 ${operation}`
|
|
17289
|
+
};
|
|
17290
|
+
}
|
|
16919
17291
|
if (previousContent !== null && !options.force && !previouslyManaged) {
|
|
16920
17292
|
return {
|
|
16921
17293
|
path: target,
|
|
@@ -17004,17 +17376,55 @@ function isPlanManagedFile(plan, relativePath, role) {
|
|
|
17004
17376
|
return true;
|
|
17005
17377
|
return plan.tool === "claude" && role === "rule" && relativePath.startsWith("rules/");
|
|
17006
17378
|
}
|
|
17007
|
-
function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options) {
|
|
17379
|
+
function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes) {
|
|
17008
17380
|
if (!previousManifest)
|
|
17009
17381
|
return [];
|
|
17010
|
-
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);
|
|
17011
17383
|
}
|
|
17012
|
-
function planStaleFileResult(file, targetHome, options) {
|
|
17384
|
+
function planStaleFileResult(file, targetHome, options, retiredHash) {
|
|
17013
17385
|
const target = resolveManifestRelativePath(file.relativePath, targetHome);
|
|
17014
|
-
if (!
|
|
17015
|
-
|
|
17016
|
-
|
|
17017
|
-
|
|
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
|
+
}
|
|
17018
17428
|
if (!options.force && previousSha256 !== file.sha256) {
|
|
17019
17429
|
return {
|
|
17020
17430
|
path: target,
|
|
@@ -17058,31 +17468,31 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
|
|
|
17058
17468
|
return previousHashes.get(file.relativePath) === previousSha256;
|
|
17059
17469
|
}
|
|
17060
17470
|
function resolvePlannedFilePath(plan, file, targetHome) {
|
|
17061
|
-
const target =
|
|
17062
|
-
const rel =
|
|
17063
|
-
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)) {
|
|
17064
17474
|
throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
|
|
17065
17475
|
}
|
|
17066
|
-
if (
|
|
17476
|
+
if (resolve12(file.path) !== target) {
|
|
17067
17477
|
throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
|
|
17068
17478
|
}
|
|
17069
17479
|
assertNoSymlinkSegments2(targetHome, target);
|
|
17070
17480
|
return target;
|
|
17071
17481
|
}
|
|
17072
17482
|
function resolveManifestRelativePath(relativePath, targetHome) {
|
|
17073
|
-
const target =
|
|
17074
|
-
const rel =
|
|
17075
|
-
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)) {
|
|
17076
17486
|
throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
|
|
17077
17487
|
}
|
|
17078
17488
|
assertNoSymlinkSegments2(targetHome, target);
|
|
17079
17489
|
return target;
|
|
17080
17490
|
}
|
|
17081
17491
|
function readPreviousManifest(path) {
|
|
17082
|
-
if (!
|
|
17492
|
+
if (!existsSync14(path))
|
|
17083
17493
|
return null;
|
|
17084
17494
|
try {
|
|
17085
|
-
const parsed = JSON.parse(
|
|
17495
|
+
const parsed = JSON.parse(readFileSync13(path, "utf-8"));
|
|
17086
17496
|
if (parsed.schema !== SESSION_RENDER_SCHEMA)
|
|
17087
17497
|
return null;
|
|
17088
17498
|
if (!Array.isArray(parsed.files))
|
|
@@ -17116,18 +17526,18 @@ function applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, a
|
|
|
17116
17526
|
function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
|
|
17117
17527
|
const actualHash = currentSessionFileHash(path, targetHome);
|
|
17118
17528
|
if (actualHash !== expectedHash) {
|
|
17119
|
-
throw new SessionApplyError(`Session apply path changed after planning: ${
|
|
17529
|
+
throw new SessionApplyError(`Session apply path changed after planning: ${relative5(targetHome, path)}`);
|
|
17120
17530
|
}
|
|
17121
17531
|
}
|
|
17122
17532
|
function currentSessionFileHash(path, targetHome) {
|
|
17123
17533
|
assertNoSymlinkSegments2(targetHome, path);
|
|
17124
|
-
if (!
|
|
17534
|
+
if (!existsSync14(path))
|
|
17125
17535
|
return null;
|
|
17126
|
-
const stat =
|
|
17536
|
+
const stat = lstatSync7(path);
|
|
17127
17537
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
17128
17538
|
throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
|
|
17129
17539
|
}
|
|
17130
|
-
return
|
|
17540
|
+
return sha2569(readFileSync13(path, "utf-8"));
|
|
17131
17541
|
}
|
|
17132
17542
|
function requiredPreviousHash(result) {
|
|
17133
17543
|
if (result.previousSha256 === null) {
|
|
@@ -17135,14 +17545,18 @@ function requiredPreviousHash(result) {
|
|
|
17135
17545
|
}
|
|
17136
17546
|
return result.previousSha256;
|
|
17137
17547
|
}
|
|
17138
|
-
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
|
|
17139
|
-
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").
|
|
17140
|
-
|
|
17548
|
+
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements) {
|
|
17549
|
+
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").map((result) => {
|
|
17550
|
+
assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
|
|
17551
|
+
const content = readFileSync13(result.path, "utf-8");
|
|
17552
|
+
if (sha2569(content) !== result.previousSha256) {
|
|
17553
|
+
throw new SessionApplyError(`Session snapshot preimage changed after planning: ${result.relativePath}`);
|
|
17554
|
+
}
|
|
17141
17555
|
return {
|
|
17142
17556
|
path: result.path,
|
|
17143
17557
|
relativePath: result.relativePath,
|
|
17144
17558
|
role: result.role,
|
|
17145
|
-
sha256:
|
|
17559
|
+
sha256: sha2569(content),
|
|
17146
17560
|
content
|
|
17147
17561
|
};
|
|
17148
17562
|
});
|
|
@@ -17155,7 +17569,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
17155
17569
|
};
|
|
17156
17570
|
}
|
|
17157
17571
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
17158
|
-
const snapshotPath =
|
|
17572
|
+
const snapshotPath = join18(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID4()}.json`);
|
|
17159
17573
|
const afterFiles = results.map((result) => {
|
|
17160
17574
|
if (result.action === "conflict") {
|
|
17161
17575
|
throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
|
|
@@ -17178,13 +17592,20 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
17178
17592
|
manifestPath,
|
|
17179
17593
|
previousManifest,
|
|
17180
17594
|
files: existingFiles,
|
|
17181
|
-
afterFiles
|
|
17595
|
+
afterFiles,
|
|
17596
|
+
...adoptions.length > 0 ? { adoptions } : {},
|
|
17597
|
+
...reconciliations.length > 0 ? { reconciliations } : {},
|
|
17598
|
+
...retirements.length > 0 ? { retirements } : {}
|
|
17182
17599
|
};
|
|
17600
|
+
const snapshotContent = `${JSON.stringify(snapshot, null, 2)}
|
|
17601
|
+
`;
|
|
17602
|
+
if ((adoptions.length > 0 || reconciliations.length > 0 || retirements.length > 0) && Buffer.byteLength(snapshotContent, "utf8") > 32 * 1024 * 1024) {
|
|
17603
|
+
throw new SessionApplyError("Exact file preimage snapshot exceeds the 32 MiB restore limit.");
|
|
17604
|
+
}
|
|
17183
17605
|
coordination?.assert_held();
|
|
17184
17606
|
writeProjectContextCoordinatedFile({
|
|
17185
17607
|
path: snapshotPath,
|
|
17186
|
-
content:
|
|
17187
|
-
`,
|
|
17608
|
+
content: snapshotContent,
|
|
17188
17609
|
workspace_root: sessionRenderSnapshotWorkspaceRoot(targetHome),
|
|
17189
17610
|
default_mode: 384,
|
|
17190
17611
|
expected_hash: null,
|
|
@@ -17201,45 +17622,262 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
17201
17622
|
};
|
|
17202
17623
|
}
|
|
17203
17624
|
function assertSafeTargetHome(targetHome) {
|
|
17204
|
-
if (!
|
|
17625
|
+
if (!isAbsolute8(targetHome))
|
|
17205
17626
|
throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
|
|
17206
|
-
const normalized =
|
|
17207
|
-
if (normalized ===
|
|
17627
|
+
const normalized = resolve12(targetHome);
|
|
17628
|
+
if (normalized === parse4(normalized).root) {
|
|
17208
17629
|
throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
|
|
17209
17630
|
}
|
|
17210
|
-
|
|
17211
|
-
if (
|
|
17631
|
+
assertNoSymlinkAncestors2(normalized);
|
|
17632
|
+
if (existsSync14(normalized) && lstatSync7(normalized).isSymbolicLink()) {
|
|
17212
17633
|
throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
|
|
17213
17634
|
}
|
|
17214
17635
|
return normalized;
|
|
17215
17636
|
}
|
|
17216
17637
|
function assertNoSymlinkSegments2(root, target) {
|
|
17217
|
-
|
|
17218
|
-
const rel =
|
|
17638
|
+
assertNoSymlinkAncestors2(root);
|
|
17639
|
+
const rel = relative5(root, target);
|
|
17219
17640
|
let current = root;
|
|
17220
17641
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17221
|
-
current =
|
|
17222
|
-
if (
|
|
17642
|
+
current = join18(current, segment);
|
|
17643
|
+
if (existsSync14(current) && lstatSync7(current).isSymbolicLink()) {
|
|
17223
17644
|
throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
|
|
17224
17645
|
}
|
|
17225
17646
|
}
|
|
17226
17647
|
}
|
|
17227
|
-
function
|
|
17228
|
-
const normalized =
|
|
17229
|
-
const parsed =
|
|
17648
|
+
function assertNoSymlinkAncestors2(path) {
|
|
17649
|
+
const normalized = resolve12(path);
|
|
17650
|
+
const parsed = parse4(normalized);
|
|
17230
17651
|
let current = parsed.root;
|
|
17231
|
-
const rel =
|
|
17652
|
+
const rel = relative5(parsed.root, normalized);
|
|
17232
17653
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17233
|
-
current =
|
|
17234
|
-
if (!
|
|
17654
|
+
current = join18(current, segment);
|
|
17655
|
+
if (!existsSync14(current))
|
|
17235
17656
|
return;
|
|
17236
|
-
if (
|
|
17657
|
+
if (lstatSync7(current).isSymbolicLink()) {
|
|
17237
17658
|
throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
|
|
17238
17659
|
}
|
|
17239
17660
|
}
|
|
17240
17661
|
}
|
|
17241
|
-
function
|
|
17242
|
-
return
|
|
17662
|
+
function sha2569(content) {
|
|
17663
|
+
return createHash9("sha256").update(content).digest("hex");
|
|
17664
|
+
}
|
|
17665
|
+
// src/lib/session-refresh.ts
|
|
17666
|
+
init_zod();
|
|
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";
|
|
17670
|
+
|
|
17671
|
+
// src/lib/global-source-coverage.ts
|
|
17672
|
+
var GLOBAL_SOURCE_SLUG_PREFIX = "global-";
|
|
17673
|
+
function expectedGlobalSourceSlugs(registryConfigs) {
|
|
17674
|
+
return registryConfigs.filter((c) => c.slug.startsWith(GLOBAL_SOURCE_SLUG_PREFIX)).filter((c) => instructionSourceRejection(c) === null).map((c) => c.slug).sort();
|
|
17675
|
+
}
|
|
17676
|
+
function accountedGlobalSourceSlugs(manifest) {
|
|
17677
|
+
return [...manifest.sources, ...manifest.skippedSources].map((source) => source.id).filter((id) => id.startsWith(GLOBAL_SOURCE_SLUG_PREFIX));
|
|
17678
|
+
}
|
|
17679
|
+
function computeGlobalSourceCoverage(registryConfigs, configuredSlugs) {
|
|
17680
|
+
const expected = expectedGlobalSourceSlugs(registryConfigs);
|
|
17681
|
+
const expectedSet = new Set(expected);
|
|
17682
|
+
const configuredSet = new Set(configuredSlugs);
|
|
17683
|
+
const missing = expected.filter((slug2) => !configuredSet.has(slug2));
|
|
17684
|
+
const unexpected = [...configuredSet].filter((slug2) => slug2.startsWith(GLOBAL_SOURCE_SLUG_PREFIX) && !expectedSet.has(slug2)).sort();
|
|
17685
|
+
return {
|
|
17686
|
+
expectedSlugs: expected,
|
|
17687
|
+
excludedSources: registryConfigs.filter((config) => config.slug.startsWith(GLOBAL_SOURCE_SLUG_PREFIX)).flatMap((config) => {
|
|
17688
|
+
const reason = instructionSourceRejection(config);
|
|
17689
|
+
return reason ? [{ slug: config.slug, reason }] : [];
|
|
17690
|
+
}).sort((left, right) => left.slug.localeCompare(right.slug)),
|
|
17691
|
+
configuredSlugs: [...configuredSet].sort(),
|
|
17692
|
+
missingSlugs: missing,
|
|
17693
|
+
unexpectedSlugs: unexpected,
|
|
17694
|
+
complete: missing.length === 0
|
|
17695
|
+
};
|
|
17696
|
+
}
|
|
17697
|
+
|
|
17698
|
+
// src/lib/session-refresh.ts
|
|
17699
|
+
init_instruction_graph();
|
|
17700
|
+
init_project_context();
|
|
17701
|
+
init_session_render();
|
|
17702
|
+
var nonempty = exports_external.string().min(1).max(4096);
|
|
17703
|
+
var selectorSchema = exports_external.object({
|
|
17704
|
+
schema: exports_external.literal("hasna.instructions.hosted-profile-selector/v1"),
|
|
17705
|
+
authority: nonempty,
|
|
17706
|
+
profileId: nonempty,
|
|
17707
|
+
providerVersion: nonempty,
|
|
17708
|
+
providerVariant: nonempty.optional(),
|
|
17709
|
+
model: nonempty.optional(),
|
|
17710
|
+
path: nonempty.optional(),
|
|
17711
|
+
manual: exports_external.array(nonempty).max(256),
|
|
17712
|
+
codewithNativeImports: exports_external.boolean(),
|
|
17713
|
+
allowEmptySources: exports_external.boolean(),
|
|
17714
|
+
stationProfile: exports_external.boolean(),
|
|
17715
|
+
checkGlobalCoverage: exports_external.boolean(),
|
|
17716
|
+
assetSurface: nonempty.optional(),
|
|
17717
|
+
assetScope: exports_external.enum(["global", "project", "session"]).optional()
|
|
17718
|
+
}).strict();
|
|
17719
|
+
function authority(value) {
|
|
17720
|
+
const url = new URL(value);
|
|
17721
|
+
if (url.username || url.password || url.search || url.hash || !["https:", "http:"].includes(url.protocol)) {
|
|
17722
|
+
throw new Error("SESSION_REFRESH_AUTHORITY_INVALID: expected a credential-free Instructions API authority.");
|
|
17723
|
+
}
|
|
17724
|
+
return url.toString().replace(/\/$/, "");
|
|
17725
|
+
}
|
|
17726
|
+
function normalizeSessionHostedProfileSelector(value) {
|
|
17727
|
+
const parsed = selectorSchema.parse(value);
|
|
17728
|
+
return { ...parsed, authority: authority(parsed.authority) };
|
|
17729
|
+
}
|
|
17730
|
+
function assertHostedProfileBindings(profileId, configs, bindings) {
|
|
17731
|
+
const configIds = new Set(configs.map((config) => config.id));
|
|
17732
|
+
const bindingIds = new Set(bindings.map((binding) => binding.config_id));
|
|
17733
|
+
if (configIds.size !== configs.length || bindingIds.size !== bindings.length || bindings.some((binding) => binding.profile_id !== profileId || !configIds.has(binding.config_id)) || [...configIds].some((id) => !bindingIds.has(id))) {
|
|
17734
|
+
throw new Error("HOSTED_PROFILE_BINDINGS_INCOMPLETE: exact one-to-one config membership and explicit profile bindings are required; refusing legacy activation fallback.");
|
|
17735
|
+
}
|
|
17736
|
+
for (const row of bindings) {
|
|
17737
|
+
const raw = row.binding;
|
|
17738
|
+
const invalid = () => new Error("HOSTED_PROFILE_BINDINGS_INVALID: each hosted binding must contain a complete explicit schema, activation, required flag and fallback; refusing legacy activation defaults.");
|
|
17739
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
17740
|
+
throw invalid();
|
|
17741
|
+
const record = raw;
|
|
17742
|
+
if (!["schema", "activation", "required", "fallback"].every((key) => Object.hasOwn(record, key)) || typeof record["required"] !== "boolean")
|
|
17743
|
+
throw invalid();
|
|
17744
|
+
try {
|
|
17745
|
+
normalizeProfileConfigBinding(record);
|
|
17746
|
+
} catch {
|
|
17747
|
+
throw invalid();
|
|
17748
|
+
}
|
|
17749
|
+
}
|
|
17750
|
+
}
|
|
17751
|
+
function readManagedManifest(targetHome) {
|
|
17752
|
+
const path = join19(targetHome, SESSION_RENDER_MANIFEST_RELATIVE_PATH);
|
|
17753
|
+
let ancestor = dirname11(path);
|
|
17754
|
+
while (true) {
|
|
17755
|
+
const stat = lstatSync8(ancestor);
|
|
17756
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
17757
|
+
throw new Error("SESSION_REFRESH_PATH_INVALID: managed target uses a symlink or non-directory ancestor.");
|
|
17758
|
+
const parent = dirname11(ancestor);
|
|
17759
|
+
if (parent === ancestor)
|
|
17760
|
+
break;
|
|
17761
|
+
ancestor = parent;
|
|
17762
|
+
}
|
|
17763
|
+
const fd = openSync4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
17764
|
+
let raw;
|
|
17765
|
+
try {
|
|
17766
|
+
const stat = fstatSync4(fd);
|
|
17767
|
+
if (!stat.isFile() || stat.size > SESSION_MANAGED_INPUT_MAX_BYTES)
|
|
17768
|
+
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: expected a bounded regular manifest file.");
|
|
17769
|
+
raw = readFileSync14(fd);
|
|
17770
|
+
if (raw.byteLength > SESSION_MANAGED_INPUT_MAX_BYTES)
|
|
17771
|
+
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: manifest grew beyond the read limit.");
|
|
17772
|
+
} finally {
|
|
17773
|
+
closeSync4(fd);
|
|
17774
|
+
}
|
|
17775
|
+
const manifest = JSON.parse(raw.toString("utf8"));
|
|
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) {
|
|
17777
|
+
throw new Error("SESSION_REFRESH_MANIFEST_INVALID: target is not an owned hosted profile render; apply a hosted --compile-profile first.");
|
|
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
|
+
}
|
|
17792
|
+
manifest.refreshSelector = normalizeSessionHostedProfileSelector(manifest.refreshSelector);
|
|
17793
|
+
return { manifest, sha256: createHash10("sha256").update(raw).digest("hex") };
|
|
17794
|
+
}
|
|
17795
|
+
async function refreshSessionRender(input) {
|
|
17796
|
+
const { store } = input;
|
|
17797
|
+
if (store.mode !== "api" || !store.v1BaseUrl)
|
|
17798
|
+
throw new Error("SESSION_REFRESH_HOSTED_REQUIRED: refresh requires the configured hosted Instructions API.");
|
|
17799
|
+
const targetHome = resolve13(input.targetHome);
|
|
17800
|
+
if (targetHome === parse5(targetHome).root)
|
|
17801
|
+
throw new Error("SESSION_REFRESH_PATH_INVALID: filesystem root is not a managed target.");
|
|
17802
|
+
const previous = readManagedManifest(targetHome);
|
|
17803
|
+
const selector = previous.manifest.refreshSelector;
|
|
17804
|
+
if (authority(store.v1BaseUrl) !== selector.authority)
|
|
17805
|
+
throw new Error("SESSION_REFRESH_AUTHORITY_MISMATCH: configured API differs from the recorded profile authority.");
|
|
17806
|
+
const profile = await store.getProfile(selector.profileId);
|
|
17807
|
+
if (profile.id !== selector.profileId)
|
|
17808
|
+
throw new Error("SESSION_REFRESH_PROFILE_MISMATCH: hosted profile identity changed.");
|
|
17809
|
+
const [configs, bindings, assetBindings] = await Promise.all([
|
|
17810
|
+
store.getProfileConfigs(profile.id),
|
|
17811
|
+
store.getProfileConfigBindings(profile.id, { requireExplicit: true }),
|
|
17812
|
+
store.getProfileAssetBindings(profile.id, { requireExplicit: true })
|
|
17813
|
+
]);
|
|
17814
|
+
assertHostedProfileBindings(profile.id, configs, bindings);
|
|
17815
|
+
if (configs.length > 2048 || bindings.length > 8192 || assetBindings.length > 1024)
|
|
17816
|
+
throw new Error("SESSION_REFRESH_INPUT_LIMIT: hosted profile exceeds refresh bounds.");
|
|
17817
|
+
const totalBytes = configs.reduce((sum, config) => sum + Buffer.byteLength(config.content), 0);
|
|
17818
|
+
if (totalBytes > SESSION_MANAGED_INPUT_MAX_BYTES)
|
|
17819
|
+
throw new Error("SESSION_REFRESH_INPUT_LIMIT: hosted instruction payload exceeds refresh byte bounds.");
|
|
17820
|
+
const assetConfigs = await Promise.all([...new Set(assetBindings.map((binding) => binding.source_config_id))].map((id) => store.getConfigById(id)));
|
|
17821
|
+
if (assetConfigs.reduce((sum, config) => sum + Buffer.byteLength(config.content), totalBytes) > SESSION_MANAGED_INPUT_MAX_BYTES)
|
|
17822
|
+
throw new Error("SESSION_REFRESH_INPUT_LIMIT: hosted source and asset payload exceeds refresh byte bounds.");
|
|
17823
|
+
let ownedClaudeAuthorities;
|
|
17824
|
+
if (previous.manifest.tool === "claude") {
|
|
17825
|
+
ownedClaudeAuthorities = (await store.listConfigs({ category: "rules", agent: "claude", kind: "file" })).filter((config) => config.target_path && basename6(normalizeTargetPath(config.target_path)) === "AGENTS.md").map((config) => ({ slug: config.slug, targetPath: config.target_path, content: config.content }));
|
|
17826
|
+
}
|
|
17827
|
+
const station = selector.stationProfile ? stationProfileSource() : null;
|
|
17828
|
+
const plan = planProfileSessionRender({
|
|
17829
|
+
tool: previous.manifest.tool,
|
|
17830
|
+
profile: previous.manifest.profile,
|
|
17831
|
+
profile_id: profile.id,
|
|
17832
|
+
provider_version: selector.providerVersion,
|
|
17833
|
+
targetHome,
|
|
17834
|
+
...previous.manifest.targetKind === "project-root" ? { projectRoot: targetHome } : previous.manifest.targetOwner.projectRoot ? { projectRoot: previous.manifest.targetOwner.projectRoot } : {},
|
|
17835
|
+
sessionId: previous.manifest.sessionId ?? undefined,
|
|
17836
|
+
refreshSelector: selector,
|
|
17837
|
+
codewithNativeImports: selector.codewithNativeImports,
|
|
17838
|
+
allowEmptySources: selector.allowEmptySources,
|
|
17839
|
+
configs,
|
|
17840
|
+
bindings,
|
|
17841
|
+
asset_configs: assetConfigs,
|
|
17842
|
+
asset_bindings: assetBindings,
|
|
17843
|
+
asset_plan_mode: "apply",
|
|
17844
|
+
asset_scope: selector.assetScope,
|
|
17845
|
+
asset_surface: selector.assetSurface,
|
|
17846
|
+
extra_sources: station ? [station] : undefined,
|
|
17847
|
+
ownedClaudeAuthorities,
|
|
17848
|
+
graph_context: {
|
|
17849
|
+
...selector.providerVariant ? { provider_variant: selector.providerVariant } : {},
|
|
17850
|
+
...selector.model ? { model: selector.model } : {},
|
|
17851
|
+
...selector.path ? { path: selector.path } : {},
|
|
17852
|
+
...selector.manual.length ? { manual: selector.manual } : {}
|
|
17853
|
+
}
|
|
17854
|
+
});
|
|
17855
|
+
if (selector.checkGlobalCoverage) {
|
|
17856
|
+
const coverage = computeGlobalSourceCoverage(await store.listConfigs({}), accountedGlobalSourceSlugs(plan.manifest));
|
|
17857
|
+
if (!coverage.complete)
|
|
17858
|
+
throw new Error(`SESSION_REFRESH_COVERAGE_INCOMPLETE: ${coverage.missingSlugs.length} registered global sources are absent.`);
|
|
17859
|
+
}
|
|
17860
|
+
const preview = applySessionRender(plan, { dryRun: true, expectedManifestSha256: previous.sha256, ownedClaudeAuthorities });
|
|
17861
|
+
const common = { checkedAt: new Date().toISOString(), targetHome, selector, sourceHash: plan.manifest.sourceHash };
|
|
17862
|
+
if (preview.conflicts.length)
|
|
17863
|
+
return { ...common, status: "blocked", apply: preview };
|
|
17864
|
+
if (!preview.drift.clean)
|
|
17865
|
+
throw new Error("SESSION_REFRESH_DRIFT: generated files changed or disappeared since their last managed render.");
|
|
17866
|
+
const sameSources = previous.manifest.sourceHash === plan.manifest.sourceHash;
|
|
17867
|
+
const samePayloads = preview.files.filter((file) => file.role !== "manifest").every((file) => file.action === "unchanged");
|
|
17868
|
+
if (sameSources && samePayloads) {
|
|
17869
|
+
if (readManagedManifest(targetHome).sha256 !== previous.sha256 || !checkSessionRenderDrift(targetHome).clean)
|
|
17870
|
+
throw new Error("SESSION_REFRESH_DRIFT: managed target changed during refresh.");
|
|
17871
|
+
return { ...common, status: "unchanged", apply: { ...preview, files: preview.files.map((file) => file.role === "manifest" ? { ...file, action: "unchanged", changed: false, newSha256: previous.sha256, reason: "hosted sources and generated payloads unchanged" } : file) } };
|
|
17872
|
+
}
|
|
17873
|
+
if (input.dryRun)
|
|
17874
|
+
return { ...common, status: "dry-run", apply: preview };
|
|
17875
|
+
const applied = applySessionRender(plan, { expectedManifestSha256: previous.sha256, ownedClaudeAuthorities });
|
|
17876
|
+
if (applied.conflicts.length)
|
|
17877
|
+
return { ...common, status: "blocked", apply: applied };
|
|
17878
|
+
if (!applied.applied || !checkSessionRenderDrift(targetHome).clean)
|
|
17879
|
+
throw new Error("SESSION_REFRESH_READBACK_FAILED: applied instructions did not verify against their manifest.");
|
|
17880
|
+
return { ...common, status: "updated", apply: applied };
|
|
17243
17881
|
}
|
|
17244
17882
|
|
|
17245
17883
|
// src/index.ts
|
|
@@ -17248,9 +17886,11 @@ init_transforms();
|
|
|
17248
17886
|
init_machine();
|
|
17249
17887
|
|
|
17250
17888
|
// src/lib/platform-profiles.ts
|
|
17889
|
+
init_types();
|
|
17251
17890
|
init_config_store();
|
|
17252
17891
|
|
|
17253
17892
|
// src/lib/project-dashboard-standard.ts
|
|
17893
|
+
init_types();
|
|
17254
17894
|
init_config_store();
|
|
17255
17895
|
var PROJECT_DASHBOARD_STANDARD_SLUG = "agent-managed-project-dashboard-standard";
|
|
17256
17896
|
var PROJECT_DASHBOARD_PROFILE_VARIABLES = {
|
|
@@ -17343,11 +17983,15 @@ async function ensureProjectDashboardStandardConfig(store = resolveConfigStore()
|
|
|
17343
17983
|
};
|
|
17344
17984
|
try {
|
|
17345
17985
|
const existing = await store.getConfig(PROJECT_DASHBOARD_STANDARD_SLUG);
|
|
17986
|
+
if (isRetiredInstructionSource(existing))
|
|
17987
|
+
return existing;
|
|
17346
17988
|
if (existing.content !== input.content || existing.description !== input.description || existing.category !== input.category || existing.agent !== input.agent || existing.format !== input.format || existing.kind !== input.kind) {
|
|
17347
17989
|
return await store.updateConfig(existing.id, input);
|
|
17348
17990
|
}
|
|
17349
17991
|
return existing;
|
|
17350
|
-
} catch {
|
|
17992
|
+
} catch (error) {
|
|
17993
|
+
if (!(error instanceof ConfigNotFoundError))
|
|
17994
|
+
throw error;
|
|
17351
17995
|
return await store.createConfig(input);
|
|
17352
17996
|
}
|
|
17353
17997
|
}
|
|
@@ -17360,10 +18004,9 @@ function profileHasSelectors2(profile) {
|
|
|
17360
18004
|
var PLATFORM_PROFILE_PRESETS = [
|
|
17361
18005
|
{
|
|
17362
18006
|
name: "linux-arm64",
|
|
17363
|
-
description: "Default
|
|
17364
|
-
selectors: { os: ["linux"], arch: ["arm64"]
|
|
18007
|
+
description: "Default reviewed instruction profile for Linux arm64",
|
|
18008
|
+
selectors: { os: ["linux"], arch: ["arm64"] },
|
|
17365
18009
|
variables: {
|
|
17366
|
-
WORKSPACE_ROOT: "{{HOME_DIR}}/workspace",
|
|
17367
18010
|
BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
|
|
17368
18011
|
BUN_PATH: "{{BUN_BIN_DIR}}/bun",
|
|
17369
18012
|
PATH_PREFIX: "{{BUN_BIN_DIR}}",
|
|
@@ -17372,10 +18015,9 @@ var PLATFORM_PROFILE_PRESETS = [
|
|
|
17372
18015
|
},
|
|
17373
18016
|
{
|
|
17374
18017
|
name: "macos-arm64",
|
|
17375
|
-
description: "Default
|
|
17376
|
-
selectors: { os: ["macos"], arch: ["arm64"]
|
|
18018
|
+
description: "Default reviewed instruction profile for macOS arm64",
|
|
18019
|
+
selectors: { os: ["macos"], arch: ["arm64"] },
|
|
17377
18020
|
variables: {
|
|
17378
|
-
WORKSPACE_ROOT: "{{HOME_DIR}}/Workspace",
|
|
17379
18021
|
BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
|
|
17380
18022
|
BUN_PATH: "/opt/homebrew/bin/bun",
|
|
17381
18023
|
PATH_PREFIX: "/opt/homebrew/bin:{{BUN_BIN_DIR}}",
|
|
@@ -17384,12 +18026,21 @@ var PLATFORM_PROFILE_PRESETS = [
|
|
|
17384
18026
|
}
|
|
17385
18027
|
];
|
|
17386
18028
|
async function ensurePlatformProfiles(store = resolveConfigStore()) {
|
|
17387
|
-
const configs = await store.listConfigs();
|
|
17388
18029
|
const ensured = [];
|
|
17389
|
-
|
|
17390
|
-
let profile;
|
|
18030
|
+
const existingProfiles = await Promise.all(PLATFORM_PROFILE_PRESETS.map(async (preset) => {
|
|
17391
18031
|
try {
|
|
17392
|
-
|
|
18032
|
+
return await store.getProfile(preset.name);
|
|
18033
|
+
} catch (error) {
|
|
18034
|
+
if (error instanceof ProfileNotFoundError)
|
|
18035
|
+
return null;
|
|
18036
|
+
throw error;
|
|
18037
|
+
}
|
|
18038
|
+
}));
|
|
18039
|
+
for (const [index, preset] of PLATFORM_PROFILE_PRESETS.entries()) {
|
|
18040
|
+
let profile;
|
|
18041
|
+
const existing = existingProfiles[index];
|
|
18042
|
+
if (existing) {
|
|
18043
|
+
profile = existing;
|
|
17393
18044
|
const selectors = mergeProfileSelectors(preset.selectors, profile.selectors);
|
|
17394
18045
|
const variables = mergeProfileVariables(preset.variables, profile.variables);
|
|
17395
18046
|
if (JSON.stringify(selectors) !== JSON.stringify(profile.selectors) || JSON.stringify(variables) !== JSON.stringify(profile.variables)) {
|
|
@@ -17399,12 +18050,9 @@ async function ensurePlatformProfiles(store = resolveConfigStore()) {
|
|
|
17399
18050
|
variables
|
|
17400
18051
|
});
|
|
17401
18052
|
}
|
|
17402
|
-
}
|
|
18053
|
+
} else {
|
|
17403
18054
|
profile = await store.createProfile(preset);
|
|
17404
18055
|
}
|
|
17405
|
-
for (const config of configs) {
|
|
17406
|
-
await store.addConfigToProfile(profile.id, config.id);
|
|
17407
|
-
}
|
|
17408
18056
|
ensured.push(profile);
|
|
17409
18057
|
}
|
|
17410
18058
|
return ensured;
|
|
@@ -17438,6 +18086,7 @@ function mergeUnique(preset, existing) {
|
|
|
17438
18086
|
init_global_agent_rules_standard();
|
|
17439
18087
|
|
|
17440
18088
|
// src/lib/dangerous-operation-guard-standard.ts
|
|
18089
|
+
init_types();
|
|
17441
18090
|
init_config_store();
|
|
17442
18091
|
var DANGEROUS_OPERATION_GUARD_STANDARD_SLUG = "dangerous-operation-guard-standard";
|
|
17443
18092
|
var DANGEROUS_OPERATION_GUARD_STANDARD_CONTENT = `# Dangerous Operation Guard Standard
|
|
@@ -17549,11 +18198,15 @@ async function ensureDangerousOperationGuardStandardConfig(store = resolveConfig
|
|
|
17549
18198
|
};
|
|
17550
18199
|
try {
|
|
17551
18200
|
const existing = await store.getConfig(DANGEROUS_OPERATION_GUARD_STANDARD_SLUG);
|
|
18201
|
+
if (isRetiredInstructionSource(existing))
|
|
18202
|
+
return existing;
|
|
17552
18203
|
if (existing.content !== input.content || existing.description !== input.description || existing.category !== input.category || existing.agent !== input.agent || existing.format !== input.format || existing.kind !== input.kind || JSON.stringify(existing.tags) !== JSON.stringify(input.tags)) {
|
|
17553
18204
|
return await store.updateConfig(existing.id, input);
|
|
17554
18205
|
}
|
|
17555
18206
|
return existing;
|
|
17556
|
-
} catch {
|
|
18207
|
+
} catch (error) {
|
|
18208
|
+
if (!(error instanceof ConfigNotFoundError))
|
|
18209
|
+
throw error;
|
|
17557
18210
|
return await store.createConfig(input);
|
|
17558
18211
|
}
|
|
17559
18212
|
}
|
|
@@ -17563,8 +18216,8 @@ init_codewith_shared_todos_storage_standard();
|
|
|
17563
18216
|
|
|
17564
18217
|
// src/lib/sync.ts
|
|
17565
18218
|
init_config_store();
|
|
17566
|
-
import { existsSync as
|
|
17567
|
-
import { basename 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";
|
|
17568
18221
|
init_config_agents();
|
|
17569
18222
|
init_redact();
|
|
17570
18223
|
init_machine();
|
|
@@ -17572,8 +18225,8 @@ init_transforms();
|
|
|
17572
18225
|
|
|
17573
18226
|
// src/lib/sync-dir.ts
|
|
17574
18227
|
init_config_store();
|
|
17575
|
-
import { existsSync as
|
|
17576
|
-
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";
|
|
17577
18230
|
init_redact();
|
|
17578
18231
|
var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
|
|
17579
18232
|
function shouldSkip(p) {
|
|
@@ -17582,9 +18235,9 @@ function shouldSkip(p) {
|
|
|
17582
18235
|
async function syncFromDir(dir, opts = {}) {
|
|
17583
18236
|
const store = opts.store ?? resolveConfigStore();
|
|
17584
18237
|
const absDir = expandPath(dir);
|
|
17585
|
-
if (!
|
|
18238
|
+
if (!existsSync15(absDir))
|
|
17586
18239
|
return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
|
|
17587
|
-
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());
|
|
17588
18241
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
17589
18242
|
const allConfigs = await store.listConfigs();
|
|
17590
18243
|
for (const file of files) {
|
|
@@ -17603,7 +18256,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
17603
18256
|
const existing = allConfigs.find((c) => c.target_path === targetPath);
|
|
17604
18257
|
if (!existing) {
|
|
17605
18258
|
if (!opts.dryRun)
|
|
17606
|
-
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 });
|
|
17607
18260
|
result.added++;
|
|
17608
18261
|
} else if (existing.content !== redacted.content) {
|
|
17609
18262
|
if (!opts.dryRun)
|
|
@@ -17643,7 +18296,7 @@ async function syncToDir(dir, opts = {}) {
|
|
|
17643
18296
|
}
|
|
17644
18297
|
function walkDir(dir, files = []) {
|
|
17645
18298
|
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
17646
|
-
const full =
|
|
18299
|
+
const full = join20(dir, entry.name);
|
|
17647
18300
|
if (shouldSkip(full))
|
|
17648
18301
|
continue;
|
|
17649
18302
|
if (entry.isDirectory())
|
|
@@ -17665,7 +18318,7 @@ var CLAUDE_PROMPT_OUTPUTS = [
|
|
|
17665
18318
|
{ agent: "qwen", target_path: "~/.qwen/QWEN.md", transform: "codex-flat" }
|
|
17666
18319
|
];
|
|
17667
18320
|
function claudeRuleOutputs(fileName) {
|
|
17668
|
-
const stem =
|
|
18321
|
+
const stem = basename7(fileName, extname3(fileName));
|
|
17669
18322
|
return [
|
|
17670
18323
|
{ agent: "cursor", target_path: `~/.cursor/rules/${stem}.mdc`, transform: "cursor-mdc" }
|
|
17671
18324
|
];
|
|
@@ -17704,15 +18357,15 @@ function isGeneratedOutputTarget2(config, owners) {
|
|
|
17704
18357
|
return !!ownerIds && !ownerIds.has(config.id);
|
|
17705
18358
|
}
|
|
17706
18359
|
function hasClaudePromptSource() {
|
|
17707
|
-
return
|
|
18360
|
+
return existsSync16(expandPath("~/.claude/CLAUDE.md"));
|
|
17708
18361
|
}
|
|
17709
18362
|
function hasClaudeRuleSourceForCursorTarget(targetPath) {
|
|
17710
18363
|
const absoluteTargetPath = expandPath(targetPath);
|
|
17711
18364
|
const absolutePrefix = expandPath("~/.cursor/rules");
|
|
17712
18365
|
if (!absoluteTargetPath.startsWith(`${absolutePrefix}/`) || !absoluteTargetPath.endsWith(".mdc"))
|
|
17713
18366
|
return false;
|
|
17714
|
-
const stem =
|
|
17715
|
-
return
|
|
18367
|
+
const stem = basename7(absoluteTargetPath, ".mdc");
|
|
18368
|
+
return existsSync16(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync16(expandPath(`~/.claude/rules/${stem}.mdc`));
|
|
17716
18369
|
}
|
|
17717
18370
|
function isKnownGeneratedTargetPath(targetPath) {
|
|
17718
18371
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
@@ -17777,8 +18430,8 @@ async function syncProject(opts) {
|
|
|
17777
18430
|
const allConfigs = await store.listConfigs();
|
|
17778
18431
|
const machine = detectMachineContext();
|
|
17779
18432
|
for (const pf of PROJECT_CONFIG_FILES) {
|
|
17780
|
-
const abs =
|
|
17781
|
-
if (!
|
|
18433
|
+
const abs = join21(absDir, pf.file);
|
|
18434
|
+
if (!existsSync16(abs))
|
|
17782
18435
|
continue;
|
|
17783
18436
|
try {
|
|
17784
18437
|
const rawContent = readFileSync16(abs, "utf-8");
|
|
@@ -17810,19 +18463,19 @@ async function syncProject(opts) {
|
|
|
17810
18463
|
}
|
|
17811
18464
|
}
|
|
17812
18465
|
for (const ruleDir of [
|
|
17813
|
-
{ dir:
|
|
17814
|
-
{ dir:
|
|
17815
|
-
{ dir:
|
|
17816
|
-
{ dir:
|
|
17817
|
-
{ dir:
|
|
17818
|
-
{ dir:
|
|
17819
|
-
{ 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" }
|
|
17820
18473
|
]) {
|
|
17821
|
-
if (!
|
|
18474
|
+
if (!existsSync16(ruleDir.dir))
|
|
17822
18475
|
continue;
|
|
17823
18476
|
const mdFiles = readdirSync5(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
17824
18477
|
for (const f of mdFiles) {
|
|
17825
|
-
const abs =
|
|
18478
|
+
const abs = join21(ruleDir.dir, f);
|
|
17826
18479
|
const raw = readFileSync16(abs, "utf-8");
|
|
17827
18480
|
const redacted = redactContent(raw, "markdown");
|
|
17828
18481
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
@@ -17862,14 +18515,14 @@ async function syncKnown(opts = {}) {
|
|
|
17862
18515
|
for (const known of targets) {
|
|
17863
18516
|
if (known.rulesDir) {
|
|
17864
18517
|
const absDir = expandPath(known.rulesDir);
|
|
17865
|
-
if (!
|
|
18518
|
+
if (!existsSync16(absDir)) {
|
|
17866
18519
|
result.skipped.push(known.rulesDir);
|
|
17867
18520
|
continue;
|
|
17868
18521
|
}
|
|
17869
18522
|
const extensions = known.rulesExtensions ?? [".md", ".mdc"];
|
|
17870
18523
|
const ruleFiles = readdirSync5(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
|
|
17871
18524
|
for (const f of ruleFiles) {
|
|
17872
|
-
const abs2 =
|
|
18525
|
+
const abs2 = join21(absDir, f);
|
|
17873
18526
|
const targetPath = abs2.replace(home, "~");
|
|
17874
18527
|
if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
|
|
17875
18528
|
result.skipped.push(`${targetPath} (generated output)`);
|
|
@@ -17903,7 +18556,7 @@ async function syncKnown(opts = {}) {
|
|
|
17903
18556
|
continue;
|
|
17904
18557
|
}
|
|
17905
18558
|
const abs = expandPath(known.path);
|
|
17906
|
-
if (!
|
|
18559
|
+
if (!existsSync16(abs)) {
|
|
17907
18560
|
result.skipped.push(known.path);
|
|
17908
18561
|
continue;
|
|
17909
18562
|
}
|
|
@@ -18011,7 +18664,7 @@ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
|
|
|
18011
18664
|
}
|
|
18012
18665
|
function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
|
|
18013
18666
|
const path = expandPath(targetPath);
|
|
18014
|
-
if (!
|
|
18667
|
+
if (!existsSync16(path))
|
|
18015
18668
|
return `(file not found on disk: ${path})`;
|
|
18016
18669
|
const diskContent = readFileSync16(path, "utf-8");
|
|
18017
18670
|
if (diskContent === expectedContent)
|
|
@@ -18174,8 +18827,8 @@ function detectFormat(filePath) {
|
|
|
18174
18827
|
init_types();
|
|
18175
18828
|
init_config_store();
|
|
18176
18829
|
import { createHash as createHash11 } from "crypto";
|
|
18177
|
-
import { existsSync as
|
|
18178
|
-
import { dirname as
|
|
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";
|
|
18179
18832
|
import { tmpdir } from "os";
|
|
18180
18833
|
var ARCHIVE_EXCLUSIONS = [
|
|
18181
18834
|
{
|
|
@@ -18197,7 +18850,7 @@ var ARCHIVE_EXCLUSIONS = [
|
|
|
18197
18850
|
function compareText(left, right) {
|
|
18198
18851
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
18199
18852
|
}
|
|
18200
|
-
function
|
|
18853
|
+
function sha25610(value) {
|
|
18201
18854
|
return createHash11("sha256").update(value).digest("hex");
|
|
18202
18855
|
}
|
|
18203
18856
|
function canonicalize(value) {
|
|
@@ -18322,19 +18975,19 @@ function computeIntegrity(domain, options) {
|
|
|
18322
18975
|
machines: collections.machines.length
|
|
18323
18976
|
};
|
|
18324
18977
|
const hashes = {
|
|
18325
|
-
configs:
|
|
18326
|
-
config_snapshots:
|
|
18327
|
-
profiles:
|
|
18328
|
-
profile_config_bindings:
|
|
18329
|
-
profile_asset_bindings:
|
|
18330
|
-
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))
|
|
18331
18984
|
};
|
|
18332
18985
|
return {
|
|
18333
18986
|
algorithm: "sha256",
|
|
18334
18987
|
canonicalization: options.canonicalization,
|
|
18335
18988
|
counts,
|
|
18336
18989
|
hashes,
|
|
18337
|
-
domain_sha256:
|
|
18990
|
+
domain_sha256: sha25610(canonicalDomainJson({ counts, hashes }))
|
|
18338
18991
|
};
|
|
18339
18992
|
}
|
|
18340
18993
|
function computeDomainIntegrity(domain) {
|
|
@@ -18505,19 +19158,19 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
18505
19158
|
exported_at: new Date().toISOString(),
|
|
18506
19159
|
payload: {
|
|
18507
19160
|
path: "domain.json",
|
|
18508
|
-
sha256:
|
|
19161
|
+
sha256: sha25610(payload),
|
|
18509
19162
|
size_bytes: Buffer.byteLength(payload)
|
|
18510
19163
|
},
|
|
18511
19164
|
integrity,
|
|
18512
19165
|
exclusions: ARCHIVE_EXCLUSIONS
|
|
18513
19166
|
};
|
|
18514
|
-
const absOutput =
|
|
18515
|
-
|
|
18516
|
-
const stagingDir = mkdtempSync(
|
|
19167
|
+
const absOutput = resolve14(outputPath);
|
|
19168
|
+
mkdirSync7(dirname12(absOutput), { recursive: true });
|
|
19169
|
+
const stagingDir = mkdtempSync(join22(tmpdir(), "instructions-domain-export-"));
|
|
18517
19170
|
try {
|
|
18518
|
-
|
|
19171
|
+
writeFileSync5(join22(stagingDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
18519
19172
|
`, "utf-8");
|
|
18520
|
-
|
|
19173
|
+
writeFileSync5(join22(stagingDir, "domain.json"), payload, "utf-8");
|
|
18521
19174
|
const proc = Bun.spawn(["tar", "czf", absOutput, "-C", stagingDir, "manifest.json", "domain.json"], {
|
|
18522
19175
|
stdout: "pipe",
|
|
18523
19176
|
stderr: "pipe"
|
|
@@ -18529,15 +19182,15 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
18529
19182
|
}
|
|
18530
19183
|
return { path: absOutput, count: domain.configs.length, counts: integrity.counts, integrity };
|
|
18531
19184
|
} finally {
|
|
18532
|
-
if (
|
|
18533
|
-
|
|
19185
|
+
if (existsSync17(stagingDir))
|
|
19186
|
+
rmSync3(stagingDir, { recursive: true, force: true });
|
|
18534
19187
|
}
|
|
18535
19188
|
}
|
|
18536
19189
|
// src/lib/import.ts
|
|
18537
19190
|
init_types();
|
|
18538
19191
|
init_config_store();
|
|
18539
19192
|
import { createHash as createHash12 } from "crypto";
|
|
18540
|
-
import { resolve as
|
|
19193
|
+
import { resolve as resolve15 } from "path";
|
|
18541
19194
|
function compareText2(left, right) {
|
|
18542
19195
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
18543
19196
|
}
|
|
@@ -18561,7 +19214,7 @@ function emptyResult() {
|
|
|
18561
19214
|
integrity: null
|
|
18562
19215
|
};
|
|
18563
19216
|
}
|
|
18564
|
-
function
|
|
19217
|
+
function sha25611(value) {
|
|
18565
19218
|
return createHash12("sha256").update(value).digest("hex");
|
|
18566
19219
|
}
|
|
18567
19220
|
function normalizeMemberName(name) {
|
|
@@ -18632,7 +19285,7 @@ async function readV2(reader, manifest) {
|
|
|
18632
19285
|
throw new Error("Invalid v2 archive manifest");
|
|
18633
19286
|
}
|
|
18634
19287
|
const payload = await reader.read("domain.json");
|
|
18635
|
-
if (Buffer.byteLength(payload) !== manifest.payload.size_bytes ||
|
|
19288
|
+
if (Buffer.byteLength(payload) !== manifest.payload.size_bytes || sha25611(payload) !== manifest.payload.sha256) {
|
|
18636
19289
|
throw new Error("Archive integrity failure: domain payload hash or size does not match the manifest");
|
|
18637
19290
|
}
|
|
18638
19291
|
const domain = validateInstructionsDomainArchive(JSON.parse(payload));
|
|
@@ -18825,7 +19478,7 @@ async function importV1(reader, manifest, store, conflict) {
|
|
|
18825
19478
|
async function importConfigs(bundlePath, opts = {}) {
|
|
18826
19479
|
const store = opts.store ?? resolveConfigStore();
|
|
18827
19480
|
const conflict = opts.conflict ?? "skip";
|
|
18828
|
-
const reader = await openArchive(
|
|
19481
|
+
const reader = await openArchive(resolve15(bundlePath));
|
|
18829
19482
|
const manifestText = await reader.read("manifest.json");
|
|
18830
19483
|
let manifest;
|
|
18831
19484
|
try {
|
|
@@ -18853,9 +19506,9 @@ init_redact();
|
|
|
18853
19506
|
|
|
18854
19507
|
// src/lib/package-manager-guard.ts
|
|
18855
19508
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
18856
|
-
import { existsSync as
|
|
18857
|
-
import { homedir as
|
|
18858
|
-
import { basename 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";
|
|
18859
19512
|
var SKIP_DIRS = new Set([
|
|
18860
19513
|
".git",
|
|
18861
19514
|
"node_modules",
|
|
@@ -18892,14 +19545,14 @@ var TOKEN_VALUE_PATTERNS = [
|
|
|
18892
19545
|
{ re: /xoxb-[0-9]+-[A-Za-z0-9-]+/, rule: "literal-slack-token", detail: "literal Slack token-like value" }
|
|
18893
19546
|
];
|
|
18894
19547
|
function scanPackageManagerSecrets(options = {}) {
|
|
18895
|
-
const cwd = options.cwd ?
|
|
18896
|
-
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));
|
|
18897
19550
|
const findings = [];
|
|
18898
19551
|
let scannedFiles = 0;
|
|
18899
19552
|
for (const root of roots) {
|
|
18900
|
-
if (!
|
|
19553
|
+
if (!existsSync18(root))
|
|
18901
19554
|
continue;
|
|
18902
|
-
const stat =
|
|
19555
|
+
const stat = lstatSync9(root);
|
|
18903
19556
|
if (stat.isFile()) {
|
|
18904
19557
|
if (!shouldScanRepoFile(root))
|
|
18905
19558
|
continue;
|
|
@@ -18907,14 +19560,14 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
18907
19560
|
if (text === null)
|
|
18908
19561
|
continue;
|
|
18909
19562
|
scannedFiles++;
|
|
18910
|
-
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root),
|
|
19563
|
+
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname13(root)));
|
|
18911
19564
|
continue;
|
|
18912
19565
|
}
|
|
18913
19566
|
if (!stat.isDirectory())
|
|
18914
19567
|
continue;
|
|
18915
19568
|
const tracked = trackedFiles(root);
|
|
18916
19569
|
for (const file of collectRepoFiles(root)) {
|
|
18917
|
-
const rel = toPosix(
|
|
19570
|
+
const rel = toPosix(relative7(root, file));
|
|
18918
19571
|
const isTracked = tracked.has(rel);
|
|
18919
19572
|
const text = readTextFile(file);
|
|
18920
19573
|
if (text === null)
|
|
@@ -18924,10 +19577,10 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
18924
19577
|
}
|
|
18925
19578
|
}
|
|
18926
19579
|
if (options.includeHome) {
|
|
18927
|
-
const home =
|
|
19580
|
+
const home = homedir11();
|
|
18928
19581
|
for (const name of HOME_FILES) {
|
|
18929
|
-
const file =
|
|
18930
|
-
if (!
|
|
19582
|
+
const file = join23(home, name);
|
|
19583
|
+
if (!existsSync18(file))
|
|
18931
19584
|
continue;
|
|
18932
19585
|
const text = readTextFile(file);
|
|
18933
19586
|
if (text === null)
|
|
@@ -18951,12 +19604,12 @@ function collectRepoFiles(root) {
|
|
|
18951
19604
|
if (entry.isDirectory()) {
|
|
18952
19605
|
if (SKIP_DIRS.has(entry.name))
|
|
18953
19606
|
continue;
|
|
18954
|
-
visit(
|
|
19607
|
+
visit(join23(dir, entry.name));
|
|
18955
19608
|
continue;
|
|
18956
19609
|
}
|
|
18957
19610
|
if (!entry.isFile())
|
|
18958
19611
|
continue;
|
|
18959
|
-
const file =
|
|
19612
|
+
const file = join23(dir, entry.name);
|
|
18960
19613
|
if (shouldScanRepoFile(file))
|
|
18961
19614
|
out.push(file);
|
|
18962
19615
|
}
|
|
@@ -18965,11 +19618,11 @@ function collectRepoFiles(root) {
|
|
|
18965
19618
|
return out;
|
|
18966
19619
|
}
|
|
18967
19620
|
function shouldScanRepoFile(file) {
|
|
18968
|
-
const name =
|
|
19621
|
+
const name = basename8(file);
|
|
18969
19622
|
return isNpmrcName(name) || isBunConfigName(name) || LOCKFILE_NAMES.has(name);
|
|
18970
19623
|
}
|
|
18971
19624
|
function classifyRepoFile(file) {
|
|
18972
|
-
const name =
|
|
19625
|
+
const name = basename8(file);
|
|
18973
19626
|
if (isNpmrcName(name))
|
|
18974
19627
|
return "repo-npmrc";
|
|
18975
19628
|
if (isBunConfigName(name))
|
|
@@ -18991,7 +19644,7 @@ function isNpmrcName(name) {
|
|
|
18991
19644
|
}
|
|
18992
19645
|
function readTextFile(file) {
|
|
18993
19646
|
try {
|
|
18994
|
-
const stat =
|
|
19647
|
+
const stat = lstatSync9(file);
|
|
18995
19648
|
if (!stat.isFile() || stat.size > 5000000)
|
|
18996
19649
|
return null;
|
|
18997
19650
|
const buf = readFileSync17(file);
|
|
@@ -19194,11 +19847,11 @@ function trackedFiles(root) {
|
|
|
19194
19847
|
}
|
|
19195
19848
|
function isTrackedFile(file) {
|
|
19196
19849
|
try {
|
|
19197
|
-
const repoRoot = execFileSync2("git", ["-C",
|
|
19850
|
+
const repoRoot = execFileSync2("git", ["-C", dirname13(file), "rev-parse", "--show-toplevel"], {
|
|
19198
19851
|
encoding: "utf-8",
|
|
19199
19852
|
stdio: ["ignore", "pipe", "ignore"]
|
|
19200
19853
|
}).trim();
|
|
19201
|
-
const rel = toPosix(
|
|
19854
|
+
const rel = toPosix(relative7(repoRoot, file));
|
|
19202
19855
|
execFileSync2("git", ["-C", repoRoot, "ls-files", "--error-unmatch", "--", rel], {
|
|
19203
19856
|
stdio: ["ignore", "ignore", "ignore"]
|
|
19204
19857
|
});
|
|
@@ -19224,19 +19877,20 @@ function stripInlineComment(value) {
|
|
|
19224
19877
|
return value.replace(/\s[#;].*$/, "").trim();
|
|
19225
19878
|
}
|
|
19226
19879
|
function displayPath(file, root) {
|
|
19227
|
-
const home =
|
|
19880
|
+
const home = homedir11();
|
|
19228
19881
|
if (root === home && (file === home || file.startsWith(home + "/")))
|
|
19229
|
-
return "~/" + toPosix(
|
|
19230
|
-
if (
|
|
19231
|
-
return toPosix(
|
|
19882
|
+
return "~/" + toPosix(relative7(home, file));
|
|
19883
|
+
if (isAbsolute10(root) && file.startsWith(root + "/"))
|
|
19884
|
+
return toPosix(relative7(root, file));
|
|
19232
19885
|
if (file === home || file.startsWith(home + "/"))
|
|
19233
|
-
return "~/" + toPosix(
|
|
19886
|
+
return "~/" + toPosix(relative7(home, file));
|
|
19234
19887
|
return file;
|
|
19235
19888
|
}
|
|
19236
19889
|
function toPosix(path) {
|
|
19237
19890
|
return path.split("\\").join("/");
|
|
19238
19891
|
}
|
|
19239
19892
|
export {
|
|
19893
|
+
validateExpectedConfigVersion,
|
|
19240
19894
|
uuid,
|
|
19241
19895
|
transformSkillContent,
|
|
19242
19896
|
templateizeMachineContent,
|
|
@@ -19269,9 +19923,11 @@ export {
|
|
|
19269
19923
|
renderTemplatePreview,
|
|
19270
19924
|
renderTemplate,
|
|
19271
19925
|
renderProviderFragment,
|
|
19926
|
+
renderNativeAgentContent,
|
|
19272
19927
|
renderMachineAwareContentPreview,
|
|
19273
19928
|
renderMachineAwareContent,
|
|
19274
19929
|
refreshStationProfile,
|
|
19930
|
+
refreshSessionRender,
|
|
19275
19931
|
redactContent,
|
|
19276
19932
|
reconcileManagedSkillRuntimes,
|
|
19277
19933
|
readStationProfile,
|
|
@@ -19285,6 +19941,7 @@ export {
|
|
|
19285
19941
|
parseProjectContextBundle,
|
|
19286
19942
|
parseAgentOperatingRulesVersion,
|
|
19287
19943
|
now,
|
|
19944
|
+
normalizeSessionHostedProfileSelector,
|
|
19288
19945
|
normalizeProfileConfigBinding,
|
|
19289
19946
|
normalizeProfileAssetBinding,
|
|
19290
19947
|
normalizeOsFamily,
|
|
@@ -19315,6 +19972,7 @@ export {
|
|
|
19315
19972
|
ensureGlobalAgentRulesStandardConfig,
|
|
19316
19973
|
ensureDangerousOperationGuardStandardConfig,
|
|
19317
19974
|
ensureCodewithSharedTodosStorageStandardConfig,
|
|
19975
|
+
discoverHarnesses,
|
|
19318
19976
|
diffConfig,
|
|
19319
19977
|
detectMachineContext,
|
|
19320
19978
|
detectFormat,
|
|
@@ -19331,6 +19989,7 @@ export {
|
|
|
19331
19989
|
compareAgentOperatingRulesVersions,
|
|
19332
19990
|
cleanSessionPathInput,
|
|
19333
19991
|
checkSessionRenderDrift,
|
|
19992
|
+
buildSumiAgentsMd,
|
|
19334
19993
|
buildStationProfileBlock,
|
|
19335
19994
|
buildOpenCodeAgentsMd,
|
|
19336
19995
|
buildCursorMdc,
|
|
@@ -19390,6 +20049,7 @@ export {
|
|
|
19390
20049
|
LEGACY_CONFIGS_EXECUTABLE,
|
|
19391
20050
|
LEGACY_CONFIGS_COMPAT_VERSION,
|
|
19392
20051
|
KNOWN_CONFIGS,
|
|
20052
|
+
InvalidExpectedVersionError,
|
|
19393
20053
|
InstructionGraphValidationError,
|
|
19394
20054
|
INSTRUCTION_GRAPH_PLAN_SCHEMA,
|
|
19395
20055
|
INSTRUCTION_FALLBACKS,
|
|
@@ -19398,10 +20058,13 @@ export {
|
|
|
19398
20058
|
INSTRUCTIONS_LOCAL_OPT_IN_ENV,
|
|
19399
20059
|
INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA,
|
|
19400
20060
|
INBOX_CONVERSATIONS_MINIMUM_VERSION,
|
|
20061
|
+
HARNESS_DISCOVERY_SCHEMA,
|
|
19401
20062
|
GLOBAL_AGENT_RULES_STANDARD_SLUG,
|
|
19402
20063
|
GLOBAL_AGENT_RULES_STANDARD_CONTENT,
|
|
20064
|
+
DISCOVERABLE_HARNESSES,
|
|
19403
20065
|
DANGEROUS_OPERATION_GUARD_STANDARD_SLUG,
|
|
19404
20066
|
DANGEROUS_OPERATION_GUARD_STANDARD_CONTENT,
|
|
20067
|
+
ConfigVersionConflictError,
|
|
19405
20068
|
ConfigNotFoundError,
|
|
19406
20069
|
ConfigApplyError,
|
|
19407
20070
|
CloudConfigStore,
|