agentlas 1.0.41 → 1.0.43

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 (72) hide show
  1. package/CHANGELOG.md +40 -8
  2. package/engine/agentlas-workforce.cjs +1 -1
  3. package/engine/commands/index.cjs +5 -5
  4. package/engine/hephaestus/runtime.cjs +1 -1
  5. package/engine/storm/storm.cjs +1 -1
  6. package/engine/storm/swarm.cjs +1 -1
  7. package/engine/ui/repl.cjs +4 -4
  8. package/engine/ui/{pitui-screens.cjs → screens.cjs} +143 -6
  9. package/engine/ui/{pitui-shell.cjs → shell.cjs} +42 -27
  10. package/engine/ui/width.cjs +1 -1
  11. package/engine/vendor/mermaid/LICENSE +205 -0
  12. package/engine/vendor/mermaid/ansi.js +23 -0
  13. package/engine/vendor/mermaid/canvas.js +366 -0
  14. package/engine/vendor/mermaid/graph.js +91 -0
  15. package/engine/vendor/mermaid/index.js +100 -0
  16. package/engine/vendor/mermaid/labels.js +324 -0
  17. package/engine/vendor/mermaid/layout-seq.js +194 -0
  18. package/engine/vendor/mermaid/layout.js +881 -0
  19. package/engine/vendor/mermaid/package.json +1 -0
  20. package/engine/vendor/mermaid/parse.js +1108 -0
  21. package/engine/vendor/mermaid/source-box.js +78 -0
  22. package/engine/vendor/mermaid/types.js +1 -0
  23. package/engine/vendor/mermaid/width-data.js +994 -0
  24. package/engine/vendor/mermaid/width.js +76 -0
  25. package/engine/vendor/tui/LICENSE +20 -0
  26. package/engine/vendor/tui/autocomplete.js +632 -0
  27. package/engine/vendor/tui/components/alt-screen-flash.js +37 -0
  28. package/engine/vendor/tui/components/box.js +104 -0
  29. package/engine/vendor/tui/components/cancellable-loader.js +35 -0
  30. package/engine/vendor/tui/components/editor.js +1961 -0
  31. package/engine/vendor/tui/components/h-stack.js +43 -0
  32. package/engine/vendor/tui/components/image.js +90 -0
  33. package/engine/vendor/tui/components/input.js +378 -0
  34. package/engine/vendor/tui/components/loader.js +69 -0
  35. package/engine/vendor/tui/components/markdown.js +806 -0
  36. package/engine/vendor/tui/components/scroll-view.js +173 -0
  37. package/engine/vendor/tui/components/select-list.js +159 -0
  38. package/engine/vendor/tui/components/settings-list.js +182 -0
  39. package/engine/vendor/tui/components/spacer.js +23 -0
  40. package/engine/vendor/tui/components/stack.js +111 -0
  41. package/engine/vendor/tui/components/text.js +89 -0
  42. package/engine/vendor/tui/components/truncated-text.js +51 -0
  43. package/engine/vendor/tui/components/v-stack.js +26 -0
  44. package/engine/vendor/tui/deps/east-asian-width/LICENSE +9 -0
  45. package/engine/vendor/tui/deps/east-asian-width/index.js +30 -0
  46. package/engine/vendor/tui/deps/east-asian-width/lookup-data.js +21 -0
  47. package/engine/vendor/tui/deps/east-asian-width/lookup.js +138 -0
  48. package/engine/vendor/tui/deps/east-asian-width/utilities.js +24 -0
  49. package/engine/vendor/tui/deps/marked/LICENSE +44 -0
  50. package/engine/vendor/tui/deps/marked/index.js +77 -0
  51. package/engine/vendor/tui/editor-component.js +2 -0
  52. package/engine/vendor/tui/fuzzy.js +110 -0
  53. package/engine/vendor/tui/index.js +42 -0
  54. package/engine/vendor/tui/keybindings.js +209 -0
  55. package/engine/vendor/tui/keys.js +1174 -0
  56. package/engine/vendor/tui/kill-ring.js +44 -0
  57. package/engine/vendor/tui/latex.js +1264 -0
  58. package/engine/vendor/tui/layout-node.js +6 -0
  59. package/engine/vendor/tui/layout.js +314 -0
  60. package/engine/vendor/tui/native-modifiers.js +60 -0
  61. package/engine/vendor/tui/package.json +1 -0
  62. package/engine/vendor/tui/stdin-buffer.js +361 -0
  63. package/engine/vendor/tui/terminal-colors.js +59 -0
  64. package/engine/vendor/tui/terminal-image.js +518 -0
  65. package/engine/vendor/tui/terminal.js +436 -0
  66. package/engine/vendor/tui/tui-alt-screen.js +902 -0
  67. package/engine/vendor/tui/tui-main-screen.js +533 -0
  68. package/engine/vendor/tui/tui.js +937 -0
  69. package/engine/vendor/tui/undo-stack.js +25 -0
  70. package/engine/vendor/tui/utils.js +1191 -0
  71. package/engine/vendor/tui/word-navigation.js +96 -0
  72. package/package.json +2 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.43 — 2026-08-11
