agentlas 0.9.10 → 1.0.2
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 +106 -0
- package/README.md +319 -341
- package/bin/agentlas.cjs +32 -9
- package/engine/agentlas-banner.cjs +24 -3
- package/engine/agentlas-composer.cjs +239 -31
- package/engine/agentlas-config.cjs +103 -7
- package/engine/agentlas-core-harness.cjs +48 -4
- package/engine/agentlas-evolution.cjs +3 -4
- package/engine/agentlas-i18n.cjs +88 -32
- package/engine/agentlas-input.cjs +234 -18
- package/engine/agentlas-memory-governance.cjs +3 -5
- package/engine/agentlas-memory-import.cjs +3 -3
- package/engine/agentlas-native-host.cjs +200 -9
- package/engine/agentlas-onboard.cjs +112 -20
- package/engine/agentlas-permissions.cjs +14 -7
- package/engine/agentlas-sqlite-policy.cjs +34 -0
- package/engine/agentlas-ui.cjs +93 -42
- package/engine/agentlas-workforce.cjs +472 -58
- package/engine/agentlas-workload-routing.cjs +8 -3
- package/engine/agentlas.cjs +140 -12367
- package/engine/agents/files.cjs +61 -0
- package/engine/agents/import-local.cjs +237 -0
- package/engine/agents/registry.cjs +158 -0
- package/engine/agents/router.cjs +565 -0
- package/engine/agents/routes.cjs +43 -0
- package/engine/architecture.data.json +2 -2
- package/engine/automation/daemon.cjs +335 -0
- package/engine/automation/schedule.cjs +181 -0
- package/engine/automation/store.cjs +209 -0
- package/engine/cloud/auth.cjs +279 -0
- package/engine/cloud/hub-client.cjs +239 -0
- package/engine/cloud-assets/cargo.cjs +49 -0
- package/engine/cloud-assets/cas.cjs +235 -0
- package/engine/cloud-assets/commands.cjs +273 -0
- package/engine/cloud-assets/package.cjs +936 -0
- package/engine/cloud-assets/restore.cjs +172 -0
- package/engine/cloud-assets/state.cjs +268 -0
- package/engine/commands/automation.cjs +195 -0
- package/engine/commands/billing.cjs +96 -0
- package/engine/commands/browser.cjs +20 -0
- package/engine/commands/build.cjs +33 -0
- package/engine/commands/call.cjs +24 -0
- package/engine/commands/career-graph.cjs +51 -0
- package/engine/commands/cd.cjs +22 -0
- package/engine/commands/chat.cjs +12 -0
- package/engine/commands/chats.cjs +28 -0
- package/engine/commands/cloud.cjs +17 -0
- package/engine/commands/connect.cjs +20 -0
- package/engine/commands/context.cjs +66 -0
- package/engine/commands/creds.cjs +203 -0
- package/engine/commands/doctor.cjs +68 -0
- package/engine/commands/env.cjs +33 -0
- package/engine/commands/evolve.cjs +23 -0
- package/engine/commands/experience.cjs +34 -0
- package/engine/commands/film.cjs +8 -0
- package/engine/commands/firm.cjs +115 -0
- package/engine/commands/help.cjs +65 -0
- package/engine/commands/hep.cjs +23 -0
- package/engine/commands/import.cjs +35 -0
- package/engine/commands/index.cjs +136 -0
- package/engine/commands/install.cjs +27 -0
- package/engine/commands/journal.cjs +31 -0
- package/engine/commands/legacy-network.cjs +29 -0
- package/engine/commands/list.cjs +49 -0
- package/engine/commands/login.cjs +68 -0
- package/engine/commands/logout.cjs +28 -0
- package/engine/commands/mcp.cjs +91 -0
- package/engine/commands/memory.cjs +21 -0
- package/engine/commands/multimodal.cjs +91 -0
- package/engine/commands/native.cjs +34 -0
- package/engine/commands/netadmin.cjs +31 -0
- package/engine/commands/oberon.cjs +70 -0
- package/engine/commands/ontology.cjs +24 -0
- package/engine/commands/open.cjs +48 -0
- package/engine/commands/plugin.cjs +101 -0
- package/engine/commands/project.cjs +47 -0
- package/engine/commands/research.cjs +36 -0
- package/engine/commands/route.cjs +37 -0
- package/engine/commands/run.cjs +149 -0
- package/engine/commands/search.cjs +55 -0
- package/engine/commands/setup.cjs +45 -0
- package/engine/commands/storm.cjs +75 -0
- package/engine/commands/swarm.cjs +75 -0
- package/engine/commands/telegram.cjs +32 -0
- package/engine/commands/uninstall.cjs +68 -0
- package/engine/commands/update.cjs +54 -0
- package/engine/commands/upload.cjs +18 -0
- package/engine/commands/usage.cjs +35 -0
- package/engine/commands/variant.cjs +25 -0
- package/engine/commands/version.cjs +9 -0
- package/engine/commands/whoami.cjs +38 -0
- package/engine/commands/workforce.cjs +103 -0
- package/engine/core/db.cjs +160 -0
- package/engine/core/paths.cjs +35 -0
- package/engine/experience/build.cjs +181 -0
- package/engine/experience/intents.cjs +492 -0
- package/engine/experience/runtime.cjs +242 -0
- package/engine/experience/variant.cjs +196 -0
- package/engine/firms/orchestrate.cjs +333 -0
- package/engine/hephaestus/runtime.cjs +697 -0
- package/engine/hub/install.cjs +872 -0
- package/engine/hub/plugins.cjs +213 -0
- package/engine/mcp/consent.cjs +289 -0
- package/engine/mcp/contract.cjs +202 -0
- package/engine/mcp/index.cjs +43 -0
- package/engine/mcp/inventory.cjs +322 -0
- package/engine/mcp/plan.cjs +286 -0
- package/engine/mcp/probe.cjs +151 -0
- package/engine/memory-cli/curate.cjs +163 -0
- package/engine/oberon/common.cjs +69 -0
- package/engine/oberon/manifest.cjs +164 -0
- package/engine/oberon/outputs.cjs +70 -0
- package/engine/oberon/render.cjs +164 -0
- package/engine/project/career-graph.cjs +249 -0
- package/engine/project/credentials.cjs +262 -0
- package/engine/project/env-file.cjs +46 -0
- package/engine/project/index.cjs +27 -0
- package/engine/project/memory-context.cjs +453 -0
- package/engine/project/ontology.cjs +467 -0
- package/engine/project/paths.cjs +39 -0
- package/engine/project/seed.cjs +200 -0
- package/engine/project/state.cjs +403 -0
- package/engine/project/super-ontology-seed.json +3288 -0
- package/engine/runtimes/detect.cjs +54 -0
- package/engine/runtimes/overrides.cjs +139 -0
- package/engine/runtimes/resolve.cjs +64 -0
- package/engine/sessions/apply-fences.cjs +188 -0
- package/engine/sessions/fences.cjs +362 -0
- package/engine/sessions/orchestrator.cjs +170 -0
- package/engine/sessions/prompt.cjs +212 -0
- package/engine/sessions/session.cjs +245 -0
- package/engine/sessions/sink.cjs +54 -0
- package/engine/sessions/store.cjs +79 -0
- package/engine/storm/deps.cjs +88 -0
- package/engine/storm/storm.cjs +218 -0
- package/engine/storm/swarm.cjs +422 -0
- package/engine/ui/palette.cjs +105 -0
- package/engine/ui/renderer.cjs +85 -0
- package/engine/ui/repl.cjs +444 -0
- package/engine/workforce/capture.cjs +701 -0
- package/engine/workforce/deps.cjs +472 -0
- package/package.json +2 -6
- package/engine/agentlas-experience-mcp.cjs +0 -1709
- package/engine/agentlas-parity.cjs +0 -1499
- package/engine/agentlas-repl.cjs +0 -1780
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* experience/runtime — 실행 시점 Experience 해석 + 실행 종료 후 운영 경험 적립.
|
|
5
|
+
*
|
|
6
|
+
* v1 모놀리스(engine/agentlas.cjs, legacy-v1-engine-snapshot)에서 이식:
|
|
7
|
+
* parseRunExperienceArgs · exactAgentBaseForExecution ·
|
|
8
|
+
* resolveRuntimeExperienceCli · finalizeExperienceExecutionCli
|
|
9
|
+
* 복원 계약 모듈(agentlas-experience-exchange/-intake, agentlas-desktop-loadout)은
|
|
10
|
+
* 소비만 한다 — 재구현 금지.
|
|
11
|
+
*
|
|
12
|
+
* 불변식(v1 그대로, 완화 금지):
|
|
13
|
+
* - builtin 에이전트/제네릭(no-agent) 턴은 Experience 증거를 만들지 않는다.
|
|
14
|
+
* - exact base 식별이 안 되면 null — 추정 폴백 금지.
|
|
15
|
+
* - 데스크탑 로드아웃 권위와 로컬 해석이 어긋나면 조용히 로컬을 쓰지 않고
|
|
16
|
+
* "desktop-loadout-runtime-resolution-mismatch"로 정직하게 skip한다.
|
|
17
|
+
* - 적립 실패는 stderr 한 줄로 노출하고 null 반환(성공 위장 금지).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const crypto = require("node:crypto");
|
|
21
|
+
const { userDataDir } = require("../core/paths.cjs");
|
|
22
|
+
const { tableExists } = require("../core/db.cjs");
|
|
23
|
+
const { agentFolder } = require("../agents/files.cjs");
|
|
24
|
+
const { routesMap } = require("../agents/routes.cjs");
|
|
25
|
+
const terminalExperienceExchange = require("../agentlas-experience-exchange.cjs");
|
|
26
|
+
const terminalExperienceIntake = require("../agentlas-experience-intake.cjs");
|
|
27
|
+
const desktopOntologyLoadout = require("../agentlas-desktop-loadout.cjs");
|
|
28
|
+
|
|
29
|
+
function sha(value) {
|
|
30
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// v1 prefsLang 대응. v2 prefs 키(language)와 v1 키(lang)를 모두 읽는다 —
|
|
34
|
+
// locale 기본값 결정용이며, 명령이 준 input.lang이 항상 우선한다.
|
|
35
|
+
function prefsLang() {
|
|
36
|
+
try {
|
|
37
|
+
const prefs = require("../agentlas-config.cjs").loadPrefs(userDataDir());
|
|
38
|
+
return prefs.lang || prefs.language || "en";
|
|
39
|
+
} catch {
|
|
40
|
+
return "en";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseRunExperienceArgs(args) {
|
|
45
|
+
const prompt = [];
|
|
46
|
+
const experience = { taskSignatures: [], declaredTaskClasses: [], environmentTags: [], experiencePackReleaseIds: [] };
|
|
47
|
+
let passthrough = false;
|
|
48
|
+
const addList = (target, value) => {
|
|
49
|
+
for (const item of String(value || "").split(",").map((entry) => entry.trim()).filter(Boolean)) {
|
|
50
|
+
if (!target.includes(item)) target.push(item);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
54
|
+
const token = String(args[index]);
|
|
55
|
+
if (passthrough) { prompt.push(token); continue; }
|
|
56
|
+
if (token === "--") { passthrough = true; continue; }
|
|
57
|
+
const take = () => index + 1 < args.length ? String(args[++index]) : "";
|
|
58
|
+
if (token === "--experience-base-release") experience.baseAgentReleaseId = take();
|
|
59
|
+
else if (token.startsWith("--experience-base-release=")) experience.baseAgentReleaseId = token.slice(26);
|
|
60
|
+
else if (token === "--experience-pack-release") addList(experience.experiencePackReleaseIds, take());
|
|
61
|
+
else if (token.startsWith("--experience-pack-release=")) addList(experience.experiencePackReleaseIds, token.slice(26));
|
|
62
|
+
else if (token === "--experience-agent-definition") experience.agentDefinitionId = take();
|
|
63
|
+
else if (token.startsWith("--experience-agent-definition=")) experience.agentDefinitionId = token.slice(30);
|
|
64
|
+
else if (token === "--experience-task-signature") addList(experience.taskSignatures, take());
|
|
65
|
+
else if (token.startsWith("--experience-task-signature=")) addList(experience.taskSignatures, token.slice(28));
|
|
66
|
+
else if (token === "--experience-task-class") addList(experience.declaredTaskClasses, take());
|
|
67
|
+
else if (token.startsWith("--experience-task-class=")) addList(experience.declaredTaskClasses, token.slice(24));
|
|
68
|
+
else if (token === "--experience-environment") addList(experience.environmentTags, take());
|
|
69
|
+
else if (token.startsWith("--experience-environment=")) addList(experience.environmentTags, token.slice(25));
|
|
70
|
+
else if (token === "--experience-desktop-loadout") experience.desktopLoadout = true;
|
|
71
|
+
else if (
|
|
72
|
+
token === "--experience-loadout" || token === "--experience-loadout-file" ||
|
|
73
|
+
token.startsWith("--experience-loadout=") || token.startsWith("--experience-loadout-file=")
|
|
74
|
+
) {
|
|
75
|
+
throw new Error("Custom Experience loadout paths are no longer supported; use --experience-desktop-loadout.");
|
|
76
|
+
}
|
|
77
|
+
else if (token === "--no-experience") experience.disabled = true;
|
|
78
|
+
else prompt.push(token);
|
|
79
|
+
}
|
|
80
|
+
return { prompt: prompt.join(" "), experience };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 실행 대상 에이전트의 exact base(AgentDefinition/AgentRelease) 식별.
|
|
85
|
+
* 우선순위: 명시적 런타임 바인딩 → 설치된 Hub 바인딩 → exact 로컬 packageHash.
|
|
86
|
+
* 어느 것도 증명되지 않으면 null(추정 금지) — Experience 증거 발행이 막힌다.
|
|
87
|
+
*/
|
|
88
|
+
function exactAgentBaseForExecution(db, agent, runtimeExperience = null) {
|
|
89
|
+
if (!agent || agent.builtin) return null;
|
|
90
|
+
const portableId = /^[A-Za-z0-9][A-Za-z0-9._:/@-]{2,255}$/;
|
|
91
|
+
let binding = null;
|
|
92
|
+
try {
|
|
93
|
+
if (tableExists(db, "installed_agent_hub_bindings")) {
|
|
94
|
+
binding = db.prepare(
|
|
95
|
+
"SELECT agent_definition_id,agent_release_id FROM installed_agent_hub_bindings WHERE installed_agent_id=?",
|
|
96
|
+
).get(agent.id) || null;
|
|
97
|
+
}
|
|
98
|
+
} catch { binding = null; }
|
|
99
|
+
const route = routesMap()[agent.id] || {};
|
|
100
|
+
const markerResult = terminalExperienceExchange.readExactLocalBaseMarker(agentFolder(agent), agent.slug);
|
|
101
|
+
const marker = markerResult.marker;
|
|
102
|
+
const rawHash = String(marker?.packageHash || route.packageHash || route.definitionHash || "").replace(/^sha256:/i, "").toLowerCase();
|
|
103
|
+
const packageHash = /^[a-f0-9]{64}$/.test(rawHash) ? `sha256:${rawHash}` : null;
|
|
104
|
+
const explicitDefinition = String(runtimeExperience?.agentDefinitionId || "");
|
|
105
|
+
const explicitRelease = String(runtimeExperience?.baseAgentReleaseId || "");
|
|
106
|
+
if (portableId.test(explicitDefinition) && portableId.test(explicitRelease)) {
|
|
107
|
+
return { agentDefinitionId: explicitDefinition, agentReleaseId: explicitRelease, packageHash, authority: "explicit-runtime-binding" };
|
|
108
|
+
}
|
|
109
|
+
if (binding && portableId.test(String(binding.agent_definition_id)) && portableId.test(String(binding.agent_release_id))) {
|
|
110
|
+
return { agentDefinitionId: binding.agent_definition_id, agentReleaseId: binding.agent_release_id, packageHash, authority: "installed-hub-binding" };
|
|
111
|
+
}
|
|
112
|
+
if (!packageHash) return null;
|
|
113
|
+
// 해시 파생식은 v1과 바이트 단위로 동일해야 한다(로컬 정의/릴리스 id 안정성 계약).
|
|
114
|
+
const definitionDigest = sha(`terminal-local-definition\0${agent.id}\0${agent.slug}`);
|
|
115
|
+
const releaseDigest = sha(`terminal-local-release\0${definitionDigest}\0${packageHash}`);
|
|
116
|
+
return {
|
|
117
|
+
agentDefinitionId: `local-agent-definition:${definitionDigest.slice(0, 32)}`,
|
|
118
|
+
agentReleaseId: `local-agent-release:${releaseDigest.slice(0, 32)}`,
|
|
119
|
+
packageHash,
|
|
120
|
+
authority: "exact-local-package-hash",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resolveRuntimeExperienceCli(agent, prompt, requested, cwd, overrides = {}) {
|
|
125
|
+
const prepared = desktopOntologyLoadout.prepareDesktopLoadoutRequest({
|
|
126
|
+
db: overrides.db,
|
|
127
|
+
agent,
|
|
128
|
+
userDataDir: overrides.userDataDir || userDataDir(),
|
|
129
|
+
requested: requested || {},
|
|
130
|
+
now: overrides.now,
|
|
131
|
+
});
|
|
132
|
+
if (prepared.mode === "skip") {
|
|
133
|
+
return { disabled: true, observableReason: prepared.reason, resolution: "skipped" };
|
|
134
|
+
}
|
|
135
|
+
const resolved = terminalExperienceExchange.resolveRuntimeExperienceForAgent({
|
|
136
|
+
userDataDir: overrides.userDataDir || userDataDir(),
|
|
137
|
+
cwd,
|
|
138
|
+
prompt,
|
|
139
|
+
requested: prepared.requested || requested || {},
|
|
140
|
+
agent,
|
|
141
|
+
agentRoot: agent ? (overrides.agentRoot || agentFolder(agent)) : null,
|
|
142
|
+
...(overrides.platform ? { platform: overrides.platform } : {}),
|
|
143
|
+
...(overrides.arch ? { arch: overrides.arch } : {}),
|
|
144
|
+
...(overrides.runtime ? { runtime: overrides.runtime } : {}),
|
|
145
|
+
});
|
|
146
|
+
if (prepared.mode !== "resolved") return resolved;
|
|
147
|
+
const authority = prepared.authority;
|
|
148
|
+
const tasteRuntime = {
|
|
149
|
+
tasteRuntimeOverlay: authority.tasteRuntimeOverlay || null,
|
|
150
|
+
loadoutAuthority: "desktop-terminal-exact-loadout",
|
|
151
|
+
projectionRevision: authority.projectionRevision,
|
|
152
|
+
loadoutRevision: authority.loadoutRevision,
|
|
153
|
+
};
|
|
154
|
+
if (!authority.experiencePackReleaseId) {
|
|
155
|
+
return {
|
|
156
|
+
disabled: true,
|
|
157
|
+
resolution: "desktop-loadout-taste-only",
|
|
158
|
+
...tasteRuntime,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (resolved.disabled === true) return { ...resolved, ...tasteRuntime };
|
|
162
|
+
if (
|
|
163
|
+
resolved.agentDefinitionId !== authority.agentDefinitionId ||
|
|
164
|
+
resolved.baseAgentReleaseId !== authority.baseAgentReleaseId ||
|
|
165
|
+
!Array.isArray(resolved.experiencePackReleaseIds) ||
|
|
166
|
+
resolved.experiencePackReleaseIds.length !== 1 ||
|
|
167
|
+
resolved.experiencePackReleaseIds[0] !== authority.experiencePackReleaseId
|
|
168
|
+
) {
|
|
169
|
+
return {
|
|
170
|
+
disabled: true,
|
|
171
|
+
observableReason: "desktop-loadout-runtime-resolution-mismatch",
|
|
172
|
+
resolution: "skipped",
|
|
173
|
+
...tasteRuntime,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
...resolved,
|
|
178
|
+
...tasteRuntime,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 실행 종료 후 운영 경험 적립(run-receipt + 후보 번들). 실패는 stderr 한 줄 +
|
|
184
|
+
* null — 적립이 실행 성공/실패 판정을 바꾸지 않는다.
|
|
185
|
+
*/
|
|
186
|
+
function finalizeExperienceExecutionCli(db, input) {
|
|
187
|
+
if (input.permission === "read") return null;
|
|
188
|
+
if (!input.agentId) return null;
|
|
189
|
+
let agent;
|
|
190
|
+
try { agent = db.prepare("SELECT * FROM installed_agents WHERE id=?").get(input.agentId); }
|
|
191
|
+
catch { return null; }
|
|
192
|
+
if (!agent) return null;
|
|
193
|
+
const exactBase = exactAgentBaseForExecution(db, agent, input.runtimeExperience);
|
|
194
|
+
if (!exactBase) return null;
|
|
195
|
+
const runtime = input.runtime || {};
|
|
196
|
+
const provider = runtime.mode === "cli" ? runtime.kind : runtime.backend;
|
|
197
|
+
const modelId = input.model || runtime.model || provider;
|
|
198
|
+
const usage = input.usage || {};
|
|
199
|
+
try {
|
|
200
|
+
return terminalExperienceIntake.finalizeAgentExecution({
|
|
201
|
+
db,
|
|
202
|
+
userDataDir: userDataDir(),
|
|
203
|
+
cwd: input.cwd || input.projectPath || process.cwd(),
|
|
204
|
+
agent,
|
|
205
|
+
exactBase,
|
|
206
|
+
environment: { runtime: provider || "terminal", os: process.platform, arch: process.arch },
|
|
207
|
+
model: { provider: provider || "terminal-runtime", modelId: modelId || "terminal-runtime" },
|
|
208
|
+
mcp: (input.mcpServers || []).flatMap((server) => {
|
|
209
|
+
const catalogId = server.catalog_id || server.catalogId;
|
|
210
|
+
// 검토된 런타임 allowlist는 "승인" 증거일 뿐, 이 턴의 자식 프로세스가
|
|
211
|
+
// MCP initialize/tool call을 완료했다는 증거가 아니다. exact 런타임
|
|
212
|
+
// 신호 없이 connected로 부풀리지 않는다.
|
|
213
|
+
return catalogId ? [{ catalogId, status: "approved" }] : [];
|
|
214
|
+
}),
|
|
215
|
+
outcome: input.outcome,
|
|
216
|
+
metrics: {
|
|
217
|
+
promptTokens: usage.input_tokens || usage.prompt_tokens || 0,
|
|
218
|
+
completionTokens: usage.output_tokens || usage.completion_tokens || 0,
|
|
219
|
+
totalTokens: usage.total_tokens || 0,
|
|
220
|
+
durationMs: input.durationMs || usage.duration_ms || 0,
|
|
221
|
+
retryCount: 0,
|
|
222
|
+
},
|
|
223
|
+
curatedMemories: input.curatedMemories || [],
|
|
224
|
+
taskHint: input.taskHint,
|
|
225
|
+
taskSignatures: input.runtimeExperience?.taskSignatures || [],
|
|
226
|
+
experiencePackReleaseId: input.runtimeExperience?.experiencePackReleaseIds?.[0] || null,
|
|
227
|
+
locale: input.lang || prefsLang(),
|
|
228
|
+
runId: input.runId,
|
|
229
|
+
createdAt: input.createdAt,
|
|
230
|
+
});
|
|
231
|
+
} catch (error) {
|
|
232
|
+
process.stderr.write(`▸ local Experience intake skipped · ${String((error && error.message) || error).slice(0, 180)}\n`);
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = {
|
|
238
|
+
parseRunExperienceArgs,
|
|
239
|
+
exactAgentBaseForExecution,
|
|
240
|
+
resolveRuntimeExperienceCli,
|
|
241
|
+
finalizeExperienceExecutionCli,
|
|
242
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* experience/variant — `agentlas variant resolve` 로컬 자문 해소기.
|
|
5
|
+
*
|
|
6
|
+
* v1 engine/agentlas-experience-mcp.cjs의 cmdVariant 슬라이스 이식.
|
|
7
|
+
* 결정 어휘는 계약이라 절대 변경 금지: selected | fallback | base-only | error.
|
|
8
|
+
*
|
|
9
|
+
* 권위 경계(v1 그대로):
|
|
10
|
+
* - authority는 항상 "local-advisory"다. executionAuthorized/reputationAccepted/
|
|
11
|
+
* serverResolutionReceiptPresent 는 로컬에서 절대 true가 될 수 없다 —
|
|
12
|
+
* Hub 렌탈은 Web 서버 해소 영수증을 요구한다.
|
|
13
|
+
* - required MCP 부족은 해당 variant만 제외한다(에이전트 전체 부족으로
|
|
14
|
+
* 승격 금지, requiredMcpFailureScope: "variant-only").
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const path = require("node:path");
|
|
18
|
+
const { collectSystemMcpInventory } = require("../mcp/inventory.cjs");
|
|
19
|
+
const { indexInventory, resolveMcpRequirement } = require("../mcp/plan.cjs");
|
|
20
|
+
const {
|
|
21
|
+
assertObject,
|
|
22
|
+
assertExactKeys,
|
|
23
|
+
assertId,
|
|
24
|
+
validateMcpRequirement,
|
|
25
|
+
readJsonFile,
|
|
26
|
+
parseSimpleFlags,
|
|
27
|
+
} = require("./intents.cjs");
|
|
28
|
+
|
|
29
|
+
function validateVariantCandidate(candidate, index) {
|
|
30
|
+
const allowed = new Set(["variantId", "baseAgentReleaseId", "experiencePackReleaseId", "status", "compatibilityStatus", "score", "mcpRequirements"]);
|
|
31
|
+
assertExactKeys(candidate, allowed, ["variantId", "baseAgentReleaseId", "experiencePackReleaseId", "status", "compatibilityStatus", "score", "mcpRequirements"], `candidate[${index}]`);
|
|
32
|
+
for (const key of ["variantId", "baseAgentReleaseId", "experiencePackReleaseId"]) assertId(candidate[key], `candidate[${index}].${key}`);
|
|
33
|
+
const requirements = candidate.mcpRequirements == null ? [] : candidate.mcpRequirements;
|
|
34
|
+
if (!Array.isArray(requirements) || requirements.length > 64) throw new Error(`candidate[${index}].mcpRequirements is invalid`);
|
|
35
|
+
requirements.forEach((requirement, requirementIndex) => validateMcpRequirement(requirement, `candidate[${index}].mcpRequirements[${requirementIndex}]`));
|
|
36
|
+
const score = Number(candidate.score);
|
|
37
|
+
if (!Number.isFinite(score) || score < 0 || score > 1_000_000) throw new Error(`candidate[${index}].score is outside the local-preview range`);
|
|
38
|
+
return {
|
|
39
|
+
variantId: candidate.variantId,
|
|
40
|
+
baseAgentReleaseId: candidate.baseAgentReleaseId,
|
|
41
|
+
experiencePackReleaseId: candidate.experiencePackReleaseId,
|
|
42
|
+
status: String(candidate.status || "draft"),
|
|
43
|
+
compatibilityStatus: String(candidate.compatibilityStatus || "unverified"),
|
|
44
|
+
score,
|
|
45
|
+
mcpRequirements: requirements,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveVariantCandidates(options) {
|
|
50
|
+
const candidates = (options.candidates || []).map(validateVariantCandidate);
|
|
51
|
+
const inventoryById = indexInventory(options.inventory || []);
|
|
52
|
+
if (!options.baseAgentReleaseId) {
|
|
53
|
+
return {
|
|
54
|
+
schemaVersion: "agentlas.terminal-variant-resolution.v1",
|
|
55
|
+
authority: "local-advisory",
|
|
56
|
+
executionAuthorized: false,
|
|
57
|
+
reputationAccepted: false,
|
|
58
|
+
serverResolutionReceiptPresent: false,
|
|
59
|
+
decision: "error",
|
|
60
|
+
code: "EXACT_BASE_RELEASE_REQUIRED",
|
|
61
|
+
selectedVariantId: null,
|
|
62
|
+
baseAgentReleaseId: null,
|
|
63
|
+
experiencePackReleaseId: null,
|
|
64
|
+
fallbackOrder: [],
|
|
65
|
+
degradedMcpIds: [],
|
|
66
|
+
excluded: [],
|
|
67
|
+
requiredMcpFailureScope: "variant-only",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const excluded = [];
|
|
71
|
+
const eligible = [];
|
|
72
|
+
for (const candidate of candidates) {
|
|
73
|
+
const reasons = [];
|
|
74
|
+
if (candidate.status !== "active") reasons.push(`variant-status:${candidate.status}`);
|
|
75
|
+
if (candidate.compatibilityStatus !== "verified") reasons.push(`compatibility:${candidate.compatibilityStatus}`);
|
|
76
|
+
if (options.baseAgentReleaseId && candidate.baseAgentReleaseId !== options.baseAgentReleaseId) reasons.push("base-release-mismatch");
|
|
77
|
+
const degradedMcpIds = [];
|
|
78
|
+
for (const requirement of candidate.mcpRequirements) {
|
|
79
|
+
const resolution = resolveMcpRequirement(requirement, inventoryById);
|
|
80
|
+
if (resolution.status !== "available" && requirement.required) reasons.push(`required-mcp-${resolution.status}:${requirement.catalogId}`);
|
|
81
|
+
else if (resolution.status !== "available") degradedMcpIds.push(requirement.catalogId);
|
|
82
|
+
}
|
|
83
|
+
if (reasons.length) excluded.push({ variantId: candidate.variantId, reasons });
|
|
84
|
+
else eligible.push({ ...candidate, degradedMcpIds });
|
|
85
|
+
}
|
|
86
|
+
eligible.sort((a, b) => b.score - a.score || a.variantId.localeCompare(b.variantId));
|
|
87
|
+
const selected = eligible[0] || null;
|
|
88
|
+
const baseRelease = options.baseAgentReleaseId;
|
|
89
|
+
if (selected) {
|
|
90
|
+
const decision = options.preferredVariantId && options.preferredVariantId !== selected.variantId ? "fallback" : "selected";
|
|
91
|
+
return {
|
|
92
|
+
schemaVersion: "agentlas.terminal-variant-resolution.v1",
|
|
93
|
+
authority: "local-advisory",
|
|
94
|
+
executionAuthorized: false,
|
|
95
|
+
reputationAccepted: false,
|
|
96
|
+
serverResolutionReceiptPresent: false,
|
|
97
|
+
decision,
|
|
98
|
+
selectedVariantId: selected.variantId,
|
|
99
|
+
baseAgentReleaseId: selected.baseAgentReleaseId,
|
|
100
|
+
experiencePackReleaseId: selected.experiencePackReleaseId,
|
|
101
|
+
fallbackOrder: eligible.slice(1).map((candidate) => candidate.variantId),
|
|
102
|
+
degradedMcpIds: selected.degradedMcpIds,
|
|
103
|
+
excluded,
|
|
104
|
+
requiredMcpFailureScope: "variant-only",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (options.allowBaseOnly !== false && baseRelease) {
|
|
108
|
+
return {
|
|
109
|
+
schemaVersion: "agentlas.terminal-variant-resolution.v1",
|
|
110
|
+
authority: "local-advisory",
|
|
111
|
+
executionAuthorized: false,
|
|
112
|
+
reputationAccepted: false,
|
|
113
|
+
serverResolutionReceiptPresent: false,
|
|
114
|
+
decision: "base-only",
|
|
115
|
+
selectedVariantId: null,
|
|
116
|
+
baseAgentReleaseId: baseRelease,
|
|
117
|
+
experiencePackReleaseId: null,
|
|
118
|
+
fallbackOrder: [],
|
|
119
|
+
degradedMcpIds: [],
|
|
120
|
+
excluded,
|
|
121
|
+
requiredMcpFailureScope: "variant-only",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
schemaVersion: "agentlas.terminal-variant-resolution.v1",
|
|
126
|
+
authority: "local-advisory",
|
|
127
|
+
executionAuthorized: false,
|
|
128
|
+
reputationAccepted: false,
|
|
129
|
+
serverResolutionReceiptPresent: false,
|
|
130
|
+
decision: "error",
|
|
131
|
+
code: "NO_ELIGIBLE_VARIANT_AND_NO_BASE_FALLBACK",
|
|
132
|
+
selectedVariantId: null,
|
|
133
|
+
baseAgentReleaseId: baseRelease,
|
|
134
|
+
experiencePackReleaseId: null,
|
|
135
|
+
fallbackOrder: [],
|
|
136
|
+
degradedMcpIds: [],
|
|
137
|
+
excluded,
|
|
138
|
+
requiredMcpFailureScope: "variant-only",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function renderVariantResolution(result) {
|
|
143
|
+
const lines = [
|
|
144
|
+
`VARIANT RESOLUTION: ${result.decision}`,
|
|
145
|
+
"Local compatibility preview only; Hub rental requires a Web server resolution receipt.",
|
|
146
|
+
"Candidate score/verified claims are not accepted as reputation, payment, rental, or execution authority.",
|
|
147
|
+
];
|
|
148
|
+
if (result.decision === "selected") lines.push(`selected: ${result.selectedVariantId}`);
|
|
149
|
+
else if (result.decision === "fallback") lines.push(`fallback selected: ${result.selectedVariantId}`);
|
|
150
|
+
else if (result.decision === "base-only") lines.push(`base-only: ${result.baseAgentReleaseId} (no Experience Pack attached)`);
|
|
151
|
+
else lines.push(`error: ${result.code}`);
|
|
152
|
+
if (result.fallbackOrder.length) lines.push(`next fallbacks: ${result.fallbackOrder.join(", ")}`);
|
|
153
|
+
for (const excluded of result.excluded) lines.push(`excluded only ${excluded.variantId}: ${excluded.reasons.join(", ")}`);
|
|
154
|
+
lines.push("Required MCP shortages exclude only the affected variant; they never create an agent-wide shortage.");
|
|
155
|
+
return lines.join("\n");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function cmdVariant(options) {
|
|
159
|
+
const args = options.args || [];
|
|
160
|
+
const sub = args[0] || "resolve";
|
|
161
|
+
if (sub !== "resolve") throw new Error(`unknown variant subcommand: ${sub} (resolve)`);
|
|
162
|
+
const flags = parseSimpleFlags(args.slice(1));
|
|
163
|
+
let candidates = [];
|
|
164
|
+
let baseAgentReleaseId = flags["base-release"] || null;
|
|
165
|
+
const candidateFile = flags.candidates || flags._[0];
|
|
166
|
+
if (candidateFile) {
|
|
167
|
+
const { value } = readJsonFile(path.resolve(options.cwd || process.cwd(), candidateFile), "variant candidates");
|
|
168
|
+
if (Array.isArray(value)) candidates = value;
|
|
169
|
+
else {
|
|
170
|
+
assertObject(value, "variant candidate document");
|
|
171
|
+
if (!Array.isArray(value.candidates)) throw new Error("variant candidate document must contain candidates[]");
|
|
172
|
+
candidates = value.candidates;
|
|
173
|
+
if (!baseAgentReleaseId && value.baseAgentReleaseId) baseAgentReleaseId = value.baseAgentReleaseId;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (baseAgentReleaseId) assertId(baseAgentReleaseId, "--base-release");
|
|
177
|
+
const inventory = collectSystemMcpInventory(options.db, { userDataDir: options.userDataDir, env: options.env || process.env });
|
|
178
|
+
const result = resolveVariantCandidates({
|
|
179
|
+
candidates,
|
|
180
|
+
inventory,
|
|
181
|
+
baseAgentReleaseId,
|
|
182
|
+
preferredVariantId: flags.prefer || null,
|
|
183
|
+
allowBaseOnly: flags["no-base-only"] !== true,
|
|
184
|
+
});
|
|
185
|
+
const emit = options.out || console.log;
|
|
186
|
+
emit(flags.json ? JSON.stringify(result, null, 2) : renderVariantResolution(result));
|
|
187
|
+
if (result.decision === "error") (options.setExitCode || ((code) => { process.exitCode = code; }))(2);
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
validateVariantCandidate,
|
|
193
|
+
resolveVariantCandidates,
|
|
194
|
+
renderVariantResolution,
|
|
195
|
+
cmdVariant,
|
|
196
|
+
};
|