agentlas 1.0.28 → 1.0.35

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 (70) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +5 -2
  3. package/bin/agentlas.cjs +15 -0
  4. package/engine/agentlas-cloud-runtime.cjs +54 -4
  5. package/engine/agentlas-i18n.cjs +4 -4
  6. package/engine/agentlas-input.cjs +10 -3
  7. package/engine/agentlas-judgment.cjs +0 -0
  8. package/engine/agentlas-native-host.cjs +69 -4
  9. package/engine/agentlas-onboard.cjs +20 -0
  10. package/engine/agentlas-ui.cjs +2 -0
  11. package/engine/agentlas-workforce.cjs +69 -13
  12. package/engine/agentlas.cjs +71 -3
  13. package/engine/agents/builder.cjs +84 -0
  14. package/engine/agents/router.cjs +8 -2
  15. package/engine/automation/launchd.cjs +131 -0
  16. package/engine/bootstrap-schema.sql +6 -44
  17. package/engine/browser/cdp.cjs +188 -0
  18. package/engine/browser/vault.cjs +124 -0
  19. package/engine/cli-output.cjs +262 -0
  20. package/engine/cloud-assets/package.cjs +5 -1
  21. package/engine/commands/automation.cjs +37 -1
  22. package/engine/commands/billing.cjs +3 -0
  23. package/engine/commands/browser.cjs +166 -8
  24. package/engine/commands/build.cjs +101 -20
  25. package/engine/commands/connect.cjs +162 -9
  26. package/engine/commands/creds.cjs +65 -3
  27. package/engine/commands/doctor.cjs +66 -4
  28. package/engine/commands/document.cjs +79 -0
  29. package/engine/commands/graph.cjs +1190 -0
  30. package/engine/commands/help.cjs +87 -9
  31. package/engine/commands/hep-cloud.cjs +9 -23
  32. package/engine/commands/hep-hub.cjs +9 -22
  33. package/engine/commands/hep-local.cjs +9 -24
  34. package/engine/commands/hep-network.cjs +9 -35
  35. package/engine/commands/index.cjs +50 -25
  36. package/engine/commands/list.cjs +10 -1
  37. package/engine/commands/mcp.cjs +6 -2
  38. package/engine/commands/native.cjs +18 -2
  39. package/engine/commands/plugin.cjs +22 -0
  40. package/engine/commands/project.cjs +79 -16
  41. package/engine/commands/roles.cjs +210 -0
  42. package/engine/commands/telegram.cjs +23 -16
  43. package/engine/commands/workforce.cjs +63 -12
  44. package/engine/core/desktop-core-fetch.cjs +98 -0
  45. package/engine/core/desktop-core.cjs +170 -0
  46. package/engine/graph/ask-model.cjs +159 -0
  47. package/engine/graph/interview.cjs +960 -0
  48. package/engine/graph/layout.cjs +139 -0
  49. package/engine/graph/package.cjs +223 -0
  50. package/engine/graph/vocabulary.generated.cjs +30 -0
  51. package/engine/hephaestus/local-core.cjs +159 -0
  52. package/engine/hephaestus/runtime.cjs +14 -10
  53. package/engine/project/controller.cjs +8 -8
  54. package/engine/project/team.cjs +99 -0
  55. package/engine/runtime-refusal.cjs +71 -0
  56. package/engine/runtimes/auth-evidence.cjs +78 -0
  57. package/engine/runtimes/detect.cjs +3 -0
  58. package/engine/runtimes/resolve.cjs +1 -1
  59. package/engine/sessions/prompt.cjs +16 -0
  60. package/engine/sessions/session.cjs +24 -2
  61. package/engine/telegram/connect.cjs +202 -0
  62. package/engine/tools/access-notice.cjs +86 -0
  63. package/engine/ui/palette.cjs +7 -3
  64. package/engine/ui/repl.cjs +120 -3
  65. package/engine/vendor/desktop-core.manifest.json +7 -0
  66. package/engine/workforce/capture.cjs +51 -1
  67. package/engine/workforce/deps.cjs +13 -0
  68. package/engine/workforce/local-core-transport.cjs +298 -0
  69. package/package.json +4 -2
  70. package/engine/commands/legacy-network.cjs +0 -29
