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.
Files changed (163) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +12 -4
  3. package/bin/agentlas.cjs +354 -16
  4. package/engine/acp/server.cjs +34 -3
  5. package/engine/agentlas-api-agent.cjs +151 -120
  6. package/engine/agentlas-banner.cjs +6 -2
  7. package/engine/agentlas-capabilities.cjs +17 -10
  8. package/engine/agentlas-cloud-runtime.cjs +4 -5
  9. package/engine/agentlas-config.cjs +19 -54
  10. package/engine/agentlas-core-harness.cjs +59 -5
  11. package/engine/agentlas-desktop-loadout.cjs +17 -3
  12. package/engine/agentlas-evolution.cjs +75 -18
  13. package/engine/agentlas-experience-exchange.cjs +145 -14
  14. package/engine/agentlas-experience-intake.cjs +2 -2
  15. package/engine/agentlas-i18n.cjs +0 -2
  16. package/engine/agentlas-input.cjs +273 -20
  17. package/engine/agentlas-judgment.cjs +0 -0
  18. package/engine/agentlas-mcp-env.cjs +84 -23
  19. package/engine/agentlas-mcp-wrapper.cjs +36 -4
  20. package/engine/agentlas-memory-governance.cjs +2 -1
  21. package/engine/agentlas-memory-import.cjs +49 -12
  22. package/engine/agentlas-native-host.cjs +279 -44
  23. package/engine/agentlas-onboard.cjs +23 -1
  24. package/engine/agentlas-permissions.cjs +5 -1
  25. package/engine/agentlas-sqlite-policy.cjs +40 -2
  26. package/engine/agentlas-tasks.cjs +7 -0
  27. package/engine/agentlas-tools.cjs +37 -10
  28. package/engine/agentlas-ui.cjs +26 -4
  29. package/engine/agentlas-workforce.cjs +30 -5
  30. package/engine/agentlas-workload-routing.cjs +83 -10
  31. package/engine/agentlas.cjs +21 -4
  32. package/engine/agents/builder.cjs +35 -10
  33. package/engine/agents/files.cjs +81 -10
  34. package/engine/agents/import-local.cjs +10 -2
  35. package/engine/agents/registry.cjs +10 -3
  36. package/engine/agents/router.cjs +7 -7
  37. package/engine/agents/routes.cjs +31 -9
  38. package/engine/automation/daemon.cjs +154 -40
  39. package/engine/automation/launchd.cjs +395 -16
  40. package/engine/automation/schedule.cjs +90 -3
  41. package/engine/automation/store.cjs +163 -59
  42. package/engine/browser/cdp.cjs +190 -17
  43. package/engine/browser/vault.cjs +1 -0
  44. package/engine/cli-output.cjs +96 -30
  45. package/engine/cloud/auth.cjs +131 -13
  46. package/engine/cloud/hub-client.cjs +41 -16
  47. package/engine/cloud-assets/cas.cjs +18 -9
  48. package/engine/cloud-assets/commands.cjs +108 -30
  49. package/engine/cloud-assets/package.cjs +66 -3
  50. package/engine/cloud-assets/restore.cjs +12 -9
  51. package/engine/cloud-assets/state.cjs +609 -116
  52. package/engine/commands/acp.cjs +13 -2
  53. package/engine/commands/automation.cjs +75 -24
  54. package/engine/commands/billing.cjs +6 -1
  55. package/engine/commands/browser.cjs +61 -29
  56. package/engine/commands/build.cjs +72 -14
  57. package/engine/commands/call.cjs +1 -1
  58. package/engine/commands/cd.cjs +1 -1
  59. package/engine/commands/cloud.cjs +2 -1
  60. package/engine/commands/connect.cjs +38 -13
  61. package/engine/commands/creds.cjs +74 -29
  62. package/engine/commands/doctor.cjs +17 -1
  63. package/engine/commands/document.cjs +38 -10
  64. package/engine/commands/env.cjs +6 -1
  65. package/engine/commands/firm.cjs +42 -12
  66. package/engine/commands/graph.cjs +36 -32
  67. package/engine/commands/help.cjs +11 -1
  68. package/engine/commands/hep.cjs +1 -1
  69. package/engine/commands/import.cjs +5 -4
  70. package/engine/commands/index.cjs +1 -1
  71. package/engine/commands/install.cjs +6 -4
  72. package/engine/commands/list.cjs +56 -16
  73. package/engine/commands/login.cjs +5 -0
  74. package/engine/commands/logout.cjs +6 -1
  75. package/engine/commands/mcp.cjs +150 -37
  76. package/engine/commands/memory.cjs +3 -2
  77. package/engine/commands/multimodal.cjs +12 -0
  78. package/engine/commands/native.cjs +5 -1
  79. package/engine/commands/one.cjs +78 -33
  80. package/engine/commands/plugin.cjs +13 -2
  81. package/engine/commands/project.cjs +15 -12
  82. package/engine/commands/research.cjs +18 -7
  83. package/engine/commands/roles.cjs +104 -28
  84. package/engine/commands/route.cjs +2 -2
  85. package/engine/commands/run.cjs +42 -9
  86. package/engine/commands/search.cjs +23 -2
  87. package/engine/commands/setup.cjs +6 -1
  88. package/engine/commands/storm.cjs +44 -6
  89. package/engine/commands/swarm.cjs +47 -9
  90. package/engine/commands/telegram.cjs +6 -1
  91. package/engine/commands/uninstall.cjs +12 -6
  92. package/engine/commands/update.cjs +5 -0
  93. package/engine/commands/upload.cjs +2 -1
  94. package/engine/commands/usage.cjs +6 -1
  95. package/engine/commands/version.cjs +6 -1
  96. package/engine/commands/whoami.cjs +6 -1
  97. package/engine/commands/workforce.cjs +60 -16
  98. package/engine/core/capability-grants.cjs +7 -2
  99. package/engine/core/daemon-client.cjs +36 -7
  100. package/engine/core/db.cjs +10 -2
  101. package/engine/core/desktop-core-fetch.cjs +477 -77
  102. package/engine/core/schema-ensure.cjs +23 -11
  103. package/engine/experience/build.cjs +5 -1
  104. package/engine/experience/intents.cjs +35 -82
  105. package/engine/experience/runtime.cjs +7 -1
  106. package/engine/experience/variant.cjs +58 -4
  107. package/engine/firms/orchestrate.cjs +9 -6
  108. package/engine/graph/ask-model.cjs +22 -11
  109. package/engine/graph/interview.cjs +74 -23
  110. package/engine/graph/package.cjs +63 -8
  111. package/engine/hephaestus/local-core.cjs +18 -5
  112. package/engine/hephaestus/runtime.cjs +48 -43
  113. package/engine/hub/install.cjs +484 -63
  114. package/engine/hub/plugins.cjs +391 -29
  115. package/engine/mcp/consent.cjs +25 -17
  116. package/engine/mcp/contract.cjs +229 -24
  117. package/engine/mcp/inventory.cjs +46 -10
  118. package/engine/mcp/plan.cjs +10 -1
  119. package/engine/mcp/probe.cjs +24 -9
  120. package/engine/memory-cli/curate.cjs +74 -16
  121. package/engine/oberon/common.cjs +37 -12
  122. package/engine/oberon/manifest.cjs +25 -2
  123. package/engine/oberon/outputs.cjs +49 -19
  124. package/engine/oberon/render.cjs +665 -40
  125. package/engine/project/career-graph.cjs +15 -2
  126. package/engine/project/controller.cjs +133 -15
  127. package/engine/project/credentials.cjs +382 -34
  128. package/engine/project/env-file.cjs +93 -8
  129. package/engine/project/memory-context.cjs +11 -57
  130. package/engine/project/ontology.cjs +66 -25
  131. package/engine/project/paths.cjs +42 -8
  132. package/engine/project/seed.cjs +88 -51
  133. package/engine/project/state.cjs +41 -16
  134. package/engine/project/team.cjs +26 -1
  135. package/engine/runtime-refusal.cjs +4 -1
  136. package/engine/runtimes/acp-driver.cjs +56 -9
  137. package/engine/runtimes/auth-evidence.cjs +21 -2
  138. package/engine/runtimes/detect.cjs +28 -8
  139. package/engine/runtimes/kinds.cjs +12 -4
  140. package/engine/runtimes/overrides.cjs +4 -3
  141. package/engine/runtimes/resolve.cjs +8 -7
  142. package/engine/runtimes/roles.cjs +7 -5
  143. package/engine/sessions/apply-fences.cjs +396 -56
  144. package/engine/sessions/fences.cjs +111 -27
  145. package/engine/sessions/memory-turn.cjs +62 -3
  146. package/engine/sessions/orchestrator.cjs +11 -1
  147. package/engine/sessions/prompt.cjs +66 -9
  148. package/engine/sessions/session.cjs +70 -10
  149. package/engine/sessions/sink.cjs +78 -12
  150. package/engine/sessions/store.cjs +29 -7
  151. package/engine/storm/storm.cjs +26 -3
  152. package/engine/storm/swarm.cjs +31 -3
  153. package/engine/telegram/connect.cjs +185 -22
  154. package/engine/ui/commands-catalog.cjs +1 -1
  155. package/engine/ui/palette.cjs +12 -2
  156. package/engine/ui/repl.cjs +57 -9
  157. package/engine/ui/screens.cjs +2 -4
  158. package/engine/vendor/desktop-core.manifest.json +5 -5
  159. package/engine/workforce/capture.cjs +9 -27
  160. package/engine/workforce/deps.cjs +18 -6
  161. package/engine/workforce/local-core-transport.cjs +68 -5
  162. package/package.json +1 -1
  163. package/engine/commands/career-graph.cjs +0 -51
