agentlas 1.0.45 → 1.0.47

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 (40) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +7 -7
  3. package/engine/agentlas-capabilities.cjs +3 -2
  4. package/engine/agentlas-core-harness.cjs +18 -0
  5. package/engine/agentlas-i18n.cjs +8 -8
  6. package/engine/agentlas-input.cjs +2 -2
  7. package/engine/agentlas-native-host.cjs +124 -11
  8. package/engine/agentlas-onboard.cjs +8 -3
  9. package/engine/agentlas-permissions.cjs +5 -1
  10. package/engine/agentlas-workforce.cjs +81 -24
  11. package/engine/agents/router.cjs +4 -2
  12. package/engine/architecture.data.json +6 -30
  13. package/engine/automation/daemon.cjs +1 -1
  14. package/engine/bootstrap-schema.sql +216 -191
  15. package/engine/browser/cdp.cjs +10 -4
  16. package/engine/cloud-assets/commands.cjs +1 -1
  17. package/engine/cloud-assets/package.cjs +161 -45
  18. package/engine/commands/context.cjs +14 -3
  19. package/engine/commands/doctor.cjs +7 -4
  20. package/engine/commands/graph.cjs +62 -64
  21. package/engine/commands/search.cjs +8 -3
  22. package/engine/core/desktop-core.cjs +39 -1
  23. package/engine/graph/interview.cjs +2 -11
  24. package/engine/graph/vocabulary.generated.cjs +1 -1
  25. package/engine/hephaestus/runtime.cjs +2 -6
  26. package/engine/project/memory-context.cjs +20 -7
  27. package/engine/project/seed.cjs +46 -31
  28. package/engine/project/state.cjs +8 -1
  29. package/engine/runtimes/auth-evidence.cjs +6 -0
  30. package/engine/runtimes/detect.cjs +1 -1
  31. package/engine/runtimes/resolve.cjs +27 -7
  32. package/engine/sessions/prompt.cjs +2 -2
  33. package/engine/ui/palette.cjs +1 -1
  34. package/engine/ui/repl.cjs +2 -2
  35. package/engine/ui/shell.cjs +108 -1
  36. package/engine/workforce/capture.cjs +52 -3
  37. package/engine/workforce/deps.cjs +2 -2
  38. package/engine/workforce/local-core-transport.cjs +13 -19
  39. package/package.json +1 -1
  40. package/engine/project/super-ontology-seed.json +0 -3288
@@ -6,6 +6,7 @@
6
6
  * 아무것도 없으면 no_runtime "정직 정지" — 키워드/저품질 폴백 금지(오너 결정).
7
7
  */
8
8
  const { RUNTIME_BIN, whichSync, listAvailableCliRuntimes, activeRuntimeRow } = require("./detect.cjs");
9
+ const path = require("node:path");
9
10
 
10
11
  // Session이 실제 드라이버를 갖춘 런타임만 실행 대상으로 삼는다.
11
12
  // CLI는 native-host, Ollama는 로컬 API loop를 쓴다. 다른 드라이버가 포팅되면
@@ -27,6 +28,16 @@ function apiRuntime(kind, model, source) {
27
28
  };
28
29
  }
29
30
 