package/CHANGELOG.md CHANGED
@@ -1,5 +1,107 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.34 — 2026-08-06
4
+
5
+ Telegram, standalone. The terminal can now connect a Telegram bot with no desktop app.
6
+
7
+ - `connect telegram <agent|firm>` reads a bot token from stdin (never argv), verifies it
8
+ against api.telegram.org, stores it 0600, polls getUpdates and pairs the first private
9
+ chat, then sends a confirmation.
10
+ - `connect test <id>` / `connect remove <id>` / `connect status`. The connection core is
11
+ plain HTTPS ported from the desktop; no Electron, no plugin, no raw JSON dump.
12
+
13
+ Not yet standalone: auto-creating the bot by piloting BotFather in a browser (the CDP
14
+ browser hardpoint makes this a tractable next layer; it needs a live Telegram web session).
15
+
16
+ ## 1.0.33 — 2026-08-06
17
+
18
+ Defect sweep: ran every command with a real prompt and fixed what dumped or stalled.
19
+
20
+ - `connect telegram` / `connect status` no longer dump the raw Hephaestus router JSON —
21
+ they show the local Telegram binding table and honestly note that bot issuance/pairing
22
+ still live in Desktop Connect.
23
+ - `route "<request>"` shows a progress spinner during its ~13s Hub round-trip instead of
24
+ sitting silent.
25
+
26
+ ## 1.0.32 — 2026-08-06
27
+
28
+ Standalone: the terminal no longer requires Desktop or the plugin for its core flows.
29
+ (They share artifacts and settings; they are not a prerequisite.)
30
+
31
+ - `project use <agent>` / `project team <agent>…` connect the current folder as a
32
+ project and set an ordered team, entirely from the terminal — so `run "<task>"` and
33
+ plain REPL input work without ever opening Desktop. `project status` shows the team.
34
+ - `build "<request>"` now builds locally: the terminal runtime produces an installable
35
+ agent package (AGENTS.md + manifest.md + README.md) and auto-installs it, instead of
36
+ printing "open the Claude Code / Codex plugin and run /hep-build".
37
+ - Honest-stops in a project-less folder point at `project use`, not "open Desktop".
38
+
39
+ Verified standalone end-to-end: project use → run answered correctly; build produced and
40
+ installed an agent that then ran correctly.
41
+
42
+ ## 1.0.31 — 2026-08-06
43
+
44
+ Interactive UX, measured against first-class REPLs with a real PTY.
45
+
46
+ - The empty prompt now guides: a dim ghost hint ("type a task · / commands · @ files
47
+ · ? shortcuts"), `?` prints a shortcuts card, a second empty Enter points the way.
48
+ - Tab-completing a command that takes arguments appends the space, so you can type the
49
+ argument immediately; no-arg commands are unchanged.
50
+ - A plain-language task in a folder with no connected project now says exactly why and how
51
+ to run anyway (localized), instead of a single "recovering…" line that hid the reason.
52
+ Every controller honest-stop carries a machine code so this can never be swallowed again.
53
+ - New PTY-driven gates (repl-guidance, honest-stop-not-swallowed) — these paths live inside
54
+ the readline session and are invisible to spawnSync tests.
55
+
56
+ ## 1.0.30 — 2026-08-06
57
+
58
+ CLI-conventions hardening, measured against clig.dev and first-class CLIs.
59
+
60
+ - A closed pipe is a reader saying stop: `agentlas … | head` no longer crashes with EPIPE.
61
+ - Unexpected crashes now print a one-line summary and a pre-filled GitHub issue URL, not just a raw stack.
62
+ - `TERM=dumb` disables ANSI colors (editor shells, some CI).
63
+ - Secrets stop landing in shell history: `creds save --value -` reads the secret from stdin;
64
+ passing it via argv now prints an exposure notice.
65
+ - `list --json`, `doctor --json`, `roles --json` — machine contracts for scripts.
66
+ - Workforce staffing survives transient API failures: a typed transient error
67
+ (connection closed mid-response, timeouts, overload) on a no-authority/read-only stage
68
+ is retried exactly once; write-capable stages are never retried. A 40-minute, 2.1M-token
69
+ live run previously died on the final re-verification call for exactly this.
70
+ - New gate: clig-conventions-contract; retry contract added to the workforce runtime gate.
71
+
72
+ ## 1.0.29 — 2026-08-05
73
+
74
+ Terminal-wide audit release. Every command was executed for real; what follows fixes what that audit found.
75
+
76
+ - Native federated staffing: `hep-network` / `hep-local` / `hep-cloud` / `hep-hub` now run this
77
+ terminal's own workforce loop with the local Agentlas-OS core federating the declared source scope.
78
+ (They previously passed through to an external CLI stub that always answered exit 3.)
79
+ Honest stop with install guidance when the local core is missing — no silent fallback to the public Hub.
80
+ - `workforce` now declares its sourceScope ("hub") explicitly instead of relying on the server default.
81
+ - `doctor` distinguishes installed from signed-in: local sign-in evidence per runtime, a warning
82
+ (not "all clear") when the active runtime has none.
83
+ - New `roles` command: view and set orchestrator/worker model roles from the terminal
84
+ (`roles set <role> <runtime> [--model id] [--effort lv]`, `roles set worker --inherit`).
85
+ REPL `/model`·`/runtime` now say they are session-scoped and point to `agentlas roles`.
86
+ - Setup wizard prints install/sign-in guidance when the chosen runtime is missing or unauthenticated.
87
+ - `creds list` (names and stores only — values never printed), `mcp list`, Korean `help` body.
88
+ - The graph command group ships in the npm package for the first time (it landed after 1.0.28 was published).
89
+ - New gates: user-scenarios (93 real CLI invocations), local-core transport wire contract,
90
+ doctor auth-evidence, roles round-trip, onboarding guidance, EN/KO help sync.
91
+ - `agentlas <command> --help` reaches the command's own help. It printed a two-line stub
92
+ scraped from the help table, so `graph --help` never showed the eight lines `graph help` has —
93
+ and that is the spelling people try first. (The gate had pinned the stub as correct; its
94
+ contract now asks whether real help was shown.)
95
+ - `graph install --name "<new name>"` works. The documented flag had never worked: its value was
96
+ appended to the file path, so the install died with "no such file".
97
+ - `graph show` stops indenting a straight chain deeper at every step — fourteen steps meant
98
+ twenty-eight columns. Depth now marks real branches (a fork, a failure exit).
99
+ - Building a graph follows your language setting. One Korean character anywhere in the request
100
+ forced the interview to Korean while `graph show`, the list, and errors stayed English.
101
+ - Graph steps written as code declare the pip packages they import, and a verification step is
102
+ now required whenever a step that changes something outside sends out a value an earlier step
103
+ computed — an unattended run must not ship an empty or invented result.
104
+
3
105
  ## 1.0.26 — 2026-08-03
4
106
 
5
107
  - **`agentlas list` stops letting the terminal cut its own output.** Slugs were
package/README.md CHANGED
@@ -94,10 +94,13 @@ agentlas variant resolve --base-release <id> # Preview local variant compatibili
94
94
  ```sh
95
95
  agentlas storm "<goal>" # Goal + UltraCode harness: plan -> assign -> execute -> verify [--research]
96
96
  agentlas swarm "<goal>" # Emergent multi-agent swarm [--parallel N]
97
- agentlas workforce "<prompt>" # Route through Agent Workforce Ontology
97
+ agentlas workforce "<prompt>" # Route through Agent Workforce Ontology (public Hub menu)
98
98
  agentlas network "<prompt>" # Alias for workforce
99
99
  agentlas taskforce "<prompt>" # Alias for workforce
100
- agentlas legacy-network "<prompt>" # Legacy Hephaestus solver (explicit invocation only)
100
+ agentlas hep-network "<prompt>" # Staff across Local + owner Cloud + public Hub (local Core federation)
101
+ agentlas hep-local "<prompt>" # Same staffing, registered Local agents only
102
+ agentlas hep-cloud "<prompt>" # Same staffing, owner Agent Cloud only
103
+ agentlas hep-hub "<prompt>" # Same staffing, public Hub only
101
104
  agentlas call "a,b" "<context>" # Call explicitly named Hub or Cloud agents
102
105
  agentlas browser [...] # Real browser hardpoint launcher
103
106
  agentlas route "<prompt>" [--json] # Preview routing decisions without execution
package/bin/agentlas.cjs CHANGED
@@ -18,6 +18,21 @@
18
18
  */
19
19
  "use strict";
20
20
 
21
+ /*
22
+ * EPIPE 방어 — 발행본 실설치 검증에서 발견(2026-08-06): `agentlas version | head -1`
23
+ * 처럼 파이프 소비자가 먼저 닫히면 stdout write가 EPIPE를 던져 스택트레이스로
24
+ * 크래시했다. `| head` `| less` `| grep -m1`은 CLI의 일상 사용 패턴이다.
25
+ * 파이프 단절은 오류가 아니라 "그만 읽겠다"는 신호 — 조용히 성공 종료한다.
26
+ * (시나리오 게이트가 못 잡았던 이유: spawnSync는 파이프를 끝까지 읽는다.
27
+ * test/user-scenarios-contract.cjs 의 파이프 시나리오가 이 계약을 잠근다.)
28
+ */
29
+ for (const stream of [process.stdout, process.stderr]) {
30
+ stream.on("error", (error) => {
31
+ if (error && error.code === "EPIPE") process.exit(0);
32
+ throw error;
33
+ });
34
+ }
35
+
21
36
  const fs = require("node:fs");
22
37
  const os = require("node:os");
23
38
  const path = require("node:path");
@@ -38,11 +38,56 @@ function collectFiles(root) {
38
38
  return files.sort((a, b) => a.path.localeCompare(b.path));
39
39
  }
40
40
 
41
+ // Local sourcePackage identity — the wizard manifest's `packageHash`.
42
+ //
43
+ // This is NOT the delivered Agent Cloud artifact hash. That one is
44
+ // `path-sha256-executable-v2` and lives in engine/cloud-assets/package.cjs,
45
+ // byte-identical with Desktop's hashPackage and Agentlas-OS upload.py. The two
46
+ // are separate contracts on purpose (upload.py: "distinct from agentlas.json's
47
+ // local sourcePackage hash contract").
48
+ //
49
+ // The canonical definition of THIS hash is the kernel's
50
+ // `canonical_package_hash_hex` in Agentlas-OS/agentlas_cloud/runtime.py. This
51
+ // file mirrors it and must produce the same digest for the same folder; the
52
+ // two used to disagree on every input because this copy had no version prefix
53
+ // and skipped only agentlas.json. Keep the three constants below in step with
54
+ // the kernel: PACKAGE_HASH_VERSION, PACKAGE_HASH_EXCLUDED_PATHS, and the
55
+ // experience-lineage rule.
56
+ const PACKAGE_HASH_VERSION = "agentlas-package-hash/v2";
57
+ const LOCAL_EXPERIENCE_LINEAGE_PATH = ".agentlas/experience-relations.jsonl";
58
+ const PACKAGE_HASH_EXCLUDED_PATHS = new Set([
59
+ "agentlas.json",
60
+ ".agentlas/brief.json",
61
+ ".agentlas/security-scan.json",
62
+ ".agentlas/security-llm-judgment.json",
63
+ ".agentlas/field-test-report.json",
64
+ LOCAL_EXPERIENCE_LINEAGE_PATH,
65
+ ]);
66
+
67
+ function isLocalExperienceLineagePath(filePath) {
68
+ const normalized = String(filePath || "").replaceAll("\\", "/");
69
+ return (
70
+ normalized === LOCAL_EXPERIENCE_LINEAGE_PATH ||
71
+ normalized.startsWith(`${LOCAL_EXPERIENCE_LINEAGE_PATH}.`) ||
72
+ normalized.startsWith(".agentlas/.experience-relations.jsonl.")
73
+ );
74
+ }
75
+
76
+ function packageHashIncludes(filePath) {
77
+ const normalized = String(filePath || "").replaceAll("\\", "/");
78
+ return !PACKAGE_HASH_EXCLUDED_PATHS.has(normalized) && !isLocalExperienceLineagePath(normalized);
79
+ }
80
+
41
81
  function hashPackage(files) {
42
82
  const h = crypto.createHash("sha256");
43
- for (const file of files) {
44
- if (file.path === "agentlas.json") continue;
45
- h.update(file.path);
83
+ h.update(PACKAGE_HASH_VERSION);
84
+ h.update("\0");
85
+ // Codepoint order, not localeCompare: the kernel sorts by raw string order.
86
+ const ordered = [...files].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
87
+ for (const file of ordered) {
88
+ const normalized = String(file.path || "").replaceAll("\\", "/");
89
+ if (!packageHashIncludes(normalized)) continue;
90
+ h.update(normalized);
46
91
  h.update("\0");
47
92
  h.update(file.content);
48
93
  h.update("\0");
@@ -55,9 +100,14 @@ function inferEntry(files) {
55
100
  return ["AGENTS.md", "agent.md", "CLAUDE.md", "README.md"].find((candidate) => paths.has(candidate)) || files[0]?.path || "AGENTS.md";
56
101
  }
57
102
 
103
+ // Skills actually present in the package. An empty result stays empty.
104
+ // A package with no skills used to be given a literal placeholder skill id.
105
+ // Once skills live outside the core that placeholder fires on every modular
106
+ // agent and fills the Workforce index with a skill nobody has.
107
+ // An absent value is an empty list, never a stand-in.
58
108
  function inferSkills(files) {
59
109
  const skills = files.map((file) => file.path.match(/(?:^|\/)skills\/([^/]+)\/SKILL\.md$/)?.[1]).filter(Boolean);
60
- return [...new Set(skills)].sort().length ? [...new Set(skills)].sort() : ["agentlas-package"];
110
+ return [...new Set(skills)].sort();
61
111
  }
62
112
 
63
113
  function buildManifest(root, options = {}) {
@@ -74,7 +74,6 @@ const STRINGS = {
74
74
  "effortUsage": "usage: /effort low|medium|high|max|off|auto",
75
75
  "permSet": "permission → %s",
76
76
  "permUsage": "usage: /permission read|write|full",
77
- "legacyNetworkUsage": "usage: /legacy-network <request>",
78
77
  "stormUsage": "usage: /storm <goal> [--research]",
79
78
  "buildUsage": "usage: /build <agent-or-team request>",
80
79
  "routeUsage": "usage: /route <request> [--json]",
@@ -190,7 +189,6 @@ const STRINGS = {
190
189
  "help.search": "discover agents in the Hub",
191
190
  "help.install": "install an agent from the Hub by slug",
192
191
  "help.network": "let the host LLM staff and execute an ontology-grounded exact-release task force",
193
- "help.legacyNetwork": "run the explicit compatibility-only Hephaestus network route",
194
192
  "help.browser": "real browser execution hardpoint",
195
193
  "help.connect": "wire Telegram / platforms to an agent team",
196
194
  "help.swarm": "fan out an emergent agent swarm on a goal",
@@ -251,6 +249,8 @@ const STRINGS = {
251
249
  "wiz.runtimeAuto": "Use the app's active runtime",
252
250
  "wiz.runtimeInstalled": "installed",
253
251
  "wiz.runtimeMissing": "not installed",
252
+ "wiz.runtimeInstallHint": "This runtime is not installed yet. Install it first: %s",
253
+ "wiz.runtimeLoginHint": "No local sign-in evidence for this runtime. Sign in before your first run (run: %s, then follow its login flow).",
254
254
  "wiz.permQ": "How much should agents be allowed to do by default?",
255
255
  "wiz.permRead": "read — inspect only; workspace writes blocked",
256
256
  "wiz.permWrite": "write — workspace + runtime temp writes; external MCP off (recommended)",
@@ -323,7 +323,6 @@ const STRINGS = {
323
323
  "effortUsage": "사용법: /effort low|medium|high|max|off|auto",
324
324
  "permSet": "권한 → %s",
325
325
  "permUsage": "사용법: /permission read|write|full",
326
- "legacyNetworkUsage": "사용법: /legacy-network <요청>",
327
326
  "stormUsage": "사용법: /storm <목표> [--research]",
328
327
  "buildUsage": "사용법: /build <만들 에이전트·팀 설명>",
329
328
  "routeUsage": "사용법: /route <요청> [--json]",
@@ -438,7 +437,6 @@ const STRINGS = {
438
437
  "help.search": "Hub에서 에이전트 발견",
439
438
  "help.install": "식별자로 Hub 에이전트 설치",
440
439
  "help.network": "상위 LLM이 온톨로지에서 정확한 릴리스 TF를 선발·실행",
441
- "help.legacyNetwork": "호환성 전용 Hephaestus 네트워크 경로를 명시적으로 실행",
442
440
  "help.browser": "실제 브라우저 실행 하드포인트",
443
441
  "help.connect": "Telegram/플랫폼을 에이전트 팀에 연결",
444
442
  "help.swarm": "목표에 창발형 에이전트 스웜 전개",
@@ -497,6 +495,8 @@ const STRINGS = {
497
495
  "wiz.runtimeAuto": "앱의 활성 런타임 사용",
498
496
  "wiz.runtimeInstalled": "설치됨",
499
497
  "wiz.runtimeMissing": "미설치",
498
+ "wiz.runtimeInstallHint": "이 런타임은 아직 설치되어 있지 않습니다. 먼저 설치하세요: %s",
499
+ "wiz.runtimeLoginHint": "이 런타임의 로그인 흔적이 없습니다. 첫 실행 전에 로그인하세요 (%s 실행 후 로그인 절차 진행).",
500
500
  "wiz.permQ": "에이전트가 기본적으로 어디까지 할 수 있게 할까요?",
501
501
  "wiz.permRead": "read — 조회만; 작업 공간 쓰기 차단",
502
502
  "wiz.permWrite": "write — 작업 공간·런타임 임시 경로 쓰기; 외부 MCP 꺼짐 (권장)",
@@ -300,7 +300,6 @@ const SLASH_COMMAND_META = [
300
300
  { command: "/search", description: "Discover agents in the Hub", category: "Hub", usage: "/search <what you need>", detail: "Search the Agentlas Hub + local for an agent that fits the task (hep-search)." },
301
301
  { command: "/install", description: "Install an agent from the Hub by slug", category: "Hub", usage: "/install <slug>", detail: "Install a marketplace agent into this terminal." },
302
302
  { command: "/network", description: "Staff and execute an ontology-grounded task force", category: "Engine", usage: "/network <request> [--benchmark]", detail: "The active top host LLM creates the work order, searches the Hub workforce ontology, selects exact releases, and executes a receipt-backed task force.", aliases: ["/taskforce", "/workforce"] },
303
- { command: "/legacy-network", description: "Run the compatibility Hephaestus network route", category: "Engine", usage: "/legacy-network <request>", detail: "Explicit compatibility escape hatch; never used as a fallback by /network." },
304
303
  { command: "/browser", description: "Real browser execution hardpoint", category: "Engine", usage: "/browser [sub]", detail: "Runs the Agentlas browser hardpoint (hep-browser)." },
305
304
  { command: "/connect", description: "Wire Telegram / platforms to an agent team", category: "Hub", usage: "/connect", detail: "Runs Hephaestus hep-connect for platform integration." },
306
305
  { command: "/doctor", description: "Check runtimes and local data", category: "Health", usage: "/doctor", detail: "Run local checks for runtimes, data, credentials, and setup." },
@@ -351,7 +350,6 @@ const HELP_KEY_BY_COMMAND = {
351
350
  "/research": "help.research",
352
351
  "/search": "help.search",
353
352
  "/network": "help.network",
354
- "/legacy-network": "help.legacyNetwork",
355
353
  "/browser": "help.browser",
356
354
  "/connect": "help.connect",
357
355
  "/doctor": "help.doctor",
@@ -706,7 +704,16 @@ function attachSlashPalette(rl, opts = {}) {
706
704
  const list = rows();
707
705
  if (!list.length) return false;
708
706
  if (state.selected < 0 || state.selected >= list.length) state.selected = 0;
709
- state.selectedCommand = list[state.selected].command;
707
+ const row = list[state.selected];
708
+ /*
709
+ * 인자를 받는 명령은 확정 즉시 스페이스까지 넣는다 (2026-08-06, 레퍼런스
710
+ * 대조): Tab 후 바로 인자를 타이핑하게 — 스페이스를 손으로 넣는 한 박자가
711
+ * 모든 인자형 명령에서 반복되는 마찰이었다. 인자 없는 명령(/help 등)은
712
+ * 그대로 — Enter 로 즉시 실행하는 흐름을 깨지 않는다.
713
+ */
714
+ const takesArgs = Boolean(String(row.usage || row.args || "").trim())
715
+ && String(row.usage || `${row.command} ${row.args || ""}`).trim() !== row.command;
716
+ state.selectedCommand = row.command + (takesArgs ? " " : "");
710
717
  replaceLine(state.selectedCommand);
711
718
  clear();
712
719
  return true;
Binary file
@@ -13,6 +13,7 @@
13
13
  * 스키마는 실측으로 확인됨 (cli/agentlas.cjs 상단 주석 참고).
14
14
  */
15
15
  const { execFileSync, spawn } = require("node:child_process");
16
+ const { detectRuntimeRefusal } = require("./runtime-refusal.cjs");
16
17
  const crypto = require("node:crypto");
17
18
  const fs = require("node:fs");
18
19
  const os = require("node:os");
@@ -421,11 +422,26 @@ function handleClaudeLine(line, st, ui) {
421
422
  cost_usd: obj.total_cost_usd,
422
423
  duration_ms: obj.duration_ms,
423
424
  };
424
- if (obj.is_error) st.error = obj.result || "claude error";
425
+ if (obj.is_error) {
426
+ st.error = obj.result || "claude error";
427
+ // api_error_status:429·error:"rate_limit"이 실려 오는 실측 형태 — 한도는 quota.
428
+ st.errorKind = obj.api_error_status === 429 || obj.terminal_reason === "api_error"
429
+ ? "quota" : "exit";
430
+ st.errorSource = "marker";
431
+ }
425
432
  return;
426
433
  case "rate_limit_event":
434
+ /*
435
+ * ★한도 거절은 **표식이다** — 경고만 찍고 버리면 안 된다.
436
+ * 실측(2026-08-06): 이 이벤트가 왔는데도 error가 비어 있어, 뒤따르는 거절문
437
+ * ("You've hit your weekly limit")이 정상 답으로 세어졌다. 살아 있는 다른
438
+ * 런타임으로 폴백이 안 걸린 진범. status:rejected면 그 자체로 실패다.
439
+ */
427
440
  if (obj.rate_limit_info && obj.rate_limit_info.status === "rejected") {
428
441
  ui.warn(uiText(ui, "runtime.rateLimit", "Claude"));
442
+ if (!st.error) st.error = "claude rate limit rejected";
443
+ st.errorKind = "quota";
444
+ st.errorSource = "marker";
429
445
  }
430
446
  return;
431
447
  default:
@@ -501,6 +517,8 @@ function handleCodexLine(line, st, ui) {
501
517
  case "turn.failed":
502
518
  case "error":
503
519
  st.error = (obj.error && (obj.error.message || obj.error)) || "codex error";
520
+ st.errorKind = "exit";
521
+ st.errorSource = "marker";
504
522
  ui.error(String(st.error));
505
523
  st.errorShown = true;
506
524
  return;
@@ -756,9 +774,24 @@ function handleGeminiLine(line, st, ui) {
756
774
  duration_ms: s.duration_ms,
757
775
  };
758
776
  st.finalText = st.text;
759
- if (obj.status && obj.status !== "success") st.error = `gemini ${obj.status}`;
777
+ // error 이벤트가 이미 구체 사유를 남겼으면 뭉뚱그린 상태 문자열로 덮지 않는다.
778
+ if (obj.status && obj.status !== "success" && !st.error) {
779
+ st.error = `gemini ${obj.status}`;
780
+ st.errorKind = "exit";
781
+ st.errorSource = "marker";
782
+ }
760
783
  return;
761
784
  }
785
+ case "error":
786
+ /*
787
+ * ★프로토콜이 선언하는 이벤트인데 핸들러가 없어 조용히 버려졌다
788
+ * (capture.cjs는 목록에 적어 두고도 안 읽었다). 인증 실패("IneligibleTierError")
789
+ * 같은 것이 여기로 온다 — 버리면 빈 성공이 된다.
790
+ */
791
+ st.error = (obj.error && (obj.error.message || obj.error)) || "gemini error";
792
+ st.errorKind = /auth|login|credential|tier|eligib/i.test(String(st.error)) ? "auth" : "exit";
793
+ st.errorSource = "marker";
794
+ return;
762
795
  default:
763
796
  return;
764
797
  }
@@ -806,8 +839,20 @@ function runNativeTurn(req) {
806
839
  } else if (kind === "gemini") {
807
840
  args = geminiArgs(launchReq);
808
841
  lineHandler = (l) => handleGeminiLine(l, st, ui);
842
+ } else if (kind === "agy") {
843
+ /*
844
+ * Antigravity CLI — stream-json이 없다(실측 1.1.10). 평문 stdout을 그대로 텍스트로.
845
+ * ★--prompt는 값 플래그가 아니라 --print의 별칭이다(실측: 프롬프트가 조용히 유실됐던
846
+ * 사고) — 프롬프트는 반드시 위치 인자로 넘긴다. 시스템 프롬프트는 본문에 앞세운다.
847
+ */
848
+ const agyPrompt = launchReq.systemPrompt
849
+ ? `${launchReq.systemPrompt}\n\n---\n\n${launchReq.prompt}`
850
+ : launchReq.prompt;
851
+ args = ["--print", agyPrompt, ...(launchReq.model ? ["--model", launchReq.model] : [])];
852
+ plainStream = true;
853
+ lineHandler = null;
809
854
  } else {
810
- return Promise.resolve({ text: "", session: st.session, error: `unknown runtime: ${kind}` });
855
+ return Promise.resolve({ text: "", session: st.session, error: `unknown runtime: ${kind}`, errorKind: "unsupported", errorSource: "marker" });
811
856
  }
812
857
  } catch (error) {
813
858
  const message = error && error.message ? error.message : String(error);
@@ -883,6 +928,8 @@ function runNativeTurn(req) {
883
928
  session: st.session,
884
929
  usage: st.usage,
885
930
  error: termination ? termination.message : "native runtime stopped",
931
+ errorKind: "timeout",
932
+ errorSource: "marker",
886
933
  terminationVerified: Boolean(termination?.verified),
887
934
  });
888
935
  };
@@ -976,13 +1023,31 @@ function runNativeTurn(req) {
976
1023
  ui.stopSpinner();
977
1024
  const text = (st.finalText || st.text || "").trim();
978
1025
  if (st.usage) ui.cost(st.usage);
1026
+ /*
1027
+ * ★표식 없이 완주했는데 산출물이 거절 고지문인 경우 — 실측: codex 한도는
1028
+ * 거절문이 agent_message로 오고 turn.completed로 끝난다(표식 0, exit 0).
1029
+ * 이 한 자리에서만 텍스트 판별을 허용하고, 출처를 heuristic으로 남긴다
1030
+ * (판별 규칙은 runtime-refusal.cjs 한 곳 — 여기서 정규식을 다시 쓰지 않는다).
1031
+ */
1032
+ if (code === 0 && !st.error) {
1033
+ const refusal = detectRuntimeRefusal(text);
1034
+ if (refusal) {
1035
+ st.error = refusal.message;
1036
+ st.errorKind = refusal.kind;
1037
+ st.errorSource = "heuristic";
1038
+ }
1039
+ }
979
1040
  const exitError = code === 0
980
1041
  ? st.error
981
1042
  : st.error || [
982
1043
  `${kind} exited with code ${code == null ? "unknown" : code}`,
983
1044
  stripAnsi(stderrBuf).slice(-4000),
984
1045
  ].filter(Boolean).join("\n");
985
- finish({ text, session: st.session, usage: st.usage, error: exitError });
1046
+ if (exitError && !st.errorKind) { st.errorKind = "exit"; st.errorSource = st.errorSource || "exit"; }
1047
+ finish({
1048
+ text, session: st.session, usage: st.usage, error: exitError,
1049
+ ...(exitError ? { errorKind: st.errorKind, errorSource: st.errorSource } : {}),
1050
+ });
986
1051
  });
987
1052
 
988
1053
  armIdle();
@@ -142,6 +142,26 @@ async function runOnboard({ ui, rl, helpers, persist }) {
142
142
  rtOpts.forEach((o, i) => optionLine(i + 1, o.label, i === 0));
143
143
  const ri = await pickNum(rtOpts.length);
144
144
  const runtime = rtOpts[ri - 1].value;
145
+ // missing인 런타임을 골라도 저장은 한다(미리 설정) — 하지만 조용히 저장하면
146
+ // 첫 실행 실패가 배신이 된다(2026-08-05 감사 결함 A). 설치 명령을 그 자리에서
147
+ // 알려주고, 로그인 흔적이 없으면 로그인 절차까지 말한다.
148
+ if (runtime !== "auto") {
149
+ const installHint = {
150
+ "claude-code": "npm i -g @anthropic-ai/claude-code",
151
+ codex: "npm i -g @openai/codex",
152
+ gemini: "npm i -g @google/gemini-cli",
153
+ }[runtime];
154
+ if (!H.which(H.RUNTIME_BIN[runtime])) {
155
+ if (installHint) printIndented(ui.t("wiz.runtimeInstallHint", installHint), c.dim);
156
+ } else {
157
+ try {
158
+ const { runtimeAuthEvidence } = require("./runtimes/auth-evidence.cjs");
159
+ if (runtimeAuthEvidence(runtime).status === "none") {
160
+ printIndented(ui.t("wiz.runtimeLoginHint", H.RUNTIME_BIN[runtime]), c.dim);
161
+ }
162
+ } catch { /* evidence unavailable — say nothing rather than guess */ }
163
+ }
164
+ }
145
165
 
146
166
  // Step 3 — default permission
147
167
  ui.line("");
@@ -15,6 +15,8 @@ const RESET = "\x1b[0m";
15
15
 
16
16
  function colorEnabled() {
17
17
  if (process.env.NO_COLOR != null && process.env.NO_COLOR !== "") return false;
18
+ // clig.dev: TERM=dumb 인 터미널(에디터 내장 셸, 일부 CI)은 ANSI를 렌더하지 못한다.
19
+ if (process.env.TERM === "dumb") return false;
18
20
  if (process.env.FORCE_COLOR === "1" || process.env.FORCE_COLOR === "true") return true;
19
21
  if (process.env.AGENTLAS_NO_COLOR === "1") return false;
20
22
  return !!process.stdout.isTTY;