@@ -267,6 +267,7 @@ async function startRepl(ctx, opts = {}) {
267
267
  });
268
268
  // 진행 중인 비동기 슬래시 명령 — 종료가 이걸 잘라먹지 않도록 close 핸들러가 기다린다.
269
269
  const pendingCommands = new Set();
270
+ const pendingShells = new Set();
270
271
  const trackCommand = (promise) => {
271
272
  pendingCommands.add(promise);
272
273
  promise.then(() => pendingCommands.delete(promise), () => pendingCommands.delete(promise));
@@ -394,6 +395,15 @@ async function startRepl(ctx, opts = {}) {
394
395
  prompt();
395
396
  return;
396
397
  }
398
+ if (pendingShells.size) {
399
+ const controller = [...pendingShells].at(-1);
400
+ controller.abort();
401
+ ui.ensureNl();
402
+ ui.line(ui.c.dim(en ? "(shell command interrupted)" : "(셸 명령 중단됨)"));
403
+ sigints = 0;
404
+ prompt();
405
+ return;
406
+ }
397
407
  sigints += 1;
398
408
  if (sigints >= 2) { rl.close(); return; }
399
409
  ui.line(ui.c.dim(en ? "(ctrl-c again to quit)" : "(한 번 더 ctrl-c 하면 종료)"));
@@ -487,7 +497,12 @@ async function startRepl(ctx, opts = {}) {
487
497
  }
488
498
 
489
499
  if (input.startsWith("!")) {
490
- runShell(ctx, input.slice(1).trim(), permission).then(prompt, (e) => { recoverPresentation("shell action", e); prompt(); });
500
+ const controller = new AbortController();
501
+ pendingShells.add(controller);
502
+ const command = runShell(ctx, input.slice(1).trim(), permission, { signal: controller.signal })
503
+ .then(prompt, (e) => { recoverPresentation("shell action", e); prompt(); })
504
+ .finally(() => pendingShells.delete(controller));
505
+ trackCommand(command);
491
506
  return;
492
507
  }
493
508
 
@@ -519,6 +534,7 @@ async function startRepl(ctx, opts = {}) {
519
534
  process.stdin.removeListener("keypress", onShortcutKey);
520
535
  slashPalette.detach();
521
536
  renderer.detach();
537
+ for (const controller of pendingShells) controller.abort();
522
538
  /*
523
539
  * 슬래시 명령은 비동기다. 예전에는 close 가 곧바로 resolve 해서 프로세스가 끝나 버렸고,
524
540
  * `/search …` 직후 `/quit` 을 치면 그 명령이 출력 한 줄 없이 사라졌다(실측: 같은 입력을
@@ -554,7 +570,7 @@ async function startRepl(ctx, opts = {}) {
554
570
  * env 객체는 절대 변형하지 않는다 (credential-env-regression 계약).
555
571
  * - 출력 8MB 캡, 표시 전 시크릿 마스킹, 프로세스 그룹 종료.
556
572
  */
557
- async function runShell(ctx, cmd, permission) {
573
+ async function runShell(ctx, cmd, permission, options = {}) {
558
574
  const ui = ctx.uiInstance;
559
575
  const en = ctx.lang === "en";
560
576
  if (!cmd) return;
@@ -588,13 +604,49 @@ async function runShell(ctx, cmd, permission) {
588
604
  });
589
605
  let bytes = 0;
590
606
  let capped = false;
607
+ let settled = false;
608
+ let forceKillTimer = null;
609
+ const terminate = (signal) => {
610
+ const pid = Number(child.pid);
611
+ if (grouped && Number.isInteger(pid) && pid > 1) {
612
+ try { process.kill(-pid, signal); return; } catch { /* fall through */ }
613
+ }
614
+ try { child.kill(signal); } catch { /* already gone */ }
615
+ };
616
+ const forceKillSoon = () => {
617
+ if (forceKillTimer) return;
618
+ forceKillTimer = setTimeout(() => terminate("SIGKILL"), 250);
619
+ forceKillTimer.unref?.();
620
+ };
621
+ const reapOnExit = () => terminate("SIGKILL");
622
+ process.once("exit", reapOnExit);
623
+ const abortHandler = () => {
624
+ terminate("SIGTERM");
625
+ forceKillSoon();
626
+ };
627
+ if (options.signal) {
628
+ if (options.signal.aborted) abortHandler();
629
+ else options.signal.addEventListener?.("abort", abortHandler, { once: true });
630
+ }
631
+ const finish = (code, failed = false) => {
632
+ if (settled) return;
633
+ settled = true;
634
+ if (forceKillTimer) clearTimeout(forceKillTimer);
635
+ process.removeListener("exit", reapOnExit);
636
+ if (options.signal) options.signal.removeEventListener?.("abort", abortHandler);
637
+ ui.streamEnd();
638
+ if (failed) ui.error();
639
+ else if (code !== 0 && !capped && !options.signal?.aborted) ui.line(ui.c.dim(`exit ${code}`));
640
+ resolve();
641
+ };
591
642
  const onData = (d) => {
592
643
  bytes += d.length;
593
644
  if (bytes > maxBytes) {
594
645
  if (!capped) {
595
646
  capped = true;
596
647
  ui.warn(en ? "(output capped at 8MB — command stopped)" : "(출력 8MB 초과 — 명령 중단)");
597
- try { grouped ? process.kill(-child.pid, "SIGTERM") : child.kill("SIGTERM"); } catch { /* dead */ }
648
+ terminate("SIGTERM");
649
+ forceKillSoon();
598
650
  }
599
651
  return;
600
652
  }
@@ -603,12 +655,8 @@ async function runShell(ctx, cmd, permission) {
603
655
  ui.streamStart(true);
604
656
  child.stdout.on("data", onData);
605
657
  child.stderr.on("data", onData);
606
- child.on("error", () => { ui.streamEnd(); ui.error(); resolve(); });
607
- child.on("close", (code) => {
608
- ui.streamEnd();
609
- if (code !== 0 && !capped) ui.line(ui.c.dim(`exit ${code}`));
610
- resolve();
611
- });
658
+ child.on("error", () => finish(1, true));
659
+ child.on("close", (code) => finish(code));
612
660
  });
613
661
  }
614
662
 
@@ -249,8 +249,8 @@ function settings(ui, db, en, ctx) {
249
249
  ? "change: /setup (wizard) · /runtime · /model · /effort · /permission · roles set · creds · env"
250
250
  : "변경: /setup (마법사) · /runtime · /model · /effort · /permission · roles set · creds · env"));
251
251
  ui.line(ui.c.dim(en
252
- ? "Desktop-only here: theme, mobile pairing QR, auto-update, multimodal providers"
253
- : "여기서 불가(데스크탑 전용): 테마, 모바일 페어링 QR, 자동 업데이트, 멀티모달 프로바이더"));
252
+ ? "also available: agentlas multimodal · Desktop-only: theme, mobile pairing QR, auto-update"
253
+ : "추가 설정: agentlas multimodal · 데스크탑 전용: 테마, 모바일 페어링 QR, 자동 업데이트"));
254
254
  }
