agentlas 1.0.11 → 1.0.14

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 (57) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +4 -2
  3. package/bin/agentlas.cjs +17 -3
  4. package/engine/agentlas-config.cjs +25 -18
  5. package/engine/agentlas-core-harness.cjs +14 -1
  6. package/engine/agentlas-i18n.cjs +2 -0
  7. package/engine/agentlas-input.cjs +62 -8
  8. package/engine/agentlas-memory-governance.cjs +10 -0
  9. package/engine/agentlas-onboard.cjs +22 -5
  10. package/engine/agentlas-sqlite-policy.cjs +18 -7
  11. package/engine/agentlas-workforce.cjs +989 -114
  12. package/engine/agentlas-workload-routing.cjs +61 -7
  13. package/engine/agentlas.cjs +8 -0
  14. package/engine/automation/daemon.cjs +76 -30
  15. package/engine/automation/schedule.cjs +16 -0
  16. package/engine/automation/store.cjs +92 -12
  17. package/engine/bootstrap-schema.sql +788 -29
  18. package/engine/commands/automation.cjs +8 -0
  19. package/engine/commands/chats.cjs +6 -1
  20. package/engine/commands/doctor.cjs +23 -1
  21. package/engine/commands/firm.cjs +66 -4
  22. package/engine/commands/help.cjs +31 -7
  23. package/engine/commands/hep-cloud.cjs +31 -0
  24. package/engine/commands/hep-hub.cjs +30 -0
  25. package/engine/commands/hep-local.cjs +32 -0
  26. package/engine/commands/hep-network.cjs +43 -0
  27. package/engine/commands/index.cjs +38 -7
  28. package/engine/commands/list.cjs +25 -1
  29. package/engine/commands/open.cjs +5 -1
  30. package/engine/commands/run.cjs +77 -13
  31. package/engine/commands/setup.cjs +12 -11
  32. package/engine/commands/storm.cjs +5 -9
  33. package/engine/commands/swarm.cjs +4 -4
  34. package/engine/commands/uninstall.cjs +36 -2
  35. package/engine/commands/version.cjs +29 -0
  36. package/engine/commands/workforce.cjs +10 -10
  37. package/engine/core/schema-ensure.cjs +75 -0
  38. package/engine/experience/variant.cjs +46 -2
  39. package/engine/firms/orchestrate.cjs +10 -3
  40. package/engine/hephaestus/runtime.cjs +7 -0
  41. package/engine/memory-cli/curate.cjs +3 -7
  42. package/engine/project/memory-context.cjs +3 -7
  43. package/engine/project/state.cjs +7 -6
  44. package/engine/runtimes/overrides.cjs +91 -22
  45. package/engine/runtimes/resolve.cjs +38 -8
  46. package/engine/runtimes/roles.cjs +162 -0
  47. package/engine/sessions/orchestrator.cjs +43 -4
  48. package/engine/sessions/prompt.cjs +4 -7
  49. package/engine/sessions/session.cjs +88 -20
  50. package/engine/storm/swarm.cjs +40 -12
  51. package/engine/ui/palette.cjs +52 -0
  52. package/engine/ui/renderer.cjs +37 -0
  53. package/engine/ui/repl.cjs +78 -10
  54. package/engine/workforce/capture.cjs +199 -14
  55. package/engine/workforce/concurrency.cjs +41 -0
  56. package/engine/workforce/deps.cjs +145 -29
  57. package/package.json +1 -1
@@ -92,6 +92,14 @@ async function run(ctx, args) {
92
92
  targetId = f.id;
93
93
  targetLabel = f.name;
94
94
  }
95
+ // 잘못된 IANA 존이면 nextCronRun 이 cron 파싱 실패와 똑같이 null 을 돌려준다.
96
+ // 그대로 두면 멀쩡한 cron 을 범인으로 지목해서, 사용자가 cron 만 계속 고쳐 쓰며
97
+ // 매번 같은 실패를 본다. 존을 먼저 검증해 틀린 필드를 정확히 지목한다.
98
+ if (flags.tz && !schedule.isValidTimezone(flags.tz)) {
99
+ return fail(ko
100
+ ? `타임존을 해석하지 못했습니다: "${flags.tz}" (IANA 형식이 필요합니다 — 예: Asia/Seoul, UTC, America/New_York)`
101
+ : `Could not parse timezone: "${flags.tz}" (needs IANA format — e.g. Asia/Seoul, UTC, America/New_York)`);
102
+ }
95
103
  const next = schedule.nextCronRun(flags.cron, new Date(), flags.tz || null);
