agentlas 0.7.0 → 0.9.1
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/CHANGELOG.md +190 -0
- package/README.md +161 -18
- package/bin/agentlas.cjs +8 -8
- package/engine/agentlas-core-harness.cjs +205 -0
- package/engine/agentlas-desktop-loadout.cjs +527 -0
- package/engine/agentlas-doctor.cjs +1 -1
- package/engine/agentlas-experience-exchange.cjs +835 -85
- package/engine/agentlas-experience-intake.cjs +444 -0
- package/engine/agentlas-experience-mcp.cjs +580 -18
- package/engine/agentlas-i18n.cjs +10 -10
- package/engine/agentlas-input.cjs +5 -4
- package/engine/agentlas-mcp-env.cjs +219 -0
- package/engine/agentlas-mcp-wrapper.cjs +51 -0
- package/engine/agentlas-memory-governance.cjs +1029 -0
- package/engine/agentlas-native-host.cjs +129 -39
- package/engine/agentlas-parity.cjs +339 -154
- package/engine/agentlas-repl.cjs +306 -31
- package/engine/agentlas-workforce.cjs +2991 -0
- package/engine/agentlas-workload-routing.cjs +523 -0
- package/engine/agentlas.cjs +1619 -234
- package/engine/bootstrap-schema.sql +1 -1
- package/engine/experience-taxonomy-v1.json +49 -0
- package/package.json +8 -4
- package/scripts/gen-bootstrap-schema.sh +0 -23
- package/test/bootstrap-race.cjs +0 -47
- package/test/capture-runtime-guard.cjs +0 -122
- package/test/cloud-asset-restore.cjs +0 -423
- package/test/cloud-cas-client.cjs +0 -333
- package/test/cloud-owner-restore.cjs +0 -183
- package/test/cloud-runtime-paths.cjs +0 -40
- package/test/cloud-save-publish.cjs +0 -487
- package/test/credential-env-regression.cjs +0 -52
- package/test/engine-hardening-regression.cjs +0 -74
- package/test/experience-exchange-contract.cjs +0 -569
- package/test/experience-mcp-contract.cjs +0 -391
- package/test/fixtures/portable-experience-bundle-v1-golden.json +0 -124
- package/test/login-loopback-security.cjs +0 -115
- package/test/mcp-config-isolation.cjs +0 -36
- package/test/permission-mapping.cjs +0 -180
- package/test/route-regression.cjs +0 -357
- package/test/run-api-regression.cjs +0 -322
- package/test/runtime-env-protection.cjs +0 -89
- package/test/semver-precedence.cjs +0 -39
- package/test/smoke.sh +0 -93
- package/test/sqlite-driver-probe.cjs +0 -22
- package/test/terminal-ui-regression.cjs +0 -477
- package/test/timeout-regression.cjs +0 -218
- package/test/tool-workspace-boundary.cjs +0 -165
- package/test/update-safety.cjs +0 -376
|
@@ -30,6 +30,64 @@ const EXPERIENCE_RETRIEVAL_MAX_ITEMS = 8;
|
|
|
30
30
|
const EXPERIENCE_RETRIEVAL_MAX_TOKENS = 800;
|
|
31
31
|
const EXCHANGE_LOCK_STALE_MS = 30_000;
|
|
32
32
|
const EXCHANGE_LOCK_WAIT_MS = 2_000;
|
|
33
|
+
const EXPERIENCE_TAXONOMY_PATH = path.join(__dirname, "experience-taxonomy-v1.json");
|
|
34
|
+
const EXPERIENCE_TAXONOMY_CHECKSUM = "sha256:413833472e423352518f9591cd0e051c5bc0a7971e53ab3dc7b5aaf7d50c37ab";
|
|
35
|
+
|
|
36
|
+
function deepFreeze(value) {
|
|
37
|
+
if (!value || typeof value !== "object" || Object.isFrozen(value)) return value;
|
|
38
|
+
Object.freeze(value);
|
|
39
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function validateExperienceTaxonomyContract(value) {
|
|
44
|
+
const issues = [];
|
|
45
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) issues.push("taxonomy must be an object");
|
|
46
|
+
else {
|
|
47
|
+
if (value.schema !== "agentlas.experience-taxonomy.v1") issues.push("taxonomy schema drifted");
|
|
48
|
+
if (value.kind !== "agentlas-experience-taxonomy") issues.push("taxonomy kind drifted");
|
|
49
|
+
if (value.taskSignaturePrefix !== "agentlas.task.v1/") issues.push("taxonomy task prefix drifted");
|
|
50
|
+
if (!Array.isArray(value.taskSlugs) || value.taskSlugs.length !== 23 || value.taskSlugs.includes("general")) issues.push("taxonomy task catalog drifted");
|
|
51
|
+
const environment = value.environment;
|
|
52
|
+
if (
|
|
53
|
+
!environment || environment.osPrefix !== "agentlas.env.v1/os/" ||
|
|
54
|
+
environment.archPrefix !== "agentlas.env.v1/arch/" || environment.runtimePrefix !== "agentlas.env.v1/runtime/" ||
|
|
55
|
+
JSON.stringify(environment.osValues) !== JSON.stringify(["macos", "windows", "linux", "ios", "android", "unknown"]) ||
|
|
56
|
+
JSON.stringify(environment.archValues) !== JSON.stringify(["arm64", "x64", "unknown"]) ||
|
|
57
|
+
environment.runtimePattern !== "^[a-z0-9][a-z0-9._-]{1,63}$" ||
|
|
58
|
+
environment.matching !== "all-canonical-constraints-must-match" ||
|
|
59
|
+
environment.unknownConstraint !== "item-ineligible-base-unaffected"
|
|
60
|
+
) issues.push("taxonomy environment contract drifted");
|
|
61
|
+
const normalization = value.normalization;
|
|
62
|
+
if (
|
|
63
|
+
!normalization || normalization.unicode !== "NFKC" || normalization.trim !== true || normalization.case !== "lower" ||
|
|
64
|
+
normalization.portableSource !== "canonical-id-only" || normalization.runtimeProfile !== "canonical-id-or-exact-bare-slug" ||
|
|
65
|
+
normalization.fuzzySimilarity !== false || normalization.generalAutoMatch !== false
|
|
66
|
+
) issues.push("taxonomy normalization contract drifted");
|
|
67
|
+
const checksum = `sha256:${crypto.createHash("sha256").update(canonicalJson(value), "utf8").digest("hex")}`;
|
|
68
|
+
if (checksum !== EXPERIENCE_TAXONOMY_CHECKSUM) issues.push("taxonomy checksum drifted");
|
|
69
|
+
}
|
|
70
|
+
if (issues.length) {
|
|
71
|
+
const error = new Error(issues.join("; "));
|
|
72
|
+
error.code = "experience_taxonomy_drift";
|
|
73
|
+
error.issues = issues;
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
return deepFreeze(JSON.parse(canonicalJson(value)));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function loadExperienceTaxonomyContract() {
|
|
80
|
+
const stat = fs.lstatSync(EXPERIENCE_TAXONOMY_PATH);
|
|
81
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 64 * 1024) throw new Error("Experience taxonomy artifact is unsafe");
|
|
82
|
+
return validateExperienceTaxonomyContract(JSON.parse(fs.readFileSync(EXPERIENCE_TAXONOMY_PATH, "utf8")));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const OFFICIAL_EXPERIENCE_CLOUD_HOSTS = new Set([
|
|
86
|
+
"agentlas.cloud",
|
|
87
|
+
"www.agentlas.cloud",
|
|
88
|
+
"api.agentlas.cloud",
|
|
89
|
+
"staging.agentlas.cloud",
|
|
90
|
+
]);
|
|
33
91
|
|
|
34
92
|
const ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@-]{2,255}$/;
|
|
35
93
|
const HASH_RE = /^sha256:[0-9a-f]{64}$/;
|
|
@@ -54,9 +112,7 @@ const PII_PATTERNS = [
|
|
|
54
112
|
/\b(?:account|customer|client|tenant|workspace|user)[ _-]?(?:id|key|number|no)\s*[:=#]?\s*[A-Za-z0-9_-]{4,}\b|(?:계정|고객|사용자)[ _-]?(?:id|아이디|번호)\s*[:=#]?\s*[A-Za-z0-9_-]{4,}/i,
|
|
55
113
|
];
|
|
56
114
|
const LOCAL_PATH_PATTERNS = [
|
|
57
|
-
/(?:file
|
|
58
|
-
/\b[A-Za-z]:\\(?:Users|Documents|Desktop|Downloads)\\/i,
|
|
59
|
-
/(?:^|[\s'"`])~\/(?:Desktop|Documents|Downloads|Library|\.ssh|\.config)\//i,
|
|
115
|
+
/(?:file:\/\/|(?:^|[\s"'`()\[\]{}=:,;])(?:\.\.[/\\]|~[/\\]|\/(?!\/|\s)(?:[^/\s"'`<>]+\/)*[^/\s"'`<>]+|[A-Za-z]:[/\\]\S+|\\\\[^\\/\s]+[\\/][^\\/\s]+))/i,
|
|
60
116
|
];
|
|
61
117
|
const RAW_INTERACTION_PATTERNS = [
|
|
62
118
|
/(?:^|\n)\s*(?:system|assistant|user|tool|customer|agent)\s*:\s+/i,
|
|
@@ -77,6 +133,8 @@ const BASE_PACKAGE_PATTERNS = [
|
|
|
77
133
|
/\bBEGIN AGENTLAS (?:AGENT|PACKAGE)\b/i,
|
|
78
134
|
];
|
|
79
135
|
const OPAQUE_BLOB_RE = /(?:[A-Fa-f0-9]{128,}|[A-Za-z0-9+/]{124,}={0,2})/;
|
|
136
|
+
const PUBLIC_URL_RE = /\bhttps?:\/\/[^\s<>"']+/i;
|
|
137
|
+
const CUSTOMER_DATA_RE = /\b(?:customer|client|tenant|account|workspace|order|invoice)[ _-]?(?:name|email|address|phone|id|number|ref(?:erence)?)\s*[:=#]\s*\S+|(?:고객|클라이언트|계정|주문|송장)[ _-]?(?:이름|이메일|주소|전화|아이디|번호|참조)\s*[:=#]\s*\S+/i;
|
|
80
138
|
const FORBIDDEN_KEYS = new Set([
|
|
81
139
|
"basepackage", "basepackagefiles", "baseprompt", "cloudpackage", "contentbase64",
|
|
82
140
|
"files", "fulltranscript", "rawsource", "systemprompt", "transcript", "messages",
|
|
@@ -118,7 +176,8 @@ function normalizeJson(value, seen = new Set()) {
|
|
|
118
176
|
seen.delete(value);
|
|
119
177
|
return result;
|
|
120
178
|
}
|
|
121
|
-
|
|
179
|
+
const prototype = value && typeof value === "object" ? Object.getPrototypeOf(value) : undefined;
|
|
180
|
+
if (!value || typeof value !== "object" || (prototype !== Object.prototype && prototype !== null)) {
|
|
122
181
|
throw new ExperienceBundleValidationError([`canonical JSON forbids ${typeof value}`]);
|
|
123
182
|
}
|
|
124
183
|
if (seen.has(value)) throw new ExperienceBundleValidationError(["canonical JSON forbids cyclic values"]);
|
|
@@ -153,6 +212,18 @@ function canonicalHash(value) {
|
|
|
153
212
|
return `sha256:${crypto.createHash("sha256").update(canonicalJson(value), "utf8").digest("hex")}`;
|
|
154
213
|
}
|
|
155
214
|
|
|
215
|
+
// Load the frozen activation taxonomy only after the canonical JSON machinery
|
|
216
|
+
// and its error type have initialized. Any artifact drift stops startup.
|
|
217
|
+
const EXPERIENCE_TAXONOMY_V1 = loadExperienceTaxonomyContract();
|
|
218
|
+
const CANONICAL_TASK_PREFIX = EXPERIENCE_TAXONOMY_V1.taskSignaturePrefix;
|
|
219
|
+
const CANONICAL_ENV_PREFIX = "agentlas.env.v1/";
|
|
220
|
+
const CANONICAL_TASK_SLUGS = Object.freeze([...EXPERIENCE_TAXONOMY_V1.taskSlugs]);
|
|
221
|
+
const CANONICAL_TASK_IDS = Object.freeze(CANONICAL_TASK_SLUGS.map((slug) => `${CANONICAL_TASK_PREFIX}${slug}`));
|
|
222
|
+
const CANONICAL_TASK_ID_SET = new Set(CANONICAL_TASK_IDS);
|
|
223
|
+
const CANONICAL_OS_VALUES = new Set(EXPERIENCE_TAXONOMY_V1.environment.osValues);
|
|
224
|
+
const CANONICAL_ARCH_VALUES = new Set(EXPERIENCE_TAXONOMY_V1.environment.archValues);
|
|
225
|
+
const CANONICAL_RUNTIME_RE = new RegExp(EXPERIENCE_TAXONOMY_V1.environment.runtimePattern);
|
|
226
|
+
|
|
156
227
|
function sortedUnique(values) {
|
|
157
228
|
const byCanonical = new Map();
|
|
158
229
|
for (const value of values || []) byCanonical.set(canonicalJson(value), value);
|
|
@@ -321,8 +392,12 @@ function validateCredentialMetadata(value, label, issues) {
|
|
|
321
392
|
const scopes = checkStringList(data.scopes, `${label}.scopes`, 1, 64, issues);
|
|
322
393
|
scopes.forEach((scope, index) => { if (!/^[A-Za-z0-9][A-Za-z0-9._:/@-]{0,127}$/.test(scope)) issues.push(`${label}.scopes[${index}] is invalid`); });
|
|
323
394
|
}
|
|
324
|
-
if (data.setupUrl != null &&
|
|
325
|
-
|
|
395
|
+
if (data.setupUrl != null && (
|
|
396
|
+
typeof data.setupUrl !== "string" ||
|
|
397
|
+
data.setupUrl.length > 2048 ||
|
|
398
|
+
!/^https:\/\/[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*(?:\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]*)?$/.test(data.setupUrl)
|
|
399
|
+
)) {
|
|
400
|
+
issues.push(`${label}.setupUrl must be a value-free HTTPS provider page of at most 2048 characters`);
|
|
326
401
|
}
|
|
327
402
|
if (data.brokerMode != null && !["host-bound-broker", "runtime-env-injection", "provider-managed-oauth", "manual-provider-page"].includes(data.brokerMode)) {
|
|
328
403
|
issues.push(`${label}.brokerMode is invalid`);
|
|
@@ -337,9 +412,9 @@ function validateMcpRequirement(value, label, issues) {
|
|
|
337
412
|
checkId(data.requirementId, `${label}.requirementId`, issues);
|
|
338
413
|
checkId(data.catalogId, `${label}.catalogId`, issues);
|
|
339
414
|
checkString(data.reason, `${label}.reason`, 1, 300, issues);
|
|
340
|
-
checkStringList(data.capabilities, `${label}.capabilities`, 1,
|
|
341
|
-
checkStringList(data.permissions, `${label}.permissions`, 0,
|
|
342
|
-
const alternatives = checkStringList(data.alternatives, `${label}.alternatives`, 0,
|
|
415
|
+
checkStringList(data.capabilities, `${label}.capabilities`, 1, 32, issues, { ids: true });
|
|
416
|
+
checkStringList(data.permissions, `${label}.permissions`, 0, 64, issues, { ids: true });
|
|
417
|
+
const alternatives = checkStringList(data.alternatives, `${label}.alternatives`, 0, 32, issues, { ids: true });
|
|
343
418
|
if (alternatives.includes(data.catalogId)) issues.push(`${label}.alternatives must not contain catalogId`);
|
|
344
419
|
if (typeof data.required !== "boolean" || typeof data.requiresKey !== "boolean") issues.push(`${label}.required/requiresKey must be boolean`);
|
|
345
420
|
if (!Number.isInteger(data.priority) || data.priority < 1 || data.priority > 1000) issues.push(`${label}.priority must be 1..1000`);
|
|
@@ -386,8 +461,11 @@ function validateItem(item, index, issues) {
|
|
|
386
461
|
checkString(data.summary, `${label}.summary`, 1, 320, issues);
|
|
387
462
|
if (!Array.isArray(data.instructions) || data.instructions.length < 1 || data.instructions.length > MAX_INSTRUCTIONS_PER_ITEM) issues.push(`${label}.instructions must contain 1..${MAX_INSTRUCTIONS_PER_ITEM} values`);
|
|
388
463
|
else data.instructions.forEach((step, stepIndex) => checkString(step, `${label}.instructions[${stepIndex}]`, 1, 600, issues));
|
|
389
|
-
checkStringList(data.taskSignatures, `${label}.taskSignatures`, 1, MAX_TASK_SIGNATURES_PER_ITEM, issues);
|
|
390
|
-
checkStringList(data.environmentConstraints, `${label}.environmentConstraints`, 0, 32, issues);
|
|
464
|
+
checkStringList(data.taskSignatures, `${label}.taskSignatures`, 1, MAX_TASK_SIGNATURES_PER_ITEM, issues, { ids: true });
|
|
465
|
+
const environmentConstraints = checkStringList(data.environmentConstraints, `${label}.environmentConstraints`, 0, 32, issues);
|
|
466
|
+
environmentConstraints.forEach((constraint, constraintIndex) => {
|
|
467
|
+
if (constraint.length > 240) issues.push(`${label}.environmentConstraints[${constraintIndex}] must be at most 240 characters`);
|
|
468
|
+
});
|
|
391
469
|
checkStringList(data.evidenceReceiptIds, `${label}.evidenceReceiptIds`, 1, MAX_EVIDENCE_REFS_PER_ITEM, issues, { ids: true });
|
|
392
470
|
checkStringList(data.supersedesItemIds, `${label}.supersedesItemIds`, 0, MAX_STORED_ITEMS, issues, { ids: true });
|
|
393
471
|
if (typeof data.confidence !== "number" || !Number.isFinite(data.confidence) || data.confidence < 0 || data.confidence > 1) issues.push(`${label}.confidence must be 0..1`);
|
|
@@ -431,6 +509,38 @@ function validateBundleSecurity(value, issues) {
|
|
|
431
509
|
if (nonMetadata.some((text) => OPAQUE_BLOB_RE.test(text))) issues.push("ExperienceBundle contains a long opaque encoded blob");
|
|
432
510
|
}
|
|
433
511
|
|
|
512
|
+
/**
|
|
513
|
+
* Value-free privacy classification used before a successful run can become a
|
|
514
|
+
* local Operational Experience candidate. It deliberately returns only codes;
|
|
515
|
+
* unsafe source text is never copied into an intake receipt or bundle.
|
|
516
|
+
*/
|
|
517
|
+
function portableExperienceSafetyIssues(text) {
|
|
518
|
+
const variants = [String(text || "")];
|
|
519
|
+
let current = variants[0];
|
|
520
|
+
for (let index = 0; index < 3; index += 1) {
|
|
521
|
+
try {
|
|
522
|
+
const decoded = decodeURIComponent(current);
|
|
523
|
+
if (decoded === current) break;
|
|
524
|
+
variants.push(decoded);
|
|
525
|
+
current = decoded;
|
|
526
|
+
} catch { break; }
|
|
527
|
+
}
|
|
528
|
+
const codes = [];
|
|
529
|
+
const hit = (patterns, code) => {
|
|
530
|
+
if (patterns.some((pattern) => variants.some((value) => pattern.test(value)))) codes.push(code);
|
|
531
|
+
};
|
|
532
|
+
hit(SECRET_PATTERNS, "secret-or-credential");
|
|
533
|
+
hit(PII_PATTERNS, "personal-or-customer-identifier");
|
|
534
|
+
hit(LOCAL_PATH_PATTERNS, "local-path");
|
|
535
|
+
hit(RAW_INTERACTION_PATTERNS, "raw-prompt-or-transcript");
|
|
536
|
+
hit(PROMPT_INJECTION_PATTERNS, "prompt-injection-material");
|
|
537
|
+
hit(BASE_PACKAGE_PATTERNS, "base-package-material");
|
|
538
|
+
if (variants.some((value) => PUBLIC_URL_RE.test(value))) codes.push("url");
|
|
539
|
+
if (variants.some((value) => CUSTOMER_DATA_RE.test(value))) codes.push("customer-data");
|
|
540
|
+
if (variants.some((value) => OPAQUE_BLOB_RE.test(value))) codes.push("opaque-blob");
|
|
541
|
+
return [...new Set(codes)].sort(compareCodePoints);
|
|
542
|
+
}
|
|
543
|
+
|
|
434
544
|
function validateExperienceBundle(payload) {
|
|
435
545
|
const value = normalizeExperienceBundle(payload);
|
|
436
546
|
const issues = [];
|
|
@@ -574,6 +684,42 @@ function writePrivateTextAtomic(filePath, text) {
|
|
|
574
684
|
}
|
|
575
685
|
}
|
|
576
686
|
|
|
687
|
+
function readPrivateFileSnapshot(filePath) {
|
|
688
|
+
recoverPrivateAtomicTarget(filePath);
|
|
689
|
+
if (!fs.existsSync(filePath)) return { exists: false, bytes: null };
|
|
690
|
+
const stat = fs.lstatSync(filePath);
|
|
691
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("private transaction target is unsafe");
|
|
692
|
+
return { exists: true, bytes: fs.readFileSync(filePath) };
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function writePrivateBufferAtomic(filePath, bytes) {
|
|
696
|
+
const dir = path.dirname(filePath);
|
|
697
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
698
|
+
try { fs.chmodSync(dir, 0o700); } catch { /* best effort */ }
|
|
699
|
+
const temp = path.join(dir, `.${path.basename(filePath)}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.rollback.tmp`);
|
|
700
|
+
try {
|
|
701
|
+
fs.writeFileSync(temp, bytes, { mode: 0o600, flag: "wx" });
|
|
702
|
+
replacePrivateFileAtomic(temp, filePath);
|
|
703
|
+
try { fs.chmodSync(filePath, 0o600); } catch { /* best effort */ }
|
|
704
|
+
} finally {
|
|
705
|
+
try { fs.rmSync(temp, { force: true }); } catch { /* noop */ }
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function restorePrivateFileSnapshot(filePath, snapshot) {
|
|
710
|
+
recoverPrivateAtomicTarget(filePath);
|
|
711
|
+
if (snapshot.exists) {
|
|
712
|
+
writePrivateBufferAtomic(filePath, snapshot.bytes);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (fs.existsSync(filePath)) {
|
|
716
|
+
const stat = fs.lstatSync(filePath);
|
|
717
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("private rollback target is unsafe");
|
|
718
|
+
fs.rmSync(filePath, { force: true });
|
|
719
|
+
}
|
|
720
|
+
try { fs.rmSync(`${filePath}.previous`, { force: true }); } catch { /* noop */ }
|
|
721
|
+
}
|
|
722
|
+
|
|
577
723
|
function exchangeStatePath(userDataDir) {
|
|
578
724
|
return path.join(userDataDir, "terminal", "experience-exchange-v1.json");
|
|
579
725
|
}
|
|
@@ -696,11 +842,13 @@ function projectScopeHash(cwd) {
|
|
|
696
842
|
return canonicalHash({ kind: "terminal-project-scope", path: resolved.normalize("NFC") });
|
|
697
843
|
}
|
|
698
844
|
|
|
699
|
-
function
|
|
845
|
+
function commitLocalBundleRecord(userDataDir, validation, options = {}) {
|
|
700
846
|
const bundle = validation.bundle;
|
|
701
847
|
return withExchangeStateLock(userDataDir, () => {
|
|
702
848
|
const storedPath = bundleStorePath(userDataDir, bundle.bundleId);
|
|
703
|
-
|
|
849
|
+
const statePath = exchangeStatePath(userDataDir);
|
|
850
|
+
const storedSnapshot = readPrivateFileSnapshot(storedPath);
|
|
851
|
+
const stateSnapshot = readPrivateFileSnapshot(statePath);
|
|
704
852
|
const state = loadExchangeState(userDataDir);
|
|
705
853
|
const now = new Date().toISOString();
|
|
706
854
|
const previous = state.bundles.find((row) => row.bundleId === bundle.bundleId);
|
|
@@ -713,16 +861,31 @@ function saveLocalBundle(userDataDir, validation, options = {}) {
|
|
|
713
861
|
compatibleBaseReleaseIds: [...bundle.pack.baseCompatibility.compatibleBaseReleaseIds],
|
|
714
862
|
projectScopeHash: projectScopeHash(options.cwd),
|
|
715
863
|
storedAt: now,
|
|
716
|
-
remote: previous?.remote || null,
|
|
864
|
+
remote: Object.prototype.hasOwnProperty.call(options, "remote") ? options.remote : previous?.remote || null,
|
|
717
865
|
};
|
|
718
866
|
const index = state.bundles.findIndex((item) => item.bundleId === row.bundleId);
|
|
719
867
|
if (index >= 0) state.bundles[index] = row;
|
|
720
868
|
else state.bundles.push(row);
|
|
721
|
-
|
|
722
|
-
|
|
869
|
+
try {
|
|
870
|
+
writePrivateTextAtomic(storedPath, validation.canonicalJson);
|
|
871
|
+
saveExchangeState(userDataDir, state);
|
|
872
|
+
return row;
|
|
873
|
+
} catch (error) {
|
|
874
|
+
const rollbackErrors = [];
|
|
875
|
+
for (const [filePath, snapshot] of [[storedPath, storedSnapshot], [statePath, stateSnapshot]]) {
|
|
876
|
+
try { restorePrivateFileSnapshot(filePath, snapshot); }
|
|
877
|
+
catch (rollbackError) { rollbackErrors.push(rollbackError); }
|
|
878
|
+
}
|
|
879
|
+
if (rollbackErrors.length) error.rollbackErrors = rollbackErrors;
|
|
880
|
+
throw error;
|
|
881
|
+
}
|
|
723
882
|
});
|
|
724
883
|
}
|
|
725
884
|
|
|
885
|
+
function saveLocalBundle(userDataDir, validation, options = {}) {
|
|
886
|
+
return commitLocalBundleRecord(userDataDir, validation, options);
|
|
887
|
+
}
|
|
888
|
+
|
|
726
889
|
function readStoredBundle(userDataDir, bundleId) {
|
|
727
890
|
const file = bundleStorePath(userDataDir, bundleId);
|
|
728
891
|
recoverPrivateAtomicTarget(file);
|
|
@@ -777,9 +940,8 @@ function trustedExperienceOrigin(rawValue, options = {}) {
|
|
|
777
940
|
throw new Error("Loopback Experience Web origin requires explicit AGENTLAS_EXPERIENCE_ALLOW_LOOPBACK=1 opt-in");
|
|
778
941
|
}
|
|
779
942
|
} else {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
throw new Error("Authenticated Experience exchange is restricted to an HTTPS Agentlas origin");
|
|
943
|
+
if (parsed.protocol !== "https:" || !OFFICIAL_EXPERIENCE_CLOUD_HOSTS.has(hostname) || (parsed.port && parsed.port !== "443")) {
|
|
944
|
+
throw new Error("Authenticated Experience exchange is restricted to an explicitly approved HTTPS Agentlas origin");
|
|
783
945
|
}
|
|
784
946
|
}
|
|
785
947
|
return parsed.origin;
|
|
@@ -792,9 +954,16 @@ function parseResponseJson(response, label) {
|
|
|
792
954
|
function responseError(response, label) {
|
|
793
955
|
let data = null;
|
|
794
956
|
try { data = JSON.parse(response.text || "null"); } catch { /* generic below */ }
|
|
795
|
-
const
|
|
957
|
+
const serverCode = [data?.errorCode, data?.code, data?.error]
|
|
958
|
+
.find((value) => typeof value === "string" && /^[a-z0-9][a-z0-9._-]{0,95}$/.test(value));
|
|
959
|
+
const detail = typeof data?.message === "string"
|
|
960
|
+
? data.message
|
|
961
|
+
: typeof data?.error === "string"
|
|
962
|
+
? data.error
|
|
963
|
+
: "";
|
|
964
|
+
const error = new Error(`${label} failed (${response.status})${detail ? `: ${detail.slice(0, 300)}` : ""}`);
|
|
796
965
|
error.status = response.status;
|
|
797
|
-
error.code =
|
|
966
|
+
error.code = serverCode || (response.status === 401 || response.status === 403 ? "authentication_refused" : "experience_exchange_failed");
|
|
798
967
|
error.details = data;
|
|
799
968
|
return error;
|
|
800
969
|
}
|
|
@@ -916,21 +1085,47 @@ function validateUploadReceipt(receipt, bundle) {
|
|
|
916
1085
|
};
|
|
917
1086
|
}
|
|
918
1087
|
|
|
1088
|
+
function remoteProjection(receipt, baseResolution = null, previousRemote = null) {
|
|
1089
|
+
return {
|
|
1090
|
+
uploadId: receipt.uploadId,
|
|
1091
|
+
status: receipt.status,
|
|
1092
|
+
requestedVisibility: receipt.requestedVisibility,
|
|
1093
|
+
revision: receipt.revision,
|
|
1094
|
+
serverCheckedAt: new Date().toISOString(),
|
|
1095
|
+
receipt,
|
|
1096
|
+
...(baseResolution ? { baseResolution } : previousRemote?.baseResolution ? { baseResolution: previousRemote.baseResolution } : {}),
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function commitServerAcceptedBundle(userDataDir, validation, receipt, baseResolution, options = {}) {
|
|
1101
|
+
const bundle = validation.bundle;
|
|
1102
|
+
try {
|
|
1103
|
+
const state = loadExchangeState(userDataDir);
|
|
1104
|
+
const previous = state.bundles.find((row) => row.bundleId === bundle.bundleId);
|
|
1105
|
+
return commitLocalBundleRecord(userDataDir, validation, {
|
|
1106
|
+
cwd: options.cwd,
|
|
1107
|
+
remote: remoteProjection(receipt, baseResolution, previous?.remote || null),
|
|
1108
|
+
});
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
const stateError = new Error(
|
|
1111
|
+
`Experience was accepted by the server as ${receipt.uploadId}, but Terminal could not atomically commit the canonical bundle and authoritative receipt. ` +
|
|
1112
|
+
"The prior local bundle/state were restored; rerun the same command and Idempotency-Key to reconcile the same receipt.",
|
|
1113
|
+
);
|
|
1114
|
+
stateError.code = "AGENTLAS_EXPERIENCE_LOCAL_STATE_COMMIT_FAILED";
|
|
1115
|
+
stateError.receipt = receipt;
|
|
1116
|
+
stateError.bundleId = bundle.bundleId;
|
|
1117
|
+
stateError.cause = error;
|
|
1118
|
+
throw stateError;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
919
1122
|
function persistRemoteReceipt(userDataDir, bundle, receipt, baseResolution = null) {
|
|
920
1123
|
try {
|
|
921
1124
|
return withExchangeStateLock(userDataDir, () => {
|
|
922
1125
|
const state = loadExchangeState(userDataDir);
|
|
923
1126
|
const row = state.bundles.find((item) => item.bundleId === bundle.bundleId);
|
|
924
1127
|
if (!row) throw new Error("local bundle record disappeared before receipt persistence");
|
|
925
|
-
row.remote =
|
|
926
|
-
uploadId: receipt.uploadId,
|
|
927
|
-
status: receipt.status,
|
|
928
|
-
requestedVisibility: receipt.requestedVisibility,
|
|
929
|
-
revision: receipt.revision,
|
|
930
|
-
serverCheckedAt: new Date().toISOString(),
|
|
931
|
-
receipt,
|
|
932
|
-
...(baseResolution ? { baseResolution } : row.remote?.baseResolution ? { baseResolution: row.remote.baseResolution } : {}),
|
|
933
|
-
};
|
|
1128
|
+
row.remote = remoteProjection(receipt, baseResolution, row.remote);
|
|
934
1129
|
saveExchangeState(userDataDir, state);
|
|
935
1130
|
return row;
|
|
936
1131
|
});
|
|
@@ -990,8 +1185,10 @@ async function publishBundle(validation, options = {}) {
|
|
|
990
1185
|
const existingState = loadExchangeState(options.userDataDir);
|
|
991
1186
|
const existingRow = findStateRecord(existingState, bundle.bundleId);
|
|
992
1187
|
const exactBaseDescriptor = normalizeBaseDescriptor(options, existingRow?.remote?.baseResolution);
|
|
993
|
-
|
|
994
|
-
|
|
1188
|
+
// Preflight and server acceptance happen before the canonical local envelope
|
|
1189
|
+
// is changed. A failed promotion must leave the prior private file/state
|
|
1190
|
+
// byte-identical instead of pairing a public envelope with an old receipt.
|
|
1191
|
+
const baseRelease = await resolveBaseRelease(bundle, { ...options, baseDescriptor: exactBaseDescriptor }, auth, existingRow?.remote?.baseResolution);
|
|
995
1192
|
let response;
|
|
996
1193
|
try {
|
|
997
1194
|
response = await options.fetchHub(`${auth.base}/uploads`, {
|
|
@@ -1001,7 +1198,7 @@ async function publishBundle(validation, options = {}) {
|
|
|
1001
1198
|
});
|
|
1002
1199
|
} catch (error) {
|
|
1003
1200
|
const recovered = await recoverLostUpload(bundle, key, options, auth, error);
|
|
1004
|
-
|
|
1201
|
+
commitServerAcceptedBundle(options.userDataDir, normalizedValidation, recovered.receipt, baseRelease, { cwd: options.cwd });
|
|
1005
1202
|
return { ...recovered, dryRun: false, networkUsed: true, baseRelease, publicActivation: false, evaluatorAuthority: false };
|
|
1006
1203
|
}
|
|
1007
1204
|
if (!response.ok) throw responseError(response, "Experience draft upload");
|
|
@@ -1014,7 +1211,7 @@ async function publishBundle(validation, options = {}) {
|
|
|
1014
1211
|
if (receipt.status !== expectedStatus) throw new Error(`Experience ${operation} receipt must be ${expectedStatus}, never ${receipt.status}`);
|
|
1015
1212
|
const etag = response.headers && typeof response.headers.get === "function" ? response.headers.get("etag") : null;
|
|
1016
1213
|
if (etag !== `"${receipt.revision}"`) throw new Error("Experience upload ETag does not match the exact receipt revision");
|
|
1017
|
-
|
|
1214
|
+
commitServerAcceptedBundle(options.userDataDir, normalizedValidation, receipt, baseRelease, { cwd: options.cwd });
|
|
1018
1215
|
return { receipt, replayed: body.replayed, recovered: false, dryRun: false, networkUsed: true, baseRelease, publicActivation: false, evaluatorAuthority: false };
|
|
1019
1216
|
}
|
|
1020
1217
|
|
|
@@ -1023,24 +1220,142 @@ function findStateRecord(state, ref) {
|
|
|
1023
1220
|
return matches.sort((a, b) => String(b.storedAt).localeCompare(String(a.storedAt)))[0] || null;
|
|
1024
1221
|
}
|
|
1025
1222
|
|
|
1223
|
+
function verifyStoredBundleRow(userDataDir, row) {
|
|
1224
|
+
const validation = readStoredBundle(userDataDir, row.bundleId);
|
|
1225
|
+
const bundle = validation.bundle;
|
|
1226
|
+
const sameIdentity =
|
|
1227
|
+
bundle.bundleId === row.bundleId &&
|
|
1228
|
+
bundle.bundleHash === row.bundleHash &&
|
|
1229
|
+
bundle.pack.experiencePackId === row.experiencePackId &&
|
|
1230
|
+
bundle.pack.releaseId === row.experiencePackReleaseId &&
|
|
1231
|
+
bundle.pack.baseCompatibility.agentDefinitionId === row.agentDefinitionId &&
|
|
1232
|
+
canonicalJson(bundle.pack.baseCompatibility.compatibleBaseReleaseIds) === canonicalJson(row.compatibleBaseReleaseIds);
|
|
1233
|
+
if (!sameIdentity) throw new Error("stored Experience bundle identity does not match its private index");
|
|
1234
|
+
if (row.remote) {
|
|
1235
|
+
const receipt = validateUploadReceipt(row.remote.receipt, bundle);
|
|
1236
|
+
if (
|
|
1237
|
+
receipt.uploadId !== row.remote.uploadId ||
|
|
1238
|
+
receipt.status !== row.remote.status ||
|
|
1239
|
+
receipt.requestedVisibility !== row.remote.requestedVisibility ||
|
|
1240
|
+
receipt.revision !== row.remote.revision
|
|
1241
|
+
) throw new Error("stored Experience server receipt projection drifted from its private index");
|
|
1242
|
+
if (row.remote.baseResolution) {
|
|
1243
|
+
const base = row.remote.baseResolution;
|
|
1244
|
+
if (
|
|
1245
|
+
base.agentDefinitionId !== row.agentDefinitionId ||
|
|
1246
|
+
!row.compatibleBaseReleaseIds.includes(base.agentReleaseId)
|
|
1247
|
+
) throw new Error("stored Experience exact base resolution drifted from its private index");
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return validation;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function scopedStateRows(userDataDir, cwd) {
|
|
1254
|
+
const scope = projectScopeHash(cwd);
|
|
1255
|
+
return loadExchangeState(userDataDir).bundles
|
|
1256
|
+
.filter((row) => row.projectScopeHash === scope)
|
|
1257
|
+
.sort((a, b) => compareCodePoints(a.experiencePackReleaseId, b.experiencePackReleaseId) || compareCodePoints(a.bundleId, b.bundleId));
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
function resolveScopedStoredRecord(userDataDir, ref, cwd) {
|
|
1261
|
+
if (!ref) throw new Error("an exact Experience bundle, pack release, or upload reference is required");
|
|
1262
|
+
const matches = scopedStateRows(userDataDir, cwd)
|
|
1263
|
+
.filter((row) => [row.bundleId, row.bundleHash, row.experiencePackId, row.experiencePackReleaseId, row.remote?.uploadId].includes(ref));
|
|
1264
|
+
if (!matches.length) throw new Error(`no exact local Experience record exists for this project: ${ref}`);
|
|
1265
|
+
if (matches.length > 1) throw new Error(`Experience reference is ambiguous; use an exact release, bundle, or upload id: ${ref}`);
|
|
1266
|
+
return { row: matches[0], validation: verifyStoredBundleRow(userDataDir, matches[0]) };
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function publicStoredBundleView(row, validation) {
|
|
1270
|
+
const bundle = validation.bundle;
|
|
1271
|
+
const itemStatusCounts = bundle.items.reduce((counts, item) => {
|
|
1272
|
+
counts[item.status] = (counts[item.status] || 0) + 1;
|
|
1273
|
+
return counts;
|
|
1274
|
+
}, { candidate: 0, promoted: 0, deprecated: 0, rejected: 0 });
|
|
1275
|
+
const remote = row.remote
|
|
1276
|
+
? {
|
|
1277
|
+
uploadId: row.remote.uploadId,
|
|
1278
|
+
status: row.remote.status,
|
|
1279
|
+
requestedVisibility: row.remote.requestedVisibility,
|
|
1280
|
+
revision: row.remote.revision,
|
|
1281
|
+
serverCheckedAt: row.remote.serverCheckedAt,
|
|
1282
|
+
receiptPresent: true,
|
|
1283
|
+
receiptVerified: true,
|
|
1284
|
+
...(row.remote.baseResolution ? { exactBaseAgentReleaseId: row.remote.baseResolution.agentReleaseId } : {}),
|
|
1285
|
+
}
|
|
1286
|
+
: null;
|
|
1287
|
+
return {
|
|
1288
|
+
schemaVersion: "agentlas.terminal-experience-local-view.v1",
|
|
1289
|
+
bundleId: row.bundleId,
|
|
1290
|
+
bundleHash: row.bundleHash,
|
|
1291
|
+
experiencePackId: row.experiencePackId,
|
|
1292
|
+
experiencePackReleaseId: row.experiencePackReleaseId,
|
|
1293
|
+
agentDefinitionId: row.agentDefinitionId,
|
|
1294
|
+
compatibleBaseReleaseIds: [...row.compatibleBaseReleaseIds],
|
|
1295
|
+
requestedVisibility: bundle.requestedVisibility,
|
|
1296
|
+
itemCount: bundle.items.length,
|
|
1297
|
+
itemStatusCounts,
|
|
1298
|
+
reviewState: itemStatusCounts.candidate > 0 && itemStatusCounts.promoted === 0 ? "candidate-review" : "curated",
|
|
1299
|
+
storedAt: row.storedAt,
|
|
1300
|
+
currentProjectOnly: true,
|
|
1301
|
+
localBundleVerified: true,
|
|
1302
|
+
remote,
|
|
1303
|
+
publicActivationClaimed: false,
|
|
1304
|
+
evaluatorAuthority: false,
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
function listStoredExperienceBundles(userDataDir, cwd) {
|
|
1309
|
+
return scopedStateRows(userDataDir, cwd)
|
|
1310
|
+
.map((row) => publicStoredBundleView(row, verifyStoredBundleRow(userDataDir, row)));
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
function inspectStoredExperienceBundle(userDataDir, ref, cwd) {
|
|
1314
|
+
const { row, validation } = resolveScopedStoredRecord(userDataDir, ref, cwd);
|
|
1315
|
+
return publicStoredBundleView(row, validation);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
function previewWithdrawUpload(ref, options = {}) {
|
|
1319
|
+
const { row, validation } = resolveScopedStoredRecord(options.userDataDir, ref, options.cwd);
|
|
1320
|
+
if (!row.remote?.uploadId || !row.remote?.revision || !row.remote?.receipt) {
|
|
1321
|
+
throw new Error("unpublish requires an exact locally observed server receipt; publish or run experience status first");
|
|
1322
|
+
}
|
|
1323
|
+
const receipt = validateUploadReceipt(row.remote.receipt, validation.bundle);
|
|
1324
|
+
if (receipt.status === "withdrawn") throw new Error("Experience upload is already withdrawn");
|
|
1325
|
+
return {
|
|
1326
|
+
schemaVersion: "agentlas.terminal-experience-unpublish-preview.v1",
|
|
1327
|
+
dryRun: true,
|
|
1328
|
+
action: "unpublish",
|
|
1329
|
+
bundleId: row.bundleId,
|
|
1330
|
+
experiencePackReleaseId: row.experiencePackReleaseId,
|
|
1331
|
+
uploadId: receipt.uploadId,
|
|
1332
|
+
currentStatus: receipt.status,
|
|
1333
|
+
ifMatchRevision: receipt.revision,
|
|
1334
|
+
networkUsed: false,
|
|
1335
|
+
localWriteUsed: false,
|
|
1336
|
+
serverReceiptPresent: true,
|
|
1337
|
+
authority: "local-observed-server-receipt",
|
|
1338
|
+
publicActivationClaimed: false,
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1026
1342
|
async function fetchUploadStatus(ref, options = {}) {
|
|
1343
|
+
const { row, validation } = resolveScopedStoredRecord(options.userDataDir, ref, options.cwd);
|
|
1344
|
+
const uploadId = row.remote?.uploadId;
|
|
1345
|
+
if (!uploadId) throw new Error("no exact server upload receipt is known for this local project bundle");
|
|
1027
1346
|
const auth = await authenticatedContext(options, false);
|
|
1028
|
-
const state = loadExchangeState(options.userDataDir);
|
|
1029
|
-
const row = findStateRecord(state, ref);
|
|
1030
|
-
const uploadId = UPLOAD_ID_RE.test(String(ref || "")) ? ref : row?.remote?.uploadId;
|
|
1031
|
-
if (!uploadId) throw new Error("no server upload id is known for this local bundle");
|
|
1032
1347
|
const response = await options.fetchHub(`${auth.base}/uploads/${encodeURIComponent(uploadId)}`, {
|
|
1033
1348
|
method: "GET",
|
|
1034
1349
|
headers: { accept: "application/json", cookie: auth.cookie, origin: auth.origin },
|
|
1035
1350
|
});
|
|
1036
1351
|
if (!response.ok) throw responseError(response, "Experience upload status");
|
|
1037
1352
|
const body = parseResponseJson(response, "Experience upload status");
|
|
1038
|
-
const bundle =
|
|
1353
|
+
const bundle = validation.bundle;
|
|
1039
1354
|
const receipt = validateUploadReceipt(body.receipt, bundle);
|
|
1040
1355
|
if (receipt.uploadId !== uploadId) throw new Error("status receipt id mismatch");
|
|
1041
1356
|
const etag = response.headers && typeof response.headers.get === "function" ? response.headers.get("etag") : null;
|
|
1042
1357
|
if (etag !== `"${receipt.revision}"`) throw new Error("status ETag does not match the exact receipt revision");
|
|
1043
|
-
|
|
1358
|
+
persistRemoteReceipt(options.userDataDir, bundle, receipt);
|
|
1044
1359
|
return { receipt, authoritative: "server", publicActivation: false, evaluatorAuthority: false };
|
|
1045
1360
|
}
|
|
1046
1361
|
|
|
@@ -1075,10 +1390,9 @@ function writePrivateExportAtomic(filePath, text, overwrite) {
|
|
|
1075
1390
|
}
|
|
1076
1391
|
|
|
1077
1392
|
async function fetchUploadExport(ref, options = {}) {
|
|
1078
|
-
const
|
|
1079
|
-
const
|
|
1080
|
-
|
|
1081
|
-
if (!uploadId) throw new Error("export requires a server upload id");
|
|
1393
|
+
const { row } = resolveScopedStoredRecord(options.userDataDir, ref, options.cwd);
|
|
1394
|
+
const uploadId = row.remote?.uploadId;
|
|
1395
|
+
if (!uploadId) throw new Error("export requires an exact locally observed server upload receipt");
|
|
1082
1396
|
const requestedOutput = options.outputPath
|
|
1083
1397
|
? path.resolve(options.cwd || process.cwd(), options.outputPath)
|
|
1084
1398
|
: row
|
|
@@ -1118,12 +1432,12 @@ async function fetchUploadExport(ref, options = {}) {
|
|
|
1118
1432
|
}
|
|
1119
1433
|
|
|
1120
1434
|
async function withdrawUpload(ref, options = {}) {
|
|
1121
|
-
const
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
const uploadId = UPLOAD_ID_RE.test(String(ref || "")) ? ref : row?.remote?.uploadId;
|
|
1125
|
-
if (!uploadId) throw new Error("withdraw requires a server upload id");
|
|
1435
|
+
const { row, validation } = resolveScopedStoredRecord(options.userDataDir, ref, options.cwd);
|
|
1436
|
+
const uploadId = row.remote?.uploadId;
|
|
1437
|
+
if (!uploadId) throw new Error("unpublish requires an exact locally observed server upload receipt");
|
|
1126
1438
|
if (!row?.remote?.revision) throw new Error("withdraw requires the exact locally observed server revision; run experience status first");
|
|
1439
|
+
if (row.remote.status === "withdrawn") throw new Error("Experience upload is already withdrawn");
|
|
1440
|
+
const auth = await authenticatedContext(options, false);
|
|
1127
1441
|
const response = await options.fetchHub(`${auth.base}/uploads/${encodeURIComponent(uploadId)}`, {
|
|
1128
1442
|
method: "DELETE",
|
|
1129
1443
|
headers: { accept: "application/json", cookie: auth.cookie, origin: auth.origin, "If-Match": `"${row.remote.revision}"` },
|
|
@@ -1133,7 +1447,7 @@ async function withdrawUpload(ref, options = {}) {
|
|
|
1133
1447
|
const body = parseResponseJson(response, "Experience withdrawal conflict");
|
|
1134
1448
|
const current = body.current?.receipt || body.current || body.receipt;
|
|
1135
1449
|
if (current) {
|
|
1136
|
-
const bundle =
|
|
1450
|
+
const bundle = validation.bundle;
|
|
1137
1451
|
const receipt = validateUploadReceipt(current, bundle);
|
|
1138
1452
|
persistRemoteReceipt(options.userDataDir, bundle, receipt);
|
|
1139
1453
|
const error = new Error("Experience withdrawal revision is stale; current server receipt was reconciled locally. Review status and retry.");
|
|
@@ -1146,7 +1460,7 @@ async function withdrawUpload(ref, options = {}) {
|
|
|
1146
1460
|
throw responseError(response, "Experience withdrawal (server support may be unavailable)");
|
|
1147
1461
|
}
|
|
1148
1462
|
const body = parseResponseJson(response, "Experience withdrawal");
|
|
1149
|
-
const bundle =
|
|
1463
|
+
const bundle = validation.bundle;
|
|
1150
1464
|
const receipt = validateUploadReceipt(body.receipt || body, bundle);
|
|
1151
1465
|
if (receipt.uploadId !== uploadId || receipt.status !== "withdrawn") throw new Error("withdrawal did not return the exact withdrawn server receipt");
|
|
1152
1466
|
const etag = response.headers && typeof response.headers.get === "function" ? response.headers.get("etag") : null;
|
|
@@ -1157,15 +1471,348 @@ async function withdrawUpload(ref, options = {}) {
|
|
|
1157
1471
|
return { receipt, authoritative: "server", publicActivation: false };
|
|
1158
1472
|
}
|
|
1159
1473
|
|
|
1474
|
+
const TASK_CLASS_KEYWORDS = Object.freeze({
|
|
1475
|
+
research: ["research", "investigate", "literature review", "market research", "리서치", "연구", "자료 조사"],
|
|
1476
|
+
writing: ["writing", "write article", "write copy", "copywriting", "blog post", "essay", "글쓰기", "글 작성", "카피 작성", "원고 작성"],
|
|
1477
|
+
coding: ["coding", "code implementation", "implement code", "write code", "source code", "코딩", "코드 구현", "코드 작성", "프로그래밍"],
|
|
1478
|
+
debugging: ["debug", "debugging", "bug fix", "fix bug", "troubleshoot", "error", "exception", "failure", "failed", "디버깅", "버그 수정", "오류", "오류 수정", "실패"],
|
|
1479
|
+
design: ["design", "ui design", "ux design", "wireframe", "디자인", "와이어프레임", "화면 설계"],
|
|
1480
|
+
"image-generation": ["image generation", "generate image", "create image", "text to image", "이미지 생성", "그림 생성"],
|
|
1481
|
+
"video-production": ["video production", "create video", "video editing", "영상 제작", "비디오 제작", "영상 편집"],
|
|
1482
|
+
presentation: ["presentation", "slide deck", "powerpoint", "ppt", "프레젠테이션", "발표 자료", "슬라이드", "피피티"],
|
|
1483
|
+
document: ["document", "docx", "pdf document", "document editing", "문서", "문서 작성", "문서 편집"],
|
|
1484
|
+
"data-analysis": ["data analysis", "analyze data", "analytics", "데이터 분석", "통계 분석"],
|
|
1485
|
+
"browser-automation": ["browser automation", "automate browser", "playwright", "브라우저 자동화", "웹 자동화"],
|
|
1486
|
+
"social-publishing": ["social publishing", "publish social", "post to instagram", "post to tiktok", "sns 게시", "소셜 게시", "인스타 업로드", "틱톡 업로드"],
|
|
1487
|
+
marketing: ["marketing", "campaign", "seo", "마케팅", "캠페인"],
|
|
1488
|
+
sales: ["sales", "lead generation", "sales outreach", "영업", "리드 발굴"],
|
|
1489
|
+
"customer-support": ["customer support", "customer service", "support ticket", "고객 지원", "고객 문의", "cs 응대"],
|
|
1490
|
+
ecommerce: ["ecommerce", "e commerce", "online store", "shopify", "이커머스", "온라인 쇼핑몰", "스마트스토어"],
|
|
1491
|
+
"legal-review": ["legal review", "contract review", "legal analysis", "법률 검토", "계약 검토", "법무 검토"],
|
|
1492
|
+
finance: ["finance", "financial analysis", "investment analysis", "재무", "금융", "투자 분석"],
|
|
1493
|
+
"project-planning": ["project planning", "project plan", "roadmap", "프로젝트 계획", "로드맵", "일정 계획"],
|
|
1494
|
+
"agent-building": ["agent building", "build agent", "create agent", "에이전트 빌드", "에이전트 만들", "에이전트 생성"],
|
|
1495
|
+
"workflow-automation": ["workflow automation", "automate workflow", "automation workflow", "워크플로 자동화", "업무 자동화"],
|
|
1496
|
+
"file-operations": ["file operations", "move files", "rename files", "organize files", "파일 작업", "파일 이동", "파일 이름 변경", "파일 정리"],
|
|
1497
|
+
translation: ["translation", "translate", "localization", "번역", "현지화"],
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1500
|
+
function normalizeClassificationText(value) {
|
|
1501
|
+
return String(value || "")
|
|
1502
|
+
.normalize("NFKC")
|
|
1503
|
+
.toLowerCase()
|
|
1504
|
+
.replace(/[^a-z0-9가-힣]+/gi, " ")
|
|
1505
|
+
.replace(/\s+/g, " ")
|
|
1506
|
+
.trim();
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
function normalizedTaxonomyAtom(value) {
|
|
1510
|
+
return typeof value === "string" ? value.normalize("NFKC").trim().toLowerCase() : "";
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
function canonicalSourceTaskId(value) {
|
|
1514
|
+
const normalized = normalizedTaxonomyAtom(value);
|
|
1515
|
+
return normalized.startsWith(CANONICAL_TASK_PREFIX) && CANONICAL_TASK_ID_SET.has(normalized) ? normalized : null;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
function canonicalTaskId(value) {
|
|
1519
|
+
const normalized = normalizedTaxonomyAtom(value);
|
|
1520
|
+
const source = canonicalSourceTaskId(normalized);
|
|
1521
|
+
if (source) return source;
|
|
1522
|
+
const id = `${CANONICAL_TASK_PREFIX}${normalized}`;
|
|
1523
|
+
return CANONICAL_TASK_ID_SET.has(id) ? id : null;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
function isCanonicalTaskId(value) {
|
|
1527
|
+
return typeof value === "string" && CANONICAL_TASK_ID_SET.has(value);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
function keywordOccurs(normalizedPrompt, rawKeyword) {
|
|
1531
|
+
const keyword = normalizeClassificationText(rawKeyword);
|
|
1532
|
+
if (!keyword) return false;
|
|
1533
|
+
if (/[가-힣]/.test(keyword)) return normalizedPrompt.includes(keyword);
|
|
1534
|
+
return ` ${normalizedPrompt} `.includes(` ${keyword} `);
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
function deriveCanonicalTaskClasses(prompt, options = {}) {
|
|
1538
|
+
const declaredRaw = options.declaredTaskClasses ?? options.declaredTaskClass;
|
|
1539
|
+
if (declaredRaw != null && (Array.isArray(declaredRaw) ? declaredRaw.length : String(declaredRaw).trim())) {
|
|
1540
|
+
const declared = (Array.isArray(declaredRaw) ? declaredRaw : [declaredRaw]).map(String);
|
|
1541
|
+
const taskIds = [...new Set(declared.map(canonicalTaskId).filter(Boolean))];
|
|
1542
|
+
const invalidDeclared = declared.filter((value) => !canonicalTaskId(value));
|
|
1543
|
+
return {
|
|
1544
|
+
taskIds: CANONICAL_TASK_IDS.filter((id) => taskIds.includes(id)),
|
|
1545
|
+
source: "declared-task-class",
|
|
1546
|
+
matchedTaskClasses: CANONICAL_TASK_IDS.filter((id) => taskIds.includes(id)),
|
|
1547
|
+
invalidDeclaredCount: invalidDeclared.length,
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
const normalizedPrompt = normalizeClassificationText(prompt);
|
|
1551
|
+
const matches = [];
|
|
1552
|
+
for (const slug of CANONICAL_TASK_SLUGS) {
|
|
1553
|
+
if ((TASK_CLASS_KEYWORDS[slug] || []).some((keyword) => keywordOccurs(normalizedPrompt, keyword))) {
|
|
1554
|
+
matches.push(`${CANONICAL_TASK_PREFIX}${slug}`);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
return { taskIds: matches, source: "deterministic-keyword-map", matchedTaskClasses: matches, invalidDeclaredCount: 0 };
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
function parseEnvironmentConstraint(value) {
|
|
1561
|
+
const normalized = normalizedTaxonomyAtom(value);
|
|
1562
|
+
const contract = EXPERIENCE_TAXONOMY_V1.environment;
|
|
1563
|
+
if (normalized.startsWith(contract.osPrefix)) {
|
|
1564
|
+
const selected = normalized.slice(contract.osPrefix.length);
|
|
1565
|
+
return CANONICAL_OS_VALUES.has(selected) ? { dimension: "os", value: selected } : null;
|
|
1566
|
+
}
|
|
1567
|
+
if (normalized.startsWith(contract.archPrefix)) {
|
|
1568
|
+
const selected = normalized.slice(contract.archPrefix.length);
|
|
1569
|
+
return CANONICAL_ARCH_VALUES.has(selected) ? { dimension: "arch", value: selected } : null;
|
|
1570
|
+
}
|
|
1571
|
+
if (normalized.startsWith(contract.runtimePrefix)) {
|
|
1572
|
+
const selected = normalized.slice(contract.runtimePrefix.length);
|
|
1573
|
+
return CANONICAL_RUNTIME_RE.test(selected) ? { dimension: "runtime", value: selected } : null;
|
|
1574
|
+
}
|
|
1575
|
+
return null;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
function isCanonicalEnvironmentTag(value) {
|
|
1579
|
+
return Boolean(parseEnvironmentConstraint(value));
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1160
1582
|
function defaultEnvironmentTags(options = {}) {
|
|
1161
1583
|
const platform = options.platform || process.platform;
|
|
1162
1584
|
const arch = options.arch || process.arch;
|
|
1163
|
-
const
|
|
1164
|
-
const
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1585
|
+
const platformCandidate = normalizedTaxonomyAtom(platform === "darwin" ? "macos" : platform === "win32" ? "windows" : platform);
|
|
1586
|
+
const archCandidate = normalizedTaxonomyAtom(arch === "x86_64" ? "x64" : arch === "aarch64" ? "arm64" : arch);
|
|
1587
|
+
const runtimeCandidate = normalizedTaxonomyAtom(typeof options.runtime === "string" ? options.runtime : typeof options.runtimeTag === "string" ? options.runtimeTag : "terminal");
|
|
1588
|
+
const platformName = CANONICAL_OS_VALUES.has(platformCandidate) ? platformCandidate : "unknown";
|
|
1589
|
+
const archName = CANONICAL_ARCH_VALUES.has(archCandidate) ? archCandidate : "unknown";
|
|
1590
|
+
const runtimeName = CANONICAL_RUNTIME_RE.test(runtimeCandidate) ? runtimeCandidate : "unknown";
|
|
1591
|
+
return [
|
|
1592
|
+
`${EXPERIENCE_TAXONOMY_V1.environment.osPrefix}${platformName}`,
|
|
1593
|
+
`${EXPERIENCE_TAXONOMY_V1.environment.archPrefix}${archName}`,
|
|
1594
|
+
`${EXPERIENCE_TAXONOMY_V1.environment.runtimePrefix}${runtimeName}`,
|
|
1595
|
+
];
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
function environmentConstraintsMatch(constraints, environment) {
|
|
1599
|
+
const actual = {
|
|
1600
|
+
os: normalizedTaxonomyAtom(environment?.os),
|
|
1601
|
+
arch: normalizedTaxonomyAtom(environment?.arch),
|
|
1602
|
+
runtime: normalizedTaxonomyAtom(environment?.runtime),
|
|
1603
|
+
};
|
|
1604
|
+
if (!CANONICAL_OS_VALUES.has(actual.os) || !CANONICAL_ARCH_VALUES.has(actual.arch) || !CANONICAL_RUNTIME_RE.test(actual.runtime)) return false;
|
|
1605
|
+
if (actual.os === "unknown" || actual.arch === "unknown" || actual.runtime === "unknown") return false;
|
|
1606
|
+
return (constraints || []).every((raw) => {
|
|
1607
|
+
const parsed = parseEnvironmentConstraint(raw);
|
|
1608
|
+
return Boolean(parsed && actual[parsed.dimension] === parsed.value);
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
function selectApplicablePortableItems(input = {}) {
|
|
1613
|
+
const profile = new Set([input.taskClass, ...(input.capabilityTags || [])].map(canonicalTaskId).filter(Boolean));
|
|
1614
|
+
if (!profile.size) return [];
|
|
1615
|
+
const eligible = (input.items || []).filter((item) => {
|
|
1616
|
+
if (!item || ["deprecated", "rejected"].includes(item.status)) return false;
|
|
1617
|
+
if (!(item.taskSignatures || []).map(canonicalSourceTaskId).filter(Boolean).some((task) => profile.has(task))) return false;
|
|
1618
|
+
return environmentConstraintsMatch(item.environmentConstraints || [], input.environment || {});
|
|
1619
|
+
});
|
|
1620
|
+
const superseded = new Set(eligible.flatMap((item) => item.supersedesItemIds || []));
|
|
1621
|
+
return eligible
|
|
1622
|
+
.filter((item) => typeof item.experienceItemId === "string" && !superseded.has(item.experienceItemId))
|
|
1623
|
+
.map((item) => item.experienceItemId);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function readExactLocalBaseMarker(agentRoot, expectedSlug = null) {
|
|
1627
|
+
if (!agentRoot) return { marker: null, reason: "exact-local-base-marker-unavailable" };
|
|
1628
|
+
const file = path.join(path.resolve(agentRoot), ".agentlas-cloud-package.json");
|
|
1629
|
+
try {
|
|
1630
|
+
const stat = fs.lstatSync(file);
|
|
1631
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 256 * 1024) {
|
|
1632
|
+
return { marker: null, reason: "exact-local-base-marker-unsafe" };
|
|
1633
|
+
}
|
|
1634
|
+
const raw = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
1635
|
+
const slug = String(raw.slug || expectedSlug || "").trim();
|
|
1636
|
+
const packageHashRaw = String(raw.packageHash || "").replace(/^sha256:/i, "").toLowerCase();
|
|
1637
|
+
const packageHashVersion = String(raw.packageHashVersion || "");
|
|
1638
|
+
const cloudId = raw.cloudId == null ? null : String(raw.cloudId);
|
|
1639
|
+
if (
|
|
1640
|
+
!/^[a-z0-9][a-z0-9._-]{0,95}$/.test(slug) ||
|
|
1641
|
+
(expectedSlug && slug !== expectedSlug) ||
|
|
1642
|
+
!/^[a-f0-9]{64}$/.test(packageHashRaw) ||
|
|
1643
|
+
!["path-sha256-v1", "path-sha256-executable-v2"].includes(packageHashVersion) ||
|
|
1644
|
+
(cloudId && !ID_RE.test(cloudId))
|
|
1645
|
+
) return { marker: null, reason: "exact-local-base-marker-invalid" };
|
|
1646
|
+
return {
|
|
1647
|
+
marker: {
|
|
1648
|
+
slug,
|
|
1649
|
+
cloudId,
|
|
1650
|
+
packageHash: `sha256:${packageHashRaw}`,
|
|
1651
|
+
packageHashVersion,
|
|
1652
|
+
},
|
|
1653
|
+
reason: null,
|
|
1654
|
+
};
|
|
1655
|
+
} catch (error) {
|
|
1656
|
+
return {
|
|
1657
|
+
marker: null,
|
|
1658
|
+
reason: error?.code === "ENOENT" ? "exact-local-base-marker-unavailable" : "exact-local-base-marker-invalid",
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
function exactTaskSignatureInPrompt(signature, prompt, options = {}) {
|
|
1664
|
+
if (!isCanonicalTaskId(signature)) return false;
|
|
1665
|
+
return deriveCanonicalTaskClasses(prompt, options).taskIds.includes(signature);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
/**
|
|
1669
|
+
* Resolve normal Terminal runs without fuzzy identity or semantic guessing.
|
|
1670
|
+
* Automatic retrieval additionally needs one exact Experience release selected
|
|
1671
|
+
* by an authoritative loadout. Merely saving/uploading a compatible bundle is
|
|
1672
|
+
* never attachment consent.
|
|
1673
|
+
*/
|
|
1674
|
+
function resolveRuntimeExperienceForAgent(options = {}) {
|
|
1675
|
+
const requested = options.requested || {};
|
|
1676
|
+
if (requested.disabled === true) {
|
|
1677
|
+
return { disabled: true, observableReason: "disabled-by-user", resolution: "skipped" };
|
|
1678
|
+
}
|
|
1679
|
+
const environmentTags = defaultEnvironmentTags(options);
|
|
1680
|
+
if (environmentTags.some((tag) => tag.endsWith("/unknown"))) {
|
|
1681
|
+
return { disabled: true, observableReason: "runtime-environment-unknown", resolution: "skipped" };
|
|
1682
|
+
}
|
|
1683
|
+
if (Array.isArray(requested.environmentTags) && requested.environmentTags.length) {
|
|
1684
|
+
const declaredEnvironment = [...new Set(requested.environmentTags.map(String).filter(Boolean))];
|
|
1685
|
+
const exactDefault = declaredEnvironment.length === environmentTags.length && declaredEnvironment.every((tag) => environmentTags.includes(tag));
|
|
1686
|
+
if (!declaredEnvironment.every(isCanonicalEnvironmentTag)) {
|
|
1687
|
+
return { disabled: true, observableReason: "legacy-environment-constraint-not-runtime-activatable", resolution: "skipped" };
|
|
1688
|
+
}
|
|
1689
|
+
if (!exactDefault) {
|
|
1690
|
+
return { disabled: true, observableReason: "declared-environment-does-not-match-runtime", resolution: "skipped" };
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
const explicitBase = String(requested.baseAgentReleaseId || "");
|
|
1694
|
+
const explicitSignatures = [...new Set((requested.taskSignatures || []).map(String).filter(Boolean))];
|
|
1695
|
+
const explicitPackReleases = [...new Set((requested.experiencePackReleaseIds || []).map(String).filter(Boolean))];
|
|
1696
|
+
if (explicitBase || explicitSignatures.length || requested.agentDefinitionId || explicitPackReleases.length) {
|
|
1697
|
+
if (!ID_RE.test(explicitBase) || !explicitSignatures.length || explicitPackReleases.length !== 1 || !ID_RE.test(explicitPackReleases[0])) {
|
|
1698
|
+
return { disabled: true, observableReason: "incomplete-explicit-experience-binding", resolution: "skipped" };
|
|
1699
|
+
}
|
|
1700
|
+
if (explicitSignatures.some((item) => !isCanonicalTaskId(item))) {
|
|
1701
|
+
return { disabled: true, observableReason: "legacy-task-signature-not-runtime-activatable", resolution: "skipped" };
|
|
1702
|
+
}
|
|
1703
|
+
return {
|
|
1704
|
+
disabled: false,
|
|
1705
|
+
baseAgentReleaseId: explicitBase,
|
|
1706
|
+
...(requested.agentDefinitionId && ID_RE.test(String(requested.agentDefinitionId)) ? { agentDefinitionId: String(requested.agentDefinitionId) } : {}),
|
|
1707
|
+
experiencePackReleaseIds: explicitPackReleases,
|
|
1708
|
+
taskSignatures: explicitSignatures,
|
|
1709
|
+
environmentTags,
|
|
1710
|
+
resolution: "explicit-exact",
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
const agent = options.agent;
|
|
1714
|
+
if (!agent || agent.builtin || !agent.slug) {
|
|
1715
|
+
return { disabled: true, observableReason: agent?.builtin ? "builtin-agent-has-no-owned-experience-base" : "no-exact-agent-base", resolution: "skipped" };
|
|
1716
|
+
}
|
|
1717
|
+
const local = readExactLocalBaseMarker(options.agentRoot, agent.slug);
|
|
1718
|
+
if (!local.marker) return { disabled: true, observableReason: local.reason, resolution: "skipped" };
|
|
1719
|
+
const attachedPackReleases = [...new Set(
|
|
1720
|
+
(requested.attachedExperiencePackReleaseIds || []).map(String).filter(Boolean),
|
|
1721
|
+
)];
|
|
1722
|
+
if (attachedPackReleases.length !== 1 || !ID_RE.test(attachedPackReleases[0])) {
|
|
1723
|
+
return { disabled: true, observableReason: "explicit-experience-attachment-required", resolution: "skipped" };
|
|
1724
|
+
}
|
|
1725
|
+
let state;
|
|
1726
|
+
try { state = loadExchangeState(options.userDataDir); }
|
|
1727
|
+
catch { return { disabled: true, observableReason: "local-experience-state-invalid", resolution: "skipped" }; }
|
|
1728
|
+
const scopeHash = projectScopeHash(options.cwd);
|
|
1729
|
+
const matchingRows = state.bundles.filter((row) => {
|
|
1730
|
+
const base = row.remote?.baseResolution;
|
|
1731
|
+
return attachedPackReleases.includes(row.experiencePackReleaseId) &&
|
|
1732
|
+
row.projectScopeHash === scopeHash && base &&
|
|
1733
|
+
base.slug === local.marker.slug &&
|
|
1734
|
+
(!local.marker.cloudId || base.cloudId === local.marker.cloudId) &&
|
|
1735
|
+
base.packageHash === local.marker.packageHash &&
|
|
1736
|
+
base.packageHashVersion === local.marker.packageHashVersion &&
|
|
1737
|
+
base.agentDefinitionId === row.agentDefinitionId &&
|
|
1738
|
+
row.compatibleBaseReleaseIds.includes(base.agentReleaseId);
|
|
1739
|
+
});
|
|
1740
|
+
if (!matchingRows.length) {
|
|
1741
|
+
return { disabled: true, observableReason: "exact-local-base-release-unavailable", resolution: "skipped" };
|
|
1742
|
+
}
|
|
1743
|
+
const baseKeys = new Set(matchingRows.map((row) => {
|
|
1744
|
+
const base = row.remote.baseResolution;
|
|
1745
|
+
return `${base.agentDefinitionId}\0${base.agentReleaseId}\0${base.packageHash}`;
|
|
1746
|
+
}));
|
|
1747
|
+
if (baseKeys.size !== 1) {
|
|
1748
|
+
return { disabled: true, observableReason: "ambiguous-exact-base-release", resolution: "skipped" };
|
|
1749
|
+
}
|
|
1750
|
+
const base = matchingRows[0].remote.baseResolution;
|
|
1751
|
+
const taskClassResolution = deriveCanonicalTaskClasses(options.prompt, {
|
|
1752
|
+
declaredTaskClasses: requested.declaredTaskClasses ?? options.declaredTaskClasses ?? options.declaredTaskClass,
|
|
1753
|
+
});
|
|
1754
|
+
if (taskClassResolution.invalidDeclaredCount) {
|
|
1755
|
+
return { disabled: true, observableReason: "invalid-declared-task-class", resolution: "skipped" };
|
|
1756
|
+
}
|
|
1757
|
+
if (!taskClassResolution.taskIds.length) {
|
|
1758
|
+
return { disabled: true, observableReason: "canonical-task-class-unresolved", resolution: "skipped" };
|
|
1759
|
+
}
|
|
1760
|
+
const environment = new Set(environmentTags);
|
|
1761
|
+
const classifiedTasks = new Set(taskClassResolution.taskIds);
|
|
1762
|
+
const taskSignatures = new Set();
|
|
1763
|
+
let sawPromotedItem = false;
|
|
1764
|
+
let sawCanonicalSignature = false;
|
|
1765
|
+
let sawMatchingCanonicalTask = false;
|
|
1766
|
+
let sawLegacyEnvironmentForMatch = false;
|
|
1767
|
+
let sawCanonicalEnvironmentMismatch = false;
|
|
1768
|
+
for (const row of matchingRows) {
|
|
1769
|
+
let bundle;
|
|
1770
|
+
try { bundle = readStoredBundle(options.userDataDir, row.bundleId).bundle; }
|
|
1771
|
+
catch { continue; }
|
|
1772
|
+
for (const item of bundle.items) {
|
|
1773
|
+
if (item.status !== "promoted") continue;
|
|
1774
|
+
sawPromotedItem = true;
|
|
1775
|
+
const canonicalSignatures = item.taskSignatures.filter(isCanonicalTaskId);
|
|
1776
|
+
if (canonicalSignatures.length) sawCanonicalSignature = true;
|
|
1777
|
+
const matchedSignatures = canonicalSignatures.filter((signature) => classifiedTasks.has(signature));
|
|
1778
|
+
if (!matchedSignatures.length) continue;
|
|
1779
|
+
sawMatchingCanonicalTask = true;
|
|
1780
|
+
if (!item.environmentConstraints.every(isCanonicalEnvironmentTag)) {
|
|
1781
|
+
sawLegacyEnvironmentForMatch = true;
|
|
1782
|
+
continue;
|
|
1783
|
+
}
|
|
1784
|
+
if (!item.environmentConstraints.every((constraint) => environment.has(constraint))) {
|
|
1785
|
+
sawCanonicalEnvironmentMismatch = true;
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1788
|
+
for (const signature of matchedSignatures) taskSignatures.add(signature);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
if (!taskSignatures.size) {
|
|
1792
|
+
const observableReason = sawPromotedItem && !sawCanonicalSignature
|
|
1793
|
+
? "legacy-task-signature-not-auto-activatable"
|
|
1794
|
+
: sawMatchingCanonicalTask && sawLegacyEnvironmentForMatch
|
|
1795
|
+
? "legacy-environment-constraint-not-auto-activatable"
|
|
1796
|
+
: sawMatchingCanonicalTask && sawCanonicalEnvironmentMismatch
|
|
1797
|
+
? "canonical-environment-constraint-mismatch"
|
|
1798
|
+
: "canonical-task-signature-unavailable";
|
|
1799
|
+
return {
|
|
1800
|
+
disabled: true,
|
|
1801
|
+
observableReason,
|
|
1802
|
+
resolution: "skipped",
|
|
1803
|
+
taskClassResolution,
|
|
1804
|
+
};
|
|
1805
|
+
}
|
|
1806
|
+
return {
|
|
1807
|
+
disabled: false,
|
|
1808
|
+
baseAgentReleaseId: base.agentReleaseId,
|
|
1809
|
+
agentDefinitionId: base.agentDefinitionId,
|
|
1810
|
+
experiencePackReleaseIds: attachedPackReleases,
|
|
1811
|
+
taskSignatures: [...taskSignatures].sort(compareCodePoints),
|
|
1812
|
+
environmentTags,
|
|
1813
|
+
resolution: "automatic-exact",
|
|
1814
|
+
taskClassResolution,
|
|
1815
|
+
};
|
|
1169
1816
|
}
|
|
1170
1817
|
|
|
1171
1818
|
function estimateTokens(text) {
|
|
@@ -1175,33 +1822,49 @@ function estimateTokens(text) {
|
|
|
1175
1822
|
function buildLocalExperienceAdvisory(options = {}) {
|
|
1176
1823
|
const empty = { text: "", itemIds: [], estimatedTokens: 0, authority: "local-advisory", serverRentalResolutionReceiptPresent: false };
|
|
1177
1824
|
if (!options.userDataDir || !options.cwd || !ID_RE.test(String(options.baseAgentReleaseId || ""))) return empty;
|
|
1178
|
-
const
|
|
1825
|
+
const experiencePackReleaseIds = new Set(
|
|
1826
|
+
(options.experiencePackReleaseIds || []).map(String).filter((value) => ID_RE.test(value)),
|
|
1827
|
+
);
|
|
1828
|
+
if (experiencePackReleaseIds.size !== 1) return empty;
|
|
1829
|
+
const taskSignatures = new Set((options.taskSignatures || []).map(String).filter(isCanonicalTaskId));
|
|
1179
1830
|
if (!taskSignatures.size) return empty;
|
|
1180
|
-
const
|
|
1831
|
+
const resolvedEnvironmentTags = (options.environmentTags || defaultEnvironmentTags(options)).map(String).filter(isCanonicalEnvironmentTag);
|
|
1832
|
+
if (resolvedEnvironmentTags.some((tag) => tag.endsWith("/unknown"))) return empty;
|
|
1833
|
+
const environmentTags = new Set(resolvedEnvironmentTags);
|
|
1181
1834
|
const state = loadExchangeState(options.userDataDir);
|
|
1182
1835
|
const projectHash = projectScopeHash(options.cwd);
|
|
1183
1836
|
const candidates = [];
|
|
1184
1837
|
for (const row of state.bundles) {
|
|
1185
|
-
if (
|
|
1838
|
+
if (
|
|
1839
|
+
!experiencePackReleaseIds.has(row.experiencePackReleaseId) ||
|
|
1840
|
+
row.projectScopeHash !== projectHash ||
|
|
1841
|
+
!row.compatibleBaseReleaseIds.includes(options.baseAgentReleaseId)
|
|
1842
|
+
) continue;
|
|
1186
1843
|
if (options.agentDefinitionId && row.agentDefinitionId !== options.agentDefinitionId) continue;
|
|
1187
1844
|
let validation;
|
|
1188
1845
|
try { validation = readStoredBundle(options.userDataDir, row.bundleId); } catch { continue; }
|
|
1189
1846
|
for (const item of validation.bundle.items) {
|
|
1190
1847
|
if (item.status !== "promoted") continue;
|
|
1191
1848
|
if (!item.taskSignatures.some((signature) => taskSignatures.has(signature))) continue;
|
|
1849
|
+
if (!item.environmentConstraints.every(isCanonicalEnvironmentTag)) continue;
|
|
1192
1850
|
if (!item.environmentConstraints.every((constraint) => environmentTags.has(constraint))) continue;
|
|
1193
1851
|
candidates.push(item);
|
|
1194
1852
|
}
|
|
1195
1853
|
}
|
|
1196
1854
|
candidates.sort((a, b) => Number(b.confidence) - Number(a.confidence) || compareCodePoints(a.experienceItemId, b.experienceItemId));
|
|
1197
1855
|
const header = "[AGENTLAS_LOCAL_EXPERIENCE_ADVISORY v1] NO SERVER RENTAL-RESOLUTION RECEIPT. Local user-attested procedures only; not evaluator-verified and not reputation evidence.";
|
|
1856
|
+
const reservedTokens = Number.isInteger(options.reservedTokens)
|
|
1857
|
+
? Math.max(0, Math.min(EXPERIENCE_RETRIEVAL_MAX_TOKENS, options.reservedTokens))
|
|
1858
|
+
: 0;
|
|
1859
|
+
const dynamicTokenBudget = Math.max(0, EXPERIENCE_RETRIEVAL_MAX_TOKENS - reservedTokens);
|
|
1860
|
+
if (estimateTokens(header) > dynamicTokenBudget) return empty;
|
|
1198
1861
|
let text = header;
|
|
1199
1862
|
const itemIds = [];
|
|
1200
1863
|
for (const item of candidates) {
|
|
1201
1864
|
if (itemIds.length >= EXPERIENCE_RETRIEVAL_MAX_ITEMS || itemIds.includes(item.experienceItemId)) continue;
|
|
1202
1865
|
const line = `\n- [${item.experienceItemId}] ${item.summary}\n Steps: ${item.instructions.join(" | ")}`;
|
|
1203
1866
|
const next = `${text}${line}`;
|
|
1204
|
-
if (estimateTokens(next) >
|
|
1867
|
+
if (estimateTokens(next) > dynamicTokenBudget) continue;
|
|
1205
1868
|
text = next;
|
|
1206
1869
|
itemIds.push(item.experienceItemId);
|
|
1207
1870
|
}
|
|
@@ -1237,6 +1900,39 @@ function renderPublish(result) {
|
|
|
1237
1900
|
].join("\n");
|
|
1238
1901
|
}
|
|
1239
1902
|
|
|
1903
|
+
function publicUploadReceipt(receipt) {
|
|
1904
|
+
return {
|
|
1905
|
+
schema: receipt.schema,
|
|
1906
|
+
uploadId: receipt.uploadId,
|
|
1907
|
+
bundleId: receipt.bundleId,
|
|
1908
|
+
bundleHash: receipt.bundleHash,
|
|
1909
|
+
experiencePackId: receipt.experiencePackId,
|
|
1910
|
+
experienceReleaseId: receipt.experienceReleaseId,
|
|
1911
|
+
status: receipt.status,
|
|
1912
|
+
requestedVisibility: receipt.requestedVisibility,
|
|
1913
|
+
revision: receipt.revision,
|
|
1914
|
+
createdAt: receipt.createdAt,
|
|
1915
|
+
updatedAt: receipt.updatedAt,
|
|
1916
|
+
...(receipt.errorCode ? { errorCode: receipt.errorCode } : {}),
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
function publicCommandExchangeResult(result) {
|
|
1921
|
+
return {
|
|
1922
|
+
...(result.receipt ? { receipt: publicUploadReceipt(result.receipt) } : {}),
|
|
1923
|
+
...(BUNDLE_ID_RE.test(String(result.bundleId || "")) ? { bundleId: result.bundleId } : {}),
|
|
1924
|
+
...(HASH_RE.test(String(result.bundleHash || "")) ? { bundleHash: result.bundleHash } : {}),
|
|
1925
|
+
...(["private", "unlisted", "public"].includes(result.requestedVisibility) ? { requestedVisibility: result.requestedVisibility } : {}),
|
|
1926
|
+
...(typeof result.replayed === "boolean" ? { replayed: result.replayed } : {}),
|
|
1927
|
+
...(typeof result.recovered === "boolean" ? { recovered: result.recovered } : {}),
|
|
1928
|
+
...(typeof result.dryRun === "boolean" ? { dryRun: result.dryRun } : {}),
|
|
1929
|
+
...(typeof result.networkUsed === "boolean" ? { networkUsed: result.networkUsed } : {}),
|
|
1930
|
+
...(typeof result.authoritative === "string" ? { authoritative: result.authoritative } : {}),
|
|
1931
|
+
publicActivation: false,
|
|
1932
|
+
evaluatorAuthority: false,
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1240
1936
|
function baseDescriptorFromFlags(flags) {
|
|
1241
1937
|
return {
|
|
1242
1938
|
...(flags["base-slug"] ? { slug: flags["base-slug"] } : {}),
|
|
@@ -1255,25 +1951,62 @@ async function cmdExperienceExchange(options = {}) {
|
|
|
1255
1951
|
|
|
1256
1952
|
if (sub === "help" || sub === "--help" || sub === "-h") {
|
|
1257
1953
|
const help = [
|
|
1954
|
+
"agentlas experience list",
|
|
1955
|
+
"agentlas experience inspect <exact-release-id|bundle-id|upload-id>",
|
|
1258
1956
|
"agentlas experience validate <bundle.agentlas-experience.json>",
|
|
1259
1957
|
"agentlas experience save <bundle> --base-cloud-id <id>|--base-slug <slug> --base-package-hash sha256:<hash>",
|
|
1260
1958
|
"agentlas experience publish <bundle> --visibility unlisted|public --base-cloud-id <id>|--base-slug <slug> --base-package-hash sha256:<hash>",
|
|
1261
1959
|
"agentlas experience status <bundle-id|upload-id>",
|
|
1960
|
+
"agentlas experience unpublish <exact-release-id|bundle-id|upload-id> [--dry-run]",
|
|
1262
1961
|
"agentlas experience withdraw <bundle-id|upload-id>",
|
|
1263
1962
|
"agentlas experience export <bundle-id|upload-id> [--out file] [--overwrite]",
|
|
1264
1963
|
"Options: --dry-run (zero network/write), --idempotency-key <safe-key>, save --local-only",
|
|
1265
|
-
"Legacy local intents: list|inspect|
|
|
1964
|
+
"Legacy pack-only local intents: legacy-list|legacy-inspect|legacy-publish|legacy-unpublish",
|
|
1266
1965
|
"publish requests verification only; Terminal never claims evaluator verification or public activation.",
|
|
1267
1966
|
].join("\n");
|
|
1268
1967
|
emit(help);
|
|
1269
1968
|
return { help: true };
|
|
1270
1969
|
}
|
|
1271
1970
|
|
|
1272
|
-
if (["list", "
|
|
1971
|
+
if (["legacy-list", "legacy-inspect", "legacy-publish", "legacy-unpublish"].includes(sub)) {
|
|
1273
1972
|
if (typeof options.legacyCommand !== "function") throw new Error("legacy local-intent Experience handler is unavailable");
|
|
1274
|
-
const mapped = sub
|
|
1973
|
+
const mapped = sub.slice("legacy-".length);
|
|
1275
1974
|
return options.legacyCommand({ ...options, args: [mapped, ...args.slice(1)] });
|
|
1276
1975
|
}
|
|
1976
|
+
if (sub === "list" || sub === "ls") {
|
|
1977
|
+
if (flags._.length) throw new Error("usage: agentlas experience list [--json]");
|
|
1978
|
+
const bundles = listStoredExperienceBundles(options.userDataDir, options.cwd);
|
|
1979
|
+
const result = {
|
|
1980
|
+
schemaVersion: "agentlas.terminal-experience-local-list.v1",
|
|
1981
|
+
currentProjectOnly: true,
|
|
1982
|
+
networkUsed: false,
|
|
1983
|
+
bundles,
|
|
1984
|
+
};
|
|
1985
|
+
const lines = bundles.length
|
|
1986
|
+
? ["LOCAL PORTABLE EXPERIENCE BUNDLES · current project only · no network", ...bundles.map((bundle) =>
|
|
1987
|
+
`- ${bundle.experiencePackId}@${bundle.experiencePackReleaseId} · ${bundle.itemCount} item(s) · ${bundle.reviewState} · Hub: ${bundle.remote ? `${bundle.remote.status} (${bundle.remote.uploadId})` : "not submitted"}`)]
|
|
1988
|
+
: ["No Portable Experience Bundles are stored for this project.", "Hub was not contacted."];
|
|
1989
|
+
emit(flags.json ? JSON.stringify(result, null, 2) : lines.join("\n"));
|
|
1990
|
+
return result;
|
|
1991
|
+
}
|
|
1992
|
+
if (sub === "inspect" || sub === "show") {
|
|
1993
|
+
const ref = flags._[0];
|
|
1994
|
+
if (!ref || flags._.length !== 1) throw new Error("usage: agentlas experience inspect <exact-release-id|bundle-id|upload-id>");
|
|
1995
|
+
const bundle = inspectStoredExperienceBundle(options.userDataDir, ref, options.cwd);
|
|
1996
|
+
const result = { ...bundle, networkUsed: false };
|
|
1997
|
+
emit(flags.json ? JSON.stringify(result, null, 2) : [
|
|
1998
|
+
`${bundle.experiencePackId}@${bundle.experiencePackReleaseId}`,
|
|
1999
|
+
`bundle: ${bundle.bundleId} · ${bundle.itemCount} item(s) · local integrity: verified`,
|
|
2000
|
+
`review state: ${bundle.reviewState} · candidates ${bundle.itemStatusCounts.candidate} · promoted ${bundle.itemStatusCounts.promoted}`,
|
|
2001
|
+
`compatible base releases: ${bundle.compatibleBaseReleaseIds.join(", ")}`,
|
|
2002
|
+
bundle.remote
|
|
2003
|
+
? `Hub receipt: ${bundle.remote.status} · ${bundle.remote.uploadId} · exact revision ${bundle.remote.revision}`
|
|
2004
|
+
: "Hub receipt: none · not submitted",
|
|
2005
|
+
"Owner/account, local path, raw content, prompt, transcript, and credentials are intentionally omitted.",
|
|
2006
|
+
"Public activation/evaluator authority: not claimed.",
|
|
2007
|
+
].join("\n"));
|
|
2008
|
+
return result;
|
|
2009
|
+
}
|
|
1277
2010
|
if (sub === "validate") {
|
|
1278
2011
|
const validation = readBundleFile(flags._[0], options.cwd);
|
|
1279
2012
|
const result = { valid: true, bundleId: validation.bundle.bundleId, bundleHash: validation.bundle.bundleHash, packContentHash: validation.bundle.pack.contentHash, items: validation.bundle.items.length, canonicalBytes: validation.canonicalBytes, networkUsed: false, authority: "local-validation" };
|
|
@@ -1300,24 +2033,11 @@ async function cmdExperienceExchange(options = {}) {
|
|
|
1300
2033
|
idempotencyKey: flags["idempotency-key"] || null,
|
|
1301
2034
|
baseDescriptor: baseDescriptorFromFlags(flags),
|
|
1302
2035
|
});
|
|
1303
|
-
emit(flags.json ? JSON.stringify(result, null, 2) : renderPublish(result));
|
|
2036
|
+
emit(flags.json ? JSON.stringify(publicCommandExchangeResult(result), null, 2) : renderPublish(result));
|
|
1304
2037
|
return result;
|
|
1305
2038
|
}
|
|
1306
2039
|
if (sub === "publish") {
|
|
1307
|
-
// Compatibility: the old pack-only command remains a local-intent alias.
|
|
1308
2040
|
const source = flags._[0];
|
|
1309
|
-
if (source && !BUNDLE_ID_RE.test(source)) {
|
|
1310
|
-
const absolute = path.resolve(options.cwd || process.cwd(), source);
|
|
1311
|
-
try {
|
|
1312
|
-
const parsed = JSON.parse(fs.readFileSync(absolute, "utf8"));
|
|
1313
|
-
if (parsed?.schemaVersion === "agentlas.experience-pack.v1") {
|
|
1314
|
-
if (typeof options.legacyCommand !== "function") throw new Error("legacy handler unavailable");
|
|
1315
|
-
return options.legacyCommand({ ...options, args: ["publish", ...args.slice(1)] });
|
|
1316
|
-
}
|
|
1317
|
-
} catch (error) {
|
|
1318
|
-
if (error?.code !== "ENOENT" && !String(error?.message || "").includes("JSON")) throw error;
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
2041
|
const validation = resolveBundleInput(options.userDataDir, source, options.cwd);
|
|
1322
2042
|
const result = await publishBundle(validation, {
|
|
1323
2043
|
...options,
|
|
@@ -1327,12 +2047,12 @@ async function cmdExperienceExchange(options = {}) {
|
|
|
1327
2047
|
idempotencyKey: flags["idempotency-key"] || null,
|
|
1328
2048
|
baseDescriptor: baseDescriptorFromFlags(flags),
|
|
1329
2049
|
});
|
|
1330
|
-
emit(flags.json ? JSON.stringify(result, null, 2) : renderPublish(result));
|
|
2050
|
+
emit(flags.json ? JSON.stringify(publicCommandExchangeResult(result), null, 2) : renderPublish(result));
|
|
1331
2051
|
return result;
|
|
1332
2052
|
}
|
|
1333
2053
|
if (sub === "status") {
|
|
1334
2054
|
const result = await fetchUploadStatus(flags._[0], options);
|
|
1335
|
-
emit(flags.json ? JSON.stringify(result, null, 2) : `Server-authoritative status: ${result.receipt.status} · ${result.receipt.uploadId}\nrequested visibility: ${result.receipt.requestedVisibility} · Terminal did not assert public activation/evaluator reputation`);
|
|
2055
|
+
emit(flags.json ? JSON.stringify(publicCommandExchangeResult(result), null, 2) : `Server-authoritative status: ${result.receipt.status} · ${result.receipt.uploadId}\nrequested visibility: ${result.receipt.requestedVisibility} · Terminal did not assert public activation/evaluator reputation`);
|
|
1336
2056
|
return result;
|
|
1337
2057
|
}
|
|
1338
2058
|
if (sub === "export") {
|
|
@@ -1345,17 +2065,18 @@ async function cmdExperienceExchange(options = {}) {
|
|
|
1345
2065
|
emit(flags.json ? JSON.stringify(result, null, 2) : `Experience exported: ${result.outputPath}\nbundle hash: ${result.bundleHash}`);
|
|
1346
2066
|
return result;
|
|
1347
2067
|
}
|
|
1348
|
-
if (sub === "withdraw") {
|
|
2068
|
+
if (sub === "withdraw" || sub === "unpublish") {
|
|
2069
|
+
if (!flags._[0] || flags._.length !== 1) throw new Error("usage: agentlas experience unpublish <exact-release-id|bundle-id|upload-id> [--dry-run]");
|
|
1349
2070
|
if (flags["dry-run"] === true) {
|
|
1350
|
-
const result =
|
|
1351
|
-
emit(flags.json ? JSON.stringify(result, null, 2) :
|
|
2071
|
+
const result = previewWithdrawUpload(flags._[0], options);
|
|
2072
|
+
emit(flags.json ? JSON.stringify(result, null, 2) : `DRY RUN · exact upload ${result.uploadId} at ${result.ifMatchRevision}\nnetwork/write used: no · server state unchanged · no new receipt`);
|
|
1352
2073
|
return result;
|
|
1353
2074
|
}
|
|
1354
2075
|
const result = await withdrawUpload(flags._[0], options);
|
|
1355
|
-
emit(flags.json ? JSON.stringify(result, null, 2) : `Server-authoritative
|
|
2076
|
+
emit(flags.json ? JSON.stringify(publicCommandExchangeResult(result), null, 2) : `Server-authoritative unpublication: ${result.receipt.uploadId} · withdrawn\nExisting receipts/history remain; no public activation claim.`);
|
|
1356
2077
|
return result;
|
|
1357
2078
|
}
|
|
1358
|
-
throw new Error("unknown experience subcommand (validate|save|publish|status|export|withdraw; legacy: list|inspect|
|
|
2079
|
+
throw new Error("unknown experience subcommand (list|inspect|validate|save|publish|status|export|unpublish|withdraw; legacy: legacy-list|legacy-inspect|legacy-publish|legacy-unpublish)");
|
|
1359
2080
|
}
|
|
1360
2081
|
|
|
1361
2082
|
module.exports = {
|
|
@@ -1365,6 +2086,12 @@ module.exports = {
|
|
|
1365
2086
|
MAX_STORED_ITEMS,
|
|
1366
2087
|
EXPERIENCE_RETRIEVAL_MAX_ITEMS,
|
|
1367
2088
|
EXPERIENCE_RETRIEVAL_MAX_TOKENS,
|
|
2089
|
+
EXPERIENCE_TAXONOMY_V1,
|
|
2090
|
+
EXPERIENCE_TAXONOMY_CHECKSUM,
|
|
2091
|
+
CANONICAL_TASK_PREFIX,
|
|
2092
|
+
CANONICAL_ENV_PREFIX,
|
|
2093
|
+
CANONICAL_TASK_SLUGS,
|
|
2094
|
+
CANONICAL_TASK_IDS,
|
|
1368
2095
|
ExperienceBundleValidationError,
|
|
1369
2096
|
canonicalJson,
|
|
1370
2097
|
canonicalHash,
|
|
@@ -1384,7 +2111,16 @@ module.exports = {
|
|
|
1384
2111
|
loadExchangeState,
|
|
1385
2112
|
withExchangeStateLock,
|
|
1386
2113
|
saveLocalBundle,
|
|
2114
|
+
commitServerAcceptedBundle,
|
|
1387
2115
|
readStoredBundle,
|
|
2116
|
+
verifyStoredBundleRow,
|
|
2117
|
+
scopedStateRows,
|
|
2118
|
+
resolveScopedStoredRecord,
|
|
2119
|
+
listStoredExperienceBundles,
|
|
2120
|
+
inspectStoredExperienceBundle,
|
|
2121
|
+
previewWithdrawUpload,
|
|
2122
|
+
publicUploadReceipt,
|
|
2123
|
+
publicCommandExchangeResult,
|
|
1388
2124
|
projectScopeHash,
|
|
1389
2125
|
idempotencyKeyForBundle,
|
|
1390
2126
|
idempotencyKeyHash,
|
|
@@ -1394,8 +2130,22 @@ module.exports = {
|
|
|
1394
2130
|
fetchUploadExport,
|
|
1395
2131
|
withdrawUpload,
|
|
1396
2132
|
defaultEnvironmentTags,
|
|
2133
|
+
loadExperienceTaxonomyContract,
|
|
2134
|
+
validateExperienceTaxonomyContract,
|
|
2135
|
+
canonicalSourceTaskId,
|
|
2136
|
+
canonicalTaskId,
|
|
2137
|
+
isCanonicalTaskId,
|
|
2138
|
+
parseEnvironmentConstraint,
|
|
2139
|
+
isCanonicalEnvironmentTag,
|
|
2140
|
+
environmentConstraintsMatch,
|
|
2141
|
+
selectApplicablePortableItems,
|
|
2142
|
+
deriveCanonicalTaskClasses,
|
|
2143
|
+
readExactLocalBaseMarker,
|
|
2144
|
+
exactTaskSignatureInPrompt,
|
|
2145
|
+
resolveRuntimeExperienceForAgent,
|
|
1397
2146
|
estimateTokens,
|
|
1398
2147
|
buildLocalExperienceAdvisory,
|
|
1399
2148
|
augmentRuntimeSystemWithLocalExperience,
|
|
2149
|
+
portableExperienceSafetyIssues,
|
|
1400
2150
|
cmdExperienceExchange,
|
|
1401
2151
|
};
|