4
+
5
+ Zero runtime dependencies. The renderer now lives in this repository.
6
+
7
+ - The terminal shell's renderer and diagram sources are vendored into
8
+ `engine/vendor/` and `package.json` declares no runtime dependencies at all.
9
+ An exact version pin already prevented drift and tampering, but a package
10
+ removed from the registry would still have broken installs; that last failure
11
+ mode is now gone, and the code is ours to fix.
12
+ - Upstream copyright notices are preserved beside each vendored tree, as the
13
+ MIT and Apache-2.0 terms require. The vendoring script refuses to run if a
14
+ notice is missing.
15
+ - New release gate `vendor-tui-sync` fails when the vendored tree and its
16
+ upstream differ, so the copy cannot rot silently.
17
+
18
+ ## 1.0.42 — 2026-08-11
19
+
20
+ Projects, automations and firms reach the interactive shell; the shell is
21
+ enabled with `AGENTLAS_TUI=1`.
22
+
23
+ - `/projects` — every connected project with chat/task counts, and a clear
24
+ marker for the one this folder belongs to (or an explicit warning when the
25
+ folder is not connected).
26
+ - `/automations` — the full list with schedule, next run, run count and failure
27
+ count; `/automations <name>` opens the detail view with recent run outcomes
28
+ and the exact commands to run or toggle it.
29
+ - `/firms` — teams with member counts; `/firms <slug>` shows the roster.
30
+ - Table layout no longer inflates narrow status columns when a row overflows
31
+ the terminal width.
32
+ - The shell is now enabled with `AGENTLAS_TUI=1`. Renderer internals are an
33
+ implementation detail and no longer appear in commands, environment
34
+ variables, or on screen.
35
+
3
36
  ## 1.0.41 — 2026-08-11
4
37
 
5
38
  Desktop surfaces, measured against the desktop inventory and rebuilt for the terminal.
@@ -16,28 +49,27 @@ Desktop surfaces, measured against the desktop inventory and rebuilt for the ter
16
49
  - `/settings` — language, permission, active runtime, installed CLIs, and the
17
50
  resolved orchestrator/worker model roles; names what remains Desktop-only
18
51
  instead of implying parity.
19
- - Those five honest stops now point at the pi shell instead of claiming the
52
+ - Those five honest stops now point at the interactive shell instead of claiming the
20
53
  surface is unreachable from the terminal.
21
54
 
22
55
  ## 1.0.40 — 2026-08-11
23
56
 
24
- Desktop surfaces reach the pi shell: dashboard and graph view.
57
+ Desktop surfaces reach the interactive shell: dashboard and graph view.
25
58
 
26
- - `/dashboard` (pi shell): agents/firms/telegram badges + automations panel
59
+ - `/dashboard` (interactive shell): agents/firms/telegram badges + automations panel
27
60
  (enabled state, last run) from the local store — first of the thirteen
28
61
  Desktop-only honest stops to be lifted.
29
- - `/graph show <name>` (pi shell): the automation graph renders as Unicode
62
+ - `/graph show <name>` (interactive shell): the automation graph renders as Unicode
30
63
  box art (grok-mermaid) — condition branches, loop-back edges, and Korean
31
64
  labels align correctly. No canvas emulation; editing stays declarative.
32
- - First-run onboarding now runs before the pi shell starts (sequential
33
- readline → pi-tui, no stdin contention), so `AGENTLAS_TUI=pi` works for
65
+ - First-run onboarding now runs before the interactive shell starts (sequential, no stdin contention), so `AGENTLAS_TUI=1` works for
34
66
  brand-new installs too.
35
67
 
36
68
  ## 1.0.39 — 2026-08-11
37
69
 
38
- pi-tui shell increment 2 + architecture mirror sync.
70
+ Interactive shell increment 2 + architecture mirror sync.
39
71
 