31
+ function sharedRuntimeKind(row) {
32
+ if (!row) return null;
33
+ // Desktop historically stored Antigravity as kind=gemini with the actual
34
+ // selected executable in source. Preserve the selected product surface;
35
+ // never discard `agy` and silently fall through to Gemini or another CLI.
36
+ const selectedBin = path.posix.basename(path.win32.basename(String(row.source || ""))).toLowerCase();
37
+ if (row.kind === "gemini" && /^agy(?:\.exe|\.cmd)?$/i.test(selectedBin)) return "agy";
38
+ return row.kind;
39
+ }
40
+
30
41
  class NoRuntimeError extends Error {
31
42
  constructor(message) {
32
43
  super(message);
@@ -61,12 +72,19 @@ function resolveRuntime({ db, prefs, explicit }) {
61
72
  }
62
73
  if (db) {
63
74
  const active = activeRuntimeRow(db);
64
- if (active && API_EXECUTABLE_KINDS.has(active.kind)) {
65
- return apiRuntime(active.kind, active.model || undefined, "active");
75
+ const activeKind = sharedRuntimeKind(active);
76
+ if (active && API_EXECUTABLE_KINDS.has(activeKind)) {
77
+ return apiRuntime(activeKind, active.model || undefined, "active");
66
78
  }
67
- if (active && CLI_EXECUTABLE_KINDS.has(active.kind)) {
68
- const p = whichSync(RUNTIME_BIN[active.kind]);
69
- if (p) return { kind: active.kind, bin: p, model: active.model || undefined, source: "active" };
79
+ if (active && CLI_EXECUTABLE_KINDS.has(activeKind)) {
80
+ const p = whichSync(RUNTIME_BIN[activeKind]);
81
+ if (p) return {
82
+ kind: activeKind,
83
+ bin: p,
84
+ model: active.model || undefined,
85
+ source: "active",
86
+ runtimeSource: active.source || undefined,
87
+ };
70
88
  }
71
89
  }
72
90
  const found = listAvailableCliRuntimes().filter((r) => CLI_EXECUTABLE_KINDS.has(r.kind));
@@ -76,10 +94,11 @@ function resolveRuntime({ db, prefs, explicit }) {
76
94
  throw new NoRuntimeError([
77
95
  "no_runtime: no agent CLI is connected — Agentlas runs your agents on a CLI you already subscribe to.",
78
96
  "",
79
- "Install one, then rerun:",
97
+ "Connect or install one, then rerun:",
98
+ " agy # Antigravity CLI (preferred)",
80
99
  " npm i -g @anthropic-ai/claude-code # Claude Code",
81
100
  " npm i -g @openai/codex # Codex CLI",
82
- " npm i -g @google/gemini-cli # Gemini CLI",
101
+ " npm i -g @google/gemini-cli # Gemini CLI (legacy)",
83
102
  "",
84
103
  "Already installed? Make sure its binary is on PATH (agentlas doctor shows what was detected).",
85
104
  ].join("\n"));
@@ -91,4 +110,5 @@ module.exports = {
91
110
  EXECUTABLE_KINDS,
92
111
  CLI_EXECUTABLE_KINDS,
93
112
  API_EXECUTABLE_KINDS,
113
+ sharedRuntimeKind,
94
114
  };
@@ -18,7 +18,7 @@ const { loadArch, tableExists, columnExists } = require("../core/db.cjs");
18
18
  const { userDataDir } = require("../core/paths.cjs");
19
19
  const { responseDirective } = require("../agentlas-style.cjs");
20
20
  const memoryGovernance = require("../agentlas-memory-governance.cjs");
21
- const { resolveCoreRuntimeRoot, captureCoreJsonSync } = require("../agentlas-core-harness.cjs");
21
+ const { resolveContextMapCoreRoot, captureCoreJsonSync } = require("../agentlas-core-harness.cjs");
22
22
 
23
23
  const TERMINAL_MEMORY_CORE_MAX_TOKENS = 150;
24
24
  const TERMINAL_MEMORY_CORE = [
@@ -99,7 +99,7 @@ const { ensureMemoryContextColumn } = require("../core/schema-ensure.cjs");
99
99
  function cliProjectContextSlice(projectPath, task) {
100
100
  if (!projectPath || !String(task || "").trim()) return "";
101
101
  try {
102
- const coreRoot = resolveCoreRuntimeRoot();
102
+ const coreRoot = resolveContextMapCoreRoot();
103
103
  if (!coreRoot) return "";
104
104
  const result = captureCoreJsonSync(
105
105
  "agentlas_cloud",
@@ -28,7 +28,7 @@ const SLASH_COMMANDS = catalog.forSurface("repl").map((entry) => ({
28
28
  }));
29
29
 
30
30
  const SLASH_NAMES = SLASH_COMMANDS.map((c) => c.command);
31
- const RUNTIME_KINDS = ["claude-code", "codex", "gemini"];
31
+ const RUNTIME_KINDS = ["claude-code", "codex", "agy", "gemini"];
32
32
  const EFFORT_LEVELS = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
33
33
  const PERM_LEVELS = ["read", "write", "full"];
34
34
  // 세션 인자를 받는 명령 — 완성 후보를 살아있는 세션 키(s1, s2…)로 채운다.
@@ -741,7 +741,7 @@ function handleSlash(ctx, cmdline, api) {
741
741
  }
742
742
 
743
743
  case "runtime": {
744
- if (!rest[0]) throw usageError("Usage: /runtime claude-code|codex|gemini");
744
+ if (!rest[0]) throw usageError("Usage: /runtime claude-code|codex|agy|gemini");
745
745
  // 세션 오버라이드는 저장되지 않는다 — 고지 없이는 사용자가 영구 설정으로
746
746
  // 믿는다(2026-08-05 감사 결함 C). 영구 경로를 같은 줄에서 알려준다.
747
747
  api.setRuntime(rest[0]);
@@ -774,7 +774,7 @@ function handleSlash(ctx, cmdline, api) {
774
774
  return;
775
775
  }
776
776
  case "permission": {
777
- if (!["read", "write", "full"].includes(String(rest[0] || ""))) {
777
+ if (!permissions.isLevel(rest[0])) {
778
778
  throw usageError("Usage: /permission read|write|full");
779
779
  }
780
780
  const level = permissions.normalize(rest[0]);
@@ -202,6 +202,8 @@ async function startShell(ctx, opts = {}) {
202
202
  // ctx 초크포인트 재지정 — 55파일의 ctx.out 직출력이 전부 프레임 안으로 들어온다.
203
203
  const shellCtx = {
204
204
  ...ctx,
205
+ // 명령이 "지금 사용자가 어디에 서 있는지"를 알아야 안내를 옳게 쓴다.
206
+ surface: "shell",
205
207
  uiInstance: ui,
206
208
  out: (s = "") => ui.line(String(s)),
207
209
  err: (s = "") => ui.line(ui.c.amber(String(s))),
@@ -295,6 +297,38 @@ async function startShell(ctx, opts = {}) {
295
297
  onMessage: (msg) => { ui.ensureNl(); ui.line(ui.c.dim(msg.text)); },
296
298
  });
297
299
 
300
+ /*
301
+ * 목록 피커 — 슬러그를 손으로 받아치게 하지 않는다(오너 지적).
302
+ * SelectList 는 Focusable 이 아니라 포커스로는 키가 안 온다(Loader 와 같은 함정) —
303
+ * 전역 리스너에서 직접 forward 하고, 뜨는 동안 에디터 입력을 막는다.
304
+ */
305
+ let activePicker = null;
306
+ function pick(items, opts = {}) {
307
+ return new Promise((resolve) => {
308
+ if (!items.length) { resolve(null); return; }
309
+ ui.ensureNl();
310
+ if (opts.title) ui.line(ui.c.bold(opts.title));
311
+ ui.line(ui.c.dim(en
312
+ ? "↑/↓ choose · Enter confirm · Esc cancel"
313
+ : "↑/↓ 이동 · Enter 선택 · Esc 취소"));
314
+ const list = new pi.SelectList(items, Math.min(10, items.length), editorTheme.selectList, {});
315
+ const finish = (value) => {
316
+ if (activePicker !== list) return;
317
+ activePicker = null;
318
+ bottom.removeChild(list);
319
+ tui.setFocus(editor);
320
+ tui.requestRender();
321
+ resolve(value);
322
+ };
323
+ list.onSelect = (item) => finish(item);
324
+ list.onCancel = () => finish(null);
325
+ activePicker = list;
326
+ bottom.addChild(list);
327
+ tui.setFocus(null);
328
+ tui.requestRender();
329
+ });
330
+ }
331
+
298
332
  const commands = require("../commands/index.cjs");
299
333
  const handleSlash = async (cmdline) => {
300
334
  const raw = cmdline.split(/\s+/)[0] || "";
@@ -339,6 +373,69 @@ async function startShell(ctx, opts = {}) {
339
373
  } catch { /* 렌더 실패 → 아래 클래식 폴스루가 텍스트로 보여준다 */ }
340
374
  }
341
375
  }
376
+ /*
377
+ * /search — 결과를 목록으로 띄우고 방향키로 고른다. 슬러그를 손으로 받아치게
378
+ * 하지 않는다(오너 지적). 고르면 바로 설치까지 간다.
379
+ *
380
+ * kind 는 서버 열거값을 그대로 보여주지 않는다. "cloud-callable" 은 사용자에게
381
+ * "설치 안 해도 바로 부를 수 있음"이라는 뜻이지, 설치가 안 된다는 뜻이 아니다.
382
+ */
383
+ if (cmd === "search" && rest.length) {
384
+ const query = rest.join(" ");
385
+ const { callHubTool, HubError } = require("../cloud/hub-client.cjs");
386
+ let result;
387
+ ui.updateSpinner(en ? "Searching the Hub…" : "Hub 검색 중…");
388
+ try {
389
+ result = await callHubTool("marketplace.search_agents", { q: query, limit: 12 });
390
+ } catch (e) {
391
+ ui.stopSpinner();
392
+ ui.error(Object.assign(new Error(e instanceof HubError ? e.message : String((e && e.message) || e)),
393
+ { code: "hub_search_failed", honestStop: true }));
394
+ return;
395
+ }
396
+ ui.stopSpinner();
397
+ const raw = (result && (result.results || result.agents || result.items)) || (Array.isArray(result) ? result : []);
398
+ const hidden = (slug) => /^researcher-\d+/.test(String(slug || "").toLowerCase())
399
+ || String(slug || "").toLowerCase().startsWith("hephaestus-");
400
+ const rows = (Array.isArray(raw) ? raw : []).filter((it) => !hidden(it && (it.slug || it.id)));
401
+ if (!rows.length) { ui.line(ui.c.dim(en ? `No results for "${query}"` : `"${query}" 결과 없음`)); return; }
402
+ const callable = (k) => (String(k || "").includes("cloud")
403
+ ? (en ? "callable without installing" : "설치 없이 호출 가능")
404
+ : (en ? "install to use" : "설치해야 사용"));
405
+ const chosen = await pick(rows.map((it) => ({
406
+ value: it.slug || it.id || "?",
407
+ label: `${it.slug || it.id}`,
408
+ description: `${it.name || it.title || ""} — ${callable(it.kind || it.entity_kind)}`,
409
+ })), { title: en ? `Hub results for "${query}"` : `"${query}" Hub 결과` });
410
+ if (!chosen) { ui.line(ui.c.dim(en ? "cancelled" : "취소됨")); return; }
411
+ ui.line(ui.c.dim(en ? `installing ${chosen.value}…` : `${chosen.value} 설치 중…`));
412
+ await commands.COMMANDS.install().run(shellCtx, [chosen.value]);
413
+ return;
414
+ }
415
+
416
+ /*
417
+ * /graph — 저장된 그래프를 목록으로 띄우고 고른 것을 실행한다.
418
+ * (저장 테이블 이름은 automations 이지만 이 화면이 다루는 건 그래프다.)
419
+ */
420
+ if (cmd === "graph" && (!rest.length || rest[0] === "list")) {
421
+ const rowsOf = db.prepare("SELECT name, enabled, schedule, graph_json FROM automations ORDER BY name").all();
422
+ const graphs = rowsOf.filter((r) => r.graph_json);
423
+ if (!graphs.length) { ui.line(ui.c.dim(en ? "No saved graphs yet." : "저장된 그래프가 없습니다.")); return; }
424
+ const chosen = await pick(graphs.map((g) => {
425
+ let steps = 0;
426
+ try { steps = (JSON.parse(g.graph_json).nodes || []).length; } catch { steps = 0; }
427
+ return {
428
+ value: g.name,
429
+ label: g.name,
430
+ description: `${steps} ${en ? "steps" : "단계"} · ${g.enabled ? (en ? "on" : "켜짐") : (en ? "off" : "꺼짐")}`
431
+ + (g.schedule ? ` · ${g.schedule}` : ""),
432
+ };
433
+ }), { title: en ? "Saved graphs" : "저장된 그래프" });
434
+ if (!chosen) { ui.line(ui.c.dim(en ? "cancelled" : "취소됨")); return; }
435
+ await commands.COMMANDS.graph().run(shellCtx, ["run", chosen.value]);
436
+ return;
437
+ }
438
+
342
439
  /*
343
440
  * 세션 설정 4종. 자동완성은 되는데 처리 case 가 없어 "여기서는 아직 안 됩니다"만
344
441
  * 답하던 죽은 광고였다(신설 게이트가 잡았다). 기본 REPL 과 같은 의미로 배선하고,
@@ -352,8 +449,11 @@ async function startShell(ctx, opts = {}) {
352
449
  return;
353
450
  }
354
451
  if (cmd === "permission") {
452
+ if (!permissions.isLevel(value)) {
453
+ ui.line(ui.c.dim("Usage: /permission read|write|full"));
454
+ return;
455
+ }
355
456
  const next = permissions.normalize(value);
356
- if (!next) { ui.line(ui.c.dim("Usage: /permission read|write|full")); return; }
357
457
  permission = next;
358
458
  ui.line(ui.c.dim(`permission: ${next} · ${en ? "persist: agentlas setup" : "영구 저장: agentlas setup"}`));
359
459
  return;
@@ -474,6 +574,13 @@ async function startShell(ctx, opts = {}) {
474
574
  };
475
575
 
476
576
  tui.addInputListener((data) => {
577
+ // 피커가 떠 있으면 그 키는 피커 것이다 — 에디터로 새면 목록 위에서 글이 써진다.
578
+ if (activePicker) {
579
+ if (pi.matchesKey(data, "escape")) { activePicker.onCancel && activePicker.onCancel(); return { handled: true }; }
580
+ activePicker.handleInput(data);
581
+ tui.requestRender();
582
+ return { handled: true };
583
+ }
477
584
  // Shift-Tab 권한 순환 — 렌더러가 raw mode 를 단독 소유하므로 readline 의
478
585
  // swallowCompletion 우회 없이 여기서 직접 소비한다 (D2 위험 2의 해소 형태).
479
586
  if (pi.matchesKey(data, "shift+tab")) {
@@ -29,6 +29,7 @@ const { dbPath, userDataDir } = require("../core/paths.cjs");
29
29
  const RUNTIME_BIN = {
30
30
  "claude-code": "claude",
31
31
  codex: "codex",
32
+ agy: "agy",
32
33
  gemini: "gemini",
33
34
  };
34
35
 
@@ -198,6 +199,14 @@ function buildArgs(kind, systemPrompt, prompt, permission, runtimeOptions = {})
198
199
  const mcp = native.geminiMcpIsolationArgs();
199
200
  return ["--prompt", `[SYSTEM]\n${systemPrompt}\n\n${prompt}`, ...(model ? ["-m", model] : []), ...perm, ...noAuthorityArgs, ...mcp];
200
201
  }
202
+ if (kind === "agy") {
203
+ return native.agyArgs({
204
+ prompt,
205
+ systemPrompt,
206
+ permission: noAuthority ? "read" : level,
207
+ model,
208
+ });
209
+ }
201
210
  return [prompt];
202
211
  }
203
212
 
@@ -287,6 +296,7 @@ function capturedRuntimeUsage(kind, raw) {
287
296
  }
288
297
  const direct =
289
298
  genericUsage(event?.usage) ||
299
+ genericUsage(event?.step_update?.usage) ||
290
300
  genericUsage(event?.usageMetadata) ||
291
301
  genericUsage(event?.stats);
292
302
  if (direct) return direct;
@@ -346,6 +356,12 @@ function capturedRuntimeFailure(kind, raw, text) {
346
356
  return { message: `gemini ${event.status}`, source: "marker" };
347
357
  }
348
358
  }
359
+ if (kind === "agy" && event.event === "result") {
360
+ const status = String(event.result?.status || "").toLowerCase();
361
+ if (status && !["success", "completed", "done"].includes(status)) {
362
+ return { message: `agy ${status}`, source: "marker" };
363
+ }
364
+ }
349
365
  }
350
366
  // 표식이 전혀 없는 케이스(codex 한도) — 휴리스틱 최후 그물, 출처 표기.
351
367
  const refusal = detectRuntimeRefusal(text);
@@ -399,6 +415,21 @@ function capturedRuntimeAgentText(kind, raw) {
399
415
  return final ? String(final.result ?? final.response) : "";
400
416
  }
401
417
 
418
+ if (kind === "agy") {
419
+ const isProtocol = events.some((event) =>
420
+ event?.event === "step_update" || event?.event === "result",
421
+ );
422
+ if (!isProtocol) return text.trim();
423
+ const final = [...events].reverse().find((event) =>
424
+ event?.event === "result" && typeof event.result?.response === "string",
425
+ );
426
+ if (final) return final.result.response;
427
+ return events
428
+ .filter((event) => event?.event === "step_update" && event.step_update?.step_type === "agent_response")
429
+ .map((event) => typeof event.step_update?.text_delta === "string" ? event.step_update.text_delta : "")
430
+ .join("");
431
+ }
432
+
402
433
  return text.trim();
403
434
  }
404
435
 
@@ -423,6 +454,7 @@ function captureRuntime(kind, systemPrompt, prompt, opts) {
423
454
  // 계약 테스트용 실행 파일 주입(가짜 CLI가 픽스처를 cat) — 프로덕션 경로에선 없음.
424
455
  const bin = opts.binOverride || which(RUNTIME_BIN[kind]) || RUNTIME_BIN[kind];
425
456
  let child;
457
+ let launchCleanup = () => {};
426
458
  try {
427
459
  const spawnImpl = opts.spawn || spawn;
428
460
  const env = nativeHost.runtimeEnvForKind(kind, opts.env || process.env, {
@@ -431,13 +463,28 @@ function captureRuntime(kind, systemPrompt, prompt, opts) {
431
463
  mcpAllowlistMode: kind === "gemini" ? "exact" : undefined,
432
464
  });
433
465
  const groupedChild = process.platform !== "win32" && spawnImpl === spawn;
434
- child = spawnImpl(bin, buildArgs(kind, systemPrompt, prompt, opts.permission, {
466
+ const runtimeOptions = {
435
467
  model: opts.model,
436
468
  effort: opts.effort,
437
469
  authorityMode: opts.authorityMode,
438
470
  noToolsPolicyPath: opts.noToolsPolicyPath,
439
471
  allowedNativeTools: opts.allowedNativeTools,
440
- }), {
472
+ };
473
+ let childArgs = buildArgs(kind, systemPrompt, prompt, opts.permission, runtimeOptions);
474
+ if (kind === "agy") {
475
+ const prepared = nativeHost.prepareAgyLaunch({
476
+ prompt,
477
+ systemPrompt,
478
+ permission: opts.authorityMode === "no-authority" ? "read" : opts.permission,
479
+ model: opts.model,
480
+ }, {
481
+ platform: opts.platform,
482
+ promptLimit: opts.agyPromptLimit,
483
+ });
484
+ childArgs = prepared.args;
485
+ launchCleanup = prepared.cleanup;
486
+ }
487
+ child = spawnImpl(bin, childArgs, {
441
488
  cwd,
442
489
  stdio: ["ignore", "pipe", "pipe"],
443
490
  env,
@@ -446,6 +493,7 @@ function captureRuntime(kind, systemPrompt, prompt, opts) {
446
493
  });
447
494
  child.__agentlasGroupedChild = groupedChild;
448
495
  } catch (error) {
496
+ launchCleanup();
449
497
  reject(error);
450
498
  return;
451
499
  }
@@ -479,6 +527,7 @@ function captureRuntime(kind, systemPrompt, prompt, opts) {
479
527
  child.removeListener("error", onError);
480
528
  child.removeListener("close", onClose);
481
529
  if (opts.signal) opts.signal.removeEventListener?.("abort", onAbort);
530
+ launchCleanup();
482
531
  };
483
532
  const finishReject = (error) => {
484
533
  if (settled) return;
@@ -562,7 +611,7 @@ function captureRuntime(kind, systemPrompt, prompt, opts) {
562
611
  let text;
563
612
  if (kind === "codex" && opts.authorityMode === "no-authority") {
564
613
  text = codexCaptureAgentText(raw);
565
- } else if (kind === "claude-code" || kind === "gemini") {
614
+ } else if (kind === "claude-code" || kind === "gemini" || kind === "agy") {
566
615
  text = capturedRuntimeAgentText(kind, raw);
567
616
  } else {
568
617
  text = raw;
@@ -109,7 +109,7 @@ function legacyWorkforceRuntime(db, override) {
109
109
  }
110
110
  }
111
111
  const error = new Error(
112
- "no_runtime: no agent CLI or connected API runtime found (claude / codex / gemini / BYOK / Ollama).",
112
+ "no_runtime: no agent CLI or connected API runtime found (claude / codex / Antigravity agy / legacy gemini / BYOK / Ollama).",
113
113
  );
114
114
  error.code = "no_runtime";
115
115
  throw error;
@@ -513,7 +513,7 @@ function projectContextSlice(projectPath, task) {
513
513
  if (!projectPath || !String(task || "").trim()) return "";
514
514
  try {
515
515
  const core = coreHarness();
516
- const coreRoot = core.resolveCoreRuntimeRoot();
516
+ const coreRoot = core.resolveContextMapCoreRoot();
517
517
  if (!coreRoot) return "";
518
518
  const result = core.captureCoreJsonSync(
519
519
  "agentlas_cloud",
@@ -7,16 +7,14 @@
7
7
  * search_candidates 요청 {workOrder, sourceScope}
8
8
  * 응답 agentlas.workforce-federation-result.v1 봉투
9
9
  * → 루프에는 봉투를 벗긴 candidateSet만 준다.
10
- * validate_selection 요청 {workOrder, selection, candidateSet}
11
- * (federationResult를 실으면 invalid_federation_result
12
- * Core는 자기 선택 세션에서 연합을 이미 안다)
10
+ * validate_selection 요청 {workOrder, selection}
11
+ * (Core는 selectionSessionId로 자기 핀 세션을 복원한다)
13
12
  * 응답 안의 selectionValidation이 정확히
14
13
  * agentlas.workforce-selection-validation.v1 — 루프 검증기와
15
14
  * 동일 계약이라 그대로 돌려준다. 원본 응답은 여기 상태로
16
15
  * 붙잡아 둔다(prepare가 요구).
17
- * prepare_execution 요청 {workOrder, selection, candidateSet,
18
- * federatedSelection: <validate 원본 응답>,
19
- * validationReceipt: <동일>, projectDir}
16
+ * prepare_execution 요청 {workOrder, selection,
17
+ * federatedSelection: <validate 원본 응답>, projectDir}
20
18
  * 응답 안의 executionPlan이 정확히
21
19
  * agentlas.workforce-execution-plan.v5 (roster에
22
20
  * directiveBundle·permissionPolicy 동봉) — 그대로 돌려준다.
@@ -115,7 +113,7 @@ function createLocalCoreHubTool({ sourceScope, projectDir, cwd, client } = {}) {
115
113
  const core = client || createLocalCoreClient({ cwd: cwd || projectDir });
116
114
  // 편성 계보 상태 — 전부 "Core 어휘"(opaque id) 원본이다:
117
115
  // maps: 마지막 search 의 id 지도. 재검색(refinement)마다 재구성된다.
118
- // coreCandidateSet: Core 가 준 원본 CandidateSet (validate/prepare 무수정 반송).
116
+ // coreCandidateSet: Core 가 준 요약 메뉴. 로컬 계보 확인에만 쓰며 반송하지 않는다.
119
117
  // lastValidationEnvelope: validate 원본 응답. prepare 의 federatedSelection 은
120
118
  // 이것이어야 한다 — 루프가 들고 있는 것은 벗겨낸 selectionValidation 뿐이다.
121
119
  let maps = { forward: new Map(), reverse: new Map() };
@@ -137,12 +135,11 @@ function createLocalCoreHubTool({ sourceScope, projectDir, cwd, client } = {}) {
137
135
  maps = buildIdMaps(args.workOrder);
138
136
  coreCandidateSet = null;
139
137
  lastValidationEnvelope = null;
140
- // fullDossier: 터미널 루프의 후보 검증기는 legacy full-echo 계약이다
141
- // (qualificationEvidence·packageHash·contentDigest 필수 실측: 기본
142
- // reference-first 메뉴는 candidate_set_invalid 로 거절된다).
138
+ // Current Core keeps the full dossier in its pinned session and returns a
139
+ // numbered decision menu. Do not request the legacy full-echo form.
143
140
  let envelope;
144
141
  try {
145
- envelope = await core.call(name, { workOrder: mapDeep(args.workOrder, maps.forward), sourceScope, fullDossier: true });
142
+ envelope = await core.call(name, { workOrder: mapDeep(args.workOrder, maps.forward), sourceScope });
146
143
  } catch (error) {
147
144
  // 반응형 정규화(1회): 경계가 지목한 finite 값만 opaque 로 바꿔 재시도.
148
145
  const issues = error.code === "work_order_hub_boundary_rejected" ? boundaryIssues(error) : null;
@@ -161,7 +158,7 @@ function createLocalCoreHubTool({ sourceScope, projectDir, cwd, client } = {}) {
161
158
  repaired += 1;
162
159
  }
163
160
  if (!repaired) throw error;
164
- envelope = await core.call(name, { workOrder: mapDeep(args.workOrder, maps.forward), sourceScope, fullDossier: true });
161
+ envelope = await core.call(name, { workOrder: mapDeep(args.workOrder, maps.forward), sourceScope });
165
162
  }
166
163
  const candidateSet = envelope && envelope.candidateSet;
167
164
  if (!candidateSet || typeof candidateSet !== "object") throw invalid("local Core federation returned no candidateSet");
@@ -174,9 +171,10 @@ function createLocalCoreHubTool({ sourceScope, projectDir, cwd, client } = {}) {
174
171
  error.code = "local_core_lineage_missing";
175
172
  throw error;
176
173
  }
177
- // 루프의 candidateSet(역치환본)과 보관본의 계보 일치를 다이제스트로 확인한다.
178
- if (args.candidateSet?.candidateSetDigest !== coreCandidateSet.candidateSetDigest) {
179
- throw invalid("candidateSet lineage mismatch between the loop and the local Core session");
174
+ // The host-authored selection must point at the exact summary menu just
175
+ // returned. Core independently reloads and verifies the full pinned set.
176
+ if (args.selection?.candidateSetDigest !== coreCandidateSet.candidateSetDigest) {
177
+ throw invalid("selection lineage mismatch between the loop and the local Core session");
180
178
  }
181
179
  let envelope;
182
180
  let coreSelection = mapDeep(args.selection, maps.forward);
@@ -184,7 +182,6 @@ function createLocalCoreHubTool({ sourceScope, projectDir, cwd, client } = {}) {
184
182
  envelope = await core.call(name, {
185
183
  workOrder: mapDeep(args.workOrder, maps.forward),
186
184
  selection: coreSelection,
187
- candidateSet: coreCandidateSet,
188
185
  });
189
186
  } catch (error) {
190
187
  /*
@@ -205,7 +202,6 @@ function createLocalCoreHubTool({ sourceScope, projectDir, cwd, client } = {}) {
205
202
  envelope = await core.call(name, {
206
203
  workOrder: mapDeep(args.workOrder, maps.forward),
207
204
  selection: repairedSelection,
208
- candidateSet: coreCandidateSet,
209
205
  });
210
206
  coreSelection = repairedSelection;
211
207
  }
@@ -224,9 +220,7 @@ function createLocalCoreHubTool({ sourceScope, projectDir, cwd, client } = {}) {
224
220
  workOrder: mapDeep(args.workOrder, maps.forward),
225
221
  // validate 가 수락한 정확한 본 — 반응형 reasonCode 수리를 반영한다.
226
222
  selection: lastCoreSelection || mapDeep(args.selection, maps.forward),
227
- candidateSet: coreCandidateSet,
228
223
  federatedSelection: lastValidationEnvelope,
229
- validationReceipt: lastValidationEnvelope,
230
224
  projectDir,
231
225
  });
232
226
  if (!envelope || typeof envelope.executionPlan !== "object") throw invalid("local Core preparation returned no executionPlan");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlas",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
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"