agentlas 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/engine/agentlas-capabilities.cjs +34 -3
- package/engine/agentlas-experience-exchange.cjs +1401 -0
- package/engine/agentlas-experience-mcp.cjs +1147 -0
- package/engine/agentlas-repl.cjs +21 -13
- package/engine/agentlas.cjs +281 -50
- package/package.json +1 -1
- package/test/cloud-save-publish.cjs +34 -0
- package/test/engine-hardening-regression.cjs +74 -0
- package/test/experience-exchange-contract.cjs +569 -0
- package/test/experience-mcp-contract.cjs +391 -0
- package/test/fixtures/portable-experience-bundle-v1-golden.json +124 -0
- package/test/route-regression.cjs +244 -8
- package/test/runtime-env-protection.cjs +45 -1
- package/test/smoke.sh +3 -0
- package/test/terminal-ui-regression.cjs +7 -2
package/engine/agentlas.cjs
CHANGED
|
@@ -29,6 +29,8 @@ const fs = require("node:fs");
|
|
|
29
29
|
const { spawn } = require("node:child_process");
|
|
30
30
|
const crypto = require("node:crypto");
|
|
31
31
|
const { compareSemVer, normalizeSemVer, parseSemVer } = require("./semver.cjs");
|
|
32
|
+
const terminalAssets = require("./agentlas-experience-mcp.cjs");
|
|
33
|
+
const terminalExperienceExchange = require("./agentlas-experience-exchange.cjs");
|
|
32
34
|
|
|
33
35
|
// ── 앱과 동일한 userData 경로 (electron app.getPath('userData')와 일치) ──
|
|
34
36
|
function userDataDir() {
|
|
@@ -93,6 +95,19 @@ function openNodeSqliteDb(p) {
|
|
|
93
95
|
run: (...args) => stmt.run(...args),
|
|
94
96
|
};
|
|
95
97
|
},
|
|
98
|
+
// better-sqlite3 API 패리티 — 폴백 경로에서도 db.exec/db.pragma가 있어야 한다.
|
|
99
|
+
// 누락 시 ensureMemoryContextColumn 등 ALTER TABLE(exec)이 TypeError로 조용히 죽어
|
|
100
|
+
// (try/catch 삼킴) context_json 컬럼 마이그레이션이 되지 않고 memory 조회가 깨진다.
|
|
101
|
+
exec: (sql) => db.exec(sql),
|
|
102
|
+
pragma: (source) => {
|
|
103
|
+
const rows = db.prepare(`PRAGMA ${source}`).all();
|
|
104
|
+
// better-sqlite3 pragma()의 단일값 반환 관례를 근사(단일 컬럼·단일 행 → 스칼라).
|
|
105
|
+
if (rows.length === 1) {
|
|
106
|
+
const keys = Object.keys(rows[0]);
|
|
107
|
+
if (keys.length === 1) return rows[0][keys[0]];
|
|
108
|
+
}
|
|
109
|
+
return rows;
|
|
110
|
+
},
|
|
96
111
|
transaction(fn) {
|
|
97
112
|
return (...args) => {
|
|
98
113
|
db.exec("BEGIN");
|
|
@@ -280,7 +295,17 @@ const AGENT_BUILD_TERMS = [
|
|
|
280
295
|
const BUILD_ENTITY_RE = /(에이전트|agent|팀|team|회사|company)/i;
|
|
281
296
|
const BUILD_VERB_RE = /(만들|만든|생성|구축|구성해|꾸려|세팅|패키징|scaffold|build|create|\bmake\b|set\s?up|spin\s?up)/i;
|
|
282
297
|
function isAgentBuildIntent(prompt) {
|
|
283
|
-
|
|
298
|
+
// 경로/파일 참조는 빌드 의도의 증거가 아니다 — "/Users/x/agent-tools/notes.md 요약본
|
|
299
|
+
// 만들어줘"의 디렉터리명이나 "agent-notes.md" 같은 파일명이 BUILD_ENTITY_RE를 때려
|
|
300
|
+
// 메타빌더(score 1000)로 직행하던 우회로 차단. 빌드 의도는 산문에서만 읽는다.
|
|
301
|
+
// ⚠️ 남은 슬래시는 통째로 지우지 않고 공백으로만 벌린다 — "에이전트/팀 만들어줘"의
|
|
302
|
+
// 슬래시-엔티티("에이전트/팀")를 삭제하면 BUILD_ENTITY_RE가 못 맞아 빌드 의도를 놓친다.
|
|
303
|
+
// 진짜 경로는 이미 routeStripPaths(마지막 세그먼트만)+확장자 제거가 처리했다.
|
|
304
|
+
const p = routeNormalize(
|
|
305
|
+
routeStripPaths(prompt)
|
|
306
|
+
.replace(/\S+\.[A-Za-z0-9]{1,6}(?=\s|$)/g, " ")
|
|
307
|
+
.replace(/[\\/]+/g, " "),
|
|
308
|
+
);
|
|
284
309
|
if (!p.trim() || isTrivialRoutePrompt(p)) return false;
|
|
285
310
|
if (AGENT_BUILD_TERMS.some((term) => p.includes(routeNormalize(term)))) return true;
|
|
286
311
|
// 예: "단일 에이전트 하나만 만들어줘", "팀 좀 꾸려줘", "make me an agent"
|
|
@@ -302,7 +327,9 @@ function resolveMetaBuilder(db) {
|
|
|
302
327
|
}
|
|
303
328
|
// "ai"/"llm" 같은 초범용 토큰은 모든 에이전트 프롬프트에 나오므로 판별력이 0이다 —
|
|
304
329
|
// 이런 단어 하나로 전문 에이전트가 선택되던 오라우팅(예: 일반 맥 질문 → Pitch Deck Architect)을 막는다.
|
|
305
|
-
|
|
330
|
+
// "local"/"imported"/"team"은 임포터 보일러플레이트("Imported local team")와 slug 접두/접미에
|
|
331
|
+
// 편재해 판별력이 없다 — 'team' 한 단어가 아무 임포트 팀의 slug 부분문자열(+6 strong)을 때리던 구멍.
|
|
332
|
+
const ROUTE_STOP_WORDS = new Set(["the", "and", "for", "with", "this", "that", "from", "into", "make", "build", "create", "agent", "agents", "team", "please", "ai", "llm", "local", "imported", "인공지능", "에이아이", "좀", "해주세요", "해줘", "만들어", "붙여", "연결", "작업", "요청"]);
|
|
306
333
|
const ROUTE_HINTS = [
|
|
307
334
|
{
|
|
308
335
|
slug: "agentlas-app-builder",
|
|
@@ -360,24 +387,46 @@ const ROUTE_HINTS = [
|
|
|
360
387
|
function routeNormalize(value) {
|
|
361
388
|
return String(value || "").toLowerCase().replace(/[_/]+/g, "-");
|
|
362
389
|
}
|
|
390
|
+
// 경로 디렉터리 성분은 라우팅 의도가 아니다 — 마지막 세그먼트(파일/폴더명)만 남긴다.
|
|
391
|
+
// 사고(2026-07-12): "/Users/mason/Documents/…/Appbridge_Template.이 …" 프롬프트의 경로 토큰
|
|
392
|
+
// ("users","mason","documents","users-mason-documents-")이 임포트 에이전트 system_prompt 속
|
|
393
|
+
// 절대경로와 맞아떨어져 +2씩 쌓이고 라우팅 근거에까지 노출됐다. 프롬프트/헤이스택 양쪽에
|
|
394
|
+
// 대칭 적용해 경로↔경로 우연 일치를 차단한다. 파일/폴더명은 실제 의도라서 보존한다.
|
|
395
|
+
// 규칙: 공백/인용부호/괄호 뒤(또는 문자열 시작)에서 시작하고, "세그먼트+구분자"가 2회 이상
|
|
396
|
+
// 이어지는 절대·홈·드라이브·UNC 경로만 경로로 본다 — "and/or", "서울/부산", 날짜(2026/07/12),
|
|
397
|
+
// "https://…"(콜론 뒤 //는 시작 조건 불충족)는 건드리지 않는다. 세그먼트 안의 단일 공백은
|
|
398
|
+
// 뒤가 대문자로 시작할 때만 허용해 "Mobile Documents"/"Application Support"는 접되,
|
|
399
|
+
// "/tmp/out 기획/디자인 …" 같은 한글 프로즈를 경로로 삼켜버리지 않는다. 상대경로는
|
|
400
|
+
// 확장자 있는 파일 참조("docs/plan/roadmap.md")만 접는다 — 디렉터리명("plan")이 힌트/이름
|
|
401
|
+
// strong 채널을 때리는 것을 막으면서 "서울/부산/대구" 같은 나열은 보존한다.
|
|
402
|
+
const ROUTE_PATH_RE = /(^|[\s"'`(<\[{])((?:~|[A-Za-z]:)?[\\/]{1,2}(?:[^\s\\/]+(?: [A-Z][^\s\\/]*)?[\\/]+){2,}[^\s\\/]*|(?:[^\s\\/]+[\\/]+){2,}[^\s\\/]+\.[A-Za-z0-9]{1,6})/g;
|
|
403
|
+
function routeStripPaths(value) {
|
|
404
|
+
return String(value || "").replace(ROUTE_PATH_RE, (whole, pre, p) => {
|
|
405
|
+
const segs = p.split(/[\\/]+/).filter(Boolean);
|
|
406
|
+
return pre + (segs.length ? segs[segs.length - 1] : "");
|
|
407
|
+
});
|
|
408
|
+
}
|
|
363
409
|
function routeTokenize(value) {
|
|
364
|
-
|
|
410
|
+
// 매치가 영숫자로 끝나도록 강제해 "users-mason-documents-" 같은 후행 하이픈 토큰을 원천 차단.
|
|
411
|
+
const matches = routeNormalize(routeStripPaths(value)).match(/[a-z0-9][a-z0-9-]*[a-z0-9]|[가-힣]{2,}/g) || [];
|
|
365
412
|
const expanded = matches.flatMap((term) => term.split("-").filter(Boolean).concat(term));
|
|
366
413
|
return [...new Set(expanded.filter((term) => term.length >= 2 && !ROUTE_STOP_WORDS.has(term)))];
|
|
367
414
|
}
|
|
368
415
|
// 정체성 존(slug/이름/태그라인) — 여기 적중은 강한 라우팅 신호. system_prompt 본문 적중은 약한 신호.
|
|
416
|
+
// 임포터 보일러플레이트 태그라인("Imported local team/agent")의 세 단어는 전부 스톱워드라
|
|
417
|
+
// 프롬프트 토큰이 될 수 없다 — 별도 필터 불필요.
|
|
369
418
|
function routeIdentityHaystack(agent) {
|
|
370
|
-
return routeNormalize([agent.slug, agent.name, agent.name_en, agent.tagline, agent.tagline_en].join("\n"));
|
|
419
|
+
return routeNormalize(routeStripPaths([agent.slug, agent.name, agent.name_en, agent.tagline, agent.tagline_en].join("\n")));
|
|
371
420
|
}
|
|
372
421
|
function routeHaystack(agent) {
|
|
373
|
-
return routeNormalize([
|
|
422
|
+
return routeNormalize(routeStripPaths([
|
|
374
423
|
agent.slug,
|
|
375
424
|
agent.name,
|
|
376
425
|
agent.name_en,
|
|
377
426
|
agent.tagline,
|
|
378
427
|
agent.tagline_en,
|
|
379
428
|
String(agent.system_prompt || "").slice(0, 3500),
|
|
380
|
-
].join("\n"));
|
|
429
|
+
].join("\n")));
|
|
381
430
|
}
|
|
382
431
|
const APP_BUILDER_EXPLICIT_TERMS = [
|
|
383
432
|
"apps generate", "app builder", "make an app", "build an app", "create an app",
|
|
@@ -420,7 +469,7 @@ function isTrivialRoutePrompt(promptText) {
|
|
|
420
469
|
return words.length <= 3 && TRIVIAL_ROUTE_PROMPTS.has(stripped);
|
|
421
470
|
}
|
|
422
471
|
function isAppBuilderWorthyRoutePrompt(prompt) {
|
|
423
|
-
const promptText = routeNormalize(prompt);
|
|
472
|
+
const promptText = routeNormalize(routeStripPaths(prompt));
|
|
424
473
|
if (!promptText.trim() || isTrivialRoutePrompt(promptText)) return false;
|
|
425
474
|
const explicit = routeMatchedTerms(promptText, APP_BUILDER_EXPLICIT_TERMS);
|
|
426
475
|
if (explicit.length) return true;
|
|
@@ -442,8 +491,10 @@ function routeHint(promptText, agent, lang) {
|
|
|
442
491
|
if (!terms.length) return { score: 0, terms: [], reason: "" };
|
|
443
492
|
return { score: 12 + terms.length * 3, terms, reason: lang === "ko" ? hint.reasonKo : hint.reasonEn };
|
|
444
493
|
}
|
|
445
|
-
function scoreRouteAgent(prompt, promptTerms, agent, lang) {
|
|
446
|
-
|
|
494
|
+
function scoreRouteAgent(prompt, promptTerms, agent, lang, pre) {
|
|
495
|
+
// 대칭 스트리핑 필수: promptText는 이름(+20)·힌트(+12↑) strong 채널의 입력이라, 여기서
|
|
496
|
+
// 경로를 안 벗기면 "/Users/x/project-plan/…"의 디렉터리명이 strong 게이트를 그대로 뚫는다.
|
|
497
|
+
const promptText = routeNormalize(routeStripPaths(prompt));
|
|
447
498
|
if (agent.slug === "agentlas-app-builder" && !isAppBuilderWorthyRoutePrompt(promptText)) {
|
|
448
499
|
return {
|
|
449
500
|
agent,
|
|
@@ -452,24 +503,33 @@ function scoreRouteAgent(prompt, promptTerms, agent, lang) {
|
|
|
452
503
|
? "전용 App을 만들 만큼 반복·상태·편집·자동화가 뚜렷하지 않아 App Builder 라우트를 보류했습니다"
|
|
453
504
|
: "the request does not clearly need a dedicated App with durable workflow, state, editing, or automation",
|
|
454
505
|
terms: [],
|
|
506
|
+
strong: false,
|
|
455
507
|
};
|
|
456
508
|
}
|
|
457
|
-
|
|
458
|
-
const
|
|
509
|
+
// 헤이스택은 설치/임포트 시에만 변하므로 autoRouteAgent가 미리 계산해 넘긴다(중복 계산 제거).
|
|
510
|
+
const identityHay = (pre && pre.identityHay) || routeIdentityHaystack(agent);
|
|
511
|
+
const haystack = (pre && pre.haystack) || routeHaystack(agent);
|
|
459
512
|
let score = 0;
|
|
513
|
+
let strong = false; // 이름 언급/정체성 적중/큐레이션 힌트 — 데스크탑처럼 "이름/힌트급 증거"가 있어야 위임한다
|
|
460
514
|
const terms = [];
|
|
515
|
+
const seenNames = new Set();
|
|
461
516
|
for (const name of [agent.slug, agent.name, agent.name_en].filter(Boolean)) {
|
|
462
517
|
const n = routeNormalize(name);
|
|
463
518
|
// 4자 미만 일반 단어("team","agent" 등)가 프롬프트에 우연히 들어가 +20을 독식하지 않도록 가드.
|
|
464
|
-
|
|
519
|
+
// name === name_en 인 임포트 에이전트(appbridge 등)가 +20을 두 번 받지 않도록 정규화 기준 dedupe.
|
|
520
|
+
if (!n || n.length < 4 || seenNames.has(n)) continue;
|
|
521
|
+
seenNames.add(n);
|
|
522
|
+
if (promptText.includes(n)) {
|
|
465
523
|
score += 20;
|
|
466
524
|
terms.push(name);
|
|
525
|
+
strong = true;
|
|
467
526
|
}
|
|
468
527
|
}
|
|
469
528
|
for (const term of promptTerms) {
|
|
470
529
|
if (identityHay.includes(term)) {
|
|
471
530
|
score += 6; // 이름/태그라인 적중 = 그 에이전트의 정체성 자체를 부른 것
|
|
472
531
|
terms.push(term);
|
|
532
|
+
strong = true;
|
|
473
533
|
} else if (haystack.includes(term)) {
|
|
474
534
|
score += term.length >= 5 ? 3 : 2;
|
|
475
535
|
terms.push(term);
|
|
@@ -477,6 +537,7 @@ function scoreRouteAgent(prompt, promptTerms, agent, lang) {
|
|
|
477
537
|
}
|
|
478
538
|
const hint = routeHint(promptText, agent, lang);
|
|
479
539
|
score += hint.score;
|
|
540
|
+
if (hint.score) strong = true;
|
|
480
541
|
terms.push(...hint.terms);
|
|
481
542
|
const unique = [...new Set(terms)].slice(0, 6);
|
|
482
543
|
const reason = hint.reason || (lang === "ko"
|
|
@@ -486,13 +547,14 @@ function scoreRouteAgent(prompt, promptTerms, agent, lang) {
|
|
|
486
547
|
: unique.length
|
|
487
548
|
? `request terms ${unique.map((term) => `"${term}"`).join(", ")} best match this agent's role/triggers`
|
|
488
549
|
: "no specialist matched clearly, so the default project coordinator is safest");
|
|
489
|
-
return { agent, score, reason, terms: unique };
|
|
490
|
-
}
|
|
491
|
-
// 라우팅 확신
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
-
|
|
550
|
+
return { agent, score, reason, terms: unique, strong };
|
|
551
|
+
}
|
|
552
|
+
// 라우팅 확신 임계값 — 데스크탑 auto-router의 MIN_SPECIALIST_SCORE(10)와 동일 기준.
|
|
553
|
+
// 위임에는 점수뿐 아니라 strong 신호(이름 포함 +20 / 정체성 적중 +6 / 큐레이션 힌트 +12↑)가
|
|
554
|
+
// 반드시 있어야 한다. system_prompt 본문의 약한 단어 적중(+2~3)이 몇 개 쌓여도, strong 신호가
|
|
555
|
+
// 없으면 절대 위임하지 않는다. 미달이면 "직답"(에이전트·능력 라우팅 없음) — 일반 질문이
|
|
556
|
+
// Pitch Deck Architect 같은 무관 페르소나 + gemini 이미지 런타임으로 끌려가던 사고의 근본 수리.
|
|
557
|
+
const MIN_ROUTE_SCORE = 10;
|
|
496
558
|
function directRouteChoice(lang) {
|
|
497
559
|
const resolvedLang = lang || prefsLang();
|
|
498
560
|
return {
|
|
@@ -500,6 +562,7 @@ function directRouteChoice(lang) {
|
|
|
500
562
|
agent: null,
|
|
501
563
|
score: 0,
|
|
502
564
|
terms: [],
|
|
565
|
+
strong: false,
|
|
503
566
|
reason: resolvedLang === "ko"
|
|
504
567
|
? "특정 전문 에이전트가 필요 없는 일반 요청입니다"
|
|
505
568
|
: "this is a general request that needs no specialist agent",
|
|
@@ -521,6 +584,7 @@ function autoRouteAgent(db, prompt, lang) {
|
|
|
521
584
|
return {
|
|
522
585
|
agent: meta,
|
|
523
586
|
score: 1000,
|
|
587
|
+
strong: true,
|
|
524
588
|
reason:
|
|
525
589
|
resolvedLang === "ko"
|
|
526
590
|
? "새 에이전트/팀/회사를 만드는 요청이라 메타에이전트(빌더)로 라우팅했습니다"
|
|
@@ -532,13 +596,19 @@ function autoRouteAgent(db, prompt, lang) {
|
|
|
532
596
|
const agents = listRoutableAgents(db).filter((agent) => !NON_GENERIC_ROUTE_SLUGS.has(agent.slug));
|
|
533
597
|
if (!agents.length) return directRouteChoice(resolvedLang);
|
|
534
598
|
let terms = routeTokenize(prompt);
|
|
599
|
+
// 헤이스택은 한 번만 계산해 IDF와 스코어링 양쪽에서 재사용한다.
|
|
600
|
+
const hays = agents.map((agent) => ({ identityHay: routeIdentityHaystack(agent), haystack: routeHaystack(agent) }));
|
|
535
601
|
// IDF 근사 — 설치 에이전트 절반 이상의 haystack에 나오는 단어("ai","도구" 등)는 판별력이 없어 제외.
|
|
536
602
|
if (agents.length >= 3) {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
603
|
+
terms = terms.filter((term) => hays.filter((h) => h.haystack.includes(term)).length * 2 <= agents.length);
|
|
604
|
+
}
|
|
605
|
+
const ranked = agents
|
|
606
|
+
.map((agent, i) => scoreRouteAgent(prompt, terms, agent, resolvedLang, hays[i]))
|
|
607
|
+
.sort((a, b) => b.score - a.score);
|
|
608
|
+
// 1위가 아니라 "임계값+strong을 모두 만족하는 최고 순위"를 뽑는다 — 장황한 프롬프트의
|
|
609
|
+
// 약한 단어 적중이 점수 1위를 먹어도, 자격 있는 전문 에이전트가 직답으로 밀려나지 않는다.
|
|
610
|
+
const pick = ranked.find((r) => r.score >= MIN_ROUTE_SCORE && r.strong);
|
|
611
|
+
if (pick) return pick;
|
|
542
612
|
return directRouteChoice(resolvedLang);
|
|
543
613
|
}
|
|
544
614
|
function autoRouteNote(choice, lang) {
|
|
@@ -754,9 +824,14 @@ function importLocalFolderCli(db, absPath) {
|
|
|
754
824
|
).run(id, slug, name, name, tagline, tagline, systemPrompt, envReqsJson, now, tone);
|
|
755
825
|
}
|
|
756
826
|
}
|
|
827
|
+
// detectKind 결과를 DB에도 기록 — needsImage의 팀 body-veto 등 능력 판정이
|
|
828
|
+
// 데스크탑이 써준 entity_kind에 무임승차하지 않고 터미널 단독 임포트에서도 성립한다.
|
|
829
|
+
if (columnExists(db, "installed_agents", "entity_kind")) {
|
|
830
|
+
db.prepare("UPDATE installed_agents SET entity_kind=? WHERE id=?").run(kind, id);
|
|
831
|
+
}
|
|
757
832
|
// 라우트 저장
|
|
758
833
|
routes[id] = { agentId: id, path: dir, runtime, labels, kind, importedAt: now };
|
|
759
|
-
|
|
834
|
+
writeJsonPrivateAtomicCli(path.join(userDataDir(), "agent-routes.json"), routes);
|
|
760
835
|
|
|
761
836
|
// 팀이면 회사(firm)로도 등록 → 앱 FIRMS 목록 + `agentlas firm <slug>` 사용 가능. slug 기준 멱등.
|
|
762
837
|
let firm = null;
|
|
@@ -831,6 +906,7 @@ const CLOUD_AGENT_FILES = new Set(["AGENT.md", "AGENTS.md", "CLAUDE.md", "GEMINI
|
|
|
831
906
|
const CLOUD_SKIP_DIRS = new Set([".git", ".next", ".studio-runtime", ".turbo", "build", "coverage", "dist", "node_modules", "out", "release"]);
|
|
832
907
|
const CLOUD_BLOCKED_FILE_RE = [/^\.env(?:\..*)?$/i, /^id_rsa(?:\.pub)?$/i, /^credentials(?:\..*)?$/i, /^secrets?(?:\..*)?$/i, /^cloud-asset-state\.v1\.json$/i, /(?:^|[._-])service-account(?:[._-]|$)/i, /\.(?:key|pem|p12|pfx|mobileprovision)$/i];
|
|
833
908
|
const CLOUD_ROUTING_CARD_PATH = ".agentlas/routing-card.json";
|
|
909
|
+
const CLOUD_LOCAL_EXPERIENCE_LINEAGE_PATH = ".agentlas/experience-relations.jsonl";
|
|
834
910
|
const CLOUD_ROUTING_CARD_CAPABILITY_RE = /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/;
|
|
835
911
|
const CLOUD_ROUTING_CARD_STATUSES = new Set(["draft", "searchable", "candidate", "routing_ready", "trusted"]);
|
|
836
912
|
const CLOUD_SECRET_RE = [
|
|
@@ -1620,6 +1696,12 @@ function scanCloudFolderCli(rootPath) {
|
|
|
1620
1696
|
if (entry.name.startsWith("._")) continue;
|
|
1621
1697
|
const abs = path.join(dir, entry.name);
|
|
1622
1698
|
const rel = path.relative(rootPath, abs).split(path.sep).join("/");
|
|
1699
|
+
if (cloudIsLocalExperienceLineagePath(rel)) {
|
|
1700
|
+
let bytes = 0;
|
|
1701
|
+
try { bytes = Number(fs.lstatSync(abs).size) || 0; } catch { /* excluded local state */ }
|
|
1702
|
+
files.push({ path: rel, bytes, sha256: "", kind: "text", included: false, reason: "experience-lineage-separate-asset" });
|
|
1703
|
+
continue;
|
|
1704
|
+
}
|
|
1623
1705
|
if (cloudPortablePathKey(rel) === cloudPortablePathKey(CLOUD_RESTORE_MARKER_PATH)) {
|
|
1624
1706
|
// Local restore/CAS metadata is runtime state, never portable asset
|
|
1625
1707
|
// data, but it must be captured with the same no-follow stability gate.
|
|
@@ -2364,6 +2446,17 @@ function persistCloudListingCli(db, listing) {
|
|
|
2364
2446
|
throw error;
|
|
2365
2447
|
}
|
|
2366
2448
|
const localPath = restore?.path || null;
|
|
2449
|
+
// entity_kind 기록 — needsImage의 팀 body-veto가 로컬 폴더 임포트(detectKind)뿐 아니라
|
|
2450
|
+
// 클라우드/Hub 소스 설치 팀에도 걸리게 한다. 안 하면 팀 CEO 두뇌의 부서 키워드
|
|
2451
|
+
// ("Design HQ" 등)로 needsImage가 참이 되어 세션 런타임이 통째로 gemini로 하이재킹된다.
|
|
2452
|
+
// Hub가 준 entityKind를 우선하고, 없으면 materialize된 팩 폴더 구조로 판정한다.
|
|
2453
|
+
if (columnExists(db, "installed_agents", "entity_kind")) {
|
|
2454
|
+
let kind = String(listing.entityKind || "").toLowerCase();
|
|
2455
|
+
if (kind !== "team" && kind !== "agent") {
|
|
2456
|
+
kind = localPath && fs.existsSync(localPath) ? detectKind(localPath) : "agent";
|
|
2457
|
+
}
|
|
2458
|
+
db.prepare("UPDATE installed_agents SET entity_kind=? WHERE id=?").run(kind, id);
|
|
2459
|
+
}
|
|
2367
2460
|
return existing
|
|
2368
2461
|
? { ...existing, slug, name: dbExpected.name, ...(localPath ? { localPath } : {}) }
|
|
2369
2462
|
: { id, slug, name: dbExpected.name, ...(localPath ? { localPath } : {}) };
|
|
@@ -2709,6 +2802,13 @@ function printCloudPackageResult(result) {
|
|
|
2709
2802
|
function cloudPackageSnapshot(files) {
|
|
2710
2803
|
return new Map(files.map((file) => [file.path, file]));
|
|
2711
2804
|
}
|
|
2805
|
+
function cloudIsLocalExperienceLineagePath(value) {
|
|
2806
|
+
const normalized = cloudPortablePathKey(String(value || "").replace(/\\/g, "/"));
|
|
2807
|
+
const canonical = cloudPortablePathKey(CLOUD_LOCAL_EXPERIENCE_LINEAGE_PATH);
|
|
2808
|
+
return normalized === canonical
|
|
2809
|
+
|| normalized.startsWith(`${canonical}.`)
|
|
2810
|
+
|| normalized.startsWith(cloudPortablePathKey(".agentlas/.experience-relations.jsonl."));
|
|
2811
|
+
}
|
|
2712
2812
|
function cloudReadPublicCareerCard(snapshot, findings) {
|
|
2713
2813
|
const relativePath = ".agentlas/public-career-card.json";
|
|
2714
2814
|
const file = snapshot.get(relativePath);
|
|
@@ -2901,7 +3001,7 @@ function cloudHashPackage(files, version = CLOUD_PACKAGE_HASH_V1) {
|
|
|
2901
3001
|
// 서버 package-contract.ts와 바이트 동일해야 한다: 경로 코드포인트 순 정렬.
|
|
2902
3002
|
// 정렬 없이 스캔 순서로 해시하면 대소문자 혼합 경로 패키지(AGENTS.md + agents/…)가
|
|
2903
3003
|
// 전부 package_hash_mismatch로 거절된다(2026-07-02 근본 수정).
|
|
2904
|
-
for (const file of [...files].sort(cloudCodePointPathOrder)) {
|
|
3004
|
+
for (const file of [...files].filter((file) => !cloudIsLocalExperienceLineagePath(file.path)).sort(cloudCodePointPathOrder)) {
|
|
2905
3005
|
h.update(file.path);
|
|
2906
3006
|
h.update("\0");
|
|
2907
3007
|
h.update(file.sha256);
|
|
@@ -6986,6 +7086,21 @@ function readJsonSafeCli(filePath, fallback) {
|
|
|
6986
7086
|
function writeJsonSafeCli(filePath, value) {
|
|
6987
7087
|
fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
6988
7088
|
}
|
|
7089
|
+
// 원자적(temp+rename) + 소유자 전용(0600) JSON 쓰기. 세션 ID/경로 등 민감 상태 파일용:
|
|
7090
|
+
// (1) 크래시 중간 쓰기로 JSON이 깨져 routesMap()이 {}를 돌려주며 임포트 매핑을 통째로 잃던 사고,
|
|
7091
|
+
// (2) 기본 umask(0644)로 cli-sessions.json/agent-routes.json이 world-readable이던 정보 노출을 함께 막는다.
|
|
7092
|
+
function writeJsonPrivateAtomicCli(filePath, value) {
|
|
7093
|
+
const dir = path.dirname(filePath);
|
|
7094
|
+
const tmp = path.join(dir, `.${path.basename(filePath)}.${process.pid}.tmp`);
|
|
7095
|
+
fs.writeFileSync(tmp, JSON.stringify(value, null, 2) + "\n", { encoding: "utf8", mode: 0o600 });
|
|
7096
|
+
try {
|
|
7097
|
+
fs.renameSync(tmp, filePath);
|
|
7098
|
+
} catch (e) {
|
|
7099
|
+
try { fs.unlinkSync(tmp); } catch { /* ignore */ }
|
|
7100
|
+
throw e;
|
|
7101
|
+
}
|
|
7102
|
+
try { fs.chmodSync(filePath, 0o600); } catch { /* 일부 FS는 chmod 미지원 — best-effort */ }
|
|
7103
|
+
}
|
|
6989
7104
|
|
|
6990
7105
|
function ontologySourceManifestSkeletonCli(root) {
|
|
6991
7106
|
return {
|
|
@@ -7848,10 +7963,30 @@ async function runApi(backend, model, system, prompt, options) {
|
|
|
7848
7963
|
async function executeOnce(db, system, prompt, override, ctx) {
|
|
7849
7964
|
ctx = ctx || { projectPath: null, agentId: null };
|
|
7850
7965
|
if (!ctx.cwdAtRequest) ctx.cwdAtRequest = projectCwd();
|
|
7966
|
+
let runtimeSystem = system;
|
|
7967
|
+
let localExperienceContext = null;
|
|
7968
|
+
if (ctx.runtimeExperience && ctx.runtimeExperience.disabled !== true) {
|
|
7969
|
+
const runtimeExperience = ctx.runtimeExperience;
|
|
7970
|
+
const augmented = terminalExperienceExchange.augmentRuntimeSystemWithLocalExperience(system, {
|
|
7971
|
+
userDataDir: userDataDir(),
|
|
7972
|
+
cwd: ctx.projectPath || ctx.cwdAtRequest,
|
|
7973
|
+
baseAgentReleaseId: runtimeExperience.baseAgentReleaseId,
|
|
7974
|
+
agentDefinitionId: runtimeExperience.agentDefinitionId,
|
|
7975
|
+
taskSignatures: runtimeExperience.taskSignatures || [],
|
|
7976
|
+
environmentTags: Array.isArray(runtimeExperience.environmentTags) && runtimeExperience.environmentTags.length
|
|
7977
|
+
? runtimeExperience.environmentTags
|
|
7978
|
+
: terminalExperienceExchange.defaultEnvironmentTags(),
|
|
7979
|
+
});
|
|
7980
|
+
runtimeSystem = augmented.systemPrompt;
|
|
7981
|
+
localExperienceContext = augmented.experienceContext;
|
|
7982
|
+
if (localExperienceContext.itemIds.length) {
|
|
7983
|
+
process.stderr.write(`▸ local Experience advisory · ${localExperienceContext.itemIds.length} item(s) · ~${localExperienceContext.estimatedTokens} tokens · no server rental receipt\n`);
|
|
7984
|
+
}
|
|
7985
|
+
}
|
|
7851
7986
|
const rt = resolveRuntime(db, override);
|
|
7852
7987
|
if (rt.mode === "cli") {
|
|
7853
7988
|
// 네이티브 CLI는 자체 세션을 가지므로 emitter는 넣지 않고(노이즈 방지) 메모리 컨텍스트만 주입.
|
|
7854
|
-
const sys = augmentSystem(db,
|
|
7989
|
+
const sys = augmentSystem(db, runtimeSystem, ctx, false);
|
|
7855
7990
|
const cwd = ctx.projectPath || projectCwd();
|
|
7856
7991
|
const permission = ctx.permission || "write";
|
|
7857
7992
|
const env = await buildChildEnvCli(db, { ...ctx, cwd });
|
|
@@ -7885,7 +8020,7 @@ async function executeOnce(db, system, prompt, override, ctx) {
|
|
|
7885
8020
|
return res.error ? 1 : 0;
|
|
7886
8021
|
}
|
|
7887
8022
|
// API 경로 — emitter 동봉 → 답변에서 메모리 이벤트를 파싱·큐레이션하고 블록은 제거.
|
|
7888
|
-
const sys = augmentSystem(db,
|
|
8023
|
+
const sys = augmentSystem(db, runtimeSystem, ctx, true);
|
|
7889
8024
|
const env = await buildChildEnvCli(db, { ...ctx, cwd: ctx.cwd || projectCwd() });
|
|
7890
8025
|
Object.assign(process.env, env);
|
|
7891
8026
|
process.stderr.write(`▸ ${rt.backend}${rt.model ? " · " + rt.model : ""}\n`);
|
|
@@ -8055,13 +8190,28 @@ const PROTECTED_CHILD_ENV_KEYS_CLI = new Set([
|
|
|
8055
8190
|
"AGENTLAS_NATIVE_IDLE_TIMEOUT_MS", "AGENTLAS_NATIVE_TOTAL_TIMEOUT_MS", "AGENTLAS_NATIVE_KILL_GRACE_MS",
|
|
8056
8191
|
"AGENTLAS_CAPTURE_MAX_OUTPUT_BYTES",
|
|
8057
8192
|
]);
|
|
8058
|
-
|
|
8059
|
-
|
|
8193
|
+
// 네트워크 무결성 키 — TLS 검증·프록시·CA·엔드포인트·세션. 프로젝트/에이전트 dotenv(신뢰 불가:
|
|
8194
|
+
// 클론한 레포에 딸려올 수 있음)로 주입되면 MITM/SSRF/세션 하이재킹이 된다. 단, 사용자 본인의
|
|
8195
|
+
// 전역 credentials.env와 호스트 셸 env는 신뢰하므로 그대로 허용한다. 사고 방지: 원샷 API 경로는
|
|
8196
|
+
// buildChildEnvCli 결과를 process.env에 병합(Object.assign)하므로, 프로젝트 .env가 부모 프로세스의
|
|
8197
|
+
// 클라우드 호출(세션 쿠키 동반)까지 오염시킬 수 있었다.
|
|
8198
|
+
const UNTRUSTED_PROTECTED_ENV_KEYS_CLI = new Set([
|
|
8199
|
+
"NODE_TLS_REJECT_UNAUTHORIZED", "NODE_EXTRA_CA_CERTS", "SSL_CERT_FILE", "SSL_CERT_DIR",
|
|
8200
|
+
"OPENSSL_CONF", "REQUESTS_CA_BUNDLE", "CURL_CA_BUNDLE",
|
|
8201
|
+
"HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "NO_PROXY", "GRPC_PROXY", "NPM_CONFIG_PROXY",
|
|
8202
|
+
"AGENTLAS_SESSION", "AGENTLAS_MCP_BASE_URL", "AGENTLAS_WEB_BASE_URL", "AGENTLAS_API_BASE_URL",
|
|
8203
|
+
"AGENTLAS_HUB_BASE_URL", "AGENTLAS_CLOUD_BASE_URL", "OLLAMA_HOST",
|
|
8204
|
+
]);
|
|
8205
|
+
function isProtectedChildEnvKeyCli(key, trusted) {
|
|
8206
|
+
const k = String(key || "").trim().toUpperCase();
|
|
8207
|
+
if (PROTECTED_CHILD_ENV_KEYS_CLI.has(k)) return true; // 호스트 신원/플러그인 루트 — 모든 출처 차단
|
|
8208
|
+
if (!trusted && UNTRUSTED_PROTECTED_ENV_KEYS_CLI.has(k)) return true; // 네트워크 무결성 — 비신뢰 출처만 차단
|
|
8209
|
+
return false;
|
|
8060
8210
|
}
|
|
8061
|
-
function mergeChildEnvValuesCli(target, values, overwrite) {
|
|
8211
|
+
function mergeChildEnvValuesCli(target, values, overwrite, trusted) {
|
|
8062
8212
|
const injected = [];
|
|
8063
8213
|
for (const [key, value] of Object.entries(values || {})) {
|
|
8064
|
-
if (!value || isProtectedChildEnvKeyCli(key)) continue;
|
|
8214
|
+
if (!value || isProtectedChildEnvKeyCli(key, trusted)) continue;
|
|
8065
8215
|
if (!overwrite && target[key]) continue;
|
|
8066
8216
|
target[key] = value;
|
|
8067
8217
|
injected.push(key);
|
|
@@ -8070,19 +8220,20 @@ function mergeChildEnvValuesCli(target, values, overwrite) {
|
|
|
8070
8220
|
}
|
|
8071
8221
|
async function buildChildEnvCli(db, ctx) {
|
|
8072
8222
|
const env = { ...process.env };
|
|
8073
|
-
|
|
8074
|
-
|
|
8223
|
+
// trusted=true: 사용자 본인의 전역 자격/볼트. trusted=false: 프로젝트·에이전트 폴더 dotenv.
|
|
8224
|
+
const apply = (values, overwrite, trusted) => {
|
|
8225
|
+
mergeChildEnvValuesCli(env, values, overwrite, trusted);
|
|
8075
8226
|
};
|
|
8076
8227
|
const globalCredentials = {
|
|
8077
8228
|
...readDotEnvFileCli(path.join(userDataDir(), "credentials.env")),
|
|
8078
8229
|
...readDotEnvFileCli(path.join(os.homedir(), ".agentlas", "credentials.env")),
|
|
8079
8230
|
};
|
|
8080
|
-
apply(globalCredentials, false);
|
|
8081
|
-
if (ctx && ctx.projectPath) apply(projectScopedEnvValuesCli(globalCredentials, ctx.projectPath), true);
|
|
8082
|
-
if (ctx && ctx.cwd) apply(readDotEnvDirCli(ctx.cwd), true);
|
|
8083
|
-
if (ctx && ctx.projectPath) apply(readDotEnvDirCli(ctx.projectPath), true);
|
|
8231
|
+
apply(globalCredentials, false, true);
|
|
8232
|
+
if (ctx && ctx.projectPath) apply(projectScopedEnvValuesCli(globalCredentials, ctx.projectPath), true, true);
|
|
8233
|
+
if (ctx && ctx.cwd) apply(readDotEnvDirCli(ctx.cwd), true, false);
|
|
8234
|
+
if (ctx && ctx.projectPath) apply(readDotEnvDirCli(ctx.projectPath), true, false);
|
|
8084
8235
|
const agentDir = agentEnvDirCli(ctx && ctx.agentId);
|
|
8085
|
-
if (agentDir) apply(readDotEnvDirCli(agentDir), true);
|
|
8236
|
+
if (agentDir) apply(readDotEnvDirCli(agentDir), true, false);
|
|
8086
8237
|
|
|
8087
8238
|
const mm = loadMultimodalCatalog();
|
|
8088
8239
|
const settings = getMultimodalSettingsCli(db);
|
|
@@ -8091,7 +8242,7 @@ async function buildChildEnvCli(db, ctx) {
|
|
|
8091
8242
|
if (req && req.key) keys.add(req.key);
|
|
8092
8243
|
}
|
|
8093
8244
|
const vaultValues = await readVaultEnvValuesCli([...keys].filter((key) => !env[key]), ctx && ctx.projectPath);
|
|
8094
|
-
apply(vaultValues, false);
|
|
8245
|
+
apply(vaultValues, false, true); // 볼트는 사용자 본인 저장소 — 신뢰
|
|
8095
8246
|
env.AGENTLAS_MULTIMODAL_IMAGE_PROVIDER = settings.imageProvider;
|
|
8096
8247
|
env.AGENTLAS_MULTIMODAL_VIDEO_PROVIDER = settings.videoProvider;
|
|
8097
8248
|
env.AGENTLAS_MULTIMODAL_AUDIO_PROVIDER = settings.audioProvider;
|
|
@@ -8211,11 +8362,21 @@ function buildHelpers(db) {
|
|
|
8211
8362
|
try { return JSON.parse(fs.readFileSync(path.join(userDataDir(), "cli-sessions.json"), "utf8")) || []; } catch { return []; }
|
|
8212
8363
|
},
|
|
8213
8364
|
sessionsSave: (list) => {
|
|
8214
|
-
try {
|
|
8365
|
+
try { writeJsonPrivateAtomicCli(path.join(userDataDir(), "cli-sessions.json"), (list || []).slice(0, 30)); } catch { /* ignore */ }
|
|
8215
8366
|
},
|
|
8216
8367
|
// 패리티: REPL의 /storm·/swarm·/build·/route·/research 가 그대로 호출한다.
|
|
8217
8368
|
stormRun: (db_, goal, ctx) => parity().stormRun(db_, goal, ctx),
|
|
8218
8369
|
swarmRun: (db_, goal, ctx) => parity().swarmRun(db_, goal, ctx),
|
|
8370
|
+
terminalBuild: (db_, args, ctx = {}) => terminalAssets.cmdBuild({
|
|
8371
|
+
db: db_,
|
|
8372
|
+
args: Array.isArray(args) ? args : terminalAssets.tokenizeBuildCommandLine(String(args || "")),
|
|
8373
|
+
userDataDir: userDataDir(),
|
|
8374
|
+
cwd: ctx.cwd || projectCwd(),
|
|
8375
|
+
input: ctx.input || process.stdin,
|
|
8376
|
+
promptOutput: ctx.promptOutput || process.stderr,
|
|
8377
|
+
out: ctx.out || out,
|
|
8378
|
+
invokeBuild: (request) => parity().cmdHep(db_, request ? ["hep-build", request] : ["hep-build"]),
|
|
8379
|
+
}),
|
|
8219
8380
|
hepRun: (args, opts) => parity().runHephaestusInteractive(args, opts),
|
|
8220
8381
|
cloudSearch: (db_, args) => parity().cloudSearch(db_, args),
|
|
8221
8382
|
careerGraphCommand: (text, ctx) => runCareerGraphNaturalCli(text, {
|
|
@@ -8564,22 +8725,52 @@ function cmdCd(db, query) {
|
|
|
8564
8725
|
process.stdout.write(folder + "\n");
|
|
8565
8726
|
}
|
|
8566
8727
|
|
|
8567
|
-
|
|
8728
|
+
function parseRunExperienceArgs(args) {
|
|
8729
|
+
const prompt = [];
|
|
8730
|
+
const experience = { taskSignatures: [], environmentTags: [] };
|
|
8731
|
+
let passthrough = false;
|
|
8732
|
+
const addList = (target, value) => {
|
|
8733
|
+
for (const item of String(value || "").split(",").map((entry) => entry.trim()).filter(Boolean)) {
|
|
8734
|
+
if (!target.includes(item)) target.push(item);
|
|
8735
|
+
}
|
|
8736
|
+
};
|
|
8737
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
8738
|
+
const token = String(args[index]);
|
|
8739
|
+
if (passthrough) { prompt.push(token); continue; }
|
|
8740
|
+
if (token === "--") { passthrough = true; continue; }
|
|
8741
|
+
const take = () => index + 1 < args.length ? String(args[++index]) : "";
|
|
8742
|
+
if (token === "--experience-base-release") experience.baseAgentReleaseId = take();
|
|
8743
|
+
else if (token.startsWith("--experience-base-release=")) experience.baseAgentReleaseId = token.slice(26);
|
|
8744
|
+
else if (token === "--experience-agent-definition") experience.agentDefinitionId = take();
|
|
8745
|
+
else if (token.startsWith("--experience-agent-definition=")) experience.agentDefinitionId = token.slice(30);
|
|
8746
|
+
else if (token === "--experience-task-signature") addList(experience.taskSignatures, take());
|
|
8747
|
+
else if (token.startsWith("--experience-task-signature=")) addList(experience.taskSignatures, token.slice(28));
|
|
8748
|
+
else if (token === "--experience-environment") addList(experience.environmentTags, take());
|
|
8749
|
+
else if (token.startsWith("--experience-environment=")) addList(experience.environmentTags, token.slice(25));
|
|
8750
|
+
else if (token === "--no-experience") experience.disabled = true;
|
|
8751
|
+
else prompt.push(token);
|
|
8752
|
+
}
|
|
8753
|
+
return { prompt: prompt.join(" "), experience };
|
|
8754
|
+
}
|
|
8755
|
+
|
|
8756
|
+
async function cmdRun(db, query, prompt, runtimeOverride, runtimeExperience = null) {
|
|
8568
8757
|
const agent = resolveAgent(db, query);
|
|
8569
8758
|
if (!agent) {
|
|
8570
8759
|
const routedPrompt = [query, prompt].filter(Boolean).join(" ").trim() || (await readStdin());
|
|
8571
8760
|
if (!routedPrompt || !routedPrompt.trim()) fail("프롬프트가 비어 있습니다. agentlas run <agent> \"...\" 또는 agentlas run \"...\" 형식으로 입력하세요.");
|
|
8572
|
-
return cmdAutoRun(db, routedPrompt.trim(), runtimeOverride);
|
|
8761
|
+
return cmdAutoRun(db, routedPrompt.trim(), runtimeOverride, runtimeExperience);
|
|
8573
8762
|
}
|
|
8574
8763
|
let userPrompt = prompt;
|
|
8575
8764
|
if (!userPrompt) userPrompt = await readStdin();
|
|
8576
8765
|
if (!userPrompt || !userPrompt.trim()) fail("프롬프트가 비어 있습니다. agentlas run <agent> \"...\" 또는 stdin으로 전달하세요.");
|
|
8577
8766
|
process.stderr.write(`▸ ${agent.name}\n`);
|
|
8578
|
-
const code = await executeOnce(db, agentSystemPromptCli(agent), userPrompt.trim(), runtimeOverride, {
|
|
8767
|
+
const code = await executeOnce(db, agentSystemPromptCli(agent), userPrompt.trim(), runtimeOverride, {
|
|
8768
|
+
projectPath: activeProjectPath(db), agentId: agent.id, permission: PERMISSION, runtimeExperience,
|
|
8769
|
+
});
|
|
8579
8770
|
process.exit(code);
|
|
8580
8771
|
}
|
|
8581
8772
|
|
|
8582
|
-
async function cmdAutoRun(db, prompt, runtimeOverride) {
|
|
8773
|
+
async function cmdAutoRun(db, prompt, runtimeOverride, runtimeExperience = null) {
|
|
8583
8774
|
const lang = prefsLang();
|
|
8584
8775
|
const choice = autoRouteAgent(db, prompt, lang);
|
|
8585
8776
|
if (!choice) fail("자동 라우팅할 에이전트가 없습니다. agentlas list로 설치 상태를 확인하세요.");
|
|
@@ -8592,6 +8783,7 @@ async function cmdAutoRun(db, prompt, runtimeOverride) {
|
|
|
8592
8783
|
projectPath: activeProjectPath(db),
|
|
8593
8784
|
agentId: null,
|
|
8594
8785
|
permission: PERMISSION,
|
|
8786
|
+
runtimeExperience,
|
|
8595
8787
|
});
|
|
8596
8788
|
process.exit(code);
|
|
8597
8789
|
}
|
|
@@ -8602,6 +8794,7 @@ async function cmdAutoRun(db, prompt, runtimeOverride) {
|
|
|
8602
8794
|
projectPath: activeProjectPath(db),
|
|
8603
8795
|
agentId: choice.agent.id,
|
|
8604
8796
|
permission: PERMISSION,
|
|
8797
|
+
runtimeExperience,
|
|
8605
8798
|
});
|
|
8606
8799
|
process.exit(code);
|
|
8607
8800
|
}
|
|
@@ -8662,7 +8855,7 @@ async function cmdFirm(db, query, prompt, runtimeOverride) {
|
|
|
8662
8855
|
slug: firm.slug,
|
|
8663
8856
|
label: firm.name + " CEO",
|
|
8664
8857
|
system: sys,
|
|
8665
|
-
capAgent: { name: firm.name, name_en: firm.name_en || firm.name, tagline: firm.tagline, system_prompt: sys },
|
|
8858
|
+
capAgent: { name: firm.name, name_en: firm.name_en || firm.name, tagline: firm.tagline, tagline_en: firm.tagline_en, entity_kind: "team", system_prompt: sys },
|
|
8666
8859
|
};
|
|
8667
8860
|
return launchTui(db, subject, runtimeOverride);
|
|
8668
8861
|
}
|
|
@@ -9995,6 +10188,9 @@ function cmdHelp() {
|
|
|
9995
10188
|
" connect [<sub>] wire Telegram / platforms to an agent team (hep-connect)",
|
|
9996
10189
|
" import <path> import a local agent/team folder",
|
|
9997
10190
|
" list installed agents/companies + active runtime",
|
|
10191
|
+
" experience <sub> portable Experience: validate|save|publish|status|export|withdraw",
|
|
10192
|
+
" legacy local intents: list|inspect|unpublish|legacy-publish",
|
|
10193
|
+
" variant resolve local variant selection: selected|fallback|base-only|error",
|
|
9998
10194
|
"",
|
|
9999
10195
|
hdr("EXECUTE"),
|
|
10000
10196
|
" storm <goal> force-robust pipeline: route → verify → execute (Stormbreaker) [--research]",
|
|
@@ -10097,8 +10293,10 @@ async function main() {
|
|
|
10097
10293
|
return cmdImport(db, rest[1]);
|
|
10098
10294
|
case "cd":
|
|
10099
10295
|
return cmdCd(db, rest[1]);
|
|
10100
|
-
case "run":
|
|
10101
|
-
|
|
10296
|
+
case "run": {
|
|
10297
|
+
const runInput = parseRunExperienceArgs(rest.slice(2));
|
|
10298
|
+
return cmdRun(db, rest[1], runInput.prompt, runtimeOverride, runInput.experience);
|
|
10299
|
+
}
|
|
10102
10300
|
case "chat":
|
|
10103
10301
|
case "open":
|
|
10104
10302
|
return cmdOpen(db, rest[1], runtimeOverride);
|
|
@@ -10133,8 +10331,37 @@ async function main() {
|
|
|
10133
10331
|
return parity().cmdHep(db, rest.slice(1));
|
|
10134
10332
|
// ── Agentlas OS 정식 표면 (hep-*) 1급 노출 ──
|
|
10135
10333
|
case "build":
|
|
10136
|
-
//
|
|
10137
|
-
|
|
10334
|
+
// Terminal-owned preflight: trusted system-global MCP metadata first, one consent,
|
|
10335
|
+
// then pass only approved catalog IDs/value-free shortages to the existing builder.
|
|
10336
|
+
return terminalAssets.cmdBuild({
|
|
10337
|
+
db,
|
|
10338
|
+
args: rest.slice(1),
|
|
10339
|
+
userDataDir: userDataDir(),
|
|
10340
|
+
cwd: projectCwd(),
|
|
10341
|
+
input: process.stdin,
|
|
10342
|
+
promptOutput: process.stderr,
|
|
10343
|
+
out,
|
|
10344
|
+
invokeBuild: (request) => parity().cmdHep(db, request ? ["hep-build", request] : ["hep-build"]),
|
|
10345
|
+
});
|
|
10346
|
+
case "experience":
|
|
10347
|
+
return terminalExperienceExchange.cmdExperienceExchange({
|
|
10348
|
+
args: rest.slice(1),
|
|
10349
|
+
userDataDir: userDataDir(),
|
|
10350
|
+
cwd: projectCwd(),
|
|
10351
|
+
out,
|
|
10352
|
+
env: process.env,
|
|
10353
|
+
getSessionCookie: cloudSessionCookieCli,
|
|
10354
|
+
fetchHub: (url, init) => fetchHubCli(url, init),
|
|
10355
|
+
legacyCommand: (legacyOptions) => terminalAssets.cmdExperience(legacyOptions),
|
|
10356
|
+
});
|
|
10357
|
+
case "variant":
|
|
10358
|
+
return terminalAssets.cmdVariant({
|
|
10359
|
+
db,
|
|
10360
|
+
args: rest.slice(1),
|
|
10361
|
+
userDataDir: userDataDir(),
|
|
10362
|
+
cwd: projectCwd(),
|
|
10363
|
+
out,
|
|
10364
|
+
});
|
|
10138
10365
|
case "search": // hep-search — 에이전트 디렉터리 발견 (Hub + 로컬)
|
|
10139
10366
|
if (!rest[1]) return fail('usage: agentlas search "<찾는 일>" [--limit 10]');
|
|
10140
10367
|
return parity().cloudSearch(db, rest.slice(1));
|
|
@@ -10219,6 +10446,9 @@ module.exports = {
|
|
|
10219
10446
|
parseDotEnvCli,
|
|
10220
10447
|
isProtectedChildEnvKeyCli,
|
|
10221
10448
|
mergeChildEnvValuesCli,
|
|
10449
|
+
openNodeSqliteDb,
|
|
10450
|
+
ensureMemoryContextColumn,
|
|
10451
|
+
writeJsonPrivateAtomicCli,
|
|
10222
10452
|
resolveCredentialSourcePath,
|
|
10223
10453
|
upsertEnvLine,
|
|
10224
10454
|
fetchHubCli,
|
|
@@ -10252,6 +10482,7 @@ module.exports = {
|
|
|
10252
10482
|
cloudPackageHashVersion,
|
|
10253
10483
|
cloudPortablePathConflict,
|
|
10254
10484
|
cloudPortableExecutableForFile,
|
|
10485
|
+
parseRunExperienceArgs,
|
|
10255
10486
|
DEFAULT_API_MODEL,
|
|
10256
10487
|
ANTHROPIC_COMPAT_API,
|
|
10257
10488
|
// 자동 라우팅 회귀 테스트 표면 — 약한 매치 직답/오라우팅 방지 규칙 검증용.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Agentlas agent terminal — chat with your installed AI agents and teams from the terminal, Claude Code style. Standalone: no desktop app required.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agentlas": "bin/agentlas.cjs"
|