96
104
  if (!next) {
97
105
  return fail(ko
@@ -20,8 +20,13 @@ function run(ctx, args) {
20
20
  }
21
21
  for (const r of rows) {
22
22
  const when = String(r.updated_at || "").replace("T", " ").slice(0, 16);
23
- ctx.out(` ${ctx.ui.dim(when)} ${ctx.ui.accent((r.agent_slug || "?").padEnd(20))} ${r.title}`);
23
+ // id 앞 8자를 먼저 찍는다 — `open <id 앞부분>`이 유일한 재개 경로인데
24
+ // 목록에 id가 없으면 SQLite를 직접 열지 않는 한 재개가 불가능했다.
25
+ // 8자는 open.cjs의 모호성 안내(id.slice(0,8))와 같은 규약.
26
+ const id = String(r.id || "").slice(0, 8);
27
+ ctx.out(` ${ctx.ui.bold(id)} ${ctx.ui.dim(when)} ${ctx.ui.accent((r.agent_slug || "?").padEnd(20))} ${r.title}`);
24
28
  }
29
+ ctx.out(ctx.ui.dim(ctx.lang === "en" ? " resume: agentlas open <id>" : " 재개: agentlas open <id>"));
25
30
  return 0;
26
31
  }
27
32
 
@@ -9,6 +9,19 @@ const fs = require("node:fs");
9
9
  const path = require("node:path");
10
10
  const { dbPath, userDataDir } = require("../core/paths.cjs");
11
11
  const { listAvailableCliRuntimes, activeRuntimeRow } = require("../runtimes/detect.cjs");
12
+ const { resolvedModelRole } = require("../runtimes/roles.cjs");
13
+
14
+ function roleDetail(selection, role, en) {
15
+ if (!selection) return en ? "not set" : "미설정";
16
+ const provider = selection.kind === "byok" ? selection.backend || "byok" : selection.kind;
17
+ return [
18
+ `${role}=${provider}${selection.model ? `/${selection.model}` : ""}`,
19
+ selection.effort ? `effort=${selection.effort}` : null,
20
+ role === "worker" && selection.inherit
21
+ ? (en ? "inherits orchestrator" : "오케스트레이터 상속")
22
+ : null,
23
+ ].filter(Boolean).join(" · ");
24
+ }
12
25
 
13
26
  function run(ctx) {
14
27
  const en = ctx.lang === "en";
@@ -42,8 +55,17 @@ function run(ctx) {
42
55
  ctx.out(ctx.ui.dim(" npm i -g @anthropic-ai/claude-code · @openai/codex · @google/gemini-cli"));
43
56
  }
44
57
  try {
45
- const active = activeRuntimeRow(ctx.db());
58
+ const db = ctx.db();
59
+ const active = activeRuntimeRow(db);
46
60
  if (active) ok(en ? "active runtime" : "활성 런타임", `${active.kind}${active.model ? ` (${active.model})` : ""}`);
61
+ const orchestrator = resolvedModelRole(db, "orchestrator");
62
+ const worker = resolvedModelRole(db, "worker");
63
+ if (orchestrator && worker) {
64
+ ok(
65
+ en ? "model roles" : "모델 역할",
66
+ `${roleDetail(orchestrator, "orchestrator", en)} · ${roleDetail(worker, "worker", en)}`,
67
+ );
68
+ }
47
69
  } catch { /* db issue already reported */ }
48
70
 
49
71
  // 3) 로그인 상태 (세션 파일 관측만 — 네트워크 호출 없음)
@@ -50,11 +50,14 @@ async function run(ctx, args) {
50
50
  }
51
51
  const ceo = rowToAgent(ceoRow);
52
52
 
53
- // 간단 플래그 파싱(--runtime/--permission) — 명령끼리 참조 금지 규칙상 run.cjs 미차용.
53
+ // 간단 플래그 파싱 — 명령끼리 참조 금지 규칙상 run.cjs 미차용.
54
54
  const rest = args.slice(1);
55
- const flags = { runtime: null, permission: null, task: [] };
55
+ const flags = { runtime: null, model: null, effort: null, tier: null, permission: null, task: [] };
56
56
  for (let i = 0; i < rest.length; i++) {
57
57
  if (rest[i] === "--runtime") flags.runtime = rest[++i];
58
+ else if (rest[i] === "--model") flags.model = rest[++i];
59
+ else if (rest[i] === "--effort") flags.effort = rest[++i];
60
+ else if (rest[i] === "--tier") flags.tier = rest[++i];
58
61
  else if (rest[i] === "--permission") flags.permission = rest[++i];
59
62
  else flags.task.push(rest[i]);
60
63
  }
@@ -62,26 +65,71 @@ async function run(ctx, args) {
62
65
  if (task) {
63
66
  // 3-tier 위임 실행 — PLAN → DELEGATE → SYNTHESIZE (firms/orchestrate.cjs).
64
67
  const { runFirmTurn } = require("../firms/orchestrate.cjs");
65
- const { resolveRuntimeForAgent, unavailableOverrideNote } = require("../runtimes/overrides.cjs");
68
+ const {
69
+ resolveRuntimeForAgent,
70
+ unavailableOverrideNote,
71
+ unavailableRoleNote,
72
+ } = require("../runtimes/overrides.cjs");
73
+ const { EFFORTS, TIERS } = require("../agentlas-workload-routing.cjs");
66
74
  const { Orchestrator } = require("../sessions/orchestrator.cjs");
67
75
  const permissions = require("../agentlas-permissions.cjs");
76
+ // 플래그 검증은 CEO 계획 턴을 부르기 전에. normalize는 모르는 값을 read로 fail-closed
77
+ // 강등하는데, firm은 그 권한을 CEO·본부 전 세션·종합 턴까지 그대로 물려주므로 조용히
78
+ // 강등하면 full을 요청한 사용자가 쓰기 차단된 채 3-tier 전체를 돌린 줄 모른다 (run.cjs 동일 가드).
79
+ if (flags.permission && !permissions.LEVELS.includes(String(flags.permission))) {
80
+ ctx.err(`unknown --permission ${flags.permission} (use: ${permissions.LEVELS.join(" | ")})`);
81
+ return 1;
82
+ }
83
+ if (flags.effort && !EFFORTS.includes(String(flags.effort))) {
84
+ ctx.err(`unknown --effort ${flags.effort} (use: ${EFFORTS.join(" | ")})`);
85
+ return 1;
86
+ }
87
+ if (flags.tier && !TIERS.includes(String(flags.tier))) {
88
+ ctx.err(`unknown --tier ${flags.tier} (use: ${TIERS.join(" | ")})`);
89
+ return 1;
90
+ }
91
+ if (flags.tier && !flags.model) {
92
+ ctx.err("--tier requires --model: Terminal never guesses a provider model id from a cost tier");
93
+ return 1;
94
+ }
68
95
  let runtime;
96
+ let workerRuntime;
69
97
  try {
70
- // 사다리: 명시 > (agent CEO > firm) 오버라이드 > prefs > active > detected.
98
+ // CEO는 orchestrator, 본부는 worker. 명시 핀은 기존 firm 호출과의 호환을 위해
99
+ // 두 역할 모두에 적용하고, 미지정 시 각각 model_roles 기본값을 사용한다.
71
100
  runtime = resolveRuntimeForAgent({
72
101
  db,
73
102
  prefs: ctx.prefs,
74
103
  explicit: flags.runtime,
104
+ model: flags.model,
105
+ effort: flags.effort,
106
+ role: "orchestrator",
75
107
  targets: [
76
108
  { scope: "agent", targetId: ceo.id },
77
109
  { scope: "firm", targetId: firm.id },
78
110
  ],
79
111
  });
112
+ workerRuntime = resolveRuntimeForAgent({
113
+ db,
114
+ prefs: ctx.prefs,
115
+ explicit: flags.runtime,
116
+ model: flags.model,
117
+ effort: flags.effort,
118
+ role: "worker",
119
+ targets: [{ scope: "firm", targetId: firm.id }],
120
+ });
121
+ if (flags.tier) {
122
+ runtime.modelTier = flags.tier;
123
+ workerRuntime.modelTier = flags.tier;
124
+ }
80
125
  } catch (e) {
81
126
  ctx.err(String((e && e.message) || e));
82
127
  return 1;
83
128
  }
84
129
  if (runtime.unavailableOverride) ctx.err(ctx.ui.dim(unavailableOverrideNote(runtime, ctx.lang)));
130
+ if (runtime.unavailableRoleSelection) ctx.err(ctx.ui.dim(unavailableRoleNote(runtime, ctx.lang)));
131
+ if (workerRuntime.unavailableOverride) ctx.err(ctx.ui.dim(unavailableOverrideNote(workerRuntime, ctx.lang)));
132
+ if (workerRuntime.unavailableRoleSelection) ctx.err(ctx.ui.dim(unavailableRoleNote(workerRuntime, ctx.lang)));
85
133
  const orch = new Orchestrator({ db, lang: ctx.lang });
86
134
  const dim = ctx.ui.dim;
87
135
  const result = await runFirmTurn({
@@ -91,6 +139,20 @@ async function run(ctx, args) {
91
139
  ceoAgent: ceo,
92
140
  task,
93
141
  runtime,
142
+ workerRuntime,
143
+ resolveWorkerRuntime: flags.runtime || flags.model || flags.effort
144
+ ? null
145
+ : (node) => resolveRuntimeForAgent({
146
+ db,
147
+ prefs: ctx.prefs,
148
+ explicit: null,
149
+ role: "worker",
150
+ targets: [
151
+ { scope: "agent", targetId: node.agent.id },
152
+ { scope: "division", targetId: `${firm.id}:${node.role}` },
153
+ { scope: "firm", targetId: firm.id },
154
+ ],
155
+ }),
94
156
  permission: permissions.normalize(flags.permission || (ctx.prefs && ctx.prefs.permission) || "write"),
95
157
  cwd: process.cwd(),
96
158
  onEvent: (ev) => {
@@ -9,8 +9,8 @@ const HELP = `agentlas — the operating system for agents, in your terminal
9
9
 
10
10
  TALK & RUN
11
11
  <agent> · chat <agent> jump into a chat with one agent
12
- run [agent] [prompt] one-shot (-p print · --runtime · --permission; stdin ok)
13
- firm <firm> [task] delegate to a company's CEO
12
+ run [agent] [prompt] one-shot (-p · --runtime · --model · --effort · --permission)
13
+ firm <firm> [task] delegate to a CEO (--runtime · --model · --effort)
14
14
  chats [n] · open <id> recent conversations · resume one
15
15
 
16
16
  AGENTS & HUB
@@ -20,14 +20,15 @@ AGENTS & HUB
20
20
  build "<request>" build/repair/package an agent or team
21
21
  upload <path> save owner-private in Agent Cloud (--visibility marketplace to publish)
22
22
  import <path> · cd · native prepare local folder agents
23
- list installed agents/companies + active runtime
23
+ list installed agents/companies + orchestrator/worker runtimes
24
24
  experience <sub> portable Experience: list|inspect|validate|save|publish|status|export|unpublish
25
- variant resolve local variant selection
25
+ variant resolve --base-release <id> local variant selection (variant help)
26
26
 
27
27
  EXECUTE
28
28
  storm <goal> Goal+UltraCode harness: plan → allocate → execute → verify [--research]
29
29
  swarm <goal> emergent agent swarm [--parallel N]
30
30
  workforce | network <request> Agent Workforce Ontology route
31
+ hep-local | hep-cloud | hep-hub "<request>" same, restricted to one source scope
31
32
  call "a,b" "<ctx>" · browser · route "<req>" [--json] · research <sub>
32
33
 
33
34
  KNOWLEDGE
@@ -51,10 +52,13 @@ ACCOUNT & OPS
51
52
 
52
53
  IN-REPL (agentlas → interactive, Orca multi-session)
53
54
  /spawn <agent> [task] · /sessions · /tree · /s <n> · /steer <n> <msg> ·
54
- /kill <n> · /rm <n> · /broadcast <msg> · /use · /runtime · /permission
55
+ /kill <n> · /rm <n> · /broadcast <msg> · /use · /runtime · /model · /effort · /permission
55
56
  typing during a running turn queues steering; ctrl-c interrupts the turn
56
57
 
57
- Options: -p|--print · --runtime claude-code|codex|gemini · --permission read|write|full
58
+ Options: -p|--print · --runtime claude-code|codex|gemini · --model <exact-id> ·
59
+ --effort none|minimal|low|medium|high|xhigh|max ·
60
+ --tier economy|balanced|frontier (requires --model) ·
61
+ --permission read|write|full
58
62
  `;
59
63
 
60
64
  function run(ctx) {
@@ -62,4 +66,24 @@ function run(ctx) {
62
66
  return 0;
63
67
  }
64
68
 
65
- module.exports = { run, HELP };
69
+ function runForCommand(ctx, command) {
70
+ const name = String(command || "").trim();
71
+ const rows = HELP.split("\n")
72
+ .map((line) => line.trim())
73
+ .filter((line) => line && !line.endsWith(":"))
74
+ .filter((line) => {
75
+ const commandColumn = line.split(/\s{2,}/, 1)[0];
76
+ return commandColumn
77
+ .split(/\s*·\s*|\s*\|\s*/)
78
+ .some((entry) => entry === name || entry.startsWith(`${name} `));
79
+ });
80
+ ctx.out(`Usage: agentlas ${name} [options]`);
81
+ if (rows.length > 0) {
82
+ for (const row of rows) ctx.out(` ${row}`);
83
+ } else {
84
+ ctx.out(` See "agentlas help" for the full command list.`);
85
+ }
86
+ return 0;
87
+ }
88
+
89
+ module.exports = { run, runForCommand, HELP };
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ /*
3
+ * hep-cloud — 로그인한 오너의 Agent Cloud 에이전트만으로 임시 태스크포스 편성.
4
+ *
5
+ * WHY 이 파일이 따로 있는가 (2026-07-28 수리):
6
+ * COMMAND_ALIASES 가 `hep-cloud → cloud` 로 접혀 있었다. `cloud` 는 이름만 같은
7
+ * 전혀 다른 명령 — 클라우드 자산 보관함(save|publish|package|list|restore|
8
+ * install|delete|field-test)이다. 그래서 `agentlas hep-cloud "<과제>"` 는
9
+ * 과제를 서브커맨드로 읽고 `usage: agentlas cloud <save|…>` 를 뱉으며 exit 1 —
10
+ * hep-cloud 를 한 글자도 언급하지 않는 에러라 사용자에게 다음 수가 없었다.
11
+ * 스코프(오너 Cloud 한정)를 실제로 지키는 표면은 Hephaestus 네이티브 런타임
12
+ * (`hephaestus hep-cloud`)뿐이므로 build/call/legacy-network 와 동일한
13
+ * 패스스루 계약으로 그쪽에 넘긴다. 자산 보관함은 계속 `agentlas cloud …` 다.
14
+ *
15
+ * v1 인자 가드 그대로: help 토큰 → usage 0, 무인자 → usage 실패 exit 1.
16
+ */
17
+ const { create, usageFor, isHelpToken } = require("../hephaestus/runtime.cjs");
18
+
19
+ async function run(ctx, args) {
20
+ if (args.some(isHelpToken)) {
21
+ ctx.out(usageFor("hep-cloud", ctx.lang));
22
+ return 0;
23
+ }
24
+ if (!args.length) {
25
+ ctx.err("✖ " + usageFor("hep-cloud", ctx.lang));
26
+ return 1;
27
+ }
28
+ return create(ctx).cmdHep(["hep-cloud", ...args]);
29
+ }
30
+
31
+ module.exports = { run };
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ /*
3
+ * hep-hub — 공개 Agentlas Hub 에이전트만으로 임시 태스크포스 편성.
4
+ *
5
+ * WHY 이 파일이 따로 있는가 (2026-07-28 수리):
6
+ * COMMAND_ALIASES 가 `hep-hub → search` 로 접혀 있었다. search 는 스태핑이 아니라
7
+ * 마켓플레이스 디렉터리 나열이고(그마저 Cloud+Hub 혼합 표면이다), 실행은 하지
8
+ * 않는다. 그래서 `agentlas hep-hub "<과제>"` 는 과제를 검색어로 읽고 슬러그
9
+ * 목록만 찍은 뒤 exit 0 — 아무것도 실행하지 않고 성공한 척했다(가짜 성공 금지).
10
+ * 공개 Hub 한정 스태핑을 실제로 수행하는 표면은 Hephaestus 네이티브 런타임
11
+ * (`hephaestus hep-hub`)이므로 그쪽에 그대로 넘긴다. 후보만 보고 싶으면
12
+ * 기존 `agentlas search`(=hep-search)가 그대로 남아 있다.
13
+ *
14
+ * v1 인자 가드 그대로: help 토큰 → usage 0, 무인자 → usage 실패 exit 1.
15
+ */
16
+ const { create, usageFor, isHelpToken } = require("../hephaestus/runtime.cjs");
17
+
18
+ async function run(ctx, args) {
19
+ if (args.some(isHelpToken)) {
20
+ ctx.out(usageFor("hep-hub", ctx.lang));
21
+ return 0;
22
+ }
23
+ if (!args.length) {
24
+ ctx.err("✖ " + usageFor("hep-hub", ctx.lang));
25
+ return 1;
26
+ }
27
+ return create(ctx).cmdHep(["hep-hub", ...args]);
28
+ }
29
+
30
+ module.exports = { run };
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ /*
3
+ * hep-local — 이 머신에 등록된 Local 에이전트만으로 임시 태스크포스 편성.
4
+ *
5
+ * WHY 이 파일이 따로 있는가 (2026-07-28 수리):
6
+ * COMMAND_ALIASES 가 `hep-local → workforce` 로 접혀 있었다. 터미널 workforce 는
7
+ * 공개 Hub 메뉴로 스태핑하는 Agent Workforce Ontology 경로이고(엔드포인트:
8
+ * AGENTLAS_MCP_BASE_URL, search_candidates 스키마에 sourceScope 자체가 없음),
9
+ * cmdWorkforce 는 스코프 플래그를 받지 않는다. 즉 "등록된 로컬만" 이라고
10
+ * 문서화된 명령이 조용히 Local+Cloud+공개 Hub 전체로 넓혀 실행됐다 —
11
+ * 사용자가 의도하지 않은 Hub 크레딧 소모까지 포함해서. 스코프는 이름의 전부다.
12
+ * 스코프를 지킬 수 있는 표면은 Hephaestus 네이티브 런타임(`hephaestus hep-local`)
13
+ * 뿐이므로, build/call/legacy-network 와 동일한 패스스루 계약으로 그쪽에 넘긴다.
14
+ *
15
+ * v1 인자 가드 그대로: help 토큰 → usage 0, 무인자 → usage 실패 exit 1
16
+ * (요청 문자열 없는 호출이 자연어 라우팅으로 새는 것 방지).
17
+ */
18
+ const { create, usageFor, isHelpToken } = require("../hephaestus/runtime.cjs");
19
+
20
+ async function run(ctx, args) {
21
+ if (args.some(isHelpToken)) {
22
+ ctx.out(usageFor("hep-local", ctx.lang));
23
+ return 0;
24
+ }
25
+ if (!args.length) {
26
+ ctx.err("✖ " + usageFor("hep-local", ctx.lang));
27
+ return 1;
28
+ }
29
+ return create(ctx).cmdHep(["hep-local", ...args]);
30
+ }
31
+
32
+ module.exports = { run };
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ /*
3
+ * hep-network — 등록된 Local + 오너 Cloud + 공개 Hub 를 연합해 임시 태스크포스 편성.
4
+ *
5
+ * WHY 이 파일이 따로 있는가 (2026-07-28 수리):
6
+ * COMMAND_ALIASES 가 `hep-network → workforce` 로 접혀 있었다. hep-local/hep-cloud/
7
+ * hep-hub 와 정확히 같은 결함이다 — 별칭은 이름만 바꿔주고 스코프는 어디에도
8
+ * 전달하지 않는다. 터미널 workforce(cmdWorkforce)는 callHubTool 로
9
+ * AGENTLAS_MCP_BASE_URL(기본 https://agentlas.cloud/api/mcp/v1)을 직접 치고
10
+ * `{workOrder}` 만 보낸다(engine/agentlas-workforce.cjs:3043). 그 서버는
11
+ * sourceScope 가 없으면 "hub" 로 기본값을 잡는다
12
+ * (agentlas/AgentsAtlas/app/src/lib/mcp/workforce.ts:228). 결과:
13
+ *
14
+ * - 로컬 에이전트와 오너 Cloud 에이전트는 후보 집합에 들어간 적이 없다.
15
+ * - 그런데 영수증의 로스터 핀은 전부 source:"hub" 로 기록된다
16
+ * (engine/workforce/deps.cjs). "네트워크 전량을 봤다" 로 읽힌다.
17
+ *
18
+ * 연합(federation)은 Core 가 소유한다: 세 소스 메뉴를 각각 받아 출처·계보를
19
+ * 증명하고 하나의 CandidateSet 으로 합친 뒤, 선택은 호스트 LLM 이 한다. 터미널이
20
+ * Hub 를 직접 치는 경로에는 그 계층이 통째로 없다. 그래서 build/call/hep-local
21
+ * 과 동일한 패스스루 계약으로 Hephaestus 네이티브 표면에 넘긴다.
22
+ *
23
+ * `agentlas workforce` 자체는 남겨둔다 — 공개 Hub 스태핑을 그 이름으로 명시해
24
+ * 부르는 것은 정직한 사용이다. 이름이 소스 스코프를 약속하는 hep-* 만 옮긴다.
25
+ *
26
+ * v1 인자 가드 그대로: help 토큰 → usage 0, 무인자 → usage 실패 exit 1
27
+ * (요청 문자열 없는 호출이 자연어 라우팅으로 새는 것 방지).
28
+ */
29
+ const { create, usageFor, isHelpToken } = require("../hephaestus/runtime.cjs");
30
+
31
+ async function run(ctx, args) {
32
+ if (args.some(isHelpToken)) {
33
+ ctx.out(usageFor("hep-network", ctx.lang));
34
+ return 0;
35
+ }
36
+ if (!args.length) {
37
+ ctx.err("✖ " + usageFor("hep-network", ctx.lang));
38
+ return 1;
39
+ }
40
+ return create(ctx).cmdHep(["hep-network", ...args]);
41
+ }
42
+
43
+ module.exports = { run };
@@ -47,6 +47,12 @@ const COMMANDS = {
47
47
  evolve: () => require("./evolve.cjs"),
48
48
  variant: () => require("./variant.cjs"),
49
49
  hep: () => require("./hep.cjs"),
50
+ // 소스 스코프 스태핑 3종은 1급 명령이다 — 별칭으로 접으면 스코프가 사라진다.
51
+ // (아래 COMMAND_ALIASES 주석의 2026-07-28 수리 참조.)
52
+ "hep-network": () => require("./hep-network.cjs"),
53
+ "hep-local": () => require("./hep-local.cjs"),
54
+ "hep-cloud": () => require("./hep-cloud.cjs"),
55
+ "hep-hub": () => require("./hep-hub.cjs"),
50
56
  build: () => require("./build.cjs"),
51
57
  connect: () => require("./connect.cjs"),
52
58
  call: () => require("./call.cjs"),
@@ -106,10 +112,25 @@ const GUARDED_NO_ARG = new Set(["search", "install", "upload"]);
106
112
  // 플랫폼 간 이름 통일(오너 결정 2026-07-27): 클로드코드/코덱스에서 부르는 hep-*
107
113
  // 스킬명과 터미널 명령이 서로 다르면 사용자가 어느 표면에 있는지에 따라 이름을
108
114
  // 바꿔 써야 한다. 같은 기능은 어디서든 같은 이름으로 부른다.
115
+ //
116
+ // 불변식(2026-07-28 수리): 별칭은 "같은 기능"에만 건다. 소스 스코프를 이름에
117
+ // 달고 있는 hep-local / hep-cloud / hep-hub 는 여기 넣으면 안 된다 — 별칭은
118
+ // 이름만 바꿔주고 스코프는 어디에도 전달되지 않기 때문이다. 실제로 그랬다:
119
+ // hep-cloud → cloud : 자산 보관함 명령. 과제 문자열을 서브커맨드로 읽어
120
+ // `usage: agentlas cloud <save|…>` + exit 1.
121
+ // hep-local → workforce : cmdWorkforce 는 스코프 플래그를 받지 않는다(전량
122
+ // 공개 Hub 메뉴 스태핑). "로컬 전용"이 조용히 넓어짐.
123
+ // hep-hub → search : 디렉터리 나열만 하고 아무것도 실행하지 않음.
124
+ // 세 명령은 스코프를 실제로 지키는 Hephaestus 네이티브 표면으로 가는 1급 명령
125
+ // (COMMANDS 의 hep-local/hep-cloud/hep-hub)으로 승격했다.
126
+ //
127
+ // 같은 이유로 hep-network 도 별칭에서 뺐다(2026-07-28). 이름은 "Local + owner
128
+ // Cloud + public Hub"인데 cmdWorkforce 는 스코프를 어디에도 싣지 않고
129
+ // agentlas.cloud/api/mcp/v1 을 직접 친다. 그 서버는 sourceScope 가 없으면 "hub"
130
+ // 로 기본값을 잡으므로(agentlas/.../lib/mcp/workforce.ts:228) 로컬·클라우드
131
+ // 에이전트는 후보에 들어간 적이 없는데 결과는 네트워크 전량을 본 것처럼 남았다.
132
+ // 연합은 Core 가 소유한다 — 네이티브 표면으로 넘긴다.
109
133
  const COMMAND_ALIASES = {
110
- "hep-network": "workforce",
111
- network: "workforce",
112
- "hep-cloud": "cloud",
113
134
  "hep-build": "build",
114
135
  "hep-call": "call",
115
136
  "hep-search": "search",
@@ -117,8 +138,6 @@ const COMMAND_ALIASES = {
117
138
  "hep-storm": "storm",
118
139
  "hep-browser": "browser",
119
140
  "hep-connect": "connect",
120
- "hep-local": "workforce",
121
- "hep-hub": "search",
122
141
  };
123
142
 
124
143
  function resolveCommandName(cmd) {
@@ -139,8 +158,20 @@ function dispatch(ctx, argv) {
139
158
  return 1;
140
159
  }
141
160
 
142
- if (DESKTOP_ONLY_SURFACES[cmd] && rest.length === 0) {
143
- ctx.err(`${DESKTOP_ONLY_SURFACES[cmd]}\nIt was not run as a prompt rerun with quotes if you meant a task: agentlas "${cmd} …"`);
161
+ /*
162
+ * 인자 유무와 무관하게 막는다. `rest.length === 0` 조건이 붙어 있던 동안은
163
+ * 단어 하나만 더 붙이면(`agentlas settings theme`, `marketplace browse`)
164
+ * 가드를 그냥 지나쳐 dispatch가 undefined를 반환했고, 엔진은 그걸 프롬프트로
165
+ * 보고 실제 에이전트를 띄웠다 — 제품이 "데스크탑 전용"이라고 선언한 화면
166
+ * 이름에 토큰이 청구되고 에이전트가 사용자 저장소에서 셸까지 돌렸다(실사용
167
+ * 실증: settings theme → System Optimizer가 Bash 실행, marketplace browse →
168
+ * 20883 토큰 소진). agentlas.cjs의 오타 가드는 `normalized.length === 1`
169
+ * 이라 이 경로를 못 받는다. 여기가 유일한 차단 지점이므로 arity를 보지 않는다.
170
+ * 진짜 작업이면 안내대로 따옴표로 묶어 하나의 프롬프트로 넘긴다.
171
+ */
172
+ if (DESKTOP_ONLY_SURFACES[cmd]) {
173
+ const asTask = [rawCmd, ...rest].join(" ");
174
+ ctx.err(`${DESKTOP_ONLY_SURFACES[cmd]}\nIt was not run as a prompt — rerun with quotes if you meant a task: agentlas "${asTask}"`);
144
175
  return 1;
145
176
  }
146
177
 
@@ -5,8 +5,25 @@
5
5
  * 데스크탑과 동일하게 목록에서 숨긴다.
6
6
  */
7
7
  const { activeRuntimeRow, listAvailableCliRuntimes } = require("../runtimes/detect.cjs");
8
+ const { resolvedModelRole } = require("../runtimes/roles.cjs");
8
9
  const { listAgents } = require("../agents/registry.cjs");
9
10
 
11
+ function roleRuntimeLabel(selection, role, en) {
12
+ if (!selection) return en ? "(not set)" : "(미설정)";
13
+ const provider = selection.kind === "byok"
14
+ ? selection.backend || "byok"
15
+ : selection.kind;
16
+ const bits = [
17
+ provider,
18
+ selection.model ? `(${selection.model})` : "",
19
+ selection.effort ? `· effort ${selection.effort}` : "",
20
+ ].filter(Boolean);
21
+ if (role === "worker" && selection.inherit) {
22
+ bits.push(en ? "· inherits orchestrator" : "· 오케스트레이터 상속");
23
+ }
24
+ return bits.join(" ");
25
+ }
26
+
10
27
  function run(ctx) {
11
28
  const db = ctx.db();
12
29
  // 프라이버시 정책(웹 전용/백그라운드 제외)은 registry가 소유한다 — 직접 SQL 금지.
@@ -36,7 +53,14 @@ function run(ctx) {
36
53
  const active = activeRuntimeRow(db);
37
54
  const clis = listAvailableCliRuntimes();
38
55
  ctx.out("");
39
- ctx.out(ctx.ui.bold(en ? "Runtime" : "런타임"));
56
+ ctx.out(ctx.ui.bold(en ? "Model roles" : "모델 역할"));
57
+ const orchestrator = resolvedModelRole(db, "orchestrator");
58
+ const worker = resolvedModelRole(db, "worker");
59
+ ctx.out(` orchestrator: ${roleRuntimeLabel(orchestrator, "orchestrator", en)}`);
60
+ ctx.out(` worker: ${roleRuntimeLabel(worker, "worker", en)}`);
61
+
62
+ ctx.out("");
63
+ ctx.out(ctx.ui.bold(en ? "Legacy runtime compatibility" : "레거시 런타임 호환"));
40
64
  if (active) {
41
65
  ctx.out(` active: ${active.kind}${active.model ? ` (${active.model})` : ""}${active.backend ? ` via ${active.backend}` : ""}`);
42
66
  } else {
@@ -25,7 +25,11 @@ function run(ctx, args) {
25
25
  return 1;
26
26
  }
27
27
  if (rows.length > 1) {
28
- ctx.err(ko ? `모호합니다 접두사를 쓰세요 (${rows.map((r) => r.id.slice(0, 8)).join(", ")})` : `Ambiguous use a longer prefix (${rows.map((r) => r.id.slice(0, 8)).join(", ")})`);
28
+ // 안내 접두사는 사용자가 입력한 것보다 반드시 길어야 한다chats 목록이
29
+ // 8자를 찍으므로, 8자로 고정하면 8자 충돌 시 같은 문자열 두 개를 돌려주는
30
+ // 막다른 길이 된다("더 긴 접두사"를 만들 방법이 없음).
31
+ const hintLen = Math.max(8, prefix.length + 4);
32
+ ctx.err(ko ? `모호합니다 — 더 긴 접두사를 쓰세요 (${rows.map((r) => r.id.slice(0, hintLen)).join(", ")})` : `Ambiguous — use a longer prefix (${rows.map((r) => r.id.slice(0, hintLen)).join(", ")})`);
29
33
  return 1;
30
34
  }
31
35
  const chat = rows[0];