40
- - Experimental pi shell (`AGENTLAS_TUI=pi`): persisted history (cli-history.json
72
+ - Experimental interactive shell (`AGENTLAS_TUI=1`): persisted history (cli-history.json
41
73
  v2 contract), Shift-Tab permission cycling (same two-step FULL arming state
42
74
  machine as the classic REPL), `!` shell passthrough (full-permission gate,
43
75
  secret masking), and `/s` `/switch` `/kill` `/rm` `/sessions` `/tree` session
@@ -1979,7 +1979,7 @@ function create(deps = {}) {
1979
1979
  const D = deps;
1980
1980
 
1981
1981
  /*
1982
- * pi-tui 이행 정지작업 (D3 Phase 1-2, 2026-08-11): ctx가 준 Ui가 있으면 그것을
1982
+ * 이행 정지작업 (D3 Phase 1-2, 2026-08-11): ctx가 준 Ui가 있으면 그것을
1983
1983
  * 쓴다. 자체 생성 Ui는 렌더러 교체 시 구 코드가 stdout에 직접 써 프레임을
1984
1984
  * 찢는 병렬 경로였다. 생성은 주입이 없을 때의 폴백으로만 남긴다.
1985
1985
  */
@@ -93,13 +93,13 @@ const DESKTOP_ONLY_SURFACES = {
93
93
  trex: "T-rex slide studio is Desktop-only.",
94
94
  slides: "T-rex slide studio is Desktop-only.",
95
95
  prompts: "Prompt Store is Desktop-only.",
96
- dashboard: "Dashboard: run `AGENTLAS_TUI=pi agentlas` then /dashboard — or: agentlas doctor · usage · list",
97
- marketplace: "Hub view: run `AGENTLAS_TUI=pi agentlas` then /marketplace — or: agentlas search \"<what you need>\"",
98
- library: "Library: run `AGENTLAS_TUI=pi agentlas` then /library — or: agentlas list · env · mcp",
99
- settings: "Settings: run `AGENTLAS_TUI=pi agentlas` then /settings — change with: agentlas setup · env · creds · multimodal",
96
+ dashboard: "Dashboard: run `AGENTLAS_TUI=1 agentlas` then /dashboard — or: agentlas doctor · usage · list",
97
+ marketplace: "Hub view: run `AGENTLAS_TUI=1 agentlas` then /marketplace — or: agentlas search \"<what you need>\"",
98
+ library: "Library: run `AGENTLAS_TUI=1 agentlas` then /library — or: agentlas list · env · mcp",
99
+ settings: "Settings: run `AGENTLAS_TUI=1 agentlas` then /settings — change with: agentlas setup · env · creds · multimodal",
100
100
  apps: "Apps surface is Desktop-only.",
101
101
  quests: "Quests are Desktop-only.",
102
- bookmarks: "Hub bookmarks: run `AGENTLAS_TUI=pi agentlas` then /marketplace.",
102
+ bookmarks: "Hub bookmarks: run `AGENTLAS_TUI=1 agentlas` then /marketplace.",
103
103
  one: "Agentlas One is a separate Desktop/Mobile product surface.",
104
104
  };
105
105
 
@@ -195,7 +195,7 @@ function create(ctx, deps = {}) {
195
195
  const lang = () => (ctx && ctx.lang) || "en";
196
196
 
197
197
  /*
198
- * pi-tui 이행 정지작업 (D3 Phase 1-2, 2026-08-11): ctx가 준 Ui가 있으면 그것을
198
+ * 이행 정지작업 (D3 Phase 1-2, 2026-08-11): ctx가 준 Ui가 있으면 그것을
199
199
  * 쓴다. 자체 생성 Ui는 렌더러 교체 시 구 코드가 stdout에 직접 써 프레임을
200
200
  * 찢는 병렬 경로였다. 생성은 주입이 없을 때의 폴백으로만 남긴다.
201
201
  */
@@ -37,7 +37,7 @@ function create(deps) {
37
37
  const { swarmRun } = require("./swarm.cjs").create(D);
38
38
 
39
39
  /*
40
- * pi-tui 이행 정지작업 (D3 Phase 1-2, 2026-08-11): ctx가 준 Ui가 있으면 그것을
40
+ * 이행 정지작업 (D3 Phase 1-2, 2026-08-11): ctx가 준 Ui가 있으면 그것을
41
41
  * 쓴다. 자체 생성 Ui는 렌더러 교체 시 구 코드가 stdout에 직접 써 프레임을
42
42
  * 찢는 병렬 경로였다. 생성은 주입이 없을 때의 폴백으로만 남긴다.
43
43
  */
@@ -128,7 +128,7 @@ function create(deps) {
128
128
  const D = deps;
129
129
 
130
130
  /*
131
- * pi-tui 이행 정지작업 (D3 Phase 1-2, 2026-08-11): ctx가 준 Ui가 있으면 그것을
131
+ * 이행 정지작업 (D3 Phase 1-2, 2026-08-11): ctx가 준 Ui가 있으면 그것을
132
132
  * 쓴다. 자체 생성 Ui는 렌더러 교체 시 구 코드가 stdout에 직접 써 프레임을
133
133
  * 찢는 병렬 경로였다. 생성은 주입이 없을 때의 폴백으로만 남긴다.
134
134
  */
@@ -125,12 +125,12 @@ async function startRepl(ctx, opts = {}) {
125
125
  }
126
126
 
127
127
  /*
128
- * 실험 셸 opt-in (D3 Phase 2): AGENTLAS_TUI=pi + TTY.
128
+ * 대화형 셸 opt-in (D3 Phase 2): AGENTLAS_TUI=1 + TTY.
129
129
  * 온보딩 마법사(위 readline 블록)가 먼저 끝난 뒤 진입한다 — 순차 실행이라
130
- * stdin 경합이 없고, 첫 실행 사용자도 pi 셸을 쓸 수 있다(증분 2c).
130
+ * stdin 경합이 없고, 첫 실행 사용자도 셸을 쓸 수 있다(증분 2c).
131
131
  */
132
- if (process.env.AGENTLAS_TUI === "pi" && process.stdin.isTTY) {
133
- return require("./pitui-shell.cjs").startPiShell(ctx, opts);
132
+ if (/^(1|true|on)$/i.test(String(process.env.AGENTLAS_TUI || "")) && process.stdin.isTTY) {
133
+ return require("./shell.cjs").startShell(ctx, opts);
134
134
  }
135
135
  const orch = new Orchestrator({ db, lang: ctx.lang });
136
136
  const renderer = new Renderer(ui);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * ui/pitui-screens — 데스크탑 화면의 터미널 대응물 (D3 Phase 3).
3
+ * ui/screens — 데스크탑 화면의 터미널 대응물 (D3 Phase 3).
4
4
  *
5
5
  * 대조 원본: docs/2026-08-11-terminal-tui-overhaul/D1-데스크탑-기능-인벤토리.md
6
6
  * 각 화면은 데스크탑이 IPC로 읽는 것과 같은 로컬 저장소를 직접 읽는다.
@@ -17,10 +17,24 @@ function table(ui, rows, opts = {}) {
17
17
  if (!rows.length) return;
18
18
  const cols = rows[0].length;
19
19
  const max = Number(opts.maxWidth) || 78;
20
- const widths = Array.from({ length: cols }, (_, i) =>
21
- Math.min(opts.cap?.[i] || 40, Math.max(...rows.map((r) => visWidth(String(r[i] ?? ""))))));
22
- const total = widths.reduce((a, b) => a + b + 2, 0);
23
- if (total > max && widths.length) widths[0] = Math.max(8, widths[0] - (total - max));
20
+ const widths = Array.from({ length: cols }, (_, i) => {
21
+ const cap = opts.cap?.[i];
22
+ return Math.min(Number.isFinite(cap) ? cap : 40, Math.max(...rows.map((r) => visWidth(String(r[i] ?? "")))));
23
+ });
24
+ /*
25
+ * 넘치면 "가장 넓은 열"을 깎는다. 예전엔 0번 열을 깎으면서 Math.max(8, …) 바닥을
26
+ * 뒀는데, 1칸짜리 상태 마커 열이 8칸으로 부풀어 표가 깨졌다(실측). 바닥은 4칸이고
27
+ * 이미 그보다 좁은 열은 건드리지 않는다.
28
+ */
29
+ let over = widths.reduce((a, b) => a + b + 2, 0) - max;
30
+ while (over > 0) {
31
+ let widest = -1;
32
+ widths.forEach((w, i) => { if (w > 4 && (widest < 0 || w > widths[widest])) widest = i; });
33
+ if (widest < 0) break;
34
+ const cut = Math.min(over, widths[widest] - 4);
35
+ widths[widest] -= cut;
36
+ over -= cut;
37
+ }
24
38
  rows.forEach((row, index) => {
25
39
  const line = row.map((cell, i) => {
26
40
  const text = truncateWidth(String(cell ?? ""), widths[i]);
@@ -214,4 +228,127 @@ function settings(ui, db, en, ctx) {
214
228
  : "여기서 불가(데스크탑 전용): 테마, 모바일 페어링 QR, 자동 업데이트, 멀티모달 프로바이더"));
215
229
  }
216
230
 
217
- module.exports = { dashboard, library, marketplace, settings, table };
231
+ /* ── /projects 데스크탑 workspace + project/detail ── */
232
+ function projects(ui, db, en) {
233
+ const list = rows(db,
234
+ `SELECT p.id, p.name, p.folder_path, p.source_type,
235
+ (SELECT COUNT(*) FROM chats c WHERE c.project_id = p.id AND c.archived_at IS NULL) chats,
236
+ (SELECT COUNT(*) FROM tasks t WHERE t.project_id = p.id AND t.archived_at IS NULL) tasks,
237
+ p.updated_at
238
+ FROM projects p ORDER BY COALESCE(p.updated_at,'') DESC`);
239
+ ui.ensureNl();
240
+ ui.line(ui.c.bold(en ? `Projects (${list.length})` : `프로젝트 (${list.length})`));
241
+ const cwd = process.cwd();
242
+ if (list.length) {
243
+ table(ui, [[en ? "project" : "프로젝트", en ? "source" : "소스", en ? "chats" : "채팅", en ? "tasks" : "작업", en ? "updated" : "수정"],
244
+ ...list.map((p) => [
245
+ (p.folder_path && cwd.startsWith(p.folder_path) ? "▸ " : " ") + (p.name || p.id),
246
+ p.source_type || "local", String(p.chats), String(p.tasks), shortTs(p.updated_at)])],
247
+ { cap: [30, 10, 6, 6, 16] });
248
+ const here = list.find((p) => p.folder_path && cwd.startsWith(p.folder_path));
249
+ ui.line("");
250
+ ui.line(here
251
+ ? ui.c.dim(en ? `▸ this folder is connected to "${here.name}"` : `▸ 이 폴더는 "${here.name}"에 연결돼 있습니다`)
252
+ : ui.c.amber(en ? "this folder is not connected — /project use <agent>" : "이 폴더는 연결 안 됨 — /project use <에이전트>"));
253
+ } else {
254
+ ui.line(ui.c.dim(en ? " none — /project use <agent>" : " 없음 — /project use <에이전트>"));
255
+ }
256
+ ui.line(ui.c.dim(en
257
+ ? "detail: /project status · team: /project team <agent>… · timeline lives in the project store"
258
+ : "상세: /project status · 팀: /project team <에이전트>… · 타임라인은 프로젝트 저장소에"));
259
+ }
260
+
261
+ /* ── /automations — 데스크탑 automation 목록+상세 ── */
262
+ function automations(ui, db, en, ctx, arg) {
263
+ const name = String(arg || "").trim();
264
+ if (name) {
265
+ const a = db.prepare("SELECT * FROM automations WHERE name = ? OR id = ?").get(name, name);
266
+ if (!a) { ui.line(ui.c.dim(en ? `no automation named "${name}"` : `"${name}" 자동화가 없습니다`)); return; }
267
+ ui.ensureNl();
268
+ ui.line(ui.c.bold(a.name) + " " + (a.enabled ? ui.c.green("● on") : ui.c.faint("○ off")));
269
+ table(ui, [
270
+ [en ? "field" : "항목", en ? "value" : "값"],
271
+ [en ? "schedule" : "일정", a.schedule || (a.trigger_type || "—")],
272
+ [en ? "next run" : "다음 실행", shortTs(a.next_run_at) || "—"],
273
+ [en ? "last run" : "마지막 실행", shortTs(a.last_run_at) || "—"],
274
+ [en ? "runs" : "실행 횟수", String(a.run_count || 0)],
275
+ [en ? "target" : "대상", `${a.target_type || "—"}${a.target_id ? `:${String(a.target_id).slice(0, 20)}` : ""}`],
276
+ [en ? "permission" : "권한", a.execution_permission || "—"],
277
+ [en ? "graph" : "그래프", a.graph_json ? (en ? `yes — /graph show ${a.name}` : `있음 — /graph show ${a.name}`) : (en ? "no" : "없음")],
278
+ ], { cap: [16, 54] });
279
+ const runs = rows(db,
280
+ "SELECT status, started_at FROM automation_runs WHERE automation_id = ? ORDER BY COALESCE(started_at,'') DESC LIMIT 6", [a.id]);
281
+ if (runs.length) {
282
+ ui.line("");
283
+ ui.line(ui.c.bold(en ? "Recent runs" : "최근 실행"));
284
+ for (const r of runs) {
285
+ const mark = r.status === "ok" ? ui.c.green("✓") : r.status === "error" ? ui.c.amber("✗") : ui.c.dim("·");
286
+ ui.line(` ${mark} ${r.status} ${ui.c.dim(shortTs(r.started_at))}`);
287
+ }
288
+ }
289
+ ui.line("");
290
+ ui.line(ui.c.dim(en
291
+ ? `run now: /automation run ${a.name} · toggle: /automation ${a.enabled ? "off" : "on"} ${a.name}`
292
+ : `지금 실행: /automation run ${a.name} · ${a.enabled ? "끄기: /automation off" : "켜기: /automation on"} ${a.name}`));
293
+ return;
294
+ }
295
+ const list = rows(db,
296
+ `SELECT a.name, a.enabled, a.schedule, a.next_run_at, a.run_count,
297
+ (SELECT COUNT(*) FROM automation_runs r WHERE r.automation_id = a.id AND r.status='error') fails
298
+ FROM automations a ORDER BY a.enabled DESC, COALESCE(a.next_run_at,'')`);
299
+ ui.ensureNl();
300
+ ui.line(ui.c.bold(en ? `Automations (${list.length})` : `자동화 (${list.length})`));
301
+ if (!list.length) { ui.line(ui.c.dim(en ? " none — /automation add" : " 없음 — /automation add")); return; }
302
+ table(ui, [[" ", en ? "name" : "이름", en ? "schedule" : "일정", en ? "next" : "다음", en ? "runs" : "실행", en ? "fails" : "실패"],
303
+ ...list.map((a) => [a.enabled ? "●" : "○", a.name, a.schedule || "—",
304
+ shortTs(a.next_run_at) || "—", String(a.run_count || 0), a.fails ? String(a.fails) : ""])],
305
+ { cap: [1, 30, 14, 16, 5, 5] });
306
+ ui.line("");
307
+ ui.line(ui.c.dim(en
308
+ ? "detail: /automations <name> · graph: /graph show <name> · add: /automation add"
309
+ : "상세: /automations <이름> · 그래프: /graph show <이름> · 추가: /automation add"));
310
+ void ctx;
311
+ }
312
+
313
+ /* ── /firms — 데스크탑 firm/detail (조직도) ── */
314
+ function firms(ui, db, en, ctx, arg) {
315
+ const name = String(arg || "").trim();
316
+ if (name) {
317
+ const f = db.prepare("SELECT * FROM firms WHERE slug = ? OR name = ?").get(name, name);
318
+ if (!f) { ui.line(ui.c.dim(en ? `no firm named "${name}"` : `"${name}" 회사가 없습니다`)); return; }
319
+ ui.ensureNl();
320
+ ui.line(ui.c.bold(f.name || f.slug) + (f.tagline ? ui.c.dim(` · ${f.tagline}`) : ""));
321
+ let org = null;
322
+ try { org = JSON.parse(f.org_chart_json || "null"); } catch { org = null; }
323
+ const members = rows(db,
324
+ "SELECT slug, COALESCE(local_display_name, name) nm, role FROM installed_agents WHERE parent_team_id = ? ORDER BY role, slug", [f.id]);
325
+ if (members.length) {
326
+ ui.line("");
327
+ ui.line(ui.c.bold(en ? `Roster (${members.length})` : `구성원 (${members.length})`));
328
+ table(ui, [[en ? "slug" : "슬러그", en ? "name" : "이름", en ? "role" : "역할"],
329
+ ...members.map((m) => [m.slug, m.nm || "", m.role || ""])], { cap: [30, 26, 14] });
330
+ } else if (org) {
331
+ ui.line(ui.c.dim(en ? " roster is declared in the org chart only" : " 로스터가 조직도 선언에만 있습니다"));
332
+ }
333
+ const chats = count(db, "SELECT COUNT(*) n FROM chats WHERE firm_id = ? AND archived_at IS NULL", );
334
+ void chats;
335
+ ui.line("");
336
+ ui.line(ui.c.dim(en ? `run: /firm ${f.slug} "<task>"` : `실행: /firm ${f.slug} "<할 일>"`));
337
+ return;
338
+ }
339
+ const list = rows(db,
340
+ `SELECT f.slug, f.name, f.tagline,
341
+ (SELECT COUNT(*) FROM installed_agents a WHERE a.parent_team_id = f.id) members
342
+ FROM firms f ORDER BY f.slug`);
343
+ ui.ensureNl();
344
+ ui.line(ui.c.bold(en ? `Firms (${list.length})` : `회사 (${list.length})`));
345
+ if (!list.length) { ui.line(ui.c.dim(en ? " none" : " 없음")); return; }
346
+ table(ui, [[en ? "slug" : "슬러그", en ? "name" : "이름", en ? "members" : "구성원"],
347
+ ...list.map((f) => [f.slug, f.name || "", String(f.members)])], { cap: [30, 30, 8] });
348
+ ui.line("");
349
+ ui.line(ui.c.dim(en ? "detail: /firms <slug>" : "상세: /firms <슬러그>"));
350
+ void ctx;
351
+ }
352
+
353
+ module.exports = { dashboard, library, marketplace, settings, projects, automations, firms, table };
354
+
@@ -1,16 +1,18 @@
1
1
  "use strict";
2
2
  /*
3
- * ui/pitui-shell — pi-tui 기반 실험 셸 (D3 Phase 2 증분 1, 2026-08-11).
3
+ * ui/shell — Agentlas 대화형 셸 (D3 Phase 2~3, 2026-08-11).
4
4
  *
5
- * 켜는 법: AGENTLAS_TUI=pi agentlas (TTY 필수 · 기본 REPL은 그대로 정본)
5
+ * 켜는 법: AGENTLAS_TUI=1 agentlas (TTY 필수 · 기본 REPL은 그대로 정본)
6
+ *
7
+ * 렌더러 의존은 구현 세부다 — 사용자 문구·환경변수·명령 어디에도 상류 이름을 쓰지 않는다.
6
8
  *
7
9
  * 설계 (D2 위험 5의 해법이 이 파일의 구조다):
8
- * - PiUi 는 기존 Ui 를 상속하되 write() 초크포인트만 pi-tui 트랜스크립트로 돌린다.
10
+ * - PiUi 는 기존 Ui 를 상속하되 write() 초크포인트만 렌더러 트랜스크립트로 돌린다.
9
11
  * line/_message/tool/rule 등 기존 메서드는 전부 write 로 수렴하므로 그대로 산다.
10
12
  * - 스트리밍 3종은 Markdown 누적으로 교체 — 표·코드블록이 실시간 재렌더된다.
11
- * - 스피너는 pi-tui Loader 로 교체 (기존 \r 기반 페인트는 dummy 스트림으로 무해화).
13
+ * - 스피너는 렌더러 Loader 로 교체 (기존 \r 기반 페인트는 dummy 스트림으로 무해화).
12
14
  * - ctx.out/err 55파일의 직출력은 shellCtx 재지정으로 전부 프레임 안에 들어온다.
13
- * - 자동완성은 ui/palette 정본(SLASH_COMMANDS)을 pi-tui SlashCommand 로 변환 — 목록 드리프트 금지.
15
+ * - 자동완성은 ui/palette 정본(SLASH_COMMANDS)을 렌더러 SlashCommand 로 변환 — 목록 드리프트 금지.
14
16
  *
15
17
  * 증분 1 범위 밖(기본 REPL로): Shift-Tab 권한 순환 · ! 셸 · 세션 전환(/s) ·
16
18
  * 히스토리 디스크 영속 · 스티어링 큐 표시. 이 항목들은 D3 Phase 2-2에서 이전한다.
@@ -25,20 +27,26 @@ const palette = require("./palette.cjs");
25
27
  const { readVersion } = require("../agentlas-banner.cjs");
26
28
  const { resolveProjectController, withProjectControllerContext } = require("../project/controller.cjs");
27
29
 
28
- function loadPiTui() {
30
+ function loadRenderer() {
29
31
  try {
30
- // ESM 패키지 — Node >=20.19 의 require(esm). engines 가 이 최소선을 선언한다.
31
- return require("@earendil-works/pi-tui");
32
+ /*
33
+ * 렌더러는 engine/vendor/tui 에 내재화돼 있다 — npm 의존성이 아니다.
34
+ * 이유: 정확 핀+무결성 해시는 버전 드리프트·변조를 막지만 레지스트리에서
35
+ * 그 버전이 삭제되면 설치가 실패한다. 소스가 저장소에 있으면 그 위험이 없고
36
+ * 우리가 직접 고칠 수 있다. 갱신은 scripts/vendor-tui.mjs.
37
+ * ESM 이므로 require(esm) 이 필요하다 — engines 가 Node >=20.19 를 선언한다.
38
+ */
39
+ return require("../vendor/tui/index.js");
32
40
  } catch (cause) {
33
41
  throw Object.assign(
34
- new Error("pi-tui shell needs Node >=20.19 (require(esm)). Run without AGENTLAS_TUI=pi, or upgrade Node."),
35
- { code: "pitui_unavailable", cause },
42
+ new Error("The Agentlas shell needs Node >=20.19. Run without AGENTLAS_TUI=1, or upgrade Node."),
43
+ { code: "shell_unavailable", cause },
36
44
  );
37
45
  }
38
46
  }
39
47
 
40
- /* Ui 를 상속해 write 초크포인트만 pi-tui 돌린다. */
41
- class PiUi extends Ui {
48
+ /* Ui 를 상속해 write 초크포인트만 렌더러로 돌린다. */
49
+ class ShellUi extends Ui {
42
50
  constructor(opts, pi, tui, transcript) {
43
51
  // 실터미널 대신 dummy 스트림 — 놓친 직접 out.write 가 프레임을 찢는 대신 소멸한다.
44
52
  super({ ...opts, stream: new PassThrough(), color: true });
@@ -128,39 +136,42 @@ class PiUi extends Ui {
128
136
  }
129
137
 
130
138
  /*
131
- * pi 셸 전용 화면 (D3 Phase 3). 공용 팔레트(ui/palette)에는 넣지 않는다 —
139
+ * 셸 전용 화면 (D3 Phase 3). 공용 팔레트(ui/palette)에는 넣지 않는다 —
132
140
  * 그 정본은 기본 REPL 이 실제로 처리하는 명령만 광고해야 하고,
133
141
  * palette-command-coverage-contract 가 그 계약을 잠근다.
134
142
  */
135
- const PI_SCREENS = [
143
+ const SHELL_SCREENS = [
136
144
  { name: "dashboard", ko: "관제 대시보드 — 확인 필요·실행 활동·자동화", en: "Dashboard — attention, run activity, automations" },
137
145
  { name: "library", ko: "라이브러리 — 에이전트·MCP", en: "Library — agents, MCP" },
138
146
  { name: "marketplace", ko: "Hub — 북마크·대여 현황", en: "Hub — bookmarks and borrows" },
139
147
  { name: "settings", ko: "설정 현황", en: "Settings overview" },
148
+ { name: "projects", ko: "프로젝트 목록 — 채팅·작업 수", en: "Projects — chats and tasks" },
149
+ { name: "automations", ko: "자동화 목록·상세 [이름]", en: "Automations list/detail [name]" },
150
+ { name: "firms", ko: "회사(팀) 목록·조직도 [슬러그]", en: "Firms and rosters [slug]" },
140
151
  ];
141
152
 
142
153
  function toSlashCommands(lang) {
143
- // 팔레트 정본 → pi-tui SlashCommand. "/" 접두는 pi-tui 관리하므로 벗긴다.
154
+ // 팔레트 정본 → 렌더러 SlashCommand. "/" 접두는 렌더러가 관리하므로 벗긴다.
144
155
  const base = palette.SLASH_COMMANDS.map((cmd) => ({
145
156
  name: cmd.command.slice(1),
146
157
  description: lang === "ko" ? cmd.ko : cmd.en,
147
158
  argumentHint: cmd.args || undefined,
148
159
  }));
149
160
  const seen = new Set(base.map((c) => c.name));
150
- for (const s of PI_SCREENS) {
161
+ for (const s of SHELL_SCREENS) {
151
162
  if (!seen.has(s.name)) base.push({ name: s.name, description: lang === "ko" ? s.ko : s.en });
152
163
  }
153
164
  return base;
154
165
  }
155
166
 
156
- async function startPiShell(ctx, opts = {}) {
157
- const pi = loadPiTui();
167
+ async function startShell(ctx, opts = {}) {
168
+ const pi = loadRenderer();
158
169
  const en = ctx.lang === "en";
159
170
  const db = ctx.db();
160
171
 
161
172
  const terminal = new pi.ProcessTerminal();
162
173
  const tui = new pi.TuiMainScreen(terminal);
163
- const ui = new PiUi({ lang: ctx.lang }, pi, tui, tui);
174
+ const ui = new ShellUi({ lang: ctx.lang }, pi, tui, tui);
164
175
 
165
176
  // ctx 초크포인트 재지정 — 55파일의 ctx.out 직출력이 전부 프레임 안으로 들어온다.
166
177
  const shellCtx = {
@@ -202,7 +213,7 @@ async function startPiShell(ctx, opts = {}) {
202
213
  };
203
214
 
204
215
  // ── 헤더 ──
205
- ui.line(`${ui.c.paw("▞▖")} ${ui.c.bold("AGENTLAS")} ${ui.c.dim(`${readVersion()} · pi-tui shell (experimental) · parallel ≤${maxParallel()}`)}`);
216
+ ui.line(`${ui.c.paw("▞▖")} ${ui.c.bold("AGENTLAS")} ${ui.c.dim(`${readVersion()} · parallel ≤${maxParallel()}`)}`);
206
217
  ui.line(ui.c.dim(en
207
218
  ? "plain words run a task · / commands · Esc interrupts · Ctrl+C quits"
208
219
  : "문장을 치면 실행 · / 명령 · Esc 중단 · Ctrl+C 종료"));
@@ -267,7 +278,7 @@ async function startPiShell(ctx, opts = {}) {
267
278
  const lbl = e.sourceHandle === "true" ? "|참|" : e.sourceHandle === "false" ? "|거짓|" : "";
268
279
  lines.push(` ${e.source} -->${lbl} ${e.target}`);
269
280
  }
270
- const { render, toAnsi } = require("grok-mermaid");
281
+ const { render, toAnsi } = require("../vendor/mermaid/index.js");
271
282
  const art = toAnsi(render(lines.join("\n")));
272
283
  ui.ensureNl();
273
284
  ui.line(ui.c.bold(row.name));
@@ -279,15 +290,19 @@ async function startPiShell(ctx, opts = {}) {
279
290
  }
280
291
  // 데스크탑 대응 화면 (Phase 3) — 정직 정지였던 표면들을 실물로 대체
281
292
  {
282
- const screens = require("./pitui-screens.cjs");
293
+ const screens = require("./screens.cjs");
283
294
  const SCREEN = {
284
295
  dashboard: screens.dashboard,
285
296
  library: screens.library,
286
297
  marketplace: screens.marketplace,
287
298
  bookmarks: screens.marketplace,
288
299
  settings: screens.settings,
300
+ projects: screens.projects,
301
+ automations: screens.automations,
302
+ firms: screens.firms,
289
303
  };
290
- if (SCREEN[cmd]) { SCREEN[cmd](ui, db, en, shellCtx); return; }
304
+ // 인자 있는 화면(automations/firms 상세)은 restStr 그대로 넘긴다.
305
+ if (SCREEN[cmd]) { SCREEN[cmd](ui, db, en, shellCtx, cmdline.slice(raw.length).trim()); return; }
291
306
  }
292
307
  // 세션 관찰/전환 (증분 2b) — 기본 REPL과 같은 orch/renderer 배선
293
308
  if (cmd === "sessions" || cmd === "tree") {
@@ -318,7 +333,7 @@ async function startPiShell(ctx, opts = {}) {
318
333
  ui.line(ui.c.dim(commands.DESKTOP_ONLY_SURFACES[cmd]));
319
334
  return;
320
335
  }
321
- ui.line(ui.c.dim(en ? `not in the pi shell yet: /${cmd} — use the classic REPL` : `pi 셸에는 아직 없음: /${cmd} — 기본 REPL을 쓰세요`));
336
+ ui.line(ui.c.dim(en ? `not available here yet: /${cmd}` : `여기서는 아직 됩니다: /${cmd}`));
322
337
  };
323
338
 
324
339
  let busy = false;
@@ -368,7 +383,7 @@ async function startPiShell(ctx, opts = {}) {
368
383
  };
369
384
 
370
385
  tui.addInputListener((data) => {
371
- // Shift-Tab 권한 순환 — pi-tui raw mode 를 단독 소유하므로 readline 의
386
+ // Shift-Tab 권한 순환 — 렌더러가 raw mode 를 단독 소유하므로 readline 의
372
387
  // swallowCompletion 우회 없이 여기서 직접 소비한다 (D2 위험 2의 해소 형태).
373
388
  if (pi.matchesKey(data, "shift+tab")) {
374
389
  permShortcut.handleKey("", { name: "tab", shift: true });
@@ -398,8 +413,8 @@ async function startPiShell(ctx, opts = {}) {
398
413
  });
399
414
 
400
415
  tui.start();
401
- // pi-tui 프로세스를 잡고 있는 동안 살아 있는 프라미스
416
+ // 렌더러가 프로세스를 잡고 있는 동안 살아 있는 프라미스
402
417
  return new Promise(() => {});
403
418
  }
404
419
 
405
- module.exports = { startPiShell };
420
+ module.exports = { startShell };
@@ -3,7 +3,7 @@
3
3
  * ui/width: 터미널 셀 폭·그래핌 유틸 정본.
4
4
  * agentlas-composer.cjs(데드코드가 된 바텀 입력 박스)에서 2026-08-11 추출 — 함수는
5
5
  * 바이트 동일. 소비자: agentlas-ui / agentlas-banner / agentlas-onboard /
6
- * ui/repl / hephaestus/runtime. pi-tui 이행(D3 Phase 1-3) 정지작업.
6
+ * ui/repl / hephaestus/runtime. 이행(D3 Phase 1-3) 정지작업.
7
7
  */
8
8
  const GRAPHEME_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" });
9
9
  const MARK_RE = /\p{Mark}/u;