255
255
 
256
256
  /* ── /projects — 데스크탑 workspace + project/detail ── */
@@ -355,8 +355,6 @@ function firms(ui, db, en, ctx, arg) {
355
355
  } else if (org) {
356
356
  ui.line(ui.c.dim(en ? " roster is declared in the org chart only" : " 로스터가 조직도 선언에만 있습니다"));
357
357
  }
358
- const chats = count(db, "SELECT COUNT(*) n FROM chats WHERE firm_id = ? AND archived_at IS NULL", );
359
- void chats;
360
358
  ui.line("");
361
359
  ui.line(ui.c.dim(en ? `run: /firm ${f.slug} "<task>"` : `실행: /firm ${f.slug} "<할 일>"`));
362
360
  return;
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "15",
3
- "url": "https://github.com/agentlas-ai/agentlas-terminal/releases/download/desktop-core-v15/desktop-core.tar.gz",
4
- "sha256": "df39404b55ea4e6a3571e416167f64e75bcde92fdb86f19bd26e54746156f87b",
5
- "sizeBytes": 18167726,
6
- "writtenAt": "2026-08-29T08:38:53.468Z"
2
+ "version": "16",
3
+ "url": "https://github.com/agentlas-ai/agentlas-terminal/releases/download/desktop-core-v16/desktop-core.tar.gz",
4
+ "sha256": "563370a5cb53afd0e6688e7b5fe30e5bc0e9022f70a9239f5e95fb1da6309868",
5
+ "sizeBytes": 18173415,
6
+ "writtenAt": "2026-08-30T02:34:26.615Z"
7
7
  }
