agentlas 1.0.65 → 1.0.67
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 +94 -0
- package/README.md +12 -4
- package/bin/agentlas.cjs +354 -16
- package/engine/acp/server.cjs +34 -3
- package/engine/agentlas-api-agent.cjs +151 -120
- package/engine/agentlas-banner.cjs +6 -2
- package/engine/agentlas-capabilities.cjs +17 -10
- package/engine/agentlas-cloud-runtime.cjs +4 -5
- package/engine/agentlas-config.cjs +19 -54
- package/engine/agentlas-core-harness.cjs +59 -5
- package/engine/agentlas-desktop-loadout.cjs +17 -3
- package/engine/agentlas-evolution.cjs +75 -18
- package/engine/agentlas-experience-exchange.cjs +145 -14
- package/engine/agentlas-experience-intake.cjs +2 -2
- package/engine/agentlas-i18n.cjs +0 -2
- package/engine/agentlas-input.cjs +273 -20
- package/engine/agentlas-judgment.cjs +0 -0
- package/engine/agentlas-mcp-env.cjs +84 -23
- package/engine/agentlas-mcp-wrapper.cjs +36 -4
- package/engine/agentlas-memory-governance.cjs +2 -1
- package/engine/agentlas-memory-import.cjs +49 -12
- package/engine/agentlas-native-host.cjs +279 -44
- package/engine/agentlas-onboard.cjs +23 -1
- package/engine/agentlas-permissions.cjs +5 -1
- package/engine/agentlas-sqlite-policy.cjs +40 -2
- package/engine/agentlas-tasks.cjs +7 -0
- package/engine/agentlas-tools.cjs +37 -10
- package/engine/agentlas-ui.cjs +26 -4
- package/engine/agentlas-workforce.cjs +30 -5
- package/engine/agentlas-workload-routing.cjs +83 -10
- package/engine/agentlas.cjs +21 -4
- package/engine/agents/builder.cjs +35 -10
- package/engine/agents/files.cjs +81 -10
- package/engine/agents/import-local.cjs +10 -2
- package/engine/agents/registry.cjs +10 -3
- package/engine/agents/router.cjs +7 -7
- package/engine/agents/routes.cjs +31 -9
- package/engine/automation/daemon.cjs +154 -40
- package/engine/automation/launchd.cjs +395 -16
- package/engine/automation/schedule.cjs +90 -3
- package/engine/automation/store.cjs +163 -59
- package/engine/browser/cdp.cjs +190 -17
- package/engine/browser/vault.cjs +1 -0
- package/engine/cli-output.cjs +96 -30
- package/engine/cloud/auth.cjs +131 -13
- package/engine/cloud/hub-client.cjs +41 -16
- package/engine/cloud-assets/cas.cjs +18 -9
- package/engine/cloud-assets/commands.cjs +108 -30
- package/engine/cloud-assets/package.cjs +66 -3
- package/engine/cloud-assets/restore.cjs +12 -9
- package/engine/cloud-assets/state.cjs +609 -116
- package/engine/commands/acp.cjs +13 -2
- package/engine/commands/automation.cjs +75 -24
- package/engine/commands/billing.cjs +6 -1
- package/engine/commands/browser.cjs +61 -29
- package/engine/commands/build.cjs +72 -14
- package/engine/commands/call.cjs +1 -1
- package/engine/commands/cd.cjs +1 -1
- package/engine/commands/cloud.cjs +2 -1
- package/engine/commands/connect.cjs +38 -13
- package/engine/commands/creds.cjs +74 -29
- package/engine/commands/doctor.cjs +17 -1
- package/engine/commands/document.cjs +38 -10
- package/engine/commands/env.cjs +6 -1
- package/engine/commands/firm.cjs +42 -12
- package/engine/commands/graph.cjs +36 -32
- package/engine/commands/help.cjs +11 -1
- package/engine/commands/hep.cjs +1 -1
- package/engine/commands/import.cjs +5 -4
- package/engine/commands/index.cjs +1 -1
- package/engine/commands/install.cjs +6 -4
- package/engine/commands/list.cjs +56 -16
- package/engine/commands/login.cjs +5 -0
- package/engine/commands/logout.cjs +6 -1
- package/engine/commands/mcp.cjs +150 -37
- package/engine/commands/memory.cjs +3 -2
- package/engine/commands/multimodal.cjs +12 -0
- package/engine/commands/native.cjs +5 -1
- package/engine/commands/one.cjs +78 -33
- package/engine/commands/plugin.cjs +13 -2
- package/engine/commands/project.cjs +15 -12
- package/engine/commands/research.cjs +18 -7
- package/engine/commands/roles.cjs +104 -28
- package/engine/commands/route.cjs +2 -2
- package/engine/commands/run.cjs +42 -9
- package/engine/commands/search.cjs +23 -2
- package/engine/commands/setup.cjs +6 -1
- package/engine/commands/storm.cjs +44 -6
- package/engine/commands/swarm.cjs +47 -9
- package/engine/commands/telegram.cjs +6 -1
- package/engine/commands/uninstall.cjs +12 -6
- package/engine/commands/update.cjs +5 -0
- package/engine/commands/upload.cjs +2 -1
- package/engine/commands/usage.cjs +6 -1
- package/engine/commands/version.cjs +6 -1
- package/engine/commands/whoami.cjs +6 -1
- package/engine/commands/workforce.cjs +60 -16
- package/engine/core/capability-grants.cjs +7 -2
- package/engine/core/daemon-client.cjs +36 -7
- package/engine/core/db.cjs +10 -2
- package/engine/core/desktop-core-fetch.cjs +477 -77
- package/engine/core/schema-ensure.cjs +23 -11
- package/engine/experience/build.cjs +5 -1
- package/engine/experience/intents.cjs +35 -82
- package/engine/experience/runtime.cjs +7 -1
- package/engine/experience/variant.cjs +58 -4
- package/engine/firms/orchestrate.cjs +9 -6
- package/engine/graph/ask-model.cjs +22 -11
- package/engine/graph/interview.cjs +74 -23
- package/engine/graph/package.cjs +63 -8
- package/engine/hephaestus/local-core.cjs +18 -5
- package/engine/hephaestus/runtime.cjs +48 -43
- package/engine/hub/install.cjs +484 -63
- package/engine/hub/plugins.cjs +391 -29
- package/engine/mcp/consent.cjs +25 -17
- package/engine/mcp/contract.cjs +229 -24
- package/engine/mcp/inventory.cjs +46 -10
- package/engine/mcp/plan.cjs +10 -1
- package/engine/mcp/probe.cjs +24 -9
- package/engine/memory-cli/curate.cjs +74 -16
- package/engine/oberon/common.cjs +37 -12
- package/engine/oberon/manifest.cjs +25 -2
- package/engine/oberon/outputs.cjs +49 -19
- package/engine/oberon/render.cjs +665 -40
- package/engine/project/career-graph.cjs +15 -2
- package/engine/project/controller.cjs +133 -15
- package/engine/project/credentials.cjs +382 -34
- package/engine/project/env-file.cjs +93 -8
- package/engine/project/memory-context.cjs +11 -57
- package/engine/project/ontology.cjs +66 -25
- package/engine/project/paths.cjs +42 -8
- package/engine/project/seed.cjs +88 -51
- package/engine/project/state.cjs +41 -16
- package/engine/project/team.cjs +26 -1
- package/engine/runtime-refusal.cjs +4 -1
- package/engine/runtimes/acp-driver.cjs +56 -9
- package/engine/runtimes/auth-evidence.cjs +21 -2
- package/engine/runtimes/detect.cjs +28 -8
- package/engine/runtimes/kinds.cjs +12 -4
- package/engine/runtimes/overrides.cjs +4 -3
- package/engine/runtimes/resolve.cjs +8 -7
- package/engine/runtimes/roles.cjs +7 -5
- package/engine/sessions/apply-fences.cjs +396 -56
- package/engine/sessions/fences.cjs +111 -27
- package/engine/sessions/memory-turn.cjs +62 -3
- package/engine/sessions/orchestrator.cjs +11 -1
- package/engine/sessions/prompt.cjs +66 -9
- package/engine/sessions/session.cjs +70 -10
- package/engine/sessions/sink.cjs +78 -12
- package/engine/sessions/store.cjs +29 -7
- package/engine/storm/storm.cjs +26 -3
- package/engine/storm/swarm.cjs +31 -3
- package/engine/telegram/connect.cjs +185 -22
- package/engine/ui/commands-catalog.cjs +1 -1
- package/engine/ui/palette.cjs +12 -2
- package/engine/ui/repl.cjs +57 -9
- package/engine/ui/screens.cjs +2 -4
- package/engine/vendor/desktop-core.manifest.json +5 -5
- package/engine/workforce/capture.cjs +9 -27
- package/engine/workforce/deps.cjs +18 -6
- package/engine/workforce/local-core-transport.cjs +68 -5
- package/package.json +1 -1
- package/engine/commands/career-graph.cjs +0 -51
|
@@ -13,23 +13,25 @@
|
|
|
13
13
|
* - 상태 파일은 <userData>/terminal/experience-intents-v1.json — v1과 동일
|
|
14
14
|
* 포맷/경로. 쓰기는 0600 원자적 쓰기(임시파일+rename)만 사용한다.
|
|
15
15
|
*
|
|
16
|
-
* 검증 프리미티브(assert 계열, UNSAFE_TEXT_PATTERNS)는
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* 검증 프리미티브(assert 계열, UNSAFE_TEXT_PATTERNS)는 MCP 계약과 분리해
|
|
17
|
+
* v1 원문을 여기 동결한다(우연한 규칙 완화 전파 방지). 파일 I/O와 lock은
|
|
18
|
+
* engine/mcp/contract.cjs의 공통 hardened primitive를 사용한다.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
const crypto = require("node:crypto");
|
|
22
22
|
const fs = require("node:fs");
|
|
23
23
|
const path = require("node:path");
|
|
24
|
+
const {
|
|
25
|
+
readJsonFile,
|
|
26
|
+
writePrivateJsonAtomic,
|
|
27
|
+
withPrivateStateLock,
|
|
28
|
+
} = require("../mcp/contract.cjs");
|
|
24
29
|
|
|
25
30
|
const ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@-]{2,255}$/;
|
|
26
31
|
const HASH_RE = /^sha256:[0-9a-f]{64}$/;
|
|
27
32
|
const ENV_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
28
33
|
const EXPERIENCE_STATE_SCHEMA = "agentlas.terminal-experience-intents.v1";
|
|
29
34
|
const EXPERIENCE_INTENT_SCHEMA = "agentlas.terminal-experience-intent.v1";
|
|
30
|
-
const MAX_JSON_BYTES = 2 * 1024 * 1024;
|
|
31
|
-
const EXPERIENCE_LOCK_STALE_MS = 30_000;
|
|
32
|
-
const EXPERIENCE_LOCK_WAIT_MS = 2_000;
|
|
33
35
|
|
|
34
36
|
const EXPERIENCE_PACK_REQUIRED = [
|
|
35
37
|
"schemaVersion", "kind", "experiencePackId", "releaseId", "ownerRef", "version",
|
|
@@ -190,6 +192,12 @@ function validateExperiencePack(value) {
|
|
|
190
192
|
assertUniqueIds(value.evidenceReceiptIds, "experience pack.evidenceReceiptIds");
|
|
191
193
|
if (!Array.isArray(value.mcpRequirements) || value.mcpRequirements.length > 64) throw new Error("experience pack.mcpRequirements is invalid");
|
|
192
194
|
value.mcpRequirements.forEach((requirement, index) => validateMcpRequirement(requirement, `experience pack.mcpRequirements[${index}]`));
|
|
195
|
+
const requirementIds = value.mcpRequirements.map((requirement) => requirement.requirementId);
|
|
196
|
+
if (new Set(requirementIds).size !== requirementIds.length) {
|
|
197
|
+
const error = new Error("experience pack.mcpRequirements requirementId values must be unique");
|
|
198
|
+
error.code = "duplicate_mcp_requirement_id";
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
193
201
|
// base 패키지 복사 반입 금지 — 참조만 허용(오너 결정, 완화 불가).
|
|
194
202
|
if (value.containsBasePackageMaterial !== false) throw new Error("experience pack must reference the base release; copied base material is forbidden");
|
|
195
203
|
if (!HASH_RE.test(String(value.contentHash || ""))) throw new Error("experience pack.contentHash is invalid");
|
|
@@ -199,75 +207,15 @@ function validateExperiencePack(value) {
|
|
|
199
207
|
return value;
|
|
200
208
|
}
|
|
201
209
|
|
|
202
|
-
function readJsonFile(filePath, label) {
|
|
203
|
-
const absolute = path.resolve(filePath);
|
|
204
|
-
const stat = fs.lstatSync(absolute);
|
|
205
|
-
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${label} must be a regular file (symlinks are not accepted)`);
|
|
206
|
-
if (stat.size <= 0 || stat.size > MAX_JSON_BYTES) throw new Error(`${label} has an invalid size`);
|
|
207
|
-
let value;
|
|
208
|
-
try { value = JSON.parse(fs.readFileSync(absolute, "utf8")); } catch (error) { throw new Error(`${label} is not valid JSON: ${error.message}`); }
|
|
209
|
-
return { absolute, value };
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function writePrivateJsonAtomic(filePath, value) {
|
|
213
|
-
const dir = path.dirname(filePath);
|
|
214
|
-
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
215
|
-
try { fs.chmodSync(dir, 0o700); } catch { /* best effort */ }
|
|
216
|
-
const temp = path.join(dir, `.${path.basename(filePath)}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`);
|
|
217
|
-
try {
|
|
218
|
-
fs.writeFileSync(temp, JSON.stringify(value, null, 2) + "\n", { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
219
|
-
fs.renameSync(temp, filePath);
|
|
220
|
-
try { fs.chmodSync(filePath, 0o600); } catch { /* best effort */ }
|
|
221
|
-
} finally {
|
|
222
|
-
try { fs.rmSync(temp, { force: true }); } catch { /* noop */ }
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
210
|
function experienceStatePath(userDataDir) {
|
|
227
211
|
return path.join(userDataDir, "terminal", "experience-intents-v1.json");
|
|
228
212
|
}
|
|
229
213
|
|
|
230
|
-
function waitSync(milliseconds) {
|
|
231
|
-
// Atomics.wait은 지원 Node 20+에서 스핀 없이 대기하는 유일한 동기 sleep이다.
|
|
232
|
-
const signal = new Int32Array(new SharedArrayBuffer(4));
|
|
233
|
-
Atomics.wait(signal, 0, 0, milliseconds);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
214
|
function withExperienceStateLock(userDataDir, action) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const lockFile = `${stateFile}.lock`;
|
|
242
|
-
const deadline = Date.now() + EXPERIENCE_LOCK_WAIT_MS;
|
|
243
|
-
let descriptor = null;
|
|
244
|
-
while (descriptor == null) {
|
|
245
|
-
try {
|
|
246
|
-
descriptor = fs.openSync(lockFile, "wx", 0o600);
|
|
247
|
-
fs.writeFileSync(descriptor, `${process.pid}\n${new Date().toISOString()}\n`, "utf8");
|
|
248
|
-
} catch (error) {
|
|
249
|
-
if (!error || error.code !== "EEXIST") throw error;
|
|
250
|
-
try {
|
|
251
|
-
const stat = fs.lstatSync(lockFile);
|
|
252
|
-
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("Terminal experience lock is unsafe");
|
|
253
|
-
if (Date.now() - stat.mtimeMs > EXPERIENCE_LOCK_STALE_MS) {
|
|
254
|
-
fs.unlinkSync(lockFile);
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
} catch (statError) {
|
|
258
|
-
if (statError && statError.code === "ENOENT") continue;
|
|
259
|
-
throw statError;
|
|
260
|
-
}
|
|
261
|
-
if (Date.now() >= deadline) throw new Error("Terminal experience state is busy; retry the command");
|
|
262
|
-
waitSync(25);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
try {
|
|
266
|
-
return action();
|
|
267
|
-
} finally {
|
|
268
|
-
try { fs.closeSync(descriptor); } catch { /* noop */ }
|
|
269
|
-
try { fs.unlinkSync(lockFile); } catch { /* crash recovery handles leftovers */ }
|
|
270
|
-
}
|
|
215
|
+
return withPrivateStateLock(experienceStatePath(userDataDir), {
|
|
216
|
+
unsafe: "Terminal experience lock is unsafe",
|
|
217
|
+
busy: "Terminal experience state is busy; retry the command",
|
|
218
|
+
}, action);
|
|
271
219
|
}
|
|
272
220
|
|
|
273
221
|
function emptyExperienceState() {
|
|
@@ -276,10 +224,12 @@ function emptyExperienceState() {
|
|
|
276
224
|
|
|
277
225
|
function loadExperienceState(userDataDir) {
|
|
278
226
|
const file = experienceStatePath(userDataDir);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
227
|
+
let state;
|
|
228
|
+
try { ({ value: state } = readJsonFile(file, "Terminal experience state")); }
|
|
229
|
+
catch (error) {
|
|
230
|
+
if (error && error.code === "ENOENT") return emptyExperienceState();
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
283
233
|
assertExactKeys(state, new Set(["schemaVersion", "updatedAt", "intents"]), ["schemaVersion", "updatedAt", "intents"], "Terminal experience state");
|
|
284
234
|
if (state.schemaVersion !== EXPERIENCE_STATE_SCHEMA || !Array.isArray(state.intents)) throw new Error("Terminal experience state schema is invalid");
|
|
285
235
|
assertIsoDateOrNull(state.updatedAt, "Terminal experience state.updatedAt");
|
|
@@ -336,16 +286,16 @@ function publicExperienceIntent(intent) {
|
|
|
336
286
|
|
|
337
287
|
function parseSimpleFlags(args) {
|
|
338
288
|
const flags = { _: [] };
|
|
289
|
+
let passthrough = false;
|
|
339
290
|
for (let index = 0; index < args.length; index += 1) {
|
|
340
291
|
const token = String(args[index]);
|
|
341
|
-
if (
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
flags
|
|
292
|
+
if (passthrough) { flags._.push(token); continue; }
|
|
293
|
+
if (token === "--") { passthrough = true; continue; }
|
|
294
|
+
if (token === "--json") {
|
|
295
|
+
if (flags.json === true) throw new Error("duplicate option: --json");
|
|
296
|
+
flags.json = true;
|
|
345
297
|
} else if (token.startsWith("--")) {
|
|
346
|
-
|
|
347
|
-
if (index + 1 < args.length && !String(args[index + 1]).startsWith("--")) flags[key] = String(args[++index]);
|
|
348
|
-
else flags[key] = true;
|
|
298
|
+
throw new Error(`unknown option: ${token}`);
|
|
349
299
|
} else flags._.push(token);
|
|
350
300
|
}
|
|
351
301
|
return flags;
|
|
@@ -428,13 +378,14 @@ function cmdExperience(options) {
|
|
|
428
378
|
const userData = options.userDataDir;
|
|
429
379
|
if (!userData) throw new Error("Terminal userData path is required");
|
|
430
380
|
if (sub === "list" || sub === "ls") {
|
|
381
|
+
if (flags._.length) throw new Error("usage: agentlas experience legacy-list [--json]");
|
|
431
382
|
const list = loadExperienceState(userData).intents.map(publicExperienceIntent);
|
|
432
383
|
emit(flags.json ? JSON.stringify({ localOnly: true, hubPublicationAttempted: false, intents: list }, null, 2) : renderExperienceList(list));
|
|
433
384
|
return list;
|
|
434
385
|
}
|
|
435
386
|
if (sub === "inspect" || sub === "show") {
|
|
436
387
|
const ref = flags._[0];
|
|
437
|
-
if (!ref) throw new Error("usage: agentlas experience inspect <pack-id|release-id|intent-id>");
|
|
388
|
+
if (!ref || flags._.length !== 1) throw new Error("usage: agentlas experience legacy-inspect <pack-id|release-id|intent-id> [--json]");
|
|
438
389
|
const intent = findIntent(loadExperienceState(userData), ref);
|
|
439
390
|
if (!intent) throw new Error(`local Experience Pack intent not found: ${ref}`);
|
|
440
391
|
const projected = publicExperienceIntent(intent);
|
|
@@ -448,6 +399,7 @@ function cmdExperience(options) {
|
|
|
448
399
|
return projected;
|
|
449
400
|
}
|
|
450
401
|
if (sub === "publish") {
|
|
402
|
+
if (flags._.length !== 1) throw new Error("usage: agentlas experience legacy-publish <experience-pack.json> [--json]");
|
|
451
403
|
const intent = publishExperienceIntent(userData, flags._[0], options.cwd);
|
|
452
404
|
emit(flags.json ? JSON.stringify(intent, null, 2) : [
|
|
453
405
|
`Local publish intent saved: ${intent.experiencePackId}@${intent.version}`,
|
|
@@ -457,6 +409,7 @@ function cmdExperience(options) {
|
|
|
457
409
|
return intent;
|
|
458
410
|
}
|
|
459
411
|
if (sub === "unpublish") {
|
|
412
|
+
if (flags._.length !== 1) throw new Error("usage: agentlas experience legacy-unpublish <pack-id|release-id|intent-id> [--json]");
|
|
460
413
|
const intent = unpublishExperienceIntent(userData, flags._[0]);
|
|
461
414
|
emit(flags.json ? JSON.stringify(intent, null, 2) : [
|
|
462
415
|
`Local unpublish intent saved: ${intent.experiencePackId}@${intent.version}`,
|
|
@@ -23,6 +23,7 @@ const { tableExists } = require("../core/db.cjs");
|
|
|
23
23
|
const { agentFolder } = require("../agents/files.cjs");
|
|
24
24
|
const { routesMap } = require("../agents/routes.cjs");
|
|
25
25
|
const terminalExperienceExchange = require("../agentlas-experience-exchange.cjs");
|
|
26
|
+
const permissions = require("../agentlas-permissions.cjs");
|
|
26
27
|
const terminalExperienceIntake = require("../agentlas-experience-intake.cjs");
|
|
27
28
|
const desktopOntologyLoadout = require("../agentlas-desktop-loadout.cjs");
|
|
28
29
|
|
|
@@ -103,9 +104,14 @@ function exactAgentBaseForExecution(db, agent, runtimeExperience = null) {
|
|
|
103
104
|
const packageHash = /^[a-f0-9]{64}$/.test(rawHash) ? `sha256:${rawHash}` : null;
|
|
104
105
|
const explicitDefinition = String(runtimeExperience?.agentDefinitionId || "");
|
|
105
106
|
const explicitRelease = String(runtimeExperience?.baseAgentReleaseId || "");
|
|
107
|
+
const hasExplicitBinding = !!runtimeExperience && (
|
|
108
|
+
Object.prototype.hasOwnProperty.call(runtimeExperience, "agentDefinitionId") ||
|
|
109
|
+
Object.prototype.hasOwnProperty.call(runtimeExperience, "baseAgentReleaseId")
|
|
110
|
+
);
|
|
106
111
|
if (portableId.test(explicitDefinition) && portableId.test(explicitRelease)) {
|
|
107
112
|
return { agentDefinitionId: explicitDefinition, agentReleaseId: explicitRelease, packageHash, authority: "explicit-runtime-binding" };
|
|
108
113
|
}
|
|
114
|
+
if (hasExplicitBinding) return null;
|
|
109
115
|
if (binding && portableId.test(String(binding.agent_definition_id)) && portableId.test(String(binding.agent_release_id))) {
|
|
110
116
|
return { agentDefinitionId: binding.agent_definition_id, agentReleaseId: binding.agent_release_id, packageHash, authority: "installed-hub-binding" };
|
|
111
117
|
}
|
|
@@ -185,7 +191,7 @@ function resolveRuntimeExperienceCli(agent, prompt, requested, cwd, overrides =
|
|
|
185
191
|
* null — 적립이 실행 성공/실패 판정을 바꾸지 않는다.
|
|
186
192
|
*/
|
|
187
193
|
function finalizeExperienceExecutionCli(db, input) {
|
|
188
|
-
if (input.permission === "read") return null;
|
|
194
|
+
if (permissions.normalize(input.permission) === "read") return null;
|
|
189
195
|
if (!input.agentId) return null;
|
|
190
196
|
let agent;
|
|
191
197
|
try { agent = db.prepare("SELECT * FROM installed_agents WHERE id=?").get(input.agentId); }
|
|
@@ -21,11 +21,52 @@ const {
|
|
|
21
21
|
assertObject,
|
|
22
22
|
assertExactKeys,
|
|
23
23
|
assertId,
|
|
24
|
+
assertSafeText,
|
|
24
25
|
validateMcpRequirement,
|
|
25
26
|
readJsonFile,
|
|
26
|
-
parseSimpleFlags,
|
|
27
27
|
} = require("./intents.cjs");
|
|
28
28
|
|
|
29
|
+
const MAX_VARIANT_CANDIDATES = 512;
|
|
30
|
+
|
|
31
|
+
function parseVariantFlags(args) {
|
|
32
|
+
const flags = { _: [] };
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
const valueFlags = new Set(["base-release", "prefer", "candidates"]);
|
|
35
|
+
const booleanFlags = new Set(["json", "no-base-only"]);
|
|
36
|
+
let positionalOnly = false;
|
|
37
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
38
|
+
const token = String(args[index]);
|
|
39
|
+
if (positionalOnly) {
|
|
40
|
+
flags._.push(token);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (token === "--") {
|
|
44
|
+
positionalOnly = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (!token.startsWith("--")) {
|
|
48
|
+
flags._.push(token);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const at = token.indexOf("=");
|
|
52
|
+
const key = token.slice(2, at < 0 ? undefined : at);
|
|
53
|
+
if (!valueFlags.has(key) && !booleanFlags.has(key)) throw new Error(`unknown variant option: --${key}`);
|
|
54
|
+
if (seen.has(key)) throw new Error(`duplicate variant option: --${key}`);
|
|
55
|
+
seen.add(key);
|
|
56
|
+
if (booleanFlags.has(key)) {
|
|
57
|
+
if (at >= 0) throw new Error(`--${key} does not take a value`);
|
|
58
|
+
flags[key] = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const value = at >= 0 ? token.slice(at + 1) : String(args[++index] ?? "");
|
|
62
|
+
if (!value || (at < 0 && value.startsWith("--"))) throw new Error(`--${key} requires a value`);
|
|
63
|
+
flags[key] = value;
|
|
64
|
+
}
|
|
65
|
+
if (flags._.length > 1) throw new Error("variant resolve accepts at most one candidates.json path");
|
|
66
|
+
if (flags.candidates && flags._.length) throw new Error("choose either --candidates or a positional candidates.json path");
|
|
67
|
+
return flags;
|
|
68
|
+
}
|
|
69
|
+
|
|
29
70
|
function validateVariantCandidate(candidate, index) {
|
|
30
71
|
const allowed = new Set(["variantId", "baseAgentReleaseId", "experiencePackReleaseId", "status", "compatibilityStatus", "score", "mcpRequirements"]);
|
|
31
72
|
assertExactKeys(candidate, allowed, ["variantId", "baseAgentReleaseId", "experiencePackReleaseId", "status", "compatibilityStatus", "score", "mcpRequirements"], `candidate[${index}]`);
|
|
@@ -39,15 +80,19 @@ function validateVariantCandidate(candidate, index) {
|
|
|
39
80
|
variantId: candidate.variantId,
|
|
40
81
|
baseAgentReleaseId: candidate.baseAgentReleaseId,
|
|
41
82
|
experiencePackReleaseId: candidate.experiencePackReleaseId,
|
|
42
|
-
status:
|
|
43
|
-
compatibilityStatus:
|
|
83
|
+
status: assertSafeText(candidate.status || "draft", `candidate[${index}].status`, 64),
|
|
84
|
+
compatibilityStatus: assertSafeText(candidate.compatibilityStatus || "unverified", `candidate[${index}].compatibilityStatus`, 64),
|
|
44
85
|
score,
|
|
45
86
|
mcpRequirements: requirements,
|
|
46
87
|
};
|
|
47
88
|
}
|
|
48
89
|
|
|
49
90
|
function resolveVariantCandidates(options) {
|
|
91
|
+
if (!options || !Array.isArray(options.candidates || [])) throw new Error("variant candidates must be an array");
|
|
92
|
+
if ((options.candidates || []).length > MAX_VARIANT_CANDIDATES) throw new Error("too many variant candidates");
|
|
50
93
|
const candidates = (options.candidates || []).map(validateVariantCandidate);
|
|
94
|
+
const candidateIds = candidates.map((candidate) => candidate.variantId);
|
|
95
|
+
if (new Set(candidateIds).size !== candidateIds.length) throw new Error("variantId values must be unique");
|
|
51
96
|
const inventoryById = indexInventory(options.inventory || []);
|
|
52
97
|
if (!options.baseAgentReleaseId) {
|
|
53
98
|
return {
|
|
@@ -67,6 +112,8 @@ function resolveVariantCandidates(options) {
|
|
|
67
112
|
requiredMcpFailureScope: "variant-only",
|
|
68
113
|
};
|
|
69
114
|
}
|
|
115
|
+
assertId(options.baseAgentReleaseId, "baseAgentReleaseId");
|
|
116
|
+
if (options.preferredVariantId) assertId(options.preferredVariantId, "preferredVariantId");
|
|
70
117
|
const excluded = [];
|
|
71
118
|
const eligible = [];
|
|
72
119
|
for (const candidate of candidates) {
|
|
@@ -202,7 +249,7 @@ function cmdVariant(options) {
|
|
|
202
249
|
return null;
|
|
203
250
|
}
|
|
204
251
|
if (sub !== "resolve") throw new Error(`unknown variant subcommand: ${sub} (resolve|help)`);
|
|
205
|
-
const flags =
|
|
252
|
+
const flags = parseVariantFlags(args.slice(1));
|
|
206
253
|
let candidates = [];
|
|
207
254
|
let baseAgentReleaseId = flags["base-release"] || null;
|
|
208
255
|
const candidateFile = flags.candidates || flags._[0];
|
|
@@ -211,6 +258,12 @@ function cmdVariant(options) {
|
|
|
211
258
|
if (Array.isArray(value)) candidates = value;
|
|
212
259
|
else {
|
|
213
260
|
assertObject(value, "variant candidate document");
|
|
261
|
+
assertExactKeys(
|
|
262
|
+
value,
|
|
263
|
+
new Set(["baseAgentReleaseId", "candidates"]),
|
|
264
|
+
["candidates"],
|
|
265
|
+
"variant candidate document",
|
|
266
|
+
);
|
|
214
267
|
if (!Array.isArray(value.candidates)) throw new Error("variant candidate document must contain candidates[]");
|
|
215
268
|
candidates = value.candidates;
|
|
216
269
|
if (!baseAgentReleaseId && value.baseAgentReleaseId) baseAgentReleaseId = value.baseAgentReleaseId;
|
|
@@ -236,5 +289,6 @@ module.exports = {
|
|
|
236
289
|
resolveVariantCandidates,
|
|
237
290
|
renderVariantResolution,
|
|
238
291
|
variantUsage,
|
|
292
|
+
parseVariantFlags,
|
|
239
293
|
cmdVariant,
|
|
240
294
|
};
|
|
@@ -238,7 +238,9 @@ async function parallelCap(items, cap, fn) {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
function isVerificationDivision(node) {
|
|
241
|
-
|
|
241
|
+
// key에는 `<firm-slug>:` 접두사가 들어간다. 회사 이름이 `firm-test`/`qa-studio`인
|
|
242
|
+
// 것만으로 모든 본부를 검증 본부로 오분류하면 구현 슬롯이 하나도 실행되지 않는다.
|
|
243
|
+
const label = `${node && node.role || ""} ${node && node.name || ""}`
|
|
242
244
|
.toLowerCase()
|
|
243
245
|
.replace(/[_-]+/g, " ");
|
|
244
246
|
return /\b(?:eval|qa|quality|test|verification|verifier)\b|policy\s+gate/.test(label);
|
|
@@ -278,9 +280,9 @@ function verificationResultOk(text, sessionOk) {
|
|
|
278
280
|
if (!sessionOk) return false;
|
|
279
281
|
const source = String(text || "").trim();
|
|
280
282
|
const explicit = source.match(/<verification_verdict>\s*(PASS|FAIL)\s*<\/verification_verdict>/i);
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
return
|
|
283
|
+
// 검증 프롬프트가 요구한 machine-readable verdict가 유일한 성공 근거다.
|
|
284
|
+
// 본문 어조로 성공을 추측하면 태그를 빠뜨린 응답이나 명시적 FAIL까지 통과할 수 있다.
|
|
285
|
+
return Boolean(explicit && explicit[1].toUpperCase() === "PASS");
|
|
284
286
|
}
|
|
285
287
|
|
|
286
288
|
function latestResultsAllOk(results) {
|
|
@@ -400,9 +402,10 @@ async function runFirmTurn(p) {
|
|
|
400
402
|
? `${m.brief || task}\n\n[Release-blocking repair stage]\nIndependent verification found the following failures in the current integrated product. Inspect the evidence and current files, repair the actual shipped experience, and rerun the relevant checks before returning. Do not merely describe the fix.\n${stageContext}`
|
|
401
403
|
: (m.brief || task);
|
|
402
404
|
const res = await session.send(prompt);
|
|
403
|
-
|
|
405
|
+
const rawText = turnText(res);
|
|
406
|
+
text = cleanFenceText(rawText);
|
|
404
407
|
ok = stageKind === "verification"
|
|
405
|
-
? verificationResultOk(
|
|
408
|
+
? verificationResultOk(rawText, session.status === "done")
|
|
406
409
|
: session.status === "done";
|
|
407
410
|
if (!ok && !text) text = session.lastError || "no response";
|
|
408
411
|
} catch (e) {
|
|
@@ -61,7 +61,7 @@ function consentedMcpServersFor(ctx) {
|
|
|
61
61
|
try {
|
|
62
62
|
const mcp = require("../mcp/index.cjs");
|
|
63
63
|
if (typeof mcp.readConsentedSystemMcpServers !== "function") return [];
|
|
64
|
-
return mcp.readConsentedSystemMcpServers(ctx.db(), { env: process.env }) || [];
|
|
64
|
+
return mcp.readConsentedSystemMcpServers(ctx.db(), { env: process.env, createRuntimeHome: false }) || [];
|
|
65
65
|
} catch {
|
|
66
66
|
return [];
|
|
67
67
|
}
|
|
@@ -85,7 +85,7 @@ async function askModel(ctx, prompt, opts = {}) {
|
|
|
85
85
|
|
|
86
86
|
// 시도 순서: 사용자가 정한 것 먼저, 그다음 이 컴퓨터에 있는 나머지.
|
|
87
87
|
const candidates = [];
|
|
88
|
-
if (primary && primary.kind && primary.bin) candidates.push(primary);
|
|
88
|
+
if (primary && primary.kind && (primary.bin || primary.kind === "ollama")) candidates.push(primary);
|
|
89
89
|
if (!opts.runtime) {
|
|
90
90
|
// listAvailableCliRuntimes()는 문자열이 아니라 {kind, bin, path} 객체를 돌려준다.
|
|
91
91
|
// 문자열로 읽으면 후보가 하나도 안 쌓여 폴백이 통째로 죽는다(실측).
|
|
@@ -114,12 +114,23 @@ async function askModel(ctx, prompt, opts = {}) {
|
|
|
114
114
|
for (const runtime of candidates) {
|
|
115
115
|
let res;
|
|
116
116
|
try {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
const cwd = opts.cwd || process.cwd();
|
|
118
|
+
const ui = quietSink();
|
|
119
|
+
res = runtime.kind === "ollama"
|
|
120
|
+
? await require("../agentlas-api-agent.cjs").runApiTurn({
|
|
121
|
+
backend: "ollama",
|
|
122
|
+
model: runtime.model || "llama3.1",
|
|
123
|
+
system: "",
|
|
124
|
+
messages: [{ role: "user", content: prompt }],
|
|
125
|
+
ctx: { cwd, permission: "read", env: process.env, db },
|
|
126
|
+
ui,
|
|
127
|
+
})
|
|
128
|
+
: await nativeHost.runNativeTurn({
|
|
129
|
+
kind: runtime.kind,
|
|
130
|
+
bin: runtime.bin,
|
|
131
|
+
ui,
|
|
132
|
+
cwd,
|
|
133
|
+
prompt,
|
|
123
134
|
/*
|
|
124
135
|
* 읽기 권한 — 만드는 동안 바깥을 바꾸지 않는다. 메일이 나가거나 글이 올라가면 안 된다.
|
|
125
136
|
* ★런타임의 "read"는 **쓰기 금지가 아니라 도구 금지에 가깝다**(이 저장소 실측:
|
|
@@ -140,9 +151,9 @@ async function askModel(ctx, prompt, opts = {}) {
|
|
|
140
151
|
* 새로 동의를 받지 않는다 — 이미 받아 둔 것만 그대로 쓴다(consent 영수증 기준).
|
|
141
152
|
* 못 읽으면 빈 배열로 간다: 예전과 같아질 뿐 나빠지지 않는다.
|
|
142
153
|
*/
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
154
|
+
mcpServers: consentedMcpServersFor(ctx),
|
|
155
|
+
mcpAllowlistMode: "exact",
|
|
156
|
+
});
|
|
146
157
|
} catch (err) {
|
|
147
158
|
failures.push(`${runtime.kind}: ${(err && err.message) || err}`);
|
|
148
159
|
continue;
|