agentlas 0.9.10 → 1.0.2
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.
- package/CHANGELOG.md +106 -0
- package/README.md +319 -341
- package/bin/agentlas.cjs +32 -9
- package/engine/agentlas-banner.cjs +24 -3
- package/engine/agentlas-composer.cjs +239 -31
- package/engine/agentlas-config.cjs +103 -7
- package/engine/agentlas-core-harness.cjs +48 -4
- package/engine/agentlas-evolution.cjs +3 -4
- package/engine/agentlas-i18n.cjs +88 -32
- package/engine/agentlas-input.cjs +234 -18
- package/engine/agentlas-memory-governance.cjs +3 -5
- package/engine/agentlas-memory-import.cjs +3 -3
- package/engine/agentlas-native-host.cjs +200 -9
- package/engine/agentlas-onboard.cjs +112 -20
- package/engine/agentlas-permissions.cjs +14 -7
- package/engine/agentlas-sqlite-policy.cjs +34 -0
- package/engine/agentlas-ui.cjs +93 -42
- package/engine/agentlas-workforce.cjs +472 -58
- package/engine/agentlas-workload-routing.cjs +8 -3
- package/engine/agentlas.cjs +140 -12367
- package/engine/agents/files.cjs +61 -0
- package/engine/agents/import-local.cjs +237 -0
- package/engine/agents/registry.cjs +158 -0
- package/engine/agents/router.cjs +565 -0
- package/engine/agents/routes.cjs +43 -0
- package/engine/architecture.data.json +2 -2
- package/engine/automation/daemon.cjs +335 -0
- package/engine/automation/schedule.cjs +181 -0
- package/engine/automation/store.cjs +209 -0
- package/engine/cloud/auth.cjs +279 -0
- package/engine/cloud/hub-client.cjs +239 -0
- package/engine/cloud-assets/cargo.cjs +49 -0
- package/engine/cloud-assets/cas.cjs +235 -0
- package/engine/cloud-assets/commands.cjs +273 -0
- package/engine/cloud-assets/package.cjs +936 -0
- package/engine/cloud-assets/restore.cjs +172 -0
- package/engine/cloud-assets/state.cjs +268 -0
- package/engine/commands/automation.cjs +195 -0
- package/engine/commands/billing.cjs +96 -0
- package/engine/commands/browser.cjs +20 -0
- package/engine/commands/build.cjs +33 -0
- package/engine/commands/call.cjs +24 -0
- package/engine/commands/career-graph.cjs +51 -0
- package/engine/commands/cd.cjs +22 -0
- package/engine/commands/chat.cjs +12 -0
- package/engine/commands/chats.cjs +28 -0
- package/engine/commands/cloud.cjs +17 -0
- package/engine/commands/connect.cjs +20 -0
- package/engine/commands/context.cjs +66 -0
- package/engine/commands/creds.cjs +203 -0
- package/engine/commands/doctor.cjs +68 -0
- package/engine/commands/env.cjs +33 -0
- package/engine/commands/evolve.cjs +23 -0
- package/engine/commands/experience.cjs +34 -0
- package/engine/commands/film.cjs +8 -0
- package/engine/commands/firm.cjs +115 -0
- package/engine/commands/help.cjs +65 -0
- package/engine/commands/hep.cjs +23 -0
- package/engine/commands/import.cjs +35 -0
- package/engine/commands/index.cjs +136 -0
- package/engine/commands/install.cjs +27 -0
- package/engine/commands/journal.cjs +31 -0
- package/engine/commands/legacy-network.cjs +29 -0
- package/engine/commands/list.cjs +49 -0
- package/engine/commands/login.cjs +68 -0
- package/engine/commands/logout.cjs +28 -0
- package/engine/commands/mcp.cjs +91 -0
- package/engine/commands/memory.cjs +21 -0
- package/engine/commands/multimodal.cjs +91 -0
- package/engine/commands/native.cjs +34 -0
- package/engine/commands/netadmin.cjs +31 -0
- package/engine/commands/oberon.cjs +70 -0
- package/engine/commands/ontology.cjs +24 -0
- package/engine/commands/open.cjs +48 -0
- package/engine/commands/plugin.cjs +101 -0
- package/engine/commands/project.cjs +47 -0
- package/engine/commands/research.cjs +36 -0
- package/engine/commands/route.cjs +37 -0
- package/engine/commands/run.cjs +149 -0
- package/engine/commands/search.cjs +55 -0
- package/engine/commands/setup.cjs +45 -0
- package/engine/commands/storm.cjs +75 -0
- package/engine/commands/swarm.cjs +75 -0
- package/engine/commands/telegram.cjs +32 -0
- package/engine/commands/uninstall.cjs +68 -0
- package/engine/commands/update.cjs +54 -0
- package/engine/commands/upload.cjs +18 -0
- package/engine/commands/usage.cjs +35 -0
- package/engine/commands/variant.cjs +25 -0
- package/engine/commands/version.cjs +9 -0
- package/engine/commands/whoami.cjs +38 -0
- package/engine/commands/workforce.cjs +103 -0
- package/engine/core/db.cjs +160 -0
- package/engine/core/paths.cjs +35 -0
- package/engine/experience/build.cjs +181 -0
- package/engine/experience/intents.cjs +492 -0
- package/engine/experience/runtime.cjs +242 -0
- package/engine/experience/variant.cjs +196 -0
- package/engine/firms/orchestrate.cjs +333 -0
- package/engine/hephaestus/runtime.cjs +697 -0
- package/engine/hub/install.cjs +872 -0
- package/engine/hub/plugins.cjs +213 -0
- package/engine/mcp/consent.cjs +289 -0
- package/engine/mcp/contract.cjs +202 -0
- package/engine/mcp/index.cjs +43 -0
- package/engine/mcp/inventory.cjs +322 -0
- package/engine/mcp/plan.cjs +286 -0
- package/engine/mcp/probe.cjs +151 -0
- package/engine/memory-cli/curate.cjs +163 -0
- package/engine/oberon/common.cjs +69 -0
- package/engine/oberon/manifest.cjs +164 -0
- package/engine/oberon/outputs.cjs +70 -0
- package/engine/oberon/render.cjs +164 -0
- package/engine/project/career-graph.cjs +249 -0
- package/engine/project/credentials.cjs +262 -0
- package/engine/project/env-file.cjs +46 -0
- package/engine/project/index.cjs +27 -0
- package/engine/project/memory-context.cjs +453 -0
- package/engine/project/ontology.cjs +467 -0
- package/engine/project/paths.cjs +39 -0
- package/engine/project/seed.cjs +200 -0
- package/engine/project/state.cjs +403 -0
- package/engine/project/super-ontology-seed.json +3288 -0
- package/engine/runtimes/detect.cjs +54 -0
- package/engine/runtimes/overrides.cjs +139 -0
- package/engine/runtimes/resolve.cjs +64 -0
- package/engine/sessions/apply-fences.cjs +188 -0
- package/engine/sessions/fences.cjs +362 -0
- package/engine/sessions/orchestrator.cjs +170 -0
- package/engine/sessions/prompt.cjs +212 -0
- package/engine/sessions/session.cjs +245 -0
- package/engine/sessions/sink.cjs +54 -0
- package/engine/sessions/store.cjs +79 -0
- package/engine/storm/deps.cjs +88 -0
- package/engine/storm/storm.cjs +218 -0
- package/engine/storm/swarm.cjs +422 -0
- package/engine/ui/palette.cjs +105 -0
- package/engine/ui/renderer.cjs +85 -0
- package/engine/ui/repl.cjs +444 -0
- package/engine/workforce/capture.cjs +701 -0
- package/engine/workforce/deps.cjs +472 -0
- package/package.json +2 -6
- package/engine/agentlas-experience-mcp.cjs +0 -1709
- package/engine/agentlas-parity.cjs +0 -1499
- package/engine/agentlas-repl.cjs +0 -1780
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* ui/palette — v2 슬래시 명령 정본 + readline 완성기.
|
|
4
|
+
*
|
|
5
|
+
* 왜 v1 input 모듈의 SLASH_COMMANDS를 쓰지 않는가: 그 목록은 v1 REPL 전용이라
|
|
6
|
+
* v2에 없는 명령(/status /team /model /effort …)을 광고하고, v2의 오르카 명령
|
|
7
|
+
* (/spawn /sessions /s /steer /kill /broadcast …)은 빠져 있었다. 팔레트가
|
|
8
|
+
* 거짓말하면 사용자는 없는 기능을 부른다(실사용 Tab 테스트에서 실증).
|
|
9
|
+
* 경로 완성만 v1 모듈의 completePath를 재사용한다.
|
|
10
|
+
*/
|
|
11
|
+
const { completePath, isAbsolutePathTask } = require("../agentlas-input.cjs");
|
|
12
|
+
|
|
13
|
+
// command, 인자 힌트, 한 줄 설명 — /help 팔레트와 Tab 완성이 같은 정본을 쓴다.
|
|
14
|
+
const SLASH_COMMANDS = [
|
|
15
|
+
{ command: "/help", args: "", ko: "명령·단축키 보기", en: "Show commands and shortcuts" },
|
|
16
|
+
{ command: "/sessions", args: "", ko: "세션 표", en: "Session table" },
|
|
17
|
+
{ command: "/tree", args: "", ko: "세션 트리", en: "Session tree" },
|
|
18
|
+
{ command: "/s", args: "<n>", ko: "활성 세션 전환", en: "Switch active session" },
|
|
19
|
+
{ command: "/switch", args: "<n>", ko: "활성 세션 전환", en: "Switch active session" },
|
|
20
|
+
{ command: "/spawn", args: "<agent> [task]", ko: "서브에이전트 세션 생성", en: "Spawn a subagent session" },
|
|
21
|
+
{ command: "/steer", args: "<n> <msg>", ko: "해당 세션에 지시 큐잉", en: "Queue steering for a session" },
|
|
22
|
+
{ command: "/kill", args: "<n>", ko: "실행 중 턴 중단", en: "Interrupt a running turn" },
|
|
23
|
+
{ command: "/rm", args: "<n>", ko: "세션 제거", en: "Remove a session" },
|
|
24
|
+
{ command: "/broadcast", args: "<msg>", ko: "모든 세션에 지시", en: "Send to every session" },
|
|
25
|
+
{ command: "/use", args: "<agent>", ko: "메인 세션 에이전트 교체", en: "Switch the main agent" },
|
|
26
|
+
{ command: "/agents", args: "", ko: "설치 에이전트 목록", en: "List installed agents" },
|
|
27
|
+
{ command: "/list", args: "", ko: "설치 에이전트 목록", en: "List installed agents" },
|
|
28
|
+
{ command: "/chats", args: "[n]", ko: "최근 대화", en: "Recent conversations" },
|
|
29
|
+
{ command: "/mcp", args: "", ko: "MCP 서버 목록", en: "MCP servers" },
|
|
30
|
+
{ command: "/doctor", args: "", ko: "런타임·데이터 점검", en: "Health check" },
|
|
31
|
+
{ command: "/runtime", args: "<kind>", ko: "새 세션 런타임 지정", en: "Set runtime for new sessions" },
|
|
32
|
+
{ command: "/permission", args: "<level>", ko: "새 세션 권한 지정", en: "Set permission for new sessions" },
|
|
33
|
+
{ command: "/login", args: "", ko: "Agentlas Cloud 로그인", en: "Sign in to Agentlas Cloud" },
|
|
34
|
+
{ command: "/whoami", args: "", ko: "로그인 상태", en: "Signed-in account" },
|
|
35
|
+
{ command: "/search", args: "\"<what you need>\"", ko: "Hub 에이전트 검색", en: "Search Hub agents" },
|
|
36
|
+
{ command: "/install", args: "<slug>", ko: "Hub 에이전트 설치", en: "Install a Hub agent" },
|
|
37
|
+
{ command: "/usage", args: "", ko: "로컬 사용 현황", en: "Local usage" },
|
|
38
|
+
{ command: "/billing", args: "", ko: "크레딧 잔액", en: "Credit balance" },
|
|
39
|
+
{ command: "/automation", args: "[sub]", ko: "자동화", en: "Automations" },
|
|
40
|
+
{ command: "/storm", args: "<goal>", ko: "Goal+UltraCode 하니스", en: "Goal+UltraCode harness" },
|
|
41
|
+
{ command: "/swarm", args: "<goal>", ko: "에이전트 스웜", en: "Agent swarm" },
|
|
42
|
+
{ command: "/network", args: "<request>", ko: "Workforce 라우트", en: "Workforce route" },
|
|
43
|
+
{ command: "/quit", args: "", ko: "종료", en: "Quit" },
|
|
44
|
+
{ command: "/exit", args: "", ko: "종료", en: "Quit" },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const SLASH_NAMES = SLASH_COMMANDS.map((c) => c.command);
|
|
48
|
+
const RUNTIME_KINDS = ["claude-code", "codex", "gemini"];
|
|
49
|
+
const PERM_LEVELS = ["read", "write", "full"];
|
|
50
|
+
// 세션 인자를 받는 명령 — 완성 후보를 살아있는 세션 키(s1, s2…)로 채운다.
|
|
51
|
+
const SESSION_ARG_COMMANDS = new Set(["/s", "/switch", "/steer", "/kill", "/rm"]);
|
|
52
|
+
const AGENT_ARG_COMMANDS = new Set(["/use", "/spawn"]);
|
|
53
|
+
|
|
54
|
+
function uniqStartsWith(list, prefix) {
|
|
55
|
+
const p = String(prefix || "");
|
|
56
|
+
const hits = [...new Set(list)].filter((v) => v.startsWith(p));
|
|
57
|
+
return hits.length ? hits : [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* ctx: { getAgentSlugs, getFirmSlugs, getSessionKeys, getCwd }
|
|
62
|
+
* readline completer 계약: [candidates, prefix] 반환.
|
|
63
|
+
*/
|
|
64
|
+
function makeCompleter(ctx = {}) {
|
|
65
|
+
const getAgents = ctx.getAgentSlugs || (() => []);
|
|
66
|
+
const getFirms = ctx.getFirmSlugs || (() => []);
|
|
67
|
+
const getSessions = ctx.getSessionKeys || (() => []);
|
|
68
|
+
const getCwd = ctx.getCwd || (() => process.cwd());
|
|
69
|
+
return function completer(line) {
|
|
70
|
+
const lineStr = line || "";
|
|
71
|
+
const tokens = lineStr.split(/\s+/);
|
|
72
|
+
const last = tokens[tokens.length - 1] || "";
|
|
73
|
+
|
|
74
|
+
// @경로 멘션은 어느 위치에서나 파일 완성
|
|
75
|
+
if (last.startsWith("@")) return [completePath(last.slice(1), getCwd(), "@"), last];
|
|
76
|
+
|
|
77
|
+
if (tokens.length === 1) {
|
|
78
|
+
if (isAbsolutePathTask(lineStr)) return [completePath(lineStr, getCwd(), ""), last];
|
|
79
|
+
if (lineStr.startsWith("/")) return [uniqStartsWith(SLASH_NAMES, last), last];
|
|
80
|
+
// 첫 토큰이 자유 텍스트면 에이전트 이름을 제안한다(에이전트 점프 UX).
|
|
81
|
+
return [uniqStartsWith(getAgents().concat(getFirms()), last), last];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const cmd = tokens[0];
|
|
85
|
+
if (cmd === "/runtime") return [uniqStartsWith(RUNTIME_KINDS, last), last];
|
|
86
|
+
if (cmd === "/permission") return [uniqStartsWith(PERM_LEVELS, last), last];
|
|
87
|
+
if (SESSION_ARG_COMMANDS.has(cmd) && tokens.length === 2) return [uniqStartsWith(getSessions(), last), last];
|
|
88
|
+
if (AGENT_ARG_COMMANDS.has(cmd) && tokens.length === 2) {
|
|
89
|
+
return [uniqStartsWith(getAgents().concat(getFirms()), last), last];
|
|
90
|
+
}
|
|
91
|
+
return [[], last];
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** /help 팔레트 렌더 — Tab 완성과 같은 정본에서 나온다. */
|
|
96
|
+
function renderPalette(lang) {
|
|
97
|
+
const ko = lang === "ko";
|
|
98
|
+
const width = Math.max(...SLASH_COMMANDS.map((c) => (c.command + " " + c.args).length));
|
|
99
|
+
return SLASH_COMMANDS
|
|
100
|
+
.filter((c, i, all) => all.findIndex((x) => (ko ? x.ko : x.en) === (ko ? c.ko : c.en)) === i)
|
|
101
|
+
.map((c) => ` ${(c.command + (c.args ? " " + c.args : "")).padEnd(width + 2)}${ko ? c.ko : c.en}`)
|
|
102
|
+
.join("\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { SLASH_COMMANDS, SLASH_NAMES, makeCompleter, renderPalette };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* ui/renderer — 세션 이벤트를 Ui(터미널)로 그리는 유일한 곳.
|
|
4
|
+
* 활성 세션 하나에만 attach된다. 백그라운드 세션 이벤트는 orchestrator의
|
|
5
|
+
* notice로만 표면화된다(오르카 규칙: 화면은 한 세션, 나머지는 상태로).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class Renderer {
|
|
9
|
+
constructor(ui) {
|
|
10
|
+
this.ui = ui;
|
|
11
|
+
this.session = null;
|
|
12
|
+
this._listener = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
attach(session, { replay = true } = {}) {
|
|
16
|
+
this.detach();
|
|
17
|
+
this.session = session;
|
|
18
|
+
if (replay) this._replayTail(session);
|
|
19
|
+
this._listener = (ev) => this._render(ev);
|
|
20
|
+
session.on("event", this._listener);
|
|
21
|
+
if (session.isBusy()) this._beginChrome();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
detach() {
|
|
25
|
+
if (this.session && this._listener) this.session.removeListener("event", this._listener);
|
|
26
|
+
if (this.session) this.ui.endTurn();
|
|
27
|
+
this.session = null;
|
|
28
|
+
this._listener = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_beginChrome() {
|
|
32
|
+
this.ui.beginTurn({
|
|
33
|
+
permissionLabel: this.session ? this.session.permission : "",
|
|
34
|
+
status: this.session ? this.session.agent.slug : "",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_replayTail(session) {
|
|
39
|
+
const tail = session.eventsTail(120);
|
|
40
|
+
if (!tail.length) return;
|
|
41
|
+
this.ui.ensureNl();
|
|
42
|
+
this.ui.rule(`${session.key || ""} ${session.agent.slug} · ${session.status}`);
|
|
43
|
+
// 마지막 턴의 스트림 텍스트만 복원한다(도구 소음 제외) — 전환 시 맥락 파악용.
|
|
44
|
+
let text = "";
|
|
45
|
+
for (const ev of tail) {
|
|
46
|
+
if (ev.type === "turn-start") text = "";
|
|
47
|
+
else if (ev.type === "stream-delta") text += ev.text;
|
|
48
|
+
}
|
|
49
|
+
const lines = text.trim().split(/\r?\n/).slice(-12);
|
|
50
|
+
for (const l of lines) this.ui.line(" " + l);
|
|
51
|
+
if (session.lastError) this.ui.error(session.lastError);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_render(ev) {
|
|
55
|
+
const ui = this.ui;
|
|
56
|
+
switch (ev.type) {
|
|
57
|
+
case "turn-start": this._beginChrome(); return;
|
|
58
|
+
case "turn-end":
|
|
59
|
+
ui.endTurn();
|
|
60
|
+
if (!ev.ok && ev.error) ui.error(ev.error);
|
|
61
|
+
return;
|
|
62
|
+
case "status": ui.status(ev.text); return;
|
|
63
|
+
case "warn": ui.warn(ev.text); return;
|
|
64
|
+
case "error":
|
|
65
|
+
// 사용자가 의도적으로 중단(kill)한 턴의 종료 에러(SIGTERM exit 등)는 소음 —
|
|
66
|
+
// 실사용 테스트에서 확인된 UX 결함. 중단 안내는 REPL이 이미 출력했다.
|
|
67
|
+
if (this.session && this.session.status === "killed") return;
|
|
68
|
+
ui.error(ev.text);
|
|
69
|
+
return;
|
|
70
|
+
case "line": ui.line(ev.text); return;
|
|
71
|
+
case "tool": ui.tool(ev.name, ev.summary); return;
|
|
72
|
+
case "tool-result": ui.toolResult(ev.text, ev.ok); return;
|
|
73
|
+
case "stream-start": ui.streamStart(); return;
|
|
74
|
+
case "stream-delta": ui.streamDelta(ev.text); return;
|
|
75
|
+
case "stream-end": ui.streamEnd(); return;
|
|
76
|
+
case "task-tool": ui.applyTaskTool(ev.name, ev.input, ev.id); return;
|
|
77
|
+
case "task-result": ui.applyTaskResult(ev.name, ev.result, ev.id); return;
|
|
78
|
+
case "cost": ui.cost(ev.usage); return;
|
|
79
|
+
case "queued": ui.line(ui.c.dim(` ↳ queued for next turn: ${ev.text}`)); return;
|
|
80
|
+
default: return;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { Renderer };
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* ui/repl — v2 REPL (Claude Code 방식 + 오르카 멀티세션).
|
|
4
|
+
*
|
|
5
|
+
* 원칙:
|
|
6
|
+
* - 실행 경로는 orchestrator 하나. 포그라운드 턴도 세션이다.
|
|
7
|
+
* - 화면은 활성 세션 하나만 스트리밍(Renderer). 백그라운드 턴 종료는 한 줄 notice.
|
|
8
|
+
* - 실행 중 입력: 텍스트는 다음 턴 스티어링 큐로, ctrl-c는 현재 턴 중단.
|
|
9
|
+
*
|
|
10
|
+
* 세션 조종(오르카):
|
|
11
|
+
* /spawn <agent> [task] 백그라운드 서브세션 생성(+실행)
|
|
12
|
+
* /sessions · /tree 세션 표 / 부모-자식 트리
|
|
13
|
+
* /s <n> · /switch <n> 활성 세션 전환 (tail 재생 + 라이브 구독)
|
|
14
|
+
* /steer <n> <msg> 해당 세션 다음 턴 큐잉
|
|
15
|
+
* /kill <n> · /rm <n> 턴 중단 / 세션 제거
|
|
16
|
+
* /broadcast <msg> 전 세션에 동일 지시
|
|
17
|
+
*/
|
|
18
|
+
const readline = require("node:readline");
|
|
19
|
+
const { renderBanner, readVersion } = require("../agentlas-banner.cjs");
|
|
20
|
+
const { Orchestrator, maxParallel } = require("../sessions/orchestrator.cjs");
|
|
21
|
+
const { Renderer } = require("./renderer.cjs");
|
|
22
|
+
const { findAgent, listAgents } = require("../agents/registry.cjs");
|
|
23
|
+
const { resolveRuntime, NoRuntimeError } = require("../runtimes/resolve.cjs");
|
|
24
|
+
const permissions = require("../agentlas-permissions.cjs");
|
|
25
|
+
|
|
26
|
+
const DEFAULT_AGENT_SLUG = "agentlas-orchestrator";
|
|
27
|
+
|
|
28
|
+
function pickDefaultAgent(db) {
|
|
29
|
+
const visible = listAgents(db);
|
|
30
|
+
if (visible.length) return visible[0];
|
|
31
|
+
const builtin = findAgent(db, DEFAULT_AGENT_SLUG);
|
|
32
|
+
if (builtin) return builtin;
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function startRepl(ctx, opts = {}) {
|
|
37
|
+
const en = ctx.lang === "en";
|
|
38
|
+
const ui = ctx.uiInstance;
|
|
39
|
+
const db = ctx.db();
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
process.stdout.write(renderBanner({ version: readVersion(), lang: ctx.lang }) + "\n");
|
|
43
|
+
} catch {
|
|
44
|
+
ctx.out(`agentlas ${readVersion()}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 첫 실행 온보딩 (언어 → 런타임 → 권한). setup 명령으로 언제든 재실행 가능.
|
|
48
|
+
if (!ctx.prefs.onboarded && process.stdin.isTTY) {
|
|
49
|
+
const wizardRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
50
|
+
try {
|
|
51
|
+
const { runWizard } = require("../commands/setup.cjs");
|
|
52
|
+
const result = await runWizard(ctx, wizardRl);
|
|
53
|
+
if (result) {
|
|
54
|
+
if (result.lang) { ctx.prefs.language = result.lang; }
|
|
55
|
+
if (result.permission) ctx.prefs.permission = result.permission;
|
|
56
|
+
if (result.runtime) ctx.prefs.runtime = result.runtime;
|
|
57
|
+
ctx.prefs.onboarded = !!result.onboarded;
|
|
58
|
+
}
|
|
59
|
+
} catch (e) {
|
|
60
|
+
// 온보딩 실패는 REPL 진입을 막지 않지만, 조용히 삼키면 마법사 프롬프트와
|
|
61
|
+
// REPL이 stdin을 경합하는 사고(실사용 테스트에서 실증)가 위장된다 — 표시한다.
|
|
62
|
+
ctx.err(ui.c.dim((en ? "setup wizard failed: " : "설정 마법사 실패: ") + String((e && e.message) || e)));
|
|
63
|
+
} finally {
|
|
64
|
+
wizardRl.close();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const orch = new Orchestrator({ db, lang: ctx.lang });
|
|
69
|
+
const renderer = new Renderer(ui);
|
|
70
|
+
let permission = permissions.normalize(opts.permission || (ctx.prefs && ctx.prefs.permission) || "write");
|
|
71
|
+
let runtimeOverride = opts.runtime || null;
|
|
72
|
+
|
|
73
|
+
const resolveRt = () => resolveRuntime({ db, prefs: ctx.prefs, explicit: runtimeOverride });
|
|
74
|
+
|
|
75
|
+
let resumeChatId = opts.chatId || null;
|
|
76
|
+
const ensureMainSession = (agentToken) => {
|
|
77
|
+
const agent = agentToken ? findAgent(db, agentToken) : (orch.active() ? orch.active().agent : pickDefaultAgent(db));
|
|
78
|
+
if (!agent) {
|
|
79
|
+
throw new Error(en
|
|
80
|
+
? (agentToken ? `agent not found: ${agentToken}` : "no installed agent (agentlas search/install first)")
|
|
81
|
+
: (agentToken ? `에이전트를 찾을 수 없음: ${agentToken}` : "설치된 에이전트가 없습니다 (agentlas search/install 먼저)"));
|
|
82
|
+
}
|
|
83
|
+
const active = orch.active();
|
|
84
|
+
if (active && active.agent.id === agent.id) return active;
|
|
85
|
+
const session = orch.spawn({ agent, runtime: resolveRt(), permission, cwd: process.cwd(), activate: true, chatId: resumeChatId });
|
|
86
|
+
resumeChatId = null; // 재개는 첫 세션에만 적용
|
|
87
|
+
renderer.attach(session, { replay: false });
|
|
88
|
+
return session;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
ctx.out(ui.c.dim(en
|
|
92
|
+
? `v2 engine · runtime auto · permission ${permission} · parallel ≤${maxParallel()} — /help, /sessions, /quit`
|
|
93
|
+
: `v2 엔진 · 런타임 auto · 권한 ${permission} · 동시 ≤${maxParallel()} — /help, /sessions, /quit`));
|
|
94
|
+
|
|
95
|
+
if (opts.agent) {
|
|
96
|
+
try {
|
|
97
|
+
const session = ensureMainSession(opts.agent);
|
|
98
|
+
ctx.out(ui.c.dim(`agent: ${session.agent.slug} · ${session.runtime.kind}`));
|
|
99
|
+
} catch (e) {
|
|
100
|
+
ctx.err(String((e && e.message) || e));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
// 히스토리는 v1 input 모듈 재사용, 완성기는 v2 팔레트(ui/palette)가 정본이다.
|
|
106
|
+
const input = require("../agentlas-input.cjs");
|
|
107
|
+
const palette = require("./palette.cjs");
|
|
108
|
+
const rl = readline.createInterface({
|
|
109
|
+
input: process.stdin,
|
|
110
|
+
output: process.stdout,
|
|
111
|
+
completer: palette.makeCompleter({
|
|
112
|
+
getAgentSlugs: () => { try { return listAgents(db).map((a) => a.slug); } catch { return []; } },
|
|
113
|
+
getFirmSlugs: () => {
|
|
114
|
+
try { return db.prepare("SELECT slug FROM firms ORDER BY slug").all().map((r) => r.slug); } catch { return []; }
|
|
115
|
+
},
|
|
116
|
+
getSessionKeys: () => orch.list().map((r) => r.key),
|
|
117
|
+
getCwd: () => process.cwd(),
|
|
118
|
+
}),
|
|
119
|
+
});
|
|
120
|
+
input.attachHistory(rl);
|
|
121
|
+
const PROMPT = "› ";
|
|
122
|
+
const prompt = () => {
|
|
123
|
+
if (!renderer.session || !renderer.session.isBusy()) {
|
|
124
|
+
rl.setPrompt(PROMPT);
|
|
125
|
+
rl.prompt();
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
orch.on("notice", ({ text, ok }) => {
|
|
130
|
+
ui.ensureNl();
|
|
131
|
+
ui.line((ok ? ui.c.emerald("◆ ") : ui.c.amber("◆ ")) + ui.c.dim(text));
|
|
132
|
+
prompt();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
let sigints = 0;
|
|
136
|
+
rl.on("SIGINT", () => {
|
|
137
|
+
const active = orch.active();
|
|
138
|
+
if (active && active.isBusy()) {
|
|
139
|
+
active.kill();
|
|
140
|
+
ui.ensureNl();
|
|
141
|
+
ui.line(ui.c.dim(en ? "(turn interrupted)" : "(턴 중단됨)"));
|
|
142
|
+
sigints = 0;
|
|
143
|
+
prompt();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
sigints += 1;
|
|
147
|
+
if (sigints >= 2) { rl.close(); return; }
|
|
148
|
+
ui.line(ui.c.dim(en ? "(ctrl-c again to quit)" : "(한 번 더 ctrl-c 하면 종료)"));
|
|
149
|
+
prompt();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const runForeground = (session, text) => {
|
|
153
|
+
const p = orch.sendTo(session.key, text);
|
|
154
|
+
if (p && typeof p.then === "function") {
|
|
155
|
+
p.then(() => prompt(), (e) => { ui.error(String((e && e.message) || e)); prompt(); });
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
rl.on("line", (line) => {
|
|
160
|
+
sigints = 0;
|
|
161
|
+
const input = line.trim();
|
|
162
|
+
if (!input) { prompt(); return; }
|
|
163
|
+
|
|
164
|
+
if (input.startsWith("/")) {
|
|
165
|
+
try {
|
|
166
|
+
const quit = handleSlash(ctx, input.slice(1), { orch, renderer, ensureMainSession, resolveRt, setPermission: (p) => { permission = p; }, getPermission: () => permission, setRuntime: (r) => { runtimeOverride = r; } });
|
|
167
|
+
if (quit === "quit") { rl.close(); return; }
|
|
168
|
+
} catch (e) {
|
|
169
|
+
ui.error(String((e && e.message) || e));
|
|
170
|
+
}
|
|
171
|
+
prompt();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (input.startsWith("!")) {
|
|
176
|
+
runShell(ctx, input.slice(1).trim(), permission).then(prompt, (e) => { ui.error(String((e && e.message) || e)); prompt(); });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let session;
|
|
181
|
+
try {
|
|
182
|
+
session = orch.active() || ensureMainSession(null);
|
|
183
|
+
if (!renderer.session) renderer.attach(session, { replay: false });
|
|
184
|
+
} catch (e) {
|
|
185
|
+
ui.error(String((e && e.message) || e));
|
|
186
|
+
prompt();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (session.isBusy()) {
|
|
191
|
+
// 실행 중 타이핑 = 스티어링(다음 턴 큐)
|
|
192
|
+
session.send(input);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
runForeground(session, input);
|
|
197
|
+
} catch (e) {
|
|
198
|
+
ui.error(String((e && e.message) || e));
|
|
199
|
+
prompt();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
rl.on("close", () => {
|
|
204
|
+
input.persistHistory(rl);
|
|
205
|
+
renderer.detach();
|
|
206
|
+
orch.shutdown();
|
|
207
|
+
ui.ensureNl();
|
|
208
|
+
resolve(0);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
prompt();
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/*
|
|
216
|
+
* !셸 실행 (v1 runShell의 v2 이식 — 핵심 계약 보존):
|
|
217
|
+
* - full 권한에서만 실행 (셸은 작업 공간 경계를 강제할 수 없다).
|
|
218
|
+
* - env는 턴 한정: buildChildEnv 결과를 spawn에만 전달하고 호스트 프로세스의
|
|
219
|
+
* env 객체는 절대 변형하지 않는다 (credential-env-regression 계약).
|
|
220
|
+
* - 출력 8MB 캡, 표시 전 시크릿 마스킹, 프로세스 그룹 종료.
|
|
221
|
+
*/
|
|
222
|
+
async function runShell(ctx, cmd, permission) {
|
|
223
|
+
const ui = ctx.uiInstance;
|
|
224
|
+
const en = ctx.lang === "en";
|
|
225
|
+
if (!cmd) return;
|
|
226
|
+
if (permission !== "full") {
|
|
227
|
+
ui.warn(en
|
|
228
|
+
? "Shell commands require full permission because they cannot enforce the workspace boundary."
|
|
229
|
+
: "셸 명령은 작업 공간 경계를 강제할 수 없어 무제한 권한에서만 실행됩니다.");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const { spawn } = require("node:child_process");
|
|
233
|
+
const { buildChildEnv } = require("../workforce/capture.cjs");
|
|
234
|
+
const { redactCommandSecrets } = (() => {
|
|
235
|
+
try { return require("../agentlas-ui.cjs"); } catch { return { redactCommandSecrets: (s) => s }; }
|
|
236
|
+
})();
|
|
237
|
+
let turnEnv;
|
|
238
|
+
try {
|
|
239
|
+
turnEnv = await buildChildEnv(ctx.db(), { cwd: process.cwd() });
|
|
240
|
+
} catch {
|
|
241
|
+
turnEnv = { ...process.env };
|
|
242
|
+
}
|
|
243
|
+
ui.tool("$", typeof redactCommandSecrets === "function" ? redactCommandSecrets(cmd) : cmd);
|
|
244
|
+
const maxBytes = 8 * 1024 * 1024;
|
|
245
|
+
await new Promise((resolve) => {
|
|
246
|
+
const grouped = process.platform !== "win32";
|
|
247
|
+
const child = spawn(cmd, {
|
|
248
|
+
shell: true,
|
|
249
|
+
cwd: process.cwd(),
|
|
250
|
+
env: turnEnv,
|
|
251
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
252
|
+
detached: grouped,
|
|
253
|
+
});
|
|
254
|
+
let bytes = 0;
|
|
255
|
+
let capped = false;
|
|
256
|
+
const onData = (d) => {
|
|
257
|
+
bytes += d.length;
|
|
258
|
+
if (bytes > maxBytes) {
|
|
259
|
+
if (!capped) {
|
|
260
|
+
capped = true;
|
|
261
|
+
ui.warn(en ? "(output capped at 8MB — command stopped)" : "(출력 8MB 초과 — 명령 중단)");
|
|
262
|
+
try { grouped ? process.kill(-child.pid, "SIGTERM") : child.kill("SIGTERM"); } catch { /* dead */ }
|
|
263
|
+
}
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
ui.streamDelta(d.toString("utf8"));
|
|
267
|
+
};
|
|
268
|
+
ui.streamStart(true);
|
|
269
|
+
child.stdout.on("data", onData);
|
|
270
|
+
child.stderr.on("data", onData);
|
|
271
|
+
child.on("error", (e) => { ui.streamEnd(); ui.error(e.message); resolve(); });
|
|
272
|
+
child.on("close", (code) => {
|
|
273
|
+
ui.streamEnd();
|
|
274
|
+
if (code !== 0 && !capped) ui.line(ui.c.dim(`exit ${code}`));
|
|
275
|
+
resolve();
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function fmtElapsed(ms) {
|
|
281
|
+
const s = Math.floor(ms / 1000);
|
|
282
|
+
return s >= 60 ? `${Math.floor(s / 60)}m${s % 60}s` : `${s}s`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function statusGlyph(ui, row) {
|
|
286
|
+
if (row.busy) return ui.c.emerald("●");
|
|
287
|
+
if (row.status === "failed") return ui.c.amber("✗");
|
|
288
|
+
if (row.status === "killed") return ui.c.dim("■");
|
|
289
|
+
if (row.status === "done") return ui.c.dim("✓");
|
|
290
|
+
return ui.c.dim("○");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function printSessions(ctx, orch) {
|
|
294
|
+
const ui = ctx.uiInstance;
|
|
295
|
+
const rows = orch.list();
|
|
296
|
+
if (!rows.length) {
|
|
297
|
+
ctx.out(ui.c.dim(ctx.lang === "en" ? "no sessions yet — type a task or /spawn <agent> <task>" : "세션 없음 — 작업을 입력하거나 /spawn <agent> <task>"));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
for (const r of rows) {
|
|
301
|
+
const indent = " ".repeat(r.depth);
|
|
302
|
+
const mark = r.active ? ui.c.paw("▸") : " ";
|
|
303
|
+
const queued = r.queued ? ui.c.dim(` +${r.queued}q`) : "";
|
|
304
|
+
const elapsed = r.busy ? ui.c.dim(` ${fmtElapsed(r.elapsedMs)}`) : "";
|
|
305
|
+
ctx.out(` ${mark} ${statusGlyph(ui, r)} ${ui.c.bold(r.key.padEnd(4))}${indent}${ui.c.paw(r.agent.padEnd(20))}${elapsed}${queued} ${ui.c.dim(r.lastLine || r.status)}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function handleSlash(ctx, cmdline, api) {
|
|
310
|
+
const en = ctx.lang === "en";
|
|
311
|
+
const ui = ctx.uiInstance;
|
|
312
|
+
const { orch, renderer, ensureMainSession } = api;
|
|
313
|
+
const [cmd, ...rest] = cmdline.split(/\s+/);
|
|
314
|
+
const restStr = cmdline.slice(cmd.length).trim();
|
|
315
|
+
|
|
316
|
+
switch (cmd) {
|
|
317
|
+
case "quit": case "exit": return "quit";
|
|
318
|
+
case "help": {
|
|
319
|
+
require("../commands/help.cjs").run(ctx, rest);
|
|
320
|
+
ctx.out("");
|
|
321
|
+
ctx.out(ui.c.bold(en ? "In-REPL session control (Orca)" : "REPL 세션 조종 (오르카)"));
|
|
322
|
+
// 팔레트는 Tab 완성과 같은 정본(ui/palette)에서 렌더한다 — 목록 드리프트 금지.
|
|
323
|
+
ctx.out(require("./palette.cjs").renderPalette(ctx.lang));
|
|
324
|
+
ctx.out("");
|
|
325
|
+
ctx.out(ctx.uiInstance.c.dim(en
|
|
326
|
+
? "Tab completes commands, agent names and session keys · ↑/↓ history · typing during a run queues steering · ctrl-c interrupts"
|
|
327
|
+
: "Tab: 명령·에이전트·세션키 완성 · ↑/↓ 히스토리 · 실행 중 입력은 스티어링 큐 · ctrl-c 턴 중단"));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
case "agents": case "list": require("../commands/list.cjs").run(ctx, rest); return;
|
|
331
|
+
case "chats": require("../commands/chats.cjs").run(ctx, rest); return;
|
|
332
|
+
case "doctor": require("../commands/doctor.cjs").run(ctx, rest); return;
|
|
333
|
+
case "mcp": require("../commands/mcp.cjs").run(ctx, rest); return;
|
|
334
|
+
|
|
335
|
+
case "sessions": case "tree": printSessions(ctx, orch); return;
|
|
336
|
+
|
|
337
|
+
case "s": case "switch": {
|
|
338
|
+
const key = rest[0] && rest[0].startsWith("s") ? rest[0] : `s${rest[0]}`;
|
|
339
|
+
const session = orch.setActive(key);
|
|
340
|
+
renderer.attach(session, { replay: true });
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
case "spawn": {
|
|
345
|
+
const agentToken = rest[0];
|
|
346
|
+
if (!agentToken) throw new Error("Usage: /spawn <agent> [task]");
|
|
347
|
+
const agent = findAgent(ctx.db(), agentToken);
|
|
348
|
+
if (!agent) throw new Error((en ? "agent not found: " : "에이전트를 찾을 수 없음: ") + agentToken);
|
|
349
|
+
const task = restStr.slice(agentToken.length).trim();
|
|
350
|
+
const parent = orch.active();
|
|
351
|
+
const session = orch.spawn({
|
|
352
|
+
agent,
|
|
353
|
+
runtime: parent ? parent.runtime : api.resolveRt(),
|
|
354
|
+
permission: api.getPermission(),
|
|
355
|
+
cwd: process.cwd(),
|
|
356
|
+
parentKey: parent ? parent.key : null,
|
|
357
|
+
activate: false,
|
|
358
|
+
title: task ? `sub: ${task.slice(0, 60)}` : undefined,
|
|
359
|
+
});
|
|
360
|
+
if (task) orch.sendTo(session.key, task);
|
|
361
|
+
ctx.out(ui.c.dim(`${session.key} ${agent.slug} ${task ? (en ? "started" : "시작됨") : (en ? "ready (use /steer)" : "대기 (—/steer 로 지시)")}`));
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
case "steer": {
|
|
366
|
+
const key = rest[0] && rest[0].startsWith("s") ? rest[0] : `s${rest[0]}`;
|
|
367
|
+
const msg = restStr.slice(rest[0].length).trim();
|
|
368
|
+
if (!msg) throw new Error("Usage: /steer <n> <message>");
|
|
369
|
+
orch.sendTo(key, msg);
|
|
370
|
+
ctx.out(ui.c.dim(`→ ${key}`));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
case "kill": {
|
|
375
|
+
const key = rest[0] && rest[0].startsWith("s") ? rest[0] : `s${rest[0]}`;
|
|
376
|
+
orch.kill(key);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
case "rm": {
|
|
380
|
+
const key = rest[0] && rest[0].startsWith("s") ? rest[0] : `s${rest[0]}`;
|
|
381
|
+
orch.remove(key);
|
|
382
|
+
const active = orch.active();
|
|
383
|
+
if (active) renderer.attach(active, { replay: false });
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
case "broadcast": {
|
|
388
|
+
if (!restStr) throw new Error("Usage: /broadcast <message>");
|
|
389
|
+
const sent = orch.broadcast(restStr);
|
|
390
|
+
ctx.out(ui.c.dim(`→ ${sent.join(", ") || "(none)"}`));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
case "use": {
|
|
395
|
+
if (!rest[0]) throw new Error("Usage: /use <agent>");
|
|
396
|
+
const session = ensureMainSession(rest[0]);
|
|
397
|
+
renderer.attach(session, { replay: false });
|
|
398
|
+
ctx.out(ui.c.dim(`agent: ${session.agent.slug}`));
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
case "runtime": {
|
|
403
|
+
if (!rest[0]) throw new Error("Usage: /runtime claude-code|codex|gemini");
|
|
404
|
+
api.setRuntime(rest[0]);
|
|
405
|
+
ctx.out(ui.c.dim(`runtime: ${rest[0]} (${en ? "applies to new sessions" : "새 세션부터 적용"})`));
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
case "permission": {
|
|
409
|
+
if (!["read", "write", "full"].includes(String(rest[0] || ""))) {
|
|
410
|
+
throw new Error("Usage: /permission read|write|full");
|
|
411
|
+
}
|
|
412
|
+
const level = permissions.normalize(rest[0]);
|
|
413
|
+
api.setPermission(level);
|
|
414
|
+
ctx.out(ui.c.dim(`permission: ${level} (${en ? "applies to new sessions" : "새 세션부터 적용"})`));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
default: {
|
|
419
|
+
/*
|
|
420
|
+
* 최상위 명령 폴스루: REPL 안에서 /search /install /storm /usage … 를 그대로
|
|
421
|
+
* 쓸 수 있어야 한다(없으면 사용자가 REPL을 나갔다 들어와야 했다).
|
|
422
|
+
* 제외: 자기 자신을 다시 여는 대화형 명령(chat/open/firm/setup)과 run
|
|
423
|
+
* (REPL의 평문 입력이 곧 run이다).
|
|
424
|
+
*/
|
|
425
|
+
// help/agents/list/chats/mcp/doctor 등은 위 케이스에서 이미 처리된다.
|
|
426
|
+
const REPL_EXCLUDED = new Set(["chat", "open", "firm", "setup", "run"]);
|
|
427
|
+
const commands = require("../commands/index.cjs");
|
|
428
|
+
if (!REPL_EXCLUDED.has(cmd) && commands.COMMANDS[cmd]) {
|
|
429
|
+
const result = commands.COMMANDS[cmd]().run(ctx, rest);
|
|
430
|
+
if (result && typeof result.catch === "function") {
|
|
431
|
+
result.catch((e) => ctx.err(String((e && e.message) || e)));
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (commands.DESKTOP_ONLY_SURFACES && commands.DESKTOP_ONLY_SURFACES[cmd]) {
|
|
436
|
+
ctx.out(ui.c.dim(commands.DESKTOP_ONLY_SURFACES[cmd]));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
ctx.out(ui.c.dim(en ? `unknown: /${cmd} (see /help)` : `알 수 없는 명령: /${cmd} (/help 참고)`));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
module.exports = { startRepl, printSessions };
|