@@ -23,6 +23,7 @@ const os = require("node:os");
23
23
  const path = require("node:path");
24
24
  const { spawn } = require("node:child_process");
25
25
  const { dbPath, userDataDir } = require("../core/paths.cjs");
26
+ const { runCwd, projectCwd } = require("../project/paths.cjs");
26
27
 
27
28
  // 캡처 드라이버가 검증된 런타임만. 정본(runtimes/kinds.cjs)의 RUNTIME_BIN에는 kimi/grok/cursor도
28
29
  // 있지만 buildArgs/텍스트 추출 계약이 없으므로 캡처 검증 파생본만 쓴다 — 새 kind 를
@@ -726,8 +727,12 @@ async function runApi(backend, model, system, prompt, options) {
726
727
  model = model || DEFAULT_API_MODEL[backend];
727
728
  const fetchImpl = options.fetch || globalThis.fetch;
728
729
  if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable in this runtime (run through the app runtime).");
730
+ const request = (url, init) => fetchImpl(
731
+ url,
732
+ options.signal ? { ...init, signal: options.signal } : init,
733
+ );
729
734
  if (backend === "ollama") {
730
- const resp = await fetchImpl("http://127.0.0.1:11434/api/chat", {
735
+ const resp = await request("http://127.0.0.1:11434/api/chat", {
731
736
  method: "POST",
732
737
  headers: { "content-type": "application/json" },
733
738
  body: JSON.stringify({ model, stream: false, messages: [{ role: "system", content: system }, { role: "user", content: prompt }] }),
@@ -760,7 +765,7 @@ async function runApi(backend, model, system, prompt, options) {
760
765
  const systemField = backend === "anthropic"
761
766
  ? [{ type: "text", text: system, cache_control: { type: "ephemeral" } }]
762
767
  : system;
763
- const resp = await fetchImpl(`${base}/v1/messages`, {
768
+ const resp = await request(`${base}/v1/messages`, {
764
769
  method: "POST",
765
770
  headers: { "content-type": "application/json", ...authHeaders, "anthropic-version": "2023-06-01" },
766
771
  body: JSON.stringify({ model, max_tokens: 4096, system: systemField, messages: [{ role: "user", content: prompt }] }),
@@ -781,7 +786,7 @@ async function runApi(backend, model, system, prompt, options) {
781
786
  : readCustomApiBaseUrl())
782
787
  : "https://api.openai.com/v1";
783
788
  const label = backend === "custom" ? "Custom API" : backend === "upstage" ? "Upstage" : "OpenAI";
784
- const resp = await fetchImpl(`${base}/chat/completions`, {
789
+ const resp = await request(`${base}/chat/completions`, {
785
790
  method: "POST",
786
791
  headers: { "content-type": "application/json", authorization: "Bearer " + key },
787
792
  body: JSON.stringify({ model, messages: [{ role: "system", content: system }, { role: "user", content: prompt }] }),
@@ -795,7 +800,7 @@ async function runApi(backend, model, system, prompt, options) {
795
800
  }
796
801
  if (backend === "google") {
797
802
  const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${encodeURIComponent(key)}`;
798
- const resp = await fetchImpl(url, {
803
+ const resp = await request(url, {
799
804
  method: "POST",
800
805
  headers: { "content-type": "application/json" },
801
806
  body: JSON.stringify({ systemInstruction: { parts: [{ text: system }] }, contents: [{ role: "user", parts: [{ text: prompt }] }] }),
@@ -811,29 +816,6 @@ async function runApi(backend, model, system, prompt, options) {
811
816
  throw new Error("Unsupported backend: " + backend);
812
817
  }
813
818
 
814
- // ── 작업 폴더 규칙 (v1 runCwd/projectCwd 포팅) ─────────────────────────
815
- function runCwd() {
816
- const dir = path.join(userDataDir(), "agent-cwd");
817
- try {
818
- fs.mkdirSync(dir, { recursive: true });
819
- return dir;
820
- } catch {
821
- return os.homedir();
822
- }
823
- }
824
-
825
- // 에이전트가 실행될 작업 폴더 = 사용자가 명령을 친 현재 디렉터리(= 대상 프로젝트).
826
- // 단, home/userData/agent-cwd 같은 "프로젝트 아님" 위치면 안전한 전용 폴더로 폴백.
827
- function projectCwd() {
828
- try {
829
- const cwd = process.cwd();
830
- if (!cwd || cwd === os.homedir() || cwd === userDataDir() || cwd === runCwd()) return runCwd();
831
- return cwd;
832
- } catch {
833
- return runCwd();
834
- }
835
- }
836
-
837
819
  // ── 자식 env 빌더 (v1 buildChildEnvCli 포팅) ───────────────────────────
838
820
  function parseDotEnv(text) {
839
821
  const out = {};
@@ -32,7 +32,8 @@ const path = require("node:path");
32
32
  const { userDataDir } = require("../core/paths.cjs");
33
33
  const hubClient = require("../cloud/hub-client.cjs");
34
34
  const detect = require("../runtimes/detect.cjs");
35
- const { roleMembers } = require("../runtimes/roles.cjs");
35
+ const { canonicalRuntimeKind } = require("../runtimes/kinds.cjs");
36
+ const { pickRoleFromPool } = require("../runtimes/roles.cjs");
36
37
  const capture = require("./capture.cjs");
37
38
 
38
39
  // ── 런타임 해석 (v1 resolveRuntime의 워크포스 어댑터) ─────────────────────
@@ -49,6 +50,8 @@ function runtimeFromSelection(selection) {
49
50
  efforts: [],
50
51
  source: selection.sourceLayer || selection.source || null,
51
52
  role: selection.role || null,
53
+ inherit: Boolean(selection.inherit),
54
+ skipped: Array.isArray(selection.skipped) ? selection.skipped : [],
52
55
  };
53
56
  if (capture.RUNTIME_BIN[selection.kind]) {
54
57
  return { mode: "cli", kind: selection.kind, ...common };
@@ -63,10 +66,10 @@ function runtimeFromSelection(selection) {
63
66
  }
64
67
 
65
68
  function legacyWorkforceRuntime(db, override) {
66
- let selectedOverride = override;
69
+ let selectedOverride = canonicalRuntimeKind(override);
67
70
  if (!selectedOverride) {
68
71
  try {
69
- const saved = require("../agentlas-config.cjs").loadPrefs(userDataDir()).runtime;
72
+ const saved = canonicalRuntimeKind(require("../agentlas-config.cjs").loadPrefs(userDataDir()).runtime);
70
73
  if (
71
74
  saved &&
72
75
  saved !== "auto" &&
@@ -81,7 +84,7 @@ function legacyWorkforceRuntime(db, override) {
81
84
  const active = ar
82
85
  ? runtimeFromSelection({
83
86
  role: "orchestrator",
84
- kind: ar.kind,
87
+ kind: canonicalRuntimeKind(ar.kind),
85
88
  backend: ar.backend,
86
89
  source: ar.source,
87
90
  model: ar.model,
@@ -128,8 +131,17 @@ function resolveWorkforceRuntime(db, override) {
128
131
  },
129
132
  };
130
133
  }
131
- const orchestrator = runtimeFromSelection(roleMembers(db, "orchestrator")[0] || null);
132
- const worker = runtimeFromSelection(roleMembers(db, "worker")[0] || null);
134
+ const isAvailable = (selection) => {
135
+ const kind = canonicalRuntimeKind(selection && selection.kind);
136
+ if (capture.RUNTIME_BIN[kind]) return Boolean(capture.which(capture.RUNTIME_BIN[kind]));
137
+ return kind === "byok" && Boolean(selection.backend) || kind === "ollama";
138
+ };
139
+ const pickAvailableRole = (role) => {
140
+ const selected = pickRoleFromPool(db, role, isAvailable);
141
+ return selected && isAvailable(selected) ? runtimeFromSelection(selected) : null;
142
+ };
143
+ const orchestrator = pickAvailableRole("orchestrator");
144
+ const worker = pickAvailableRole("worker");
133
145
  if (!orchestrator || !worker) {
134
146
  const error = new Error("Dashboard orchestrator and worker priorities are required for Workforce execution.");
135
147
  error.code = "no_runtime_role_priority";
@@ -26,6 +26,10 @@ const crypto = require("node:crypto");
26
26
  const { createLocalCoreClient } = require("../hephaestus/local-core.cjs");
27
27
 
28
28
  const SOURCE_SCOPES = new Set(["network", "local", "cloud", "hub"]);
29
+ const MAX_MAPPED_NODES = 100_000;
30
+ const MAX_MAPPED_DEPTH = 64;
31
+ const MAX_MAPPED_STRING_BYTES = 16 * 1024 * 1024;
32
+ const FORBIDDEN_OBJECT_KEYS = new Set(["__proto__", "prototype", "constructor"]);
29
33
 
30
34
  /*
31
35
  * ── id 정규화 (실측 2026-08-05 라이브 런 실패의 수리) ──
@@ -87,18 +91,58 @@ function boundaryIssues(error) {
87
91
  }
88
92
 
89
93
  /** 깊은 순회로 문자열 값을 정확 일치 치환한 사본을 만든다. */
90
- function mapDeep(value, map) {
91
- if (map.size === 0) return value;
92
- if (typeof value === "string") return map.get(value) || value;
93
- if (Array.isArray(value)) return value.map((item) => mapDeep(item, map));
94
+ function mapDeep(value, map, state = { nodes: 0, stringBytes: 0 }, depth = 0) {
95
+ state.nodes += 1;
96
+ if (state.nodes > MAX_MAPPED_NODES || depth > MAX_MAPPED_DEPTH) {
97
+ const error = new Error("local Core payload exceeds the safe structure limit");
98
+ error.code = "local_core_payload_invalid";
99
+ throw error;
100
+ }
101
+ if (typeof value === "string") {
102
+ state.stringBytes += Buffer.byteLength(value, "utf8");
103
+ if (state.stringBytes > MAX_MAPPED_STRING_BYTES) {
104
+ const error = new Error("local Core payload exceeds the safe text limit");
105
+ error.code = "local_core_payload_invalid";
106
+ throw error;
107
+ }
108
+ return map.get(value) || value;
109
+ }
110
+ if (Array.isArray(value)) return value.map((item) => mapDeep(item, map, state, depth + 1));
94
111
  if (value && typeof value === "object") {
112
+ const proto = Object.getPrototypeOf(value);
113
+ if (proto !== Object.prototype && proto !== null) {
114
+ const error = new Error("local Core payload contains a non-plain object");
115
+ error.code = "local_core_payload_invalid";
116
+ throw error;
117
+ }
95
118
  const out = {};
96
- for (const [key, item] of Object.entries(value)) out[key] = mapDeep(item, map);
119
+ for (const [key, item] of Object.entries(value)) {
120
+ if (FORBIDDEN_OBJECT_KEYS.has(key)) {
121
+ const error = new Error(`local Core payload contains a forbidden key: ${key}`);
122
+ error.code = "local_core_payload_invalid";
123
+ throw error;
124
+ }
125
+ out[key] = mapDeep(item, map, state, depth + 1);
126
+ }
97
127
  return out;
98
128
  }
99
129
  return value;
100
130
  }
101
131
 
132
+ function stablePayloadDigest(value) {
133
+ const mapped = mapDeep(value, new Map());
134
+ const canonical = (item) => {
135
+ if (Array.isArray(item)) return `[${item.map(canonical).join(",")}]`;
136
+ if (item && typeof item === "object") {
137
+ return `{${Object.keys(item).sort().filter((key) => item[key] !== undefined)
138
+ .map((key) => `${JSON.stringify(key)}:${canonical(item[key])}`).join(",")}}`;
139
+ }
140
+ const encoded = JSON.stringify(item);
141
+ return encoded === undefined ? "null" : encoded;
142
+ };
143
+ return sha256hex(canonical(mapped));
144
+ }
145
+
102
146
  function createLocalCoreHubTool({ sourceScope, projectDir, cwd, client } = {}) {
103
147
  if (!SOURCE_SCOPES.has(sourceScope)) {
104
148
  const error = new Error(`sourceScope must be network|local|cloud|hub, got: ${String(sourceScope)}`);
@@ -123,6 +167,8 @@ function createLocalCoreHubTool({ sourceScope, projectDir, cwd, client } = {}) {
123
167
  // 반송해야 한다 — 반응형 reasonCode 수리가 있었으면 루프의 selection 을 다시
124
168
  // 변환한 것과 다르다(exact binding).
125
169
  let lastCoreSelection = null;
170
+ let lastWorkOrderDigest = null;
171
+ let lastSelectionDigest = null;
126
172
 
127
173
  const invalid = (message) => {
128
174
  const error = new Error(message);
@@ -135,6 +181,9 @@ function createLocalCoreHubTool({ sourceScope, projectDir, cwd, client } = {}) {
135
181
  maps = buildIdMaps(args.workOrder);
136
182
  coreCandidateSet = null;
137
183
  lastValidationEnvelope = null;
184
+ lastCoreSelection = null;
185
+ lastSelectionDigest = null;
186
+ lastWorkOrderDigest = stablePayloadDigest(args.workOrder);
138
187
  // Current Core keeps the full dossier in its pinned session and returns a
139
188
  // numbered decision menu. Do not request the legacy full-echo form.
140
189
  let envelope;
@@ -171,6 +220,11 @@ function createLocalCoreHubTool({ sourceScope, projectDir, cwd, client } = {}) {
171
220
  error.code = "local_core_lineage_missing";
172
221
  throw error;
173
222
  }
223
+ if (stablePayloadDigest(args.workOrder) !== lastWorkOrderDigest) {
224
+ const error = new Error("validate_selection workOrder differs from the searched local Core lineage");
225
+ error.code = "local_core_lineage_mismatch";
226
+ throw error;
227
+ }
174
228
  // The host-authored selection must point at the exact summary menu just
175
229
  // returned. Core independently reloads and verifies the full pinned set.
176
230
  if (args.selection?.candidateSetDigest !== coreCandidateSet.candidateSetDigest) {
@@ -208,6 +262,7 @@ function createLocalCoreHubTool({ sourceScope, projectDir, cwd, client } = {}) {
208
262
  if (!envelope || typeof envelope.selectionValidation !== "object") throw invalid("local Core validation returned no selectionValidation receipt");
209
263
  lastValidationEnvelope = envelope;
210
264
  lastCoreSelection = coreSelection;
265
+ lastSelectionDigest = stablePayloadDigest(args.selection);
211
266
  return mapDeep(envelope.selectionValidation, maps.reverse);
212
267
  }
213
268
  if (name === "workforce.prepare_execution") {
@@ -216,6 +271,14 @@ function createLocalCoreHubTool({ sourceScope, projectDir, cwd, client } = {}) {
216
271
  error.code = "local_core_lineage_missing";
217
272
  throw error;
218
273
  }
274
+ if (
275
+ stablePayloadDigest(args.workOrder) !== lastWorkOrderDigest ||
276
+ stablePayloadDigest(args.selection) !== lastSelectionDigest
277
+ ) {
278
+ const error = new Error("prepare_execution input differs from the validated local Core lineage");
279
+ error.code = "local_core_lineage_mismatch";
280
+ throw error;
281
+ }
219
282
  const envelope = await core.call(name, {
220
283
  workOrder: mapDeep(args.workOrder, maps.forward),
221
284
  // validate 가 수락한 정확한 본 — 반응형 reasonCode 수리를 반영한다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlas",
3
- "version": "1.0.65",
3
+ "version": "1.0.67",
4
4
  "description": "Agentlas project terminal — run project controllers and task-scoped agent teams from the terminal. Standalone: no desktop app process required.",
5
5
  "bin": {
6
6
  "agentlas": "bin/agentlas.cjs"
@@ -1,51 +0,0 @@
1
- "use strict";
2
- /*
3
- * career-graph — 커리어 그래프 상태/소스 등록 + 인덱스 실행 위임 (v1 cmdCareerGraph 포팅).
4
- *
5
- * ingest|query|verify|trace|public-card 는 파생 인덱스 실행이며 Agentlas OS /
6
- * Hephaestus 런타임(career_graph 파이썬 모듈)이 소유한다 — 런타임이 있으면
7
- * 그대로 위임 실행하고, 없으면 정직 정지(대신 계산하는 척 금지).
8
- */
9
- const { runCareerGraphCli } = require("../project/career-graph.cjs");
10
- const { resolveCoreRuntimeRoot, resolvePython, spawnCoreModule } = require("../agentlas-core-harness.cjs");
11
-
12
- const INDEX_SUBCOMMANDS = new Set(["ingest", "query", "verify", "trace", "public-card"]);
13
-
14
- async function run(ctx, args) {
15
- const sub = String((args && args[0]) || "status");
16
- if (INDEX_SUBCOMMANDS.has(sub)) {
17
- const coreRoot = resolveCoreRuntimeRoot();
18
- if (!coreRoot || !resolvePython()) {
19
- ctx.err(ctx.lang === "ko"
20
- ? "Career Graph 인덱스 실행은 Agentlas OS / Hephaestus 런타임이 필요합니다 (~/.agentlas/runtime/current). 런타임 없이 결과를 지어내지 않습니다."
21
- : "Career Graph index execution requires the Agentlas OS / Hephaestus runtime (~/.agentlas/runtime/current). No runtime — no fabricated result.");
22
- return 1;
23
- }
24
- const graphArgs = args.slice();
25
- if (!graphArgs.includes("--project")) graphArgs.push("--project", process.cwd());
26
- const child = spawnCoreModule("career_graph", graphArgs, { cwd: process.cwd(), stdio: "inherit" }, coreRoot);
27
- if (!child) {
28
- ctx.err("Agentlas Core runtime or Python 3.9+ is unavailable.");
29
- return 1;
30
- }
31
- return await new Promise((resolve) => {
32
- child.on("error", (e) => { ctx.err(String((e && e.message) || e)); resolve(1); });
33
- child.on("close", (code) => resolve(code == null ? 1 : code));
34
- });
35
- }
36
- try {
37
- const lines = await runCareerGraphCli(args, {
38
- cwd: process.cwd(),
39
- projectPath: process.cwd(),
40
- lang: ctx.lang,
41
- notify: (line) => ctx.out(line),
42
- });
43
- for (const line of lines) ctx.out(line);
44
- return 0;
45
- } catch (e) {
46
- ctx.err(String((e && e.message) || e));
47
- return 1;
48
- }
49
- }
50
-
51
- module.exports = { run };