agentlas 0.9.9 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/README.md +319 -342
  3. package/bin/agentlas.cjs +32 -9
  4. package/engine/agentlas-banner.cjs +24 -3
  5. package/engine/agentlas-composer.cjs +239 -31
  6. package/engine/agentlas-config.cjs +103 -7
  7. package/engine/agentlas-core-harness.cjs +48 -4
  8. package/engine/agentlas-evolution.cjs +3 -4
  9. package/engine/agentlas-i18n.cjs +88 -32
  10. package/engine/agentlas-input.cjs +234 -18
  11. package/engine/agentlas-memory-governance.cjs +3 -5
  12. package/engine/agentlas-memory-import.cjs +3 -3
  13. package/engine/agentlas-native-host.cjs +200 -9
  14. package/engine/agentlas-onboard.cjs +112 -20
  15. package/engine/agentlas-permissions.cjs +14 -7
  16. package/engine/agentlas-sqlite-policy.cjs +34 -0
  17. package/engine/agentlas-ui.cjs +93 -42
  18. package/engine/agentlas-workforce.cjs +318 -8
  19. package/engine/agentlas-workload-routing.cjs +8 -3
  20. package/engine/agentlas.cjs +140 -12336
  21. package/engine/agents/files.cjs +61 -0
  22. package/engine/agents/import-local.cjs +237 -0
  23. package/engine/agents/registry.cjs +158 -0
  24. package/engine/agents/router.cjs +565 -0
  25. package/engine/agents/routes.cjs +43 -0
  26. package/engine/architecture.data.json +2 -2
  27. package/engine/automation/daemon.cjs +335 -0
  28. package/engine/automation/schedule.cjs +181 -0
  29. package/engine/automation/store.cjs +209 -0
  30. package/engine/cloud/auth.cjs +279 -0
  31. package/engine/cloud/hub-client.cjs +239 -0
  32. package/engine/cloud-assets/cargo.cjs +49 -0
  33. package/engine/cloud-assets/cas.cjs +235 -0
  34. package/engine/cloud-assets/commands.cjs +273 -0
  35. package/engine/cloud-assets/package.cjs +936 -0
  36. package/engine/cloud-assets/restore.cjs +172 -0
  37. package/engine/cloud-assets/state.cjs +268 -0
  38. package/engine/commands/automation.cjs +195 -0
  39. package/engine/commands/billing.cjs +96 -0
  40. package/engine/commands/browser.cjs +20 -0
  41. package/engine/commands/build.cjs +33 -0
  42. package/engine/commands/call.cjs +24 -0
  43. package/engine/commands/career-graph.cjs +51 -0
  44. package/engine/commands/cd.cjs +22 -0
  45. package/engine/commands/chat.cjs +12 -0
  46. package/engine/commands/chats.cjs +28 -0
  47. package/engine/commands/cloud.cjs +17 -0
  48. package/engine/commands/connect.cjs +20 -0
  49. package/engine/commands/context.cjs +66 -0
  50. package/engine/commands/creds.cjs +203 -0
  51. package/engine/commands/doctor.cjs +68 -0
  52. package/engine/commands/env.cjs +33 -0
  53. package/engine/commands/evolve.cjs +23 -0
  54. package/engine/commands/experience.cjs +34 -0
  55. package/engine/commands/film.cjs +8 -0
  56. package/engine/commands/firm.cjs +115 -0
  57. package/engine/commands/help.cjs +65 -0
  58. package/engine/commands/hep.cjs +23 -0
  59. package/engine/commands/import.cjs +35 -0
  60. package/engine/commands/index.cjs +136 -0
  61. package/engine/commands/install.cjs +27 -0
  62. package/engine/commands/journal.cjs +31 -0
  63. package/engine/commands/legacy-network.cjs +29 -0
  64. package/engine/commands/list.cjs +49 -0
  65. package/engine/commands/login.cjs +68 -0
  66. package/engine/commands/logout.cjs +28 -0
  67. package/engine/commands/mcp.cjs +91 -0
  68. package/engine/commands/memory.cjs +21 -0
  69. package/engine/commands/multimodal.cjs +91 -0
  70. package/engine/commands/native.cjs +34 -0
  71. package/engine/commands/netadmin.cjs +31 -0
  72. package/engine/commands/oberon.cjs +70 -0
  73. package/engine/commands/ontology.cjs +24 -0
  74. package/engine/commands/open.cjs +48 -0
  75. package/engine/commands/plugin.cjs +101 -0
  76. package/engine/commands/project.cjs +47 -0
  77. package/engine/commands/research.cjs +36 -0
  78. package/engine/commands/route.cjs +37 -0
  79. package/engine/commands/run.cjs +149 -0
  80. package/engine/commands/search.cjs +55 -0
  81. package/engine/commands/setup.cjs +45 -0
  82. package/engine/commands/storm.cjs +75 -0
  83. package/engine/commands/swarm.cjs +75 -0
  84. package/engine/commands/telegram.cjs +32 -0
  85. package/engine/commands/uninstall.cjs +68 -0
  86. package/engine/commands/update.cjs +54 -0
  87. package/engine/commands/upload.cjs +18 -0
  88. package/engine/commands/usage.cjs +35 -0
  89. package/engine/commands/variant.cjs +25 -0
  90. package/engine/commands/version.cjs +9 -0
  91. package/engine/commands/whoami.cjs +38 -0
  92. package/engine/commands/workforce.cjs +103 -0
  93. package/engine/core/db.cjs +160 -0
  94. package/engine/core/paths.cjs +35 -0
  95. package/engine/experience/build.cjs +181 -0
  96. package/engine/experience/intents.cjs +492 -0
  97. package/engine/experience/runtime.cjs +242 -0
  98. package/engine/experience/variant.cjs +196 -0
  99. package/engine/firms/orchestrate.cjs +333 -0
  100. package/engine/hephaestus/runtime.cjs +697 -0
  101. package/engine/hub/install.cjs +872 -0
  102. package/engine/hub/plugins.cjs +213 -0
  103. package/engine/mcp/consent.cjs +289 -0
  104. package/engine/mcp/contract.cjs +202 -0
  105. package/engine/mcp/index.cjs +43 -0
  106. package/engine/mcp/inventory.cjs +322 -0
  107. package/engine/mcp/plan.cjs +286 -0
  108. package/engine/mcp/probe.cjs +151 -0
  109. package/engine/memory-cli/curate.cjs +163 -0
  110. package/engine/oberon/common.cjs +69 -0
  111. package/engine/oberon/manifest.cjs +164 -0
  112. package/engine/oberon/outputs.cjs +70 -0
  113. package/engine/oberon/render.cjs +164 -0
  114. package/engine/project/career-graph.cjs +249 -0
  115. package/engine/project/credentials.cjs +262 -0
  116. package/engine/project/env-file.cjs +46 -0
  117. package/engine/project/index.cjs +27 -0
  118. package/engine/project/memory-context.cjs +453 -0
  119. package/engine/project/ontology.cjs +467 -0
  120. package/engine/project/paths.cjs +39 -0
  121. package/engine/project/seed.cjs +200 -0
  122. package/engine/project/state.cjs +403 -0
  123. package/engine/project/super-ontology-seed.json +3288 -0
  124. package/engine/runtimes/detect.cjs +54 -0
  125. package/engine/runtimes/overrides.cjs +139 -0
  126. package/engine/runtimes/resolve.cjs +64 -0
  127. package/engine/sessions/apply-fences.cjs +188 -0
  128. package/engine/sessions/fences.cjs +362 -0
  129. package/engine/sessions/orchestrator.cjs +170 -0
  130. package/engine/sessions/prompt.cjs +212 -0
  131. package/engine/sessions/session.cjs +245 -0
  132. package/engine/sessions/sink.cjs +54 -0
  133. package/engine/sessions/store.cjs +79 -0
  134. package/engine/storm/deps.cjs +88 -0
  135. package/engine/storm/storm.cjs +218 -0
  136. package/engine/storm/swarm.cjs +422 -0
  137. package/engine/ui/palette.cjs +105 -0
  138. package/engine/ui/renderer.cjs +85 -0
  139. package/engine/ui/repl.cjs +444 -0
  140. package/engine/workforce/capture.cjs +701 -0
  141. package/engine/workforce/deps.cjs +472 -0
  142. package/package.json +3 -7
  143. package/engine/agentlas-experience-mcp.cjs +0 -1709
  144. package/engine/agentlas-parity.cjs +0 -1499
  145. package/engine/agentlas-repl.cjs +0 -1780
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ /*
3
+ * update — npm 레지스트리에서 새 agentlas 확인.
4
+ *
5
+ * v2 경계: 이 CLI는 자기 자신(npm 패키지)만 업데이트 안내한다. v1이 여기 섞어놓았던
6
+ * 데스크탑 .app 다운로드/서명검증/교체기는 데스크탑 제품 소관으로 반환했다
7
+ * (터미널에서 hdiutil로 앱을 갈아끼우는 코드는 관심사 혼합의 대표 사례였다).
8
+ * 버전 비교는 반드시 compareSemVer — 문자열 비교는 0.9.10 < 0.9.9 오판을 낳는다.
9
+ */
10
+ const { compareSemVer } = require("../semver.cjs");
11
+ const { readVersion } = require("../agentlas-banner.cjs");
12
+
13
+ async function checkNpmLatest({ fetch: fetchImpl } = {}) {
14
+ const f = fetchImpl || globalThis.fetch;
15
+ const currentVersion = readVersion();
16
+ let latestVersion = null;
17
+ try {
18
+ const resp = await f("https://registry.npmjs.org/agentlas/latest", { headers: { accept: "application/json" } });
19
+ if (resp.ok) latestVersion = String((await resp.json()).version || "");
20
+ } catch { /* offline 등 — 호출부에서 안내 */ }
21
+ const comparison = latestVersion ? compareSemVer(currentVersion, latestVersion) : null;
22
+ return {
23
+ currentVersion,
24
+ latestVersion,
25
+ updateAvailable: comparison == null ? null : comparison < 0,
26
+ channel: "npm",
27
+ };
28
+ }
29
+
30
+ async function run(ctx, args) {
31
+ const json = args.includes("--json");
32
+ const status = await checkNpmLatest();
33
+ if (json) {
34
+ ctx.out(JSON.stringify(status, null, 2));
35
+ return 0;
36
+ }
37
+ ctx.out(`Current version: ${status.currentVersion}`);
38
+ if (!status.latestVersion) {
39
+ ctx.out("Could not check the latest version on the npm registry (offline or not published yet).");
40
+ ctx.out("Manual update: npm i -g agentlas@latest");
41
+ return 0;
42
+ }
43
+ ctx.out(`Latest version: ${status.latestVersion}`);
44
+ if (status.updateAvailable == null) {
45
+ ctx.out("Could not compare version formats. Manual update: npm i -g agentlas@latest");
46
+ } else if (status.updateAvailable) {
47
+ ctx.out("Update: npm i -g agentlas@latest");
48
+ } else {
49
+ ctx.out("Already on the latest version.");
50
+ }
51
+ return 0;
52
+ }
53
+
54
+ module.exports = { run, checkNpmLatest };
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ /*
3
+ * upload — `agentlas upload <path> [--visibility marketplace]`.
4
+ * 기본은 owner-private `cloud save`다. 공개 Hub 발행은 오직 명시적
5
+ * `--visibility marketplace` 로만 일어난다 (조용한 공개 승격 금지).
6
+ */
7
+ const { runUpload } = require("../cloud-assets/commands.cjs");
8
+
9
+ async function run(ctx, args) {
10
+ try {
11
+ return await runUpload(ctx, args);
12
+ } catch (e) {
13
+ ctx.err(String((e && e.message) || e));
14
+ return 1;
15
+ }
16
+ }
17
+
18
+ module.exports = { run };
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /* usage — 로컬 사용 현황 요약 (공유 DB 읽기 전용). 공급자 쿼터 대시보드는 데스크탑 소관. */
3
+ const { listAgents } = require("../agents/registry.cjs");
4
+
5
+ function run(ctx) {
6
+ const ko = ctx.lang === "ko";
7
+ const db = ctx.db();
8
+ const day = new Date(Date.now() - 86400000).toISOString();
9
+ const week = new Date(Date.now() - 7 * 86400000).toISOString();
10
+ const q = (sql, ...p) => {
11
+ try { return db.prepare(sql).get(...p) || {}; } catch { return {}; }
12
+ };
13
+ const ar = q("SELECT kind FROM active_runtime WHERE id=1");
14
+ const agents = { n: listAgents(db).length };
15
+ const chats = q("SELECT COUNT(*) AS n FROM chats WHERE archived_at IS NULL");
16
+ const msg24 = q("SELECT COUNT(*) AS n FROM chat_messages WHERE created_at > ?", day);
17
+ const msg7 = q("SELECT COUNT(*) AS n FROM chat_messages WHERE created_at > ?", week);
18
+ const auto = q("SELECT COUNT(*) AS n FROM automations WHERE enabled=1");
19
+ const runs7 = q("SELECT COUNT(*) AS n, SUM(CASE WHEN status='error' OR error IS NOT NULL THEN 1 ELSE 0 END) AS err FROM run_history WHERE ran_at > ?", week);
20
+ ctx.out(ko ? `활성 런타임 ${ar.kind || "(없음)"}` : `Active runtime ${ar.kind || "(none)"}`);
21
+ ctx.out(ko ? `설치 에이전트 ${agents.n ?? "?"}` : `Installed agents ${agents.n ?? "?"}`);
22
+ ctx.out(ko ? `활성 대화 ${chats.n ?? "?"}` : `Active chats ${chats.n ?? "?"}`);
23
+ ctx.out(ko ? `메시지 24시간 ${msg24.n ?? 0} · 7일 ${msg7.n ?? 0}` : `Messages 24h ${msg24.n ?? 0} · 7d ${msg7.n ?? 0}`);
24
+ ctx.out(ko ? `자동화 ${auto.n ?? 0}` : `Automations ${auto.n ?? 0}`);
25
+ ctx.out(ko
26
+ ? `실행(7일) ${runs7.n ?? 0}${runs7.err ? ` (실패 ${runs7.err})` : ""}`
27
+ : `Runs (7d) ${runs7.n ?? 0}${runs7.err ? ` (${runs7.err} failed)` : ""}`);
28
+ ctx.out("");
29
+ ctx.out(ctx.ui.dim(ko
30
+ ? "세션 토큰·비용은 대화에서 /cost로 확인합니다. 공급자 할당량 대시보드는 Desktop에 있습니다."
31
+ : "Session tokens/cost: /cost in chat. Provider quota dashboards are in Desktop."));
32
+ return 0;
33
+ }
34
+
35
+ module.exports = { run };
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ /*
3
+ * variant — 로컬 자문 variant 해소 프리뷰.
4
+ * agentlas variant resolve [candidates.json] [--base-release <id>] [--prefer <id>] [--json]
5
+ * 로직은 engine/experience/variant.cjs (v1 cmdVariant 이식) 가 소유한다.
6
+ * 결정 어휘 selected|fallback|base-only|error 는 계약 — 변경 금지.
7
+ * decision=error 는 v1과 동일하게 exit 2 로 끝난다(0/1과 구분되는 자문 오류).
8
+ */
9
+ const { userDataDir } = require("../core/paths.cjs");
10
+ const { cmdVariant } = require("../experience/variant.cjs");
11
+
12
+ function run(ctx, args) {
13
+ let code = 0;
14
+ cmdVariant({
15
+ db: ctx.db(),
16
+ args,
17
+ userDataDir: userDataDir(),
18
+ cwd: process.cwd(),
19
+ out: ctx.out,
20
+ setExitCode: (value) => { code = value; },
21
+ });
22
+ return code;
23
+ }
24
+
25
+ module.exports = { run };
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ const { readVersion } = require("../agentlas-banner.cjs");
3
+
4
+ function run(ctx) {
5
+ ctx.out(`agentlas ${readVersion()}`);
6
+ return 0;
7
+ }
8
+
9
+ module.exports = { run };
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ /*
3
+ * whoami — Agentlas Cloud 세션 상태 확인.
4
+ * 네트워크 계약: GET <web>/api/auth/session (cookie 헤더). 서명 아웃/만료는 exit 1.
5
+ */
6
+ const auth = require("../cloud/auth.cjs");
7
+
8
+ async function run(ctx) {
9
+ const ko = ctx.lang === "ko";
10
+ const cookie = auth.cloudSessionCookie();
11
+ if (!cookie) {
12
+ ctx.out(ko
13
+ ? "로그아웃 상태입니다. `agentlas login`으로 로그인하세요."
14
+ : "You are signed out. Sign in with agentlas login.");
15
+ return 1;
16
+ }
17
+ let j;
18
+ try {
19
+ j = await auth.fetchSessionMeta(cookie);
20
+ } catch (e) {
21
+ ctx.err((ko ? "세션 확인 실패: " : "Session check failed: ") + String((e && e.message) || e));
22
+ return 1;
23
+ }
24
+ if (j && j.authenticated) {
25
+ const email = (j.user && j.user.email) || "?";
26
+ const ws = j.workspace || {};
27
+ ctx.out(ko
28
+ ? `로그인됨: ${ctx.ui.bold(email)} · 작업 공간: ${ws.name || "?"} (${ws.plan || "free"})`
29
+ : `Signed in: ${ctx.ui.bold(email)} · workspace: ${ws.name || "?"} (${ws.plan || "free"})`);
30
+ return 0;
31
+ }
32
+ ctx.out(ko
33
+ ? "세션이 만료되었거나 유효하지 않습니다. `agentlas login`으로 다시 로그인하세요."
34
+ : "The session is expired or invalid. Sign in again with agentlas login.");
35
+ return 1;
36
+ }
37
+
38
+ module.exports = { run };
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ /*
3
+ * commands/workforce — `agentlas workforce|network|taskforce <request>`.
4
+ *
5
+ * v1 모놀리스 디스패치(legacy-v1-engine-snapshot, engine/agentlas.cjs 13149행)의 포팅:
6
+ * case workforce/network/taskforce → workforce().cmdWorkforce(db, rest,
7
+ * runtimeOverride, { cwd, projectPath, permission })
8
+ *
9
+ * 라우팅 불변식: 이 표면은 Agent Workforce Ontology 전용 fail-closed 경로다.
10
+ * 어휘 라우터/hep-network 로 조용히 폴백하지 않는다. legacy-network 는 v1에서
11
+ * parity().cmdHep(["hep-network", ...]) 호환 탈출구였는데, 그 parity 모듈이 아직
12
+ * v2에 없으므로 정직 정지로 안내만 한다(가짜 성공 금지).
13
+ *
14
+ * projectPath 에 대한 정직한 메모: v1은 ensureTerminalProjectForExecutionCli(db,
15
+ * cwd, permission, "terminal-workforce")로 프로젝트 상태 머신을 돌려 활성 프로젝트
16
+ * 경로를 만들었다. 그 프로젝트-상태 기계(폴더 방문 기록/활성화 판정)는 아직 v2로
17
+ * 포팅되지 않았다. cmdWorkforce 가 projectPath 를 쓰는 곳은 buildChildEnv 의
18
+ * 프로젝트 dotenv 스코핑뿐이고(agentlas-workforce.cjs 1926행) 부재 시 fail-closed
19
+ * 의존이 없음을 확인했다 — 그래서 여기서는 cwd 를 projectPath 로 넘긴다(같은 폴더
20
+ * 의 .env/.env.local 이 비신뢰 소스로 한 번 더 스캔될 뿐, 권한이 넓어지지 않는다).
21
+ * 프로젝트-상태 모듈이 v2에 착륙하면 이 자리를 그 결과로 교체한다.
22
+ */
23
+ const { workforceRuntime } = require("../workforce/deps.cjs");
24
+ const { projectCwd } = require("../workforce/capture.cjs");
25
+
26
+ function resolvePermission(ctx) {
27
+ // v1 resolveTerminalPermission 포팅: 저장된 `full` 은 세션 한정 계약이라
28
+ // persistent()가 fail-closed 로 강등한다.
29
+ try {
30
+ const policy = require("../agentlas-permissions.cjs");
31
+ return policy.persistent(ctx && ctx.prefs && ctx.prefs.permission, "write");
32
+ } catch {
33
+ return "write";
34
+ }
35
+ }
36
+
37
+ /** `--runtime <kind>` 만 명령 자체 플래그로 벗겨낸다. 나머지는 cmdWorkforce 가 파싱. */
38
+ function splitRuntimeOverride(args) {
39
+ const rest = [];
40
+ let runtimeOverride = null;
41
+ for (let i = 0; i < args.length; i += 1) {
42
+ if (args[i] === "--runtime" && args[i + 1]) {
43
+ runtimeOverride = String(args[++i]);
44
+ continue;
45
+ }
46
+ rest.push(args[i]);
47
+ }
48
+ return { rest, runtimeOverride };
49
+ }
50
+
51
+ /**
52
+ * command 별 디스패치. commands/index.cjs 는 명령 이름을 별도 인자로 넘기지 않으므로
53
+ * run()이 기본 "workforce" 로 진입하고, 별칭 배선 시 이 dispatch 를 직접 쓴다.
54
+ */
55
+ async function dispatch(ctx, command, args) {
56
+ switch (command) {
57
+ case "workforce":
58
+ case "network":
59
+ case "taskforce": {
60
+ const { rest, runtimeOverride } = splitRuntimeOverride(args);
61
+ // usage 경로(작업 텍스트 없음)는 SQLite를 열 이유가 없다 — cmdWorkforce 의
62
+ // parseArgs 와 동일한 판정: 플래그가 아닌 토큰이 하나도 없으면 빈 작업이다.
63
+ const hasTask = rest.some((token, i) =>
64
+ !["--benchmark", "--json", "--parallel", "-n"].includes(String(token))
65
+ && !(i > 0 && ["--parallel", "-n"].includes(String(rest[i - 1]))));
66
+ if (!hasTask) {
67
+ ctx.err("usage: agentlas workforce <task> [--parallel N] [--benchmark] [--json] [--runtime <kind>]");
68
+ return 1;
69
+ }
70
+ const db = ctx.db();
71
+ const cwd = projectCwd();
72
+ const permission = resolvePermission(ctx);
73
+ const runtime = workforceRuntime({ lang: ctx.lang, out: ctx.out });
74
+ const result = await runtime.cmdWorkforce(db, rest, runtimeOverride, {
75
+ cwd,
76
+ // v1 ensureTerminalProjectForExecutionCli 미포팅 — 위 파일 머리 주석 참조.
77
+ projectPath: cwd,
78
+ permission,
79
+ });
80
+ return result && result.ok ? 0 : 1;
81
+ }
82
+ case "legacy-network": {
83
+ // v1의 명시적 호환 탈출구(parity cmdHep → hep-network)는 아직 v2에 없다.
84
+ // 조용히 workforce 로 대체 실행하지 않는다 — 두 경로는 라우팅 권한 모델이 다르다.
85
+ ctx.err(
86
+ "'legacy-network' (hep-network compatibility) is not wired into the v2 engine yet.\n" +
87
+ "Use `agentlas workforce <request>` for the fail-closed Agent Workforce Ontology route,\n" +
88
+ "or the v1 build: git tag legacy-v1-engine-snapshot.",
89
+ );
90
+ return 1;
91
+ }
92
+ default: {
93
+ ctx.err(`unknown workforce command: ${command}`);
94
+ return 1;
95
+ }
96
+ }
97
+ }
98
+
99
+ function run(ctx, args) {
100
+ return dispatch(ctx, "workforce", args);
101
+ }
102
+
103
+ module.exports = { run, dispatch };
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ /*
3
+ * core/db — 공유 SQLite(데스크탑과 동일 DB) 열기 + 드라이버 사다리 + 빌트인 시드.
4
+ *
5
+ * 드라이버: better-sqlite3(네이티브, optionalDependency) → node:sqlite(Node 22.5+).
6
+ * 두 드라이버 모두 prepare().get/all/run 표면이 같으므로 얇은 어댑터만 둔다.
7
+ * 스키마 소유권: 정본 스키마는 데스크탑 앱. 터미널은 bootstrap-schema.sql로
8
+ * 첫 부트스트랩만 하고(런처가 수행), 이후 마이그레이션은 앱이 한다.
9
+ * 터미널은 "있으면 쓰는" 방어적 열 확인(columnExists)으로만 전진 호환한다.
10
+ */
11
+ const fs = require("node:fs");
12
+ const { configureSqliteConnection } = require("../agentlas-sqlite-policy.cjs");
13
+ const { parseSemVer, compareSemVer } = require("../semver.cjs");
14
+ const { dbPath } = require("./paths.cjs");
15
+
16
+ function loadNodeSqliteQuietly() {
17
+ const originalEmitWarning = process.emitWarning;
18
+ process.emitWarning = function agentlasSqliteWarningFilter(warning, ...args) {
19
+ const message = typeof warning === "string" ? warning : String((warning && warning.message) || warning || "");
20
+ const type = typeof args[0] === "string" ? args[0] : "";
21
+ if (type === "ExperimentalWarning" && /SQLite/i.test(message)) return;
22
+ if (/SQLite is an experimental feature/i.test(message)) return;
23
+ return originalEmitWarning.call(process, warning, ...args);
24
+ };
25
+ try {
26
+ return require("node:sqlite");
27
+ } finally {
28
+ process.emitWarning = originalEmitWarning;
29
+ }
30
+ }
31
+
32
+ function openRaw(file) {
33
+ try {
34
+ const Database = require("better-sqlite3");
35
+ const db = new Database(file);
36
+ configureSqliteConnection(db);
37
+ db.__driver = "better-sqlite3";
38
+ return db;
39
+ } catch { /* optional dep 미설치/ABI 불일치 → node:sqlite */ }
40
+ const { DatabaseSync } = loadNodeSqliteQuietly();
41
+ const db = new DatabaseSync(file);
42
+ configureSqliteConnection(db);
43
+ db.__driver = "node:sqlite";
44
+ return db;
45
+ }
46
+
47
+ /**
48
+ * DB 파일이 없으면 하드 실패한다. 부트스트랩은 런처(bin/agentlas.cjs)의 책임 —
49
+ * 엔진이 임의 경로에 빈 DB를 만들면 데스크탑과의 공유 계약이 조용히 깨진다.
50
+ */
51
+ function openDb() {
52
+ const file = dbPath();
53
+ if (!fs.existsSync(file)) {
54
+ throw new Error(`Agentlas database not found: ${file} (run via bin/agentlas.cjs)`);
55
+ }
56
+ return openRaw(file);
57
+ }
58
+
59
+ function tableExists(db, name) {
60
+ try { return !!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(name); } catch { return false; }
61
+ }
62
+
63
+ function columnExists(db, table, col) {
64
+ try { return db.prepare(`PRAGMA table_info(${table})`).all().some((c) => c.name === col); } catch { return false; }
65
+ }
66
+
67
+ function runWriteTransaction(db, fn) {
68
+ db.exec("BEGIN IMMEDIATE");
69
+ try {
70
+ const result = fn();
71
+ db.exec("COMMIT");
72
+ return result;
73
+ } catch (e) {
74
+ try { db.exec("ROLLBACK"); } catch { /* already rolled back */ }
75
+ throw e;
76
+ }
77
+ }
78
+
79
+ // ── 빌트인 아키텍처 시드 (데스크탑 seedBuiltinAgents와 동일 멱등·버전 게이팅) ──
80
+ let _arch = null;
81
+ function loadArch() {
82
+ if (_arch) return _arch;
83
+ try {
84
+ _arch = require("../architecture.data.json");
85
+ } catch {
86
+ _arch = { version: "0", agents: [] };
87
+ }
88
+ return _arch;
89
+ }
90
+
91
+ /**
92
+ * 설치본 시드 버전이 번들보다 최신이거나 파싱 불가면 fail-closed(덮어쓰기 금지):
93
+ * 더 새로운 데스크탑이 심은 공유 데이터를 옛 터미널이 되감으면 안 된다.
94
+ */
95
+ function shouldApplyBuiltinArchitectureSeed(installedVersion, bundleVersion, installedCount, bundleCount) {
96
+ if (!parseSemVer(bundleVersion)) return false;
97
+ if (installedVersion == null || installedVersion === "") return true;
98
+ const precedence = compareSemVer(installedVersion, bundleVersion);
99
+ if (precedence == null || precedence > 0) return false;
100
+ if (precedence < 0) return true;
101
+ const have = Number.isSafeInteger(Number(installedCount)) ? Number(installedCount) : 0;
102
+ const expected = Number.isSafeInteger(Number(bundleCount)) ? Number(bundleCount) : 0;
103
+ return have < expected;
104
+ }
105
+
106
+ function seedBuiltins(db) {
107
+ const arch = loadArch();
108
+ if (!arch.agents || !arch.agents.length) return;
109
+ if (!tableExists(db, "meta") || !columnExists(db, "installed_agents", "builtin")) return;
110
+ const now = new Date().toISOString();
111
+ try {
112
+ runWriteTransaction(db, () => {
113
+ const installed = db.prepare("SELECT value FROM meta WHERE key='architecture_version'").get();
114
+ const have = db.prepare("SELECT COUNT(*) AS n FROM installed_agents WHERE builtin=1").get();
115
+ if (!shouldApplyBuiltinArchitectureSeed(
116
+ installed ? installed.value : null,
117
+ arch.version,
118
+ have ? have.n : 0,
119
+ arch.agents.length,
120
+ )) return false;
121
+ const hasVisibility = columnExists(db, "installed_agents", "visibility");
122
+ for (const def of arch.agents) {
123
+ const visibility = def.visibility || "background";
124
+ const existing = db.prepare("SELECT id FROM installed_agents WHERE id=? OR slug=?").get(def.id, def.slug);
125
+ if (existing) {
126
+ if (hasVisibility) {
127
+ db.prepare(
128
+ "UPDATE installed_agents SET name=?, name_en=?, tagline=?, tagline_en=?, system_prompt=?, tone=?, role=?, builtin=1, trust_grade='A', visibility=? WHERE id=?",
129
+ ).run(def.name, def.nameEn, def.tagline, def.taglineEn, def.systemPrompt, def.tone, def.role, visibility, existing.id);
130
+ } else {
131
+ db.prepare(
132
+ "UPDATE installed_agents SET name=?, name_en=?, tagline=?, tagline_en=?, system_prompt=?, tone=?, role=?, builtin=1, trust_grade='A' WHERE id=?",
133
+ ).run(def.name, def.nameEn, def.tagline, def.taglineEn, def.systemPrompt, def.tone, def.role, existing.id);
134
+ }
135
+ } else if (hasVisibility) {
136
+ db.prepare(
137
+ "INSERT INTO installed_agents (id, slug, name, name_en, tagline, tagline_en, system_prompt, mcp_servers_json, env_requirements_json, preferred_backend, trust_grade, installed_at, tone, builtin, role, visibility) VALUES (?,?,?,?,?,?,?,'[]','[]',NULL,'A',?,?,1,?,?)",
138
+ ).run(def.id, def.slug, def.name, def.nameEn, def.tagline, def.taglineEn, def.systemPrompt, now, def.tone, def.role, visibility);
139
+ } else {
140
+ db.prepare(
141
+ "INSERT INTO installed_agents (id, slug, name, name_en, tagline, tagline_en, system_prompt, mcp_servers_json, env_requirements_json, preferred_backend, trust_grade, installed_at, tone, builtin, role) VALUES (?,?,?,?,?,?,?,'[]','[]',NULL,'A',?,?,1,?)",
142
+ ).run(def.id, def.slug, def.name, def.nameEn, def.tagline, def.taglineEn, def.systemPrompt, now, def.tone, def.role);
143
+ }
144
+ }
145
+ db.prepare("INSERT INTO meta(key,value) VALUES('architecture_version',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value").run(arch.version);
146
+ return true;
147
+ });
148
+ } catch { /* best-effort: 앱이 마이그레이션 중이면 다음 실행에서 재시도 */ }
149
+ }
150
+
151
+ module.exports = {
152
+ openDb,
153
+ openRaw,
154
+ tableExists,
155
+ columnExists,
156
+ runWriteTransaction,
157
+ seedBuiltins,
158
+ shouldApplyBuiltinArchitectureSeed,
159
+ loadArch,
160
+ };
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /*
3
+ * core/paths — 모든 경로 규칙의 단일 정본.
4
+ * bin/agentlas.cjs(런처)의 userDataDir 규칙과 반드시 동일해야 한다:
5
+ * 데스크탑 앱과 같은 userData를 공유하는 것이 제품 계약이다.
6
+ */
7
+ const os = require("node:os");
8
+ const path = require("node:path");
9
+
10
+ function userDataDir() {
11
+ const override = process.env.AGENTLAS_USER_DATA_DIR;
12
+ if (override) return override;
13
+ const home = os.homedir();
14
+ if (process.platform === "darwin") return path.join(home, "Library", "Application Support", "Agentlas");
15
+ if (process.platform === "win32") return path.join(process.env.APPDATA || path.join(home, "AppData", "Roaming"), "Agentlas");
16
+ return path.join(process.env.XDG_CONFIG_HOME || path.join(home, ".config"), "Agentlas");
17
+ }
18
+
19
+ function dbPath() {
20
+ return path.join(userDataDir(), "agentlas.sqlite");
21
+ }
22
+
23
+ function terminalConfigPath() {
24
+ return path.join(userDataDir(), "terminal-config.json");
25
+ }
26
+
27
+ function engineRoot() {
28
+ return path.dirname(__dirname);
29
+ }
30
+
31
+ function packageRoot() {
32
+ return path.dirname(engineRoot());
33
+ }
34
+
35
+ module.exports = { userDataDir, dbPath, terminalConfigPath, engineRoot, packageRoot };
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+
3
+ /*
4
+ * experience/build — /build 공용 핸들러 (REPL·상위 build 명령이 같은 코드를 호출).
5
+ *
6
+ * v1 engine/agentlas-experience-mcp.cjs의 cmdBuild/parseBuildArgs/
7
+ * tokenizeBuildCommandLine 슬라이스를 이식했다. MCP 계획·동의·런타임
8
+ * allowlist는 v2 engine/mcp/* 서브시스템을 "소비만" 한다(재구현 금지).
9
+ *
10
+ * 계약(v1 그대로):
11
+ * - MCP는 시스템 전역 레지스트리 메타데이터에서만 해소한다. 네트워크 발견/설치
12
+ * 폴백 없음. 부족(shortage)은 빌드를 중단시키지 않고 능력별로 degrade한다.
13
+ * - 로컬 Experience 자문은 exact base release + 정확히 1개 pack release +
14
+ * task signature가 모두 명시된 때에만 붙는다. 서버 rental-resolution
15
+ * 영수증을 로컬에서 주장하지 않는다.
16
+ */
17
+
18
+ const mcp = require("../mcp/index.cjs");
19
+ const { assertId } = require("./intents.cjs");
20
+
21
+ function parseIdList(value) {
22
+ if (!value || value === true) return [];
23
+ return [...new Set(String(value).split(",").map((item) => item.trim()).filter(Boolean))];
24
+ }
25
+
26
+ function tokenizeBuildCommandLine(value) {
27
+ const tokens = [];
28
+ let current = "";
29
+ let quote = null;
30
+ const source = String(value || "");
31
+ for (let index = 0; index < source.length; index += 1) {
32
+ const char = source[index];
33
+ if (char === "\\" && quote !== "'") {
34
+ const next = source[index + 1];
35
+ if (next && (/\s/.test(next) || next === "\\" || next === '"' || next === "'")) {
36
+ current += next;
37
+ index += 1;
38
+ } else {
39
+ // Windows 경로/일반 백슬래시 보존. 이 파서는 토큰 묶기에 필요한
40
+ // 이스케이프만 소비하며 셸 확장/명령 치환은 절대 적용하지 않는다.
41
+ current += "\\";
42
+ }
43
+ continue;
44
+ }
45
+ if (quote) {
46
+ if (char === quote) quote = null;
47
+ else current += char;
48
+ continue;
49
+ }
50
+ if (char === '"' || char === "'") {
51
+ quote = char;
52
+ continue;
53
+ }
54
+ if (/\s/.test(char)) {
55
+ if (current) tokens.push(current), (current = "");
56
+ continue;
57
+ }
58
+ current += char;
59
+ }
60
+ if (quote) throw new Error("unterminated quote in /build command");
61
+ if (current) tokens.push(current);
62
+ return tokens;
63
+ }
64
+
65
+ function parseBuildArgs(args) {
66
+ const buildArgs = args;
67
+ const options = {
68
+ task: [], requiredIds: [], recommendedIds: [], approvedIds: [],
69
+ experienceTaskSignatures: [], experienceEnvironmentTags: [],
70
+ experiencePackReleaseIds: [], experienceBaseReleaseId: null, experienceAgentDefinitionId: null,
71
+ approveAll: false, noMcp: false, noExperience: false, planOnly: false, json: false,
72
+ };
73
+ for (let index = 0; index < buildArgs.length; index += 1) {
74
+ const token = String(buildArgs[index]);
75
+ const take = () => (index + 1 < buildArgs.length ? String(buildArgs[++index]) : "");
76
+ if (token === "--mcp-plan-only") options.planOnly = true;
77
+ else if (token === "--mcp-json") options.json = true;
78
+ else if (token === "--approve-all-mcp") options.approveAll = true;
79
+ else if (token === "--no-mcp") options.noMcp = true;
80
+ else if (token === "--no-experience") options.noExperience = true;
81
+ else if (token === "--experience-base-release") options.experienceBaseReleaseId = take();
82
+ else if (token.startsWith("--experience-base-release=")) options.experienceBaseReleaseId = token.slice(26);
83
+ else if (token === "--experience-pack-release") options.experiencePackReleaseIds.push(...parseIdList(take()));
84
+ else if (token.startsWith("--experience-pack-release=")) options.experiencePackReleaseIds.push(...parseIdList(token.slice(26)));
85
+ else if (token === "--experience-agent-definition") options.experienceAgentDefinitionId = take();
86
+ else if (token.startsWith("--experience-agent-definition=")) options.experienceAgentDefinitionId = token.slice(30);
87
+ else if (token === "--experience-task-signature") options.experienceTaskSignatures.push(...parseIdList(take()));
88
+ else if (token.startsWith("--experience-task-signature=")) options.experienceTaskSignatures.push(...parseIdList(token.slice(28)));
89
+ else if (token === "--experience-environment") options.experienceEnvironmentTags.push(...parseIdList(take()));
90
+ else if (token.startsWith("--experience-environment=")) options.experienceEnvironmentTags.push(...parseIdList(token.slice(25)));
91
+ else if (token === "--approve-mcp") options.approvedIds.push(...parseIdList(take()));
92
+ else if (token.startsWith("--approve-mcp=")) options.approvedIds.push(...parseIdList(token.slice(14)));
93
+ else if (token === "--require-mcp") options.requiredIds.push(...parseIdList(take()));
94
+ else if (token.startsWith("--require-mcp=")) options.requiredIds.push(...parseIdList(token.slice(14)));
95
+ else if (token === "--recommend-mcp") options.recommendedIds.push(...parseIdList(take()));
96
+ else if (token.startsWith("--recommend-mcp=")) options.recommendedIds.push(...parseIdList(token.slice(16)));
97
+ else options.task.push(token);
98
+ }
99
+ options.request = options.task.join(" ").trim();
100
+ options.requiredIds = [...new Set(options.requiredIds)];
101
+ options.recommendedIds = [...new Set(options.recommendedIds)];
102
+ options.approvedIds = [...new Set(options.approvedIds)];
103
+ options.experienceTaskSignatures = [...new Set(options.experienceTaskSignatures)];
104
+ options.experienceEnvironmentTags = [...new Set(options.experienceEnvironmentTags)];
105
+ options.experiencePackReleaseIds = [...new Set(options.experiencePackReleaseIds)];
106
+ if (options.experienceBaseReleaseId) assertId(options.experienceBaseReleaseId, "--experience-base-release");
107
+ if (options.experienceAgentDefinitionId) assertId(options.experienceAgentDefinitionId, "--experience-agent-definition");
108
+ options.experiencePackReleaseIds.forEach((id) => assertId(id, "--experience-pack-release"));
109
+ return options;
110
+ }
111
+
112
+ async function cmdBuild(options) {
113
+ const parsed = parseBuildArgs(options.args || []);
114
+ const emit = options.out || console.log;
115
+ const inventory = mcp.collectSystemMcpInventory(options.db, { userDataDir: options.userDataDir, env: options.env || process.env });
116
+ const policy = mcp.loadProjectMcpPolicy(options.cwd || process.cwd());
117
+ const plan = mcp.buildMcpPlan({
118
+ inventory, policy, request: parsed.request,
119
+ requiredIds: parsed.requiredIds, recommendedIds: parsed.recommendedIds,
120
+ });
121
+ emit(parsed.json ? JSON.stringify(plan, null, 2) : mcp.renderMcpPlan(plan));
122
+ if (parsed.planOnly) return { plan, approvedIds: [], invoked: false };
123
+
124
+ let approvedIds = [];
125
+ if (!parsed.noMcp) {
126
+ if (parsed.approveAll) approvedIds = [...plan.availableCatalogIds];
127
+ else if (parsed.approvedIds.length) approvedIds = mcp.normalizeConsentAnswer(parsed.approvedIds.join(","), plan.availableCatalogIds);
128
+ else approvedIds = await mcp.askMcpConsentOnce(plan, { input: options.input, output: options.promptOutput });
129
+ }
130
+ approvedIds = mcp.fitApprovedMcpIds(plan, approvedIds);
131
+ const runtimeAllowlist = await mcp.resolveApprovedMcpRuntimeAllowlist({
132
+ db: options.db,
133
+ plan,
134
+ approvedIds,
135
+ cwd: options.cwd || process.cwd(),
136
+ userDataDir: options.userDataDir,
137
+ env: options.runtimeEnv || options.env || process.env,
138
+ probeServer: options.probeMcpServer,
139
+ });
140
+ const attachedIds = runtimeAllowlist.attached.map((item) => item.catalogId);
141
+ const directive = mcp.buildMcpDirective(plan, attachedIds);
142
+ let experienceContext = { text: "", itemIds: [], estimatedTokens: 0, authority: "local-advisory", serverRentalResolutionReceiptPresent: false };
143
+ if (
144
+ !parsed.noExperience &&
145
+ parsed.experienceBaseReleaseId &&
146
+ parsed.experienceTaskSignatures.length &&
147
+ parsed.experiencePackReleaseIds.length === 1
148
+ ) {
149
+ const exchange = require("../agentlas-experience-exchange.cjs");
150
+ experienceContext = exchange.buildLocalExperienceAdvisory({
151
+ userDataDir: options.userDataDir,
152
+ cwd: options.cwd || process.cwd(),
153
+ baseAgentReleaseId: parsed.experienceBaseReleaseId,
154
+ agentDefinitionId: parsed.experienceAgentDefinitionId,
155
+ experiencePackReleaseIds: parsed.experiencePackReleaseIds,
156
+ taskSignatures: parsed.experienceTaskSignatures,
157
+ environmentTags: parsed.experienceEnvironmentTags.length
158
+ ? parsed.experienceEnvironmentTags
159
+ : exchange.defaultEnvironmentTags(),
160
+ });
161
+ }
162
+ const builderRequest = [parsed.request, directive, experienceContext.text].filter(Boolean).join("\n\n");
163
+ if (typeof options.invokeBuild === "function") {
164
+ await options.invokeBuild(builderRequest, {
165
+ plan,
166
+ approvedIds,
167
+ experienceContext,
168
+ mcpRuntimeAllowlist: runtimeAllowlist,
169
+ mcpServers: runtimeAllowlist.servers,
170
+ });
171
+ }
172
+ emit(mcp.renderBuildMcpResult(plan, approvedIds, runtimeAllowlist));
173
+ if (experienceContext.itemIds.length) emit(`Local Experience advisory attached: ${experienceContext.itemIds.length} item(s), ~${experienceContext.estimatedTokens} tokens · no server rental-resolution receipt.`);
174
+ return { plan, approvedIds, mcpRuntimeAllowlist: runtimeAllowlist, experienceContext, invoked: typeof options.invokeBuild === "function" };
175
+ }
176
+
177
+ module.exports = {
178
+ tokenizeBuildCommandLine,
179
+ parseBuildArgs,
180
+ cmdBuild,
181
+ };