@aopslabs/aops 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +19 -0
- package/NOTICE +7 -0
- package/README.md +203 -0
- package/assets/agent-assets/core/SKILL.md +49 -0
- package/assets/agent-assets/core/references/agentspace/SKILL.md +78 -0
- package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +108 -0
- package/assets/agent-assets/core/references/chatv3/SKILL.md +48 -0
- package/assets/agent-assets/core/references/collaborative-work/SKILL.md +85 -0
- package/assets/agent-assets/core/references/discuss/SKILL.md +66 -0
- package/assets/agent-assets/core/references/docman/SKILL.md +69 -0
- package/assets/agent-assets/core/references/hosted-chat/SKILL.md +64 -0
- package/assets/agent-assets/core/references/mission/SKILL.md +54 -0
- package/assets/agent-assets/core/references/projectman/SKILL.md +86 -0
- package/assets/agent-assets/core/references/sys/SKILL.md +36 -0
- package/assets/agent-assets/core/references/view/SKILL.md +58 -0
- package/assets/agent-assets/core/references/working-disciplines/SKILL.md +69 -0
- package/assets/agent-assets/core/user-guides/agent-assets.md +81 -0
- package/assets/agent-assets/core/user-guides/agentspace.md +1098 -0
- package/assets/agent-assets/core/user-guides/aops-cli.md +1107 -0
- package/assets/agent-assets/core/user-guides/aops-system.md +63 -0
- package/assets/agent-assets/core/user-guides/chatv3.md +113 -0
- package/assets/agent-assets/core/user-guides/docman.md +162 -0
- package/assets/agent-assets/core/user-guides/projectman.md +212 -0
- package/assets/agent-assets/core/user-guides/sys.md +94 -0
- package/assets/agent-assets/core/user-guides/working-disciplines.md +588 -0
- package/assets/agent-assets/gateway/aops/SKILL.md +14 -0
- package/assets/r6-remote-session-baseline.json +19 -0
- package/assets/skills/aops-install/SKILL.md +231 -0
- package/assets/skills/aops-install/agents/openai.yaml +4 -0
- package/dist/commands/activity.js +352 -0
- package/dist/commands/agent-profile.js +653 -0
- package/dist/commands/agent.js +710 -0
- package/dist/commands/agents-md.js +125 -0
- package/dist/commands/api.js +84 -0
- package/dist/commands/archive.js +873 -0
- package/dist/commands/artifact.js +535 -0
- package/dist/commands/assets.js +1414 -0
- package/dist/commands/auth/device-login.js +1 -0
- package/dist/commands/auth/legacy-export.js +177 -0
- package/dist/commands/auth/login.js +426 -0
- package/dist/commands/chat.js +1363 -0
- package/dist/commands/chatv3.js +1684 -0
- package/dist/commands/checkpoint.js +549 -0
- package/dist/commands/commercial-artifact.js +475 -0
- package/dist/commands/commercial-license.js +594 -0
- package/dist/commands/community-auth.js +62 -0
- package/dist/commands/community-cockpit.js +185 -0
- package/dist/commands/community-console.js +109 -0
- package/dist/commands/community-doctor.js +335 -0
- package/dist/commands/community-server.js +2062 -0
- package/dist/commands/community-setup.js +284 -0
- package/dist/commands/discuss.js +1448 -0
- package/dist/commands/doc.js +5219 -0
- package/dist/commands/experience.js +922 -0
- package/dist/commands/host.js +1127 -0
- package/dist/commands/init.js +118 -0
- package/dist/commands/memory.js +3129 -0
- package/dist/commands/mission.js +562 -0
- package/dist/commands/official-catalog.js +134 -0
- package/dist/commands/plan.js +84 -0
- package/dist/commands/playbook.js +415 -0
- package/dist/commands/pm/context.js +6 -0
- package/dist/commands/pm/index.js +701 -0
- package/dist/commands/pm/projectman.js +6266 -0
- package/dist/commands/project.js +1080 -0
- package/dist/commands/prompt.js +1024 -0
- package/dist/commands/repo-sync.js +1907 -0
- package/dist/commands/resource.js +527 -0
- package/dist/commands/skill.js +1211 -0
- package/dist/commands/start-disciplines.js +510 -0
- package/dist/commands/start.js +1343 -0
- package/dist/commands/target.js +309 -0
- package/dist/commands/version.js +42 -0
- package/dist/commands/view.js +1852 -0
- package/dist/launchers/cockpit.js +28 -0
- package/dist/launchers/options.js +74 -0
- package/dist/launchers/server.js +41 -0
- package/dist/lib/agent-assets/envelope.js +83 -0
- package/dist/lib/agent-assets/gateway.js +15 -0
- package/dist/lib/agent-assets/guards.js +23 -0
- package/dist/lib/agent-assets/hosted-discovery.js +148 -0
- package/dist/lib/agent-assets/hosted-package-input.js +154 -0
- package/dist/lib/agent-assets/legacy-pointer-migration.js +677 -0
- package/dist/lib/agent-assets/native-fs.js +589 -0
- package/dist/lib/agent-assets/package-manifest.js +324 -0
- package/dist/lib/agent-assets/portable-path.js +67 -0
- package/dist/lib/agent-assets/release-input.js +177 -0
- package/dist/lib/agent-assets/roots.js +45 -0
- package/dist/lib/agent-assets/runtime-binding-reader.js +545 -0
- package/dist/lib/agent-assets/runtime-targets.js +50 -0
- package/dist/lib/agent-assets/store-reader.js +1212 -0
- package/dist/lib/agent-assets/store-types.js +1 -0
- package/dist/lib/agent-assets/store-writer.js +1484 -0
- package/dist/lib/agent-assets/types.js +1 -0
- package/dist/lib/agent-assets/unicode-case-folding-v15-1.generated.js +1548 -0
- package/dist/lib/commercial-final-readiness.js +504 -0
- package/dist/lib/commercial-lifecycle-smoke-isolation.js +236 -0
- package/dist/lib/commercial-managed-postgres-docker-adapter.js +274 -0
- package/dist/lib/commercial-setup-readiness-adapter.js +179 -0
- package/dist/lib/community-client-contract.js +164 -0
- package/dist/lib/community-cockpit-lifecycle.js +601 -0
- package/dist/lib/community-command-abort.js +59 -0
- package/dist/lib/community-diagnostic.js +237 -0
- package/dist/lib/community-docker-adapter.js +498 -0
- package/dist/lib/community-home.js +31 -0
- package/dist/lib/community-instance-contract.js +148 -0
- package/dist/lib/community-instance-layout.js +97 -0
- package/dist/lib/community-lifecycle.js +1631 -0
- package/dist/lib/community-migration-snapshot.js +770 -0
- package/dist/lib/community-native-application-recovery.js +425 -0
- package/dist/lib/community-native-child.js +240 -0
- package/dist/lib/community-native-database-recovery.js +456 -0
- package/dist/lib/community-native-lifecycle.js +2458 -0
- package/dist/lib/community-native-migration.js +339 -0
- package/dist/lib/community-native-postgres.js +596 -0
- package/dist/lib/community-operation-journal.js +773 -0
- package/dist/lib/community-operation-lock.js +596 -0
- package/dist/lib/community-release-resolver.js +291 -0
- package/dist/lib/community-release-verifier.js +475 -0
- package/dist/lib/community-repo-discovery.js +252 -0
- package/dist/lib/community-setup-server-env.js +203 -0
- package/dist/lib/external-url.js +110 -0
- package/dist/lib/official-catalog-gateway.js +108 -0
- package/dist/lib/official-catalog.js +364 -0
- package/dist/lib/server-bootstrap.js +240 -0
- package/dist/lib/setup-agent-assets-bridge.js +176 -0
- package/dist/lib/setup-agent-assets-release.js +52 -0
- package/dist/lib/setup-external-postgres.js +108 -0
- package/dist/lib/setup-init-orchestrator.js +990 -0
- package/dist/lib/setup-install-guide.js +52 -0
- package/dist/lib/setup-installer-release.js +47 -0
- package/dist/lib/setup-local-postgres.js +329 -0
- package/dist/lib/setup-official-catalog-bridge.js +148 -0
- package/dist/lib/setup-readiness.js +888 -0
- package/dist/lib/tui-launcher.js +59 -0
- package/dist/main.js +150 -0
- package/dist/seeds/agents-md.js +54 -0
- package/dist/utils/agent-gateway.js +275 -0
- package/dist/utils/agents-md.js +281 -0
- package/dist/utils/api.js +255 -0
- package/dist/utils/chatv3-session-store.js +185 -0
- package/dist/utils/command.js +67 -0
- package/dist/utils/community-host-runtime.js +376 -0
- package/dist/utils/config.js +1015 -0
- package/dist/utils/discussion-workspace.js +552 -0
- package/dist/utils/experience-workspace.js +326 -0
- package/dist/utils/guide-paths.js +67 -0
- package/dist/utils/hosted-sugar.js +56 -0
- package/dist/utils/hosted-workspace.js +229 -0
- package/dist/utils/memory-workspace.js +537 -0
- package/dist/utils/operator-runtime.js +199 -0
- package/dist/utils/password-crypto.js +39 -0
- package/dist/utils/playbook-workspace.js +479 -0
- package/dist/utils/project-context.js +255 -0
- package/dist/utils/prompts.js +70 -0
- package/dist/utils/refresh-race.js +28 -0
- package/dist/utils/repo-config.js +109 -0
- package/dist/utils/repo-first-projectman.js +194 -0
- package/dist/utils/repo-first-storage.js +13 -0
- package/dist/utils/repo-first-view.js +305 -0
- package/dist/utils/session-guidance.js +278 -0
- package/dist/utils/session-state.js +137 -0
- package/dist/utils/table.js +39 -0
- package/dist/utils/transient-fs.js +49 -0
- package/dist/utils/workspace-root.js +1 -0
- package/launchers/aops-cockpit.ps1 +15 -0
- package/launchers/aops-cockpit.sh +16 -0
- package/launchers/aops-server.ps1 +15 -0
- package/launchers/aops-server.sh +16 -0
- package/native/tui/win32-x64/aops-tui.exe +0 -0
- package/package.json +81 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { portablePackageCaseKeyV1, validatePortablePackagePath } from './portable-path.js';
|
|
3
|
+
const SHA256_HEX = /^[a-f0-9]{64}$/;
|
|
4
|
+
const MANIFEST_KEYS = new Set([
|
|
5
|
+
'schemaVersion',
|
|
6
|
+
'assetKind',
|
|
7
|
+
'name',
|
|
8
|
+
'version',
|
|
9
|
+
'versionId',
|
|
10
|
+
'entryFile',
|
|
11
|
+
'standard',
|
|
12
|
+
'packageSha256',
|
|
13
|
+
'files',
|
|
14
|
+
'compatibility',
|
|
15
|
+
'provenance',
|
|
16
|
+
]);
|
|
17
|
+
const FILE_DIGEST_KEYS = new Set(['path', 'sha256', 'byteLength']);
|
|
18
|
+
const COMPATIBILITY_KEYS = new Set(['minCliVersion', 'maxSchemaVersion']);
|
|
19
|
+
const PROVENANCE_KEYS = new Set([
|
|
20
|
+
'trustClass',
|
|
21
|
+
'expectedDigestSource',
|
|
22
|
+
'reference',
|
|
23
|
+
'releaseSha256',
|
|
24
|
+
'signatureRef',
|
|
25
|
+
]);
|
|
26
|
+
function isRecord(value) {
|
|
27
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
function hasOwn(record, key) {
|
|
30
|
+
return Object.prototype.hasOwnProperty.call(record, key);
|
|
31
|
+
}
|
|
32
|
+
function issue(code, at, message) {
|
|
33
|
+
return { code, at, message };
|
|
34
|
+
}
|
|
35
|
+
function requireKeys(record, required, at, issues) {
|
|
36
|
+
for (const key of required) {
|
|
37
|
+
if (!hasOwn(record, key))
|
|
38
|
+
issues.push(issue('missing_property', `${at}.${key}`, 'Required property is missing.'));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function rejectUnknownKeys(record, allowed, at, issues) {
|
|
42
|
+
for (const key of Object.keys(record)) {
|
|
43
|
+
if (!allowed.has(key))
|
|
44
|
+
issues.push(issue('unknown_property', `${at}.${key}`, 'Unknown property is not allowed.'));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function validateNonEmptyString(record, key, at, issues) {
|
|
48
|
+
if (!hasOwn(record, key))
|
|
49
|
+
return;
|
|
50
|
+
if (typeof record[key] !== 'string' || record[key].length === 0) {
|
|
51
|
+
issues.push(issue('invalid_type', `${at}.${key}`, 'Expected a non-empty string.'));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function validateSha256(record, key, at, issues) {
|
|
55
|
+
if (!hasOwn(record, key))
|
|
56
|
+
return;
|
|
57
|
+
if (typeof record[key] !== 'string' || !SHA256_HEX.test(record[key])) {
|
|
58
|
+
issues.push(issue('invalid_sha256', `${at}.${key}`, 'Expected a lowercase SHA-256 hex digest.'));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function validateRelativePath(record, key, at, issues) {
|
|
62
|
+
if (!hasOwn(record, key))
|
|
63
|
+
return;
|
|
64
|
+
const value = record[key];
|
|
65
|
+
if (typeof value !== 'string') {
|
|
66
|
+
issues.push(issue('invalid_type', `${at}.${key}`, 'Expected a package-relative path string.'));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const result = validatePortablePackagePath(value);
|
|
70
|
+
if (!result.ok) {
|
|
71
|
+
issues.push(issue('invalid_package_path', `${at}.${key}`, `Path violates the portable package contract: ${result.issues.map((entry) => entry.code).join(', ')}.`));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function validateCompatibility(value, issues) {
|
|
75
|
+
const at = '$.compatibility';
|
|
76
|
+
if (!isRecord(value)) {
|
|
77
|
+
issues.push(issue('invalid_type', at, 'Expected a compatibility object.'));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
rejectUnknownKeys(value, COMPATIBILITY_KEYS, at, issues);
|
|
81
|
+
requireKeys(value, ['minCliVersion', 'maxSchemaVersion'], at, issues);
|
|
82
|
+
validateNonEmptyString(value, 'minCliVersion', at, issues);
|
|
83
|
+
if (hasOwn(value, 'maxSchemaVersion') && value.maxSchemaVersion !== 1) {
|
|
84
|
+
issues.push(issue('schema_incompatible', `${at}.maxSchemaVersion`, 'Only schema version 1 is supported.'));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function validateProvenance(value, issues) {
|
|
88
|
+
const at = '$.provenance';
|
|
89
|
+
if (!isRecord(value)) {
|
|
90
|
+
issues.push(issue('invalid_type', at, 'Expected a provenance object.'));
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
rejectUnknownKeys(value, PROVENANCE_KEYS, at, issues);
|
|
94
|
+
requireKeys(value, ['trustClass', 'expectedDigestSource', 'reference'], at, issues);
|
|
95
|
+
validateNonEmptyString(value, 'reference', at, issues);
|
|
96
|
+
if (hasOwn(value, 'trustClass') &&
|
|
97
|
+
value.trustClass !== 'signed-community-release' &&
|
|
98
|
+
value.trustClass !== 'verified-hosted-package') {
|
|
99
|
+
issues.push(issue('invalid_value', `${at}.trustClass`, 'Unknown package trust class.'));
|
|
100
|
+
}
|
|
101
|
+
if (hasOwn(value, 'expectedDigestSource') &&
|
|
102
|
+
value.expectedDigestSource !== 'signed-release-manifest' &&
|
|
103
|
+
value.expectedDigestSource !== 'immutable-hosted-metadata') {
|
|
104
|
+
issues.push(issue('invalid_value', `${at}.expectedDigestSource`, 'Unknown expected digest source.'));
|
|
105
|
+
}
|
|
106
|
+
if (hasOwn(value, 'releaseSha256'))
|
|
107
|
+
validateSha256(value, 'releaseSha256', at, issues);
|
|
108
|
+
if (hasOwn(value, 'signatureRef'))
|
|
109
|
+
validateNonEmptyString(value, 'signatureRef', at, issues);
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
function validateFileDigests(value, issues) {
|
|
113
|
+
const at = '$.files';
|
|
114
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
115
|
+
issues.push(issue('invalid_type', at, 'Expected a non-empty file digest array.'));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const exactItems = new Set();
|
|
119
|
+
for (const [index, candidate] of value.entries()) {
|
|
120
|
+
const rowAt = `${at}[${index}]`;
|
|
121
|
+
if (!isRecord(candidate)) {
|
|
122
|
+
issues.push(issue('invalid_type', rowAt, 'Expected a file digest object.'));
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
rejectUnknownKeys(candidate, FILE_DIGEST_KEYS, rowAt, issues);
|
|
126
|
+
requireKeys(candidate, ['path', 'sha256', 'byteLength'], rowAt, issues);
|
|
127
|
+
validateRelativePath(candidate, 'path', rowAt, issues);
|
|
128
|
+
validateSha256(candidate, 'sha256', rowAt, issues);
|
|
129
|
+
if (hasOwn(candidate, 'byteLength') &&
|
|
130
|
+
(typeof candidate.byteLength !== 'number' || !Number.isInteger(candidate.byteLength) || candidate.byteLength < 0)) {
|
|
131
|
+
issues.push(issue('invalid_type', `${rowAt}.byteLength`, 'Expected a non-negative integer.'));
|
|
132
|
+
}
|
|
133
|
+
if (typeof candidate.path === 'string' &&
|
|
134
|
+
typeof candidate.sha256 === 'string' &&
|
|
135
|
+
typeof candidate.byteLength === 'number') {
|
|
136
|
+
const identity = `${candidate.path}\0${candidate.sha256}\0${candidate.byteLength}`;
|
|
137
|
+
if (exactItems.has(identity)) {
|
|
138
|
+
issues.push(issue('duplicate_manifest_item', rowAt, 'JSON Schema uniqueItems forbids duplicate file rows.'));
|
|
139
|
+
}
|
|
140
|
+
exactItems.add(identity);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export function validatePackageManifestStructureV1(value) {
|
|
145
|
+
if (!isRecord(value)) {
|
|
146
|
+
return { ok: false, issues: [issue('invalid_type', '$', 'Expected a PackageManifestV1 object.')] };
|
|
147
|
+
}
|
|
148
|
+
const issues = [];
|
|
149
|
+
rejectUnknownKeys(value, MANIFEST_KEYS, '$', issues);
|
|
150
|
+
requireKeys(value, [
|
|
151
|
+
'schemaVersion',
|
|
152
|
+
'assetKind',
|
|
153
|
+
'name',
|
|
154
|
+
'version',
|
|
155
|
+
'versionId',
|
|
156
|
+
'entryFile',
|
|
157
|
+
'standard',
|
|
158
|
+
'packageSha256',
|
|
159
|
+
'files',
|
|
160
|
+
'provenance',
|
|
161
|
+
], '$', issues);
|
|
162
|
+
if (hasOwn(value, 'schemaVersion') && value.schemaVersion !== 1) {
|
|
163
|
+
issues.push(issue('schema_incompatible', '$.schemaVersion', 'Only PackageManifest schema version 1 is supported.'));
|
|
164
|
+
}
|
|
165
|
+
if (hasOwn(value, 'assetKind') && value.assetKind !== 'community-core' && value.assetKind !== 'skill-package') {
|
|
166
|
+
issues.push(issue('invalid_value', '$.assetKind', 'Unknown package asset kind.'));
|
|
167
|
+
}
|
|
168
|
+
for (const key of ['name', 'version', 'versionId', 'standard']) {
|
|
169
|
+
validateNonEmptyString(value, key, '$', issues);
|
|
170
|
+
}
|
|
171
|
+
validateRelativePath(value, 'entryFile', '$', issues);
|
|
172
|
+
validateSha256(value, 'packageSha256', '$', issues);
|
|
173
|
+
validateFileDigests(value.files, issues);
|
|
174
|
+
if (hasOwn(value, 'compatibility'))
|
|
175
|
+
validateCompatibility(value.compatibility, issues);
|
|
176
|
+
const provenance = validateProvenance(value.provenance, issues);
|
|
177
|
+
if (value.assetKind === 'community-core') {
|
|
178
|
+
if (value.name !== 'aops')
|
|
179
|
+
issues.push(issue('invalid_value', '$.name', 'Community core name must be aops.'));
|
|
180
|
+
if (value.entryFile !== 'SKILL.md') {
|
|
181
|
+
issues.push(issue('invalid_value', '$.entryFile', 'Community core entry file must be SKILL.md.'));
|
|
182
|
+
}
|
|
183
|
+
if (value.standard !== 'aops-community-core-v1') {
|
|
184
|
+
issues.push(issue('invalid_value', '$.standard', 'Community core standard must be aops-community-core-v1.'));
|
|
185
|
+
}
|
|
186
|
+
if (provenance?.trustClass !== 'signed-community-release') {
|
|
187
|
+
issues.push(issue('invalid_value', '$.provenance.trustClass', 'Community core must use signed-community-release.'));
|
|
188
|
+
}
|
|
189
|
+
if (provenance?.expectedDigestSource !== 'signed-release-manifest') {
|
|
190
|
+
issues.push(issue('invalid_value', '$.provenance.expectedDigestSource', 'Community core must use signed-release-manifest.'));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (value.assetKind === 'skill-package') {
|
|
194
|
+
if (value.entryFile !== 'SKILL.md') {
|
|
195
|
+
issues.push(issue('invalid_value', '$.entryFile', 'Skill package entry file must be SKILL.md.'));
|
|
196
|
+
}
|
|
197
|
+
if (value.standard !== 'aops-skill-package-v1') {
|
|
198
|
+
issues.push(issue('invalid_value', '$.standard', 'Skill package standard must be aops-skill-package-v1.'));
|
|
199
|
+
}
|
|
200
|
+
if (provenance?.trustClass !== 'verified-hosted-package') {
|
|
201
|
+
issues.push(issue('invalid_value', '$.provenance.trustClass', 'Skill package must use verified-hosted-package.'));
|
|
202
|
+
}
|
|
203
|
+
if (provenance?.expectedDigestSource !== 'immutable-hosted-metadata') {
|
|
204
|
+
issues.push(issue('invalid_value', '$.provenance.expectedDigestSource', 'Skill package must use immutable-hosted-metadata.'));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return issues.length > 0
|
|
208
|
+
? { ok: false, issues }
|
|
209
|
+
: { ok: true, value: value };
|
|
210
|
+
}
|
|
211
|
+
export function sha256Bytes(bytes) {
|
|
212
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
213
|
+
}
|
|
214
|
+
export function canonicalPackageSha256V1(files) {
|
|
215
|
+
const records = files.map((file) => {
|
|
216
|
+
if (!SHA256_HEX.test(file.sha256))
|
|
217
|
+
throw new TypeError('invalid_file_sha256');
|
|
218
|
+
return { path: file.path.normalize('NFC'), sha256: file.sha256 };
|
|
219
|
+
});
|
|
220
|
+
records.sort((left, right) => Buffer.compare(Buffer.from(left.path, 'utf8'), Buffer.from(right.path, 'utf8')));
|
|
221
|
+
const hash = createHash('sha256');
|
|
222
|
+
for (const record of records) {
|
|
223
|
+
hash.update(Buffer.from(record.path, 'utf8'));
|
|
224
|
+
hash.update(Buffer.from([0]));
|
|
225
|
+
hash.update(record.sha256, 'ascii');
|
|
226
|
+
hash.update(Buffer.from([0x0a]));
|
|
227
|
+
}
|
|
228
|
+
return hash.digest('hex');
|
|
229
|
+
}
|
|
230
|
+
function transferFileRecord(value) {
|
|
231
|
+
return isRecord(value) && typeof value.path === 'string' && value.bytes instanceof Uint8Array;
|
|
232
|
+
}
|
|
233
|
+
export function validatePortablePackageV1(manifestValue, transferFiles) {
|
|
234
|
+
const structural = validatePackageManifestStructureV1(manifestValue);
|
|
235
|
+
if (!structural.ok)
|
|
236
|
+
return structural;
|
|
237
|
+
const manifest = structural.value;
|
|
238
|
+
const issues = [];
|
|
239
|
+
const rawManifestPaths = new Set();
|
|
240
|
+
const normalizedManifestPaths = new Set();
|
|
241
|
+
const foldedManifestPaths = new Set();
|
|
242
|
+
const normalizedRows = [];
|
|
243
|
+
for (const [index, row] of manifest.files.entries()) {
|
|
244
|
+
const normalizedPath = row.path.normalize('NFC');
|
|
245
|
+
const foldedPath = portablePackageCaseKeyV1(normalizedPath);
|
|
246
|
+
if (rawManifestPaths.has(row.path)) {
|
|
247
|
+
issues.push(issue('duplicate_raw_path', `$.files[${index}].path`, 'Duplicate raw manifest path.'));
|
|
248
|
+
}
|
|
249
|
+
if (normalizedManifestPaths.has(normalizedPath)) {
|
|
250
|
+
issues.push(issue('duplicate_normalized_path', `$.files[${index}].path`, 'Duplicate NFC manifest path.'));
|
|
251
|
+
}
|
|
252
|
+
if (foldedManifestPaths.has(foldedPath)) {
|
|
253
|
+
issues.push(issue('portable_case_collision', `$.files[${index}].path`, 'Portable Unicode case-fold collision.'));
|
|
254
|
+
}
|
|
255
|
+
rawManifestPaths.add(row.path);
|
|
256
|
+
normalizedManifestPaths.add(normalizedPath);
|
|
257
|
+
foldedManifestPaths.add(foldedPath);
|
|
258
|
+
normalizedRows.push({ ...row, path: normalizedPath });
|
|
259
|
+
}
|
|
260
|
+
const normalizedEntryFile = manifest.entryFile.normalize('NFC');
|
|
261
|
+
if (!normalizedManifestPaths.has(normalizedEntryFile)) {
|
|
262
|
+
issues.push(issue('entry_file_missing', '$.entryFile', 'entryFile must name exactly one declared file.'));
|
|
263
|
+
}
|
|
264
|
+
const normalizedTransfer = new Map();
|
|
265
|
+
const rawTransferPaths = new Set();
|
|
266
|
+
const foldedTransferPaths = new Set();
|
|
267
|
+
for (const [index, candidate] of transferFiles.entries()) {
|
|
268
|
+
const at = `$transfer[${index}]`;
|
|
269
|
+
if (!transferFileRecord(candidate)) {
|
|
270
|
+
issues.push(issue('invalid_type', at, 'Expected { path, bytes: Uint8Array }.'));
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const pathResult = validatePortablePackagePath(candidate.path);
|
|
274
|
+
if (!pathResult.ok) {
|
|
275
|
+
issues.push(issue('invalid_package_path', `${at}.path`, `Transfer path violates the portable package contract: ${pathResult.issues.map((entry) => entry.code).join(', ')}.`));
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const normalizedPath = pathResult.normalizedPath;
|
|
279
|
+
const foldedPath = portablePackageCaseKeyV1(normalizedPath);
|
|
280
|
+
if (rawTransferPaths.has(candidate.path) || normalizedTransfer.has(normalizedPath)) {
|
|
281
|
+
issues.push(issue('duplicate_transfer_path', `${at}.path`, 'Duplicate raw or NFC transfer path.'));
|
|
282
|
+
}
|
|
283
|
+
if (foldedTransferPaths.has(foldedPath)) {
|
|
284
|
+
issues.push(issue('portable_case_collision', `${at}.path`, 'Portable transfer path case-fold collision.'));
|
|
285
|
+
}
|
|
286
|
+
rawTransferPaths.add(candidate.path);
|
|
287
|
+
foldedTransferPaths.add(foldedPath);
|
|
288
|
+
if (!normalizedTransfer.has(normalizedPath))
|
|
289
|
+
normalizedTransfer.set(normalizedPath, candidate.bytes);
|
|
290
|
+
}
|
|
291
|
+
const missing = [...normalizedManifestPaths].filter((path) => !normalizedTransfer.has(path));
|
|
292
|
+
const undeclared = [...normalizedTransfer.keys()].filter((path) => !normalizedManifestPaths.has(path));
|
|
293
|
+
if (missing.length > 0 || undeclared.length > 0 || normalizedTransfer.size !== normalizedManifestPaths.size) {
|
|
294
|
+
issues.push(issue('transfer_membership_mismatch', '$transfer', `Transfer membership differs from the manifest (missing=${missing.length}, undeclared=${undeclared.length}).`));
|
|
295
|
+
}
|
|
296
|
+
for (const [index, row] of normalizedRows.entries()) {
|
|
297
|
+
const bytes = normalizedTransfer.get(row.path);
|
|
298
|
+
if (!bytes)
|
|
299
|
+
continue;
|
|
300
|
+
if (bytes.byteLength !== row.byteLength) {
|
|
301
|
+
issues.push(issue('file_byte_length_mismatch', `$.files[${index}].byteLength`, `Expected ${row.byteLength} bytes but received ${bytes.byteLength}.`));
|
|
302
|
+
}
|
|
303
|
+
if (sha256Bytes(bytes) !== row.sha256) {
|
|
304
|
+
issues.push(issue('file_digest_mismatch', `$.files[${index}].sha256`, 'Transferred file digest does not match.'));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const computedPackageSha256 = canonicalPackageSha256V1(normalizedRows);
|
|
308
|
+
if (computedPackageSha256 !== manifest.packageSha256) {
|
|
309
|
+
issues.push(issue('package_digest_mismatch', '$.packageSha256', 'Canonical package digest does not match.'));
|
|
310
|
+
}
|
|
311
|
+
if (issues.length > 0)
|
|
312
|
+
return { ok: false, issues };
|
|
313
|
+
const normalizedManifest = { ...manifest, entryFile: normalizedEntryFile, files: normalizedRows };
|
|
314
|
+
return {
|
|
315
|
+
ok: true,
|
|
316
|
+
value: {
|
|
317
|
+
manifest,
|
|
318
|
+
normalizedManifest,
|
|
319
|
+
packageSha256: computedPackageSha256,
|
|
320
|
+
portableValidationComplete: true,
|
|
321
|
+
nativeAliasValidation: 'required',
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { portableCaseFoldUnicode15_1 } from './unicode-case-folding-v15-1.generated.js';
|
|
2
|
+
export const MAX_PACKAGE_PATH_CODE_POINTS = 4096;
|
|
3
|
+
export const MAX_PACKAGE_SEGMENT_UTF8_BYTES = 255;
|
|
4
|
+
const WINDOWS_INVALID_SEGMENT_CHARACTERS = new Set(['<', '>', ':', '"', '\\', '|', '?', '*']);
|
|
5
|
+
const WINDOWS_DEVICE_ALIAS = /^(?:CON|PRN|AUX|NUL|CONIN\$|CONOUT\$|CLOCK\$|COM[0-9¹²³]|LPT[0-9¹²³])(?:\..*)?$/;
|
|
6
|
+
function asciiUpper(value) {
|
|
7
|
+
let result = '';
|
|
8
|
+
for (const character of value) {
|
|
9
|
+
const codePoint = character.codePointAt(0);
|
|
10
|
+
result += codePoint >= 0x61 && codePoint <= 0x7a
|
|
11
|
+
? String.fromCodePoint(codePoint - 0x20)
|
|
12
|
+
: character;
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
function isControl(codePoint) {
|
|
17
|
+
return codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f);
|
|
18
|
+
}
|
|
19
|
+
export function validatePortablePackagePath(value) {
|
|
20
|
+
const issues = [];
|
|
21
|
+
if (value.length === 0)
|
|
22
|
+
issues.push({ code: 'empty_path' });
|
|
23
|
+
if ([...value].length > MAX_PACKAGE_PATH_CODE_POINTS)
|
|
24
|
+
issues.push({ code: 'path_too_long' });
|
|
25
|
+
if (value.startsWith('/') || /^[A-Za-z]:/.test(value))
|
|
26
|
+
issues.push({ code: 'absolute_path' });
|
|
27
|
+
if (value.includes('\\'))
|
|
28
|
+
issues.push({ code: 'backslash' });
|
|
29
|
+
const segments = value.split('/');
|
|
30
|
+
for (const [segmentIndex, segment] of segments.entries()) {
|
|
31
|
+
if (segment.length === 0) {
|
|
32
|
+
issues.push({ code: 'empty_segment', segmentIndex });
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (segment === '.' || segment === '..')
|
|
36
|
+
issues.push({ code: 'dot_segment', segmentIndex });
|
|
37
|
+
if (segment.endsWith('.') || segment.endsWith(' ')) {
|
|
38
|
+
issues.push({ code: 'trailing_dot_or_space', segmentIndex });
|
|
39
|
+
}
|
|
40
|
+
if (Buffer.byteLength(segment.normalize('NFC'), 'utf8') > MAX_PACKAGE_SEGMENT_UTF8_BYTES) {
|
|
41
|
+
issues.push({ code: 'segment_too_long', segmentIndex });
|
|
42
|
+
}
|
|
43
|
+
if (WINDOWS_DEVICE_ALIAS.test(asciiUpper(segment))) {
|
|
44
|
+
issues.push({ code: 'windows_device_alias', segmentIndex });
|
|
45
|
+
}
|
|
46
|
+
for (const character of segment) {
|
|
47
|
+
const codePoint = character.codePointAt(0);
|
|
48
|
+
if (isControl(codePoint))
|
|
49
|
+
issues.push({ code: 'control_character', segmentIndex });
|
|
50
|
+
if (WINDOWS_INVALID_SEGMENT_CHARACTERS.has(character)) {
|
|
51
|
+
issues.push({
|
|
52
|
+
code: character === ':' ? 'alternate_data_stream' : 'windows_invalid_character',
|
|
53
|
+
segmentIndex,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (issues.length > 0)
|
|
59
|
+
return { ok: false, issues };
|
|
60
|
+
return { ok: true, normalizedPath: segments.map((segment) => segment.normalize('NFC')).join('/') };
|
|
61
|
+
}
|
|
62
|
+
export function portablePackageCaseKeyV1(value) {
|
|
63
|
+
return value
|
|
64
|
+
.split('/')
|
|
65
|
+
.map((segment) => portableCaseFoldUnicode15_1(segment.normalize('NFC')).normalize('NFC'))
|
|
66
|
+
.join('/');
|
|
67
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { verifyCommunityAgentAssetsReleaseBundle, } from '../community-release-verifier.js';
|
|
4
|
+
import { AgentAssetsError } from './envelope.js';
|
|
5
|
+
import { sha256Bytes, validatePortablePackageV1 } from './package-manifest.js';
|
|
6
|
+
function releaseInputError(code, message, details) {
|
|
7
|
+
return new AgentAssetsError(code, message, {
|
|
8
|
+
nextActions: ['Acquire an exact signed Community release bundle and retry without modifying its contents.'],
|
|
9
|
+
...(details === undefined ? {} : { details }),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function safeReleaseFile(releaseRoot, reference) {
|
|
13
|
+
if (!reference ||
|
|
14
|
+
path.isAbsolute(reference) ||
|
|
15
|
+
reference.includes('\\') ||
|
|
16
|
+
reference.split('/').some((segment) => !segment || segment === '.' || segment === '..')) {
|
|
17
|
+
throw releaseInputError('invalid_package_path', 'Signed release contains an unsafe asset reference.', { reference });
|
|
18
|
+
}
|
|
19
|
+
const normalizedRoot = path.normalize(releaseRoot);
|
|
20
|
+
const rootStat = lstatSync(normalizedRoot);
|
|
21
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
|
|
22
|
+
throw releaseInputError('link_unsafe_path', 'Signed release root is not a real directory.');
|
|
23
|
+
}
|
|
24
|
+
const realRoot = realpathSync.native(normalizedRoot);
|
|
25
|
+
const candidate = path.join(normalizedRoot, ...reference.split('/'));
|
|
26
|
+
const relative = path.relative(normalizedRoot, candidate);
|
|
27
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
28
|
+
throw releaseInputError('invalid_package_path', 'Signed release asset escaped its root.');
|
|
29
|
+
}
|
|
30
|
+
let cursor = normalizedRoot;
|
|
31
|
+
for (const segment of reference.split('/')) {
|
|
32
|
+
cursor = path.join(cursor, segment);
|
|
33
|
+
if (!existsSync(cursor))
|
|
34
|
+
throw releaseInputError('not_found', 'Signed release asset is missing.', { reference });
|
|
35
|
+
if (lstatSync(cursor).isSymbolicLink()) {
|
|
36
|
+
throw releaseInputError('link_unsafe_path', 'Signed release asset traverses a link.', { reference });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const stat = lstatSync(candidate);
|
|
40
|
+
if (!stat.isFile())
|
|
41
|
+
throw releaseInputError('link_unsafe_path', 'Signed release asset is not a regular file.', { reference });
|
|
42
|
+
const realCandidate = realpathSync.native(candidate);
|
|
43
|
+
const realRelative = path.relative(realRoot, realCandidate);
|
|
44
|
+
if (realRelative.startsWith('..') || path.isAbsolute(realRelative)) {
|
|
45
|
+
throw releaseInputError('link_unsafe_path', 'Signed release asset resolves outside its root.', { reference });
|
|
46
|
+
}
|
|
47
|
+
return candidate;
|
|
48
|
+
}
|
|
49
|
+
function parseJsonBytes(bytes) {
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(Buffer.from(bytes).toString('utf8'));
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw releaseInputError('schema_incompatible', 'Community agent-asset package manifest is not valid JSON.', {
|
|
55
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function loadVerifiedReleasePackage(bundle, pointer, expected) {
|
|
60
|
+
const { files, setSha256 } = bundle.agentAssets;
|
|
61
|
+
const manifestPath = safeReleaseFile(bundle.releaseRoot, pointer.manifestRef);
|
|
62
|
+
const manifestBaseRef = pointer.manifestRef.endsWith('/manifest.json')
|
|
63
|
+
? pointer.manifestRef.slice(0, -'/manifest.json'.length)
|
|
64
|
+
: '';
|
|
65
|
+
if (!manifestBaseRef) {
|
|
66
|
+
throw releaseInputError('expected_manifest_required', 'Community package manifest reference has an unexpected shape.');
|
|
67
|
+
}
|
|
68
|
+
const signedRows = new Map(files.map((row) => [row.ref, row]));
|
|
69
|
+
const manifestSignedRow = signedRows.get(pointer.manifestRef);
|
|
70
|
+
if (!manifestSignedRow) {
|
|
71
|
+
throw releaseInputError('expected_manifest_required', 'Community package manifest is outside the signed asset closure.');
|
|
72
|
+
}
|
|
73
|
+
const manifestBytes = readFileSync(manifestPath);
|
|
74
|
+
if (manifestBytes.byteLength !== manifestSignedRow.byteLength ||
|
|
75
|
+
sha256Bytes(manifestBytes) !== manifestSignedRow.sha256) {
|
|
76
|
+
throw releaseInputError('hash_mismatch', 'Community package manifest changed after release verification.');
|
|
77
|
+
}
|
|
78
|
+
const manifestValue = parseJsonBytes(manifestBytes);
|
|
79
|
+
if (!manifestValue || typeof manifestValue !== 'object' || Array.isArray(manifestValue)) {
|
|
80
|
+
throw releaseInputError('schema_incompatible', 'Community package manifest must be an object.');
|
|
81
|
+
}
|
|
82
|
+
const candidateFiles = manifestValue.files;
|
|
83
|
+
if (!Array.isArray(candidateFiles)) {
|
|
84
|
+
throw releaseInputError('schema_incompatible', 'Community package manifest does not declare files.');
|
|
85
|
+
}
|
|
86
|
+
const transferFiles = [];
|
|
87
|
+
for (const candidate of candidateFiles) {
|
|
88
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate) || typeof candidate.path !== 'string') {
|
|
89
|
+
throw releaseInputError('schema_incompatible', 'Community package manifest contains an invalid file row.');
|
|
90
|
+
}
|
|
91
|
+
const reference = `${manifestBaseRef}/files/${candidate.path}`;
|
|
92
|
+
const signedRow = signedRows.get(reference);
|
|
93
|
+
if (!signedRow) {
|
|
94
|
+
throw releaseInputError('expected_manifest_required', 'A declared Community package file is outside the signed closure.', {
|
|
95
|
+
reference,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const bytes = readFileSync(safeReleaseFile(bundle.releaseRoot, reference));
|
|
99
|
+
if (bytes.byteLength !== signedRow.byteLength || sha256Bytes(bytes) !== signedRow.sha256) {
|
|
100
|
+
throw releaseInputError('hash_mismatch', 'Signed Community package file changed after release verification.', {
|
|
101
|
+
reference,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
transferFiles.push({ path: candidate.path, bytes });
|
|
105
|
+
}
|
|
106
|
+
const validation = validatePortablePackageV1(manifestValue, transferFiles);
|
|
107
|
+
if (!validation.ok) {
|
|
108
|
+
throw releaseInputError('hash_mismatch', 'Signed Community package failed the portable package contract.', {
|
|
109
|
+
issues: validation.issues,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const manifest = validation.value.normalizedManifest;
|
|
113
|
+
if (manifest.assetKind !== expected.assetKind ||
|
|
114
|
+
pointer.name !== manifest.name ||
|
|
115
|
+
pointer.version !== manifest.version ||
|
|
116
|
+
pointer.versionId !== manifest.versionId ||
|
|
117
|
+
pointer.packageSha256 !== manifest.packageSha256 ||
|
|
118
|
+
pointer.entryFile !== manifest.entryFile ||
|
|
119
|
+
pointer.optional !== expected.optional) {
|
|
120
|
+
throw releaseInputError('hash_mismatch', 'Signed release package pointer and package manifest disagree.');
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
releaseRoot: realpathSync.native(bundle.releaseRoot),
|
|
124
|
+
manifestRef: pointer.manifestRef,
|
|
125
|
+
manifestSha256: manifestSignedRow.sha256,
|
|
126
|
+
packageRef: {
|
|
127
|
+
name: manifest.name,
|
|
128
|
+
version: manifest.version,
|
|
129
|
+
versionId: manifest.versionId,
|
|
130
|
+
packageSha256: manifest.packageSha256,
|
|
131
|
+
entryFile: manifest.entryFile,
|
|
132
|
+
origin: expected.origin,
|
|
133
|
+
trustClass: manifest.provenance.trustClass,
|
|
134
|
+
},
|
|
135
|
+
manifest,
|
|
136
|
+
transferFiles,
|
|
137
|
+
validation: validation.value,
|
|
138
|
+
releaseSetSha256: setSha256,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export function loadVerifiedCommunityCoreReleaseInput(bundle) {
|
|
142
|
+
const loaded = loadVerifiedReleasePackage(bundle, bundle.agentAssets.core, {
|
|
143
|
+
assetKind: 'community-core',
|
|
144
|
+
optional: false,
|
|
145
|
+
origin: 'bundled',
|
|
146
|
+
});
|
|
147
|
+
if (loaded.manifest.assetKind !== 'community-core') {
|
|
148
|
+
throw releaseInputError('schema_incompatible', 'Community core package kind changed after validation.');
|
|
149
|
+
}
|
|
150
|
+
return loaded;
|
|
151
|
+
}
|
|
152
|
+
export function loadVerifiedCommunityCatalogReleaseInputs(bundle) {
|
|
153
|
+
const loaded = bundle.agentAssets.catalog.packages.map((pointer) => {
|
|
154
|
+
const candidate = loadVerifiedReleasePackage(bundle, pointer, {
|
|
155
|
+
assetKind: 'skill-package',
|
|
156
|
+
optional: true,
|
|
157
|
+
origin: 'reserved-catalog',
|
|
158
|
+
});
|
|
159
|
+
if (candidate.manifest.assetKind !== 'skill-package') {
|
|
160
|
+
throw releaseInputError('schema_incompatible', 'Community catalog package kind changed after validation.');
|
|
161
|
+
}
|
|
162
|
+
return Object.freeze({
|
|
163
|
+
...candidate,
|
|
164
|
+
manifest: candidate.manifest,
|
|
165
|
+
releaseTrustClass: 'signed-community-release',
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
return Object.freeze(loaded.sort((left, right) => Buffer.compare(Buffer.from(`${left.manifest.name}\0${left.manifest.versionId}`, 'utf8'), Buffer.from(`${right.manifest.name}\0${right.manifest.versionId}`, 'utf8'))));
|
|
169
|
+
}
|
|
170
|
+
export async function verifyAndLoadCommunityCoreReleaseInput(options) {
|
|
171
|
+
const bundle = await verifyCommunityAgentAssetsReleaseBundle(options);
|
|
172
|
+
return loadVerifiedCommunityCoreReleaseInput(bundle);
|
|
173
|
+
}
|
|
174
|
+
export async function verifyAndLoadCommunityCatalogReleaseInputs(options) {
|
|
175
|
+
const bundle = await verifyCommunityAgentAssetsReleaseBundle(options);
|
|
176
|
+
return loadVerifiedCommunityCatalogReleaseInputs(bundle);
|
|
177
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getAgentAssetsConfig } from '../../utils/config.js';
|
|
4
|
+
function selectRoot(name, candidates) {
|
|
5
|
+
const selected = candidates.find((candidate) => candidate.value !== undefined && candidate.value.trim().length > 0);
|
|
6
|
+
if (!selected?.value)
|
|
7
|
+
throw new Error(`agent_assets_${name}_missing`);
|
|
8
|
+
const candidate = selected.value.trim();
|
|
9
|
+
if (!path.isAbsolute(candidate))
|
|
10
|
+
throw new Error(`agent_assets_${name}_must_be_absolute`);
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
absolutePath: path.normalize(candidate),
|
|
13
|
+
source: selected.source,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export function resolveAgentAssetsRoots(options = {}) {
|
|
17
|
+
const env = options.env ?? process.env;
|
|
18
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
19
|
+
if (!path.isAbsolute(homeDir))
|
|
20
|
+
throw new Error('agent_assets_home_must_be_absolute');
|
|
21
|
+
const config = options.config ?? getAgentAssetsConfig();
|
|
22
|
+
const dataRoot = selectRoot('dataRoot', [
|
|
23
|
+
{ value: options.dataRoot, source: 'option' },
|
|
24
|
+
{ value: env.AOPS_AGENT_ASSETS_DATA_ROOT, source: 'environment' },
|
|
25
|
+
{ value: config.dataRoot, source: 'user-config' },
|
|
26
|
+
{ value: path.join(homeDir, '.aops'), source: 'default' },
|
|
27
|
+
]);
|
|
28
|
+
const codex = selectRoot('codexHome', [
|
|
29
|
+
{ value: options.codexHome, source: 'option' },
|
|
30
|
+
{ value: env.CODEX_HOME, source: 'environment' },
|
|
31
|
+
{ value: config.runtimeHomes?.codex, source: 'user-config' },
|
|
32
|
+
{ value: path.join(homeDir, '.codex'), source: 'default' },
|
|
33
|
+
]);
|
|
34
|
+
const claude = selectRoot('claudeHome', [
|
|
35
|
+
{ value: options.claudeHome, source: 'option' },
|
|
36
|
+
{ value: env.CLAUDE_HOME, source: 'environment' },
|
|
37
|
+
{ value: config.runtimeHomes?.claude, source: 'user-config' },
|
|
38
|
+
{ value: path.join(homeDir, '.claude'), source: 'default' },
|
|
39
|
+
]);
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
dataRoot,
|
|
42
|
+
assetRoot: path.join(dataRoot.absolutePath, 'agent-assets'),
|
|
43
|
+
runtimeHomes: Object.freeze({ codex, claude }),
|
|
44
|
+
});
|
|
45
|
+
}
|