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.
- package/CHANGELOG.md +102 -0
- package/README.md +5 -2
- package/bin/agentlas.cjs +15 -0
- package/engine/agentlas-cloud-runtime.cjs +54 -4
- package/engine/agentlas-i18n.cjs +4 -4
- package/engine/agentlas-input.cjs +10 -3
- package/engine/agentlas-judgment.cjs +0 -0
- package/engine/agentlas-native-host.cjs +69 -4
- package/engine/agentlas-onboard.cjs +20 -0
- package/engine/agentlas-ui.cjs +2 -0
- package/engine/agentlas-workforce.cjs +69 -13
- package/engine/agentlas.cjs +71 -3
- package/engine/agents/builder.cjs +84 -0
- package/engine/agents/router.cjs +8 -2
- package/engine/automation/launchd.cjs +131 -0
- package/engine/bootstrap-schema.sql +6 -44
- package/engine/browser/cdp.cjs +188 -0
- package/engine/browser/vault.cjs +124 -0
- package/engine/cli-output.cjs +262 -0
- package/engine/cloud-assets/package.cjs +5 -1
- package/engine/commands/automation.cjs +37 -1
- package/engine/commands/billing.cjs +3 -0
- package/engine/commands/browser.cjs +166 -8
- package/engine/commands/build.cjs +101 -20
- package/engine/commands/connect.cjs +162 -9
- package/engine/commands/creds.cjs +65 -3
- package/engine/commands/doctor.cjs +66 -4
- package/engine/commands/document.cjs +79 -0
- package/engine/commands/graph.cjs +1190 -0
- package/engine/commands/help.cjs +87 -9
- package/engine/commands/hep-cloud.cjs +9 -23
- package/engine/commands/hep-hub.cjs +9 -22
- package/engine/commands/hep-local.cjs +9 -24
- package/engine/commands/hep-network.cjs +9 -35
- package/engine/commands/index.cjs +50 -25
- package/engine/commands/list.cjs +10 -1
- package/engine/commands/mcp.cjs +6 -2
- package/engine/commands/native.cjs +18 -2
- package/engine/commands/plugin.cjs +22 -0
- package/engine/commands/project.cjs +79 -16
- package/engine/commands/roles.cjs +210 -0
- package/engine/commands/telegram.cjs +23 -16
- package/engine/commands/workforce.cjs +63 -12
- package/engine/core/desktop-core-fetch.cjs +98 -0
- package/engine/core/desktop-core.cjs +170 -0
- package/engine/graph/ask-model.cjs +159 -0
- package/engine/graph/interview.cjs +960 -0
- package/engine/graph/layout.cjs +139 -0
- package/engine/graph/package.cjs +223 -0
- package/engine/graph/vocabulary.generated.cjs +30 -0
- package/engine/hephaestus/local-core.cjs +159 -0
- package/engine/hephaestus/runtime.cjs +14 -10
- package/engine/project/controller.cjs +8 -8
- package/engine/project/team.cjs +99 -0
- package/engine/runtime-refusal.cjs +71 -0
- package/engine/runtimes/auth-evidence.cjs +78 -0
- package/engine/runtimes/detect.cjs +3 -0
- package/engine/runtimes/resolve.cjs +1 -1
- package/engine/sessions/prompt.cjs +16 -0
- package/engine/sessions/session.cjs +24 -2
- package/engine/telegram/connect.cjs +202 -0
- package/engine/tools/access-notice.cjs +86 -0
- package/engine/ui/palette.cjs +7 -3
- package/engine/ui/repl.cjs +120 -3
- package/engine/vendor/desktop-core.manifest.json +7 -0
- package/engine/workforce/capture.cjs +51 -1
- package/engine/workforce/deps.cjs +13 -0
- package/engine/workforce/local-core-transport.cjs +298 -0
- package/package.json +4 -2
- package/engine/commands/legacy-network.cjs +0 -29
|
@@ -1,31 +1,64 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
* project —
|
|
3
|
+
* project — 프로젝트 상태·초기화, 그리고 순서 팀 편성 (독립).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* `project
|
|
5
|
+
* 2026-08-06(오너 원칙): 데스크탑/플러그인은 산출물·설정을 공유할 뿐 선행 전제가
|
|
6
|
+
* 아니다. 그래서 `project team`·`project use`로 터미널에서 직접 폴더를 프로젝트로
|
|
7
|
+
* 연결하고 컨트롤러 팀을 정한다 — 이후 `run "<task>"`와 REPL 평문 입력이 데스크탑
|
|
8
|
+
* 없이 동작한다. 프로젝트 쓰기는 engine/project/team.cjs가 소유(스키마 공유).
|
|
7
9
|
*/
|
|
8
10
|
const { projectCwd } = require("../project/paths.cjs");
|
|
9
11
|
const {
|
|
10
12
|
ensureTerminalProjectForExecutionCli,
|
|
11
13
|
initializeTerminalProjectCli,
|
|
12
14
|
} = require("../project/state.cjs");
|
|
15
|
+
const { connectProjectTeam } = require("../project/team.cjs");
|
|
16
|
+
const { resolveProjectController } = require("../project/controller.cjs");
|
|
17
|
+
|
|
18
|
+
function showStatus(ctx, cwd, ko) {
|
|
19
|
+
const active = ensureTerminalProjectForExecutionCli(ctx.db(), cwd, "read", "terminal-project-status");
|
|
20
|
+
ctx.out(`${ko ? "프로젝트 폴더" : "project"}: ${cwd}`);
|
|
21
|
+
ctx.out(`${ko ? ".agentlas 상태" : "Agentlas state"}: ${active
|
|
22
|
+
? (ko ? "초기화됨" : "initialized")
|
|
23
|
+
: (ko ? "초기화되지 않음" : "not initialized")}`);
|
|
24
|
+
// 연결된 프로젝트 + 순서 팀(컨트롤러)을 보여준다 — 데스크탑 없이도 무엇이 실행될지.
|
|
25
|
+
try {
|
|
26
|
+
const resolved = resolveProjectController(ctx.db(), cwd);
|
|
27
|
+
const pool = resolved.project?.agentPool || [];
|
|
28
|
+
ctx.out(`${ko ? "연결된 프로젝트" : "connected project"}: ${resolved.project?.name || "—"}`);
|
|
29
|
+
if (pool.length) {
|
|
30
|
+
ctx.out(ko ? "순서 팀 (0번=컨트롤러):" : "ordered team (index 0 = controller):");
|
|
31
|
+
pool.forEach((m, i) => ctx.out(` ${i}. ${m.nameSnapshot}`));
|
|
32
|
+
}
|
|
33
|
+
} catch (e) {
|
|
34
|
+
if (e && e.code === "project_not_connected") {
|
|
35
|
+
ctx.out(ctx.ui.dim(ko
|
|
36
|
+
? "이 폴더는 아직 프로젝트에 연결되지 않았습니다. `agentlas project use <에이전트>`로 연결하세요."
|
|
37
|
+
: "This folder is not connected to a project yet. Connect it with `agentlas project use <agent>`."));
|
|
38
|
+
} else if (e && e.honestStop) {
|
|
39
|
+
ctx.out(ctx.ui.dim(String(e.message)));
|
|
40
|
+
}
|
|
41
|
+
// 그 외 오류는 status를 막지 않는다.
|
|
42
|
+
}
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function reportTeam(ctx, result, ko) {
|
|
47
|
+
ctx.out(`${ctx.ui.green("✓")} ${ko ? "프로젝트" : "project"} ${result.created ? (ko ? "생성" : "created") : (ko ? "갱신" : "updated")}: ${result.name}`);
|
|
48
|
+
ctx.out(ko ? "순서 팀 (0번=컨트롤러):" : "ordered team (index 0 = controller):");
|
|
49
|
+
result.team.forEach((m, i) => ctx.out(` ${i}. ${m.nameSnapshot}`));
|
|
50
|
+
ctx.out(ctx.ui.dim(ko
|
|
51
|
+
? "이제 이 폴더에서 바로 실행: agentlas run \"<할 일>\" 또는 REPL에서 그냥 문장을 치세요."
|
|
52
|
+
: "Now run here directly: agentlas run \"<task>\" or just type a sentence in the REPL."));
|
|
53
|
+
}
|
|
13
54
|
|
|
14
55
|
function run(ctx, args) {
|
|
15
56
|
const action = String(args[0] || "status").toLowerCase();
|
|
16
57
|
const cwd = projectCwd();
|
|
17
58
|
const ko = ctx.lang === "ko";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
ctx.out(`${ko ? "Agentlas 상태" : "Agentlas state"}: ${active
|
|
22
|
-
? (ko ? "초기화됨" : "initialized")
|
|
23
|
-
: (ko ? "초기화되지 않음" : "not initialized")}`);
|
|
24
|
-
if (!active) ctx.out(ko
|
|
25
|
-
? "비공개 .agentlas 상태와 로컬 제외 파일을 만들려면 `agentlas project init`을 실행하세요."
|
|
26
|
-
: "Run `agentlas project init` to create private .agentlas state and update local ignore files.");
|
|
27
|
-
return 0;
|
|
28
|
-
}
|
|
59
|
+
|
|
60
|
+
if (action === "status") return showStatus(ctx, cwd, ko);
|
|
61
|
+
|
|
29
62
|
if (action === "init") {
|
|
30
63
|
ctx.out(ko
|
|
31
64
|
? "비공개 Agentlas 프로젝트 상태를 초기화합니다. .agentlas/ 생성, .gitignore 갱신, 로컬 자격증명·서명 템플릿 추가가 포함될 수 있습니다."
|
|
@@ -33,14 +66,44 @@ function run(ctx, args) {
|
|
|
33
66
|
try {
|
|
34
67
|
initializeTerminalProjectCli(ctx.db(), cwd);
|
|
35
68
|
} catch (e) {
|
|
36
|
-
// fail-closed: 프라이버시 경계(.gitignore/심볼릭 링크)를 보증 못 하면 생성 금지.
|
|
37
69
|
ctx.err(String((e && e.message) || e));
|
|
38
70
|
return 1;
|
|
39
71
|
}
|
|
40
72
|
ctx.out(`${ko ? "초기화됨" : "Initialized"}: ${cwd}`);
|
|
41
73
|
return 0;
|
|
42
74
|
}
|
|
43
|
-
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
* project use <에이전트> — 이 폴더를 프로젝트로 연결 + 단일 컨트롤러 팀
|
|
78
|
+
* project team <에이전트> [<에이전트>...] — 순서 팀 편성 (0번=컨트롤러)
|
|
79
|
+
* 둘 다 데스크탑 없이 동작한다.
|
|
80
|
+
*/
|
|
81
|
+
if (action === "use" || action === "team") {
|
|
82
|
+
const tokens = args.slice(1).filter((t) => t && !String(t).startsWith("-"));
|
|
83
|
+
if (!tokens.length) {
|
|
84
|
+
ctx.err(ko
|
|
85
|
+
? `사용법: agentlas project ${action} <에이전트> ${action === "team" ? "[<에이전트>...]" : ""} · 에이전트 목록: agentlas list`
|
|
86
|
+
: `Usage: agentlas project ${action} <agent> ${action === "team" ? "[<agent>...]" : ""} · list agents: agentlas list`);
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const result = connectProjectTeam(ctx.db(), cwd, tokens, {});
|
|
91
|
+
reportTeam(ctx, result, ko);
|
|
92
|
+
return 0;
|
|
93
|
+
} catch (e) {
|
|
94
|
+
if (e && e.code === "team_agent_not_found") {
|
|
95
|
+
ctx.err((ko ? "에이전트를 찾을 수 없습니다: " : "agent not found: ") + e.token);
|
|
96
|
+
ctx.err(ctx.ui.dim(ko ? "설치된 에이전트: agentlas list" : "installed agents: agentlas list"));
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
ctx.err(String((e && e.message) || e));
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
ctx.err(ko
|
|
105
|
+
? "사용법: agentlas project [status | init | use <에이전트> | team <에이전트>...]"
|
|
106
|
+
: "Usage: agentlas project [status | init | use <agent> | team <agent>...]");
|
|
44
107
|
return 1;
|
|
45
108
|
}
|
|
46
109
|
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* roles — 오케스트레이터/워커 모델 역할 조회·설정.
|
|
4
|
+
*
|
|
5
|
+
* 배경(2026-08-05 감사, 결함 C): `list`와 `doctor`는 model_roles를 보여주는데
|
|
6
|
+
* 터미널 어디에도 쓰기 경로가 없었다(전 코드베이스 SELECT만). 바꾸려면 데스크탑을
|
|
7
|
+
* 열어야 했고, REPL `/model`은 지역 변수 대입이라 검증도 저장도 안 됐다.
|
|
8
|
+
*
|
|
9
|
+
* 계약:
|
|
10
|
+
* - 스키마 소유권은 데스크탑(v79)에 있다. 여기서는 테이블·컬럼을 만들지 않는다 —
|
|
11
|
+
* 테이블이 없으면 정직 정지 + 데스크탑 안내. 있는 행에 UPSERT만 한다.
|
|
12
|
+
* - kind는 데스크탑과 같은 어휘(RUNTIME_BIN + byok 계열)로 검증한다. PATH에
|
|
13
|
+
* 없는 CLI도 저장은 허용하되(미리 설정) 그 자리에서 알린다 — 조용한 저장 금지.
|
|
14
|
+
* - worker --inherit 는 스키마 CHECK(worker만 inherit 가능)를 그대로 따른다.
|
|
15
|
+
* - 모델 id는 제공자마다 열린 어휘라 존재 검증하지 않는다(워크로드 라우팅의
|
|
16
|
+
* EFFORTS 주석과 같은 원칙: 화이트리스트를 게이트로 쓰지 않는다).
|
|
17
|
+
*/
|
|
18
|
+
const { RUNTIME_BIN, whichSync } = require("../runtimes/detect.cjs");
|
|
19
|
+
const { runtimeAuthEvidence } = require("../runtimes/auth-evidence.cjs");
|
|
20
|
+
const { MODEL_ROLE_TABLE, VALID_ROLES, resolvedModelRole, roleMembers } = require("../runtimes/roles.cjs");
|
|
21
|
+
const { runWriteTransaction } = require("../agentlas-sqlite-policy.cjs");
|
|
22
|
+
const { EFFORTS } = require("../agentlas-workload-routing.cjs");
|
|
23
|
+
|
|
24
|
+
// 데스크탑 RuntimeKind 어휘(runtimes/detect.cjs 주석과 동일). CLI가 아닌 종류는
|
|
25
|
+
// which 검사 대상이 아니다.
|
|
26
|
+
const KNOWN_KINDS = new Set([...Object.keys(RUNTIME_BIN), "byok", "ollama", "lmstudio", "mlx"]);
|
|
27
|
+
|
|
28
|
+
function fmt(selection, en) {
|
|
29
|
+
if (!selection) return en ? "not set" : "미설정";
|
|
30
|
+
const bits = [
|
|
31
|
+
`${selection.kind}${selection.model ? `/${selection.model}` : ""}`,
|
|
32
|
+
selection.effort ? `effort=${selection.effort}` : null,
|
|
33
|
+
selection.inherit ? (en ? "(inherits orchestrator)" : "(오케스트레이터 상속)") : null,
|
|
34
|
+
];
|
|
35
|
+
return bits.filter(Boolean).join(" ");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function show(ctx, args = []) {
|
|
39
|
+
const en = ctx.lang === "en";
|
|
40
|
+
const db = ctx.db();
|
|
41
|
+
if (args.includes("--json")) {
|
|
42
|
+
ctx.out(JSON.stringify({
|
|
43
|
+
orchestrator: resolvedModelRole(db, "orchestrator"),
|
|
44
|
+
worker: resolvedModelRole(db, "worker"),
|
|
45
|
+
pools: { orchestrator: roleMembers(db, "orchestrator"), worker: roleMembers(db, "worker") },
|
|
46
|
+
}, null, 2));
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
ctx.out(ctx.ui.bold(en ? "Model roles" : "모델 역할"));
|
|
50
|
+
for (const role of ["orchestrator", "worker"]) {
|
|
51
|
+
const resolved = resolvedModelRole(db, role);
|
|
52
|
+
ctx.out(` ${role.padEnd(13)} ${fmt(resolved, en)}`);
|
|
53
|
+
const pool = roleMembers(db, role);
|
|
54
|
+
if (pool.length) {
|
|
55
|
+
ctx.out(ctx.ui.dim(` ${" ".repeat(13)} ${en ? "pool: " : "풀: "}${pool.map((m) => `${m.position}.${m.kind}${m.model ? `/${m.model}` : ""}`).join(" ")}`));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
ctx.out("");
|
|
59
|
+
ctx.out(ctx.ui.dim(en
|
|
60
|
+
? 'Change: agentlas roles set <orchestrator|worker> <runtime> [--model <id>] [--effort <level>] · worker inherit: agentlas roles set worker --inherit'
|
|
61
|
+
: '변경: agentlas roles set <orchestrator|worker> <runtime> [--model <id>] [--effort <level>] · 워커 상속: agentlas roles set worker --inherit'));
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseSetFlags(args) {
|
|
66
|
+
const rest = [];
|
|
67
|
+
const flags = {};
|
|
68
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
69
|
+
const arg = args[i];
|
|
70
|
+
if (arg === "--model") { flags.model = String(args[++i] ?? "").trim(); continue; }
|
|
71
|
+
if (arg.startsWith("--model=")) { flags.model = arg.slice(8).trim(); continue; }
|
|
72
|
+
if (arg === "--effort") { flags.effort = String(args[++i] ?? "").trim().toLowerCase(); continue; }
|
|
73
|
+
if (arg.startsWith("--effort=")) { flags.effort = arg.slice(9).trim().toLowerCase(); continue; }
|
|
74
|
+
if (arg === "--inherit") { flags.inherit = true; continue; }
|
|
75
|
+
rest.push(arg);
|
|
76
|
+
}
|
|
77
|
+
return { rest, flags };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function set(ctx, args) {
|
|
81
|
+
const en = ctx.lang === "ko" ? false : true;
|
|
82
|
+
const ko = !en;
|
|
83
|
+
const { rest, flags } = parseSetFlags(args);
|
|
84
|
+
const role = String(rest[0] || "").toLowerCase();
|
|
85
|
+
const kindArg = rest[1] ? String(rest[1]).toLowerCase() : null;
|
|
86
|
+
|
|
87
|
+
const usage = ko
|
|
88
|
+
? "사용법: agentlas roles set <orchestrator|worker> <runtime> [--model <id>] [--effort <level>] | agentlas roles set worker --inherit"
|
|
89
|
+
: "Usage: agentlas roles set <orchestrator|worker> <runtime> [--model <id>] [--effort <level>] | agentlas roles set worker --inherit";
|
|
90
|
+
|
|
91
|
+
if (!VALID_ROLES.has(role)) { ctx.err(usage); return 1; }
|
|
92
|
+
if (flags.inherit && role !== "worker") {
|
|
93
|
+
ctx.err(ko ? "--inherit 는 worker 역할에만 씁니다 (스키마 계약)." : "--inherit applies to the worker role only (schema contract).");
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
if (!flags.inherit && !kindArg) { ctx.err(usage); return 1; }
|
|
97
|
+
if (kindArg && !KNOWN_KINDS.has(kindArg)) {
|
|
98
|
+
ctx.err((ko ? "모르는 런타임 종류: " : "unknown runtime kind: ") + kindArg);
|
|
99
|
+
ctx.err(ko
|
|
100
|
+
? `가능한 값: ${[...KNOWN_KINDS].join(" · ")}`
|
|
101
|
+
: `valid kinds: ${[...KNOWN_KINDS].join(" · ")}`);
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
if (flags.effort !== undefined && !EFFORTS.includes(flags.effort)) {
|
|
105
|
+
ctx.err((ko ? "모르는 effort 값: " : "unknown effort level: ") + flags.effort + ` (${EFFORTS.join("|")})`);
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const db = ctx.db();
|
|
110
|
+
// 스키마 창조 금지 — 테이블은 데스크탑 마이그레이션 v79가 만든다.
|
|
111
|
+
if (!ctx.tableExists(db, MODEL_ROLE_TABLE)) {
|
|
112
|
+
ctx.err(ko
|
|
113
|
+
? "model_roles 테이블이 아직 없습니다. 스키마는 데스크탑 앱이 소유합니다 — 데스크탑을 한 번 실행하면 생성됩니다."
|
|
114
|
+
: "The model_roles table does not exist yet. Desktop owns this schema — run the desktop app once to create it.");
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let kind = kindArg;
|
|
119
|
+
let model = flags.model !== undefined ? (flags.model || null) : undefined;
|
|
120
|
+
let inherit = 0;
|
|
121
|
+
if (flags.inherit) {
|
|
122
|
+
// 상속 = 워커가 오케스트레이터를 따른다. 스키마상 kind NOT NULL이라
|
|
123
|
+
// 현재 오케스트레이터의 좌표를 복사해 두되 inherit=1로 표시한다(리더 부재 시 정직 정지).
|
|
124
|
+
const orchestrator = resolvedModelRole(db, "orchestrator");
|
|
125
|
+
if (!orchestrator) {
|
|
126
|
+
ctx.err(ko ? "상속할 오케스트레이터 설정이 없습니다. 먼저 orchestrator를 설정하세요." : "No orchestrator to inherit from. Set the orchestrator first.");
|
|
127
|
+
return 1;
|
|
128
|
+
}
|
|
129
|
+
kind = orchestrator.kind;
|
|
130
|
+
if (model === undefined) model = orchestrator.model;
|
|
131
|
+
inherit = 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const now = new Date().toISOString();
|
|
135
|
+
runWriteTransaction(db, () => {
|
|
136
|
+
const existing = db.prepare("SELECT * FROM model_roles WHERE role=?").get(role);
|
|
137
|
+
if (existing) {
|
|
138
|
+
// kind가 바뀌면 이전 모델 id는 새 런타임의 어휘가 아니다(예: kimi에 opus).
|
|
139
|
+
// --model 미지정 시 유지가 아니라 초기화 — 무의미한 좌표를 승계하지 않는다.
|
|
140
|
+
const keepModel = existing.kind === kind ? existing.model : null;
|
|
141
|
+
db.prepare(
|
|
142
|
+
"UPDATE model_roles SET kind=?, model=?, effort=?, inherit=?, updated_at=? WHERE role=?",
|
|
143
|
+
).run(
|
|
144
|
+
kind,
|
|
145
|
+
model === undefined ? keepModel : model,
|
|
146
|
+
flags.effort === undefined ? existing.effort : (flags.effort === "none" ? null : flags.effort),
|
|
147
|
+
inherit,
|
|
148
|
+
now,
|
|
149
|
+
role,
|
|
150
|
+
);
|
|
151
|
+
} else {
|
|
152
|
+
db.prepare(
|
|
153
|
+
"INSERT INTO model_roles (role, kind, model, effort, inherit, updated_at) VALUES (?,?,?,?,?,?)",
|
|
154
|
+
).run(role, kind, model === undefined ? null : model, flags.effort === undefined || flags.effort === "none" ? null : flags.effort, inherit, now);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const saved = resolvedModelRole(db, role);
|
|
159
|
+
ctx.out(`${ctx.ui.green("✓")} ${role} = ${fmt(saved, en)}`);
|
|
160
|
+
|
|
161
|
+
// 저장은 됐지만 실행이 안 될 수 있는 상태는 그 자리에서 말한다 — 조용한 저장 금지.
|
|
162
|
+
const bin = RUNTIME_BIN[kind];
|
|
163
|
+
if (bin && !whichSync(bin)) {
|
|
164
|
+
ctx.out(ctx.ui.dim(ko
|
|
165
|
+
? `참고: '${bin}' 실행 파일이 PATH에 없습니다. 설치 전에는 이 역할의 실행이 실패합니다.`
|
|
166
|
+
: `Note: '${bin}' is not on PATH. Runs with this role will fail until it is installed.`));
|
|
167
|
+
} else if (bin) {
|
|
168
|
+
const evidence = runtimeAuthEvidence(kind);
|
|
169
|
+
if (evidence.status === "none") {
|
|
170
|
+
ctx.out(ctx.ui.dim(ko
|
|
171
|
+
? `참고: ${kind} 로그인 흔적이 없습니다. 로그인 전에는 실행이 실패할 수 있습니다.`
|
|
172
|
+
: `Note: no local sign-in evidence for ${kind}. Runs may fail until you sign in.`));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function run(ctx, args = []) {
|
|
179
|
+
const en = ctx.lang === "en";
|
|
180
|
+
const [sub, ...rest] = args;
|
|
181
|
+
if (!sub || sub === "show" || sub === "list" || sub === "--json") return show(ctx, sub === "--json" ? ["--json"] : rest);
|
|
182
|
+
if (sub === "set") return set(ctx, rest);
|
|
183
|
+
if (sub === "help" || sub === "--help" || sub === "-h") {
|
|
184
|
+
// SELF_HELP_COMMANDS 계약: --help 는 스텁이 아니라 실제 안내여야 한다.
|
|
185
|
+
ctx.out(en
|
|
186
|
+
? [
|
|
187
|
+
"agentlas roles — orchestrator/worker model roles (persisted, shared with Desktop)",
|
|
188
|
+
" roles show both roles and their pools",
|
|
189
|
+
" roles set <orchestrator|worker> <runtime> [--model <id>] [--effort <level>]",
|
|
190
|
+
" roles set worker --inherit worker follows the orchestrator",
|
|
191
|
+
"",
|
|
192
|
+
` runtimes: ${[...KNOWN_KINDS].join(" · ")}`,
|
|
193
|
+
" REPL /model and /runtime are session-scoped — this command is the persistent path.",
|
|
194
|
+
].join("\n")
|
|
195
|
+
: [
|
|
196
|
+
"agentlas roles — 오케스트레이터/워커 모델 역할 (영구 저장, 데스크탑과 공유)",
|
|
197
|
+
" roles 두 역할과 풀 조회",
|
|
198
|
+
" roles set <orchestrator|worker> <runtime> [--model <id>] [--effort <level>]",
|
|
199
|
+
" roles set worker --inherit 워커가 오케스트레이터를 따름",
|
|
200
|
+
"",
|
|
201
|
+
` 런타임: ${[...KNOWN_KINDS].join(" · ")}`,
|
|
202
|
+
" REPL /model·/runtime 은 세션 한정입니다 — 영구 설정은 이 명령입니다.",
|
|
203
|
+
].join("\n"));
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
ctx.err(en ? `unknown roles subcommand: ${sub} (show · set)` : `모르는 roles 하위 명령: ${sub} (show · set)`);
|
|
207
|
+
return 1;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = { run };
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/* telegram — 바인딩 현황 (읽기 전용). 페어링·봇 발급은 Desktop Connect 소관. */
|
|
2
|
+
/* telegram — 바인딩 현황 (읽기 전용). 페어링·봇 발급은 아직 Desktop Connect 소관. */
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/*
|
|
5
|
+
* 바인딩 표를 그린다. connect.cjs 도 같은 표를 쓰도록 공용화(2026-08-06):
|
|
6
|
+
* 예전 `connect telegram` 은 Hephaestus 플러그인으로 패스스루해 원시 라우터
|
|
7
|
+
* JSON(hub_candidates)을 사용자에게 그대로 덤프했다 — build 스텁과 같은 계열.
|
|
8
|
+
*/
|
|
9
|
+
function renderTelegram(ctx) {
|
|
5
10
|
const ko = ctx.lang === "ko";
|
|
6
11
|
const db = ctx.db();
|
|
7
12
|
let rows;
|
|
@@ -11,22 +16,24 @@ function run(ctx) {
|
|
|
11
16
|
rows = [];
|
|
12
17
|
}
|
|
13
18
|
if (!rows.length) {
|
|
14
|
-
ctx.out(ko
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
ctx.out(`${String(r.id).slice(0, 8)} ${r.target_kind}:${String(r.target_id).slice(0, 20).padEnd(21)} ${String(bot).padEnd(24)} ${String(chat).slice(0, 28).padEnd(29)} ${status}`);
|
|
19
|
+
ctx.out(ko ? "Telegram 연결이 없습니다." : "No Telegram bindings.");
|
|
20
|
+
} else {
|
|
21
|
+
for (const r of rows) {
|
|
22
|
+
const bot = r.bot_username ? "@" + r.bot_username : "(bot not set)";
|
|
23
|
+
const chat = r.telegram_chat_title || r.telegram_chat_id || "(chat not connected)";
|
|
24
|
+
const status = r.status || (r.telegram_chat_id ? "paired" : "pending");
|
|
25
|
+
ctx.out(`${String(r.id).slice(0, 8)} ${r.target_kind}:${String(r.target_id).slice(0, 20).padEnd(21)} ${String(bot).padEnd(24)} ${String(chat).slice(0, 28).padEnd(29)} ${status}`);
|
|
26
|
+
}
|
|
27
|
+
ctx.out("");
|
|
24
28
|
}
|
|
25
|
-
ctx.out("");
|
|
26
29
|
ctx.out(ctx.ui.dim(ko
|
|
27
|
-
? "
|
|
28
|
-
: "
|
|
30
|
+
? "봇 발급·페어링은 현재 Agentlas Desktop Connect에서 수행합니다. 이 명령은 바인딩 상태를 보여줍니다."
|
|
31
|
+
: "Bot issuance and pairing currently happen in Agentlas Desktop Connect. This command shows binding status."));
|
|
29
32
|
return 0;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
function run(ctx) {
|
|
36
|
+
return renderTelegram(ctx);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { run, renderTelegram };
|
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
* runtimeOverride, { cwd, projectPath, permission })
|
|
8
8
|
*
|
|
9
9
|
* 라우팅 불변식: 이 표면은 Agent Workforce Ontology 전용 fail-closed 경로다.
|
|
10
|
-
* 어휘
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* 어휘 라우터로 조용히 폴백하지 않는다.
|
|
11
|
+
*
|
|
12
|
+
* 스코프 고지(2026-08-05): cmdWorkforce 는 sourceScope 를 싣지 않고
|
|
13
|
+
* AGENTLAS_MCP_BASE_URL 을 직접 친다. 서버는 sourceScope 부재를 "hub" 로 잡으므로
|
|
14
|
+
* 이 명령이 실제로 보는 메뉴는 **공개 Hub 뿐**이다. 로컬·오너 Cloud 를 포함한
|
|
15
|
+
* 연합 편성은 MCP 호스트의 /hep-network 가 한다 — 이 표면에는 그 계층이 없어서
|
|
16
|
+
* hep-network/hep-local/hep-cloud/hep-hub/legacy-network 를 전부 삭제했다.
|
|
13
17
|
*
|
|
14
18
|
* Write-capable first contact bootstraps the exact current project through the
|
|
15
19
|
* canonical Core merge-only boundary. Read-only inspection remains passive.
|
|
@@ -79,15 +83,62 @@ async function dispatch(ctx, command, args) {
|
|
|
79
83
|
});
|
|
80
84
|
return result && result.ok ? 0 : 1;
|
|
81
85
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
/*
|
|
87
|
+
* 소스 스코프 편성 4종 — 2026-08-05 네이티브 배선.
|
|
88
|
+
*
|
|
89
|
+
* 역사: 7/28에 "별칭이 스코프를 버린다"를 고치며 hep-*를 외부 CLI 패스스루로
|
|
90
|
+
* 승격했는데, 그 CLI는 exit 3 + host_llm_required만 반환하는 스텁이었다(8/5
|
|
91
|
+
* 삭제). 재조사 결과 편성의 세 조각(4,578줄 루프·로컬 Core MCP·주입 지점)이
|
|
92
|
+
* 전부 이 머신에 있었고, 빠진 것은 배선뿐이었다. 이제 이 표면이 직접
|
|
93
|
+
* 호스트다: 루프의 리더 LLM이 WorkOrder를 작성하고, 로컬 Core가 선언된
|
|
94
|
+
* 스코프의 메뉴를 연합하며, 같은 LLM이 정확 릴리스를 고른다.
|
|
95
|
+
*
|
|
96
|
+
* 폴백 금지: 로컬 Core가 없으면 원격 Hub로 조용히 내려가지 않는다 — 이름이
|
|
97
|
+
* 약속한 스코프가 거짓이 된다(7/28 결함의 재발). 정직 정지 + 설치 안내.
|
|
98
|
+
*/
|
|
99
|
+
case "hep-network":
|
|
100
|
+
case "hep-local":
|
|
101
|
+
case "hep-cloud":
|
|
102
|
+
case "hep-hub": {
|
|
103
|
+
const sourceScope = command.slice(4); // network|local|cloud|hub
|
|
104
|
+
const ko = ctx.lang !== "en";
|
|
105
|
+
const { rest, runtimeOverride } = splitRuntimeOverride(args);
|
|
106
|
+
const hasTask = rest.some((token, i) =>
|
|
107
|
+
!["--benchmark", "--json", "--parallel", "-n"].includes(String(token))
|
|
108
|
+
&& !(i > 0 && ["--parallel", "-n"].includes(String(rest[i - 1]))));
|
|
109
|
+
if (!hasTask) {
|
|
110
|
+
ctx.err(`usage: agentlas ${command} "<task>" [--parallel N] [--json] [--runtime <kind>]`);
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
const { localCoreBin } = require("../hephaestus/local-core.cjs");
|
|
114
|
+
if (!localCoreBin()) {
|
|
115
|
+
ctx.err(ko
|
|
116
|
+
? `${sourceScope} 스코프 편성은 로컬 Agentlas-OS Core(hephaestus)가 연합을 소유합니다 — 설치되어 있지 않습니다.`
|
|
117
|
+
: `${sourceScope}-scope staffing is federated by the local Agentlas-OS core (hephaestus), which is not installed.`);
|
|
118
|
+
ctx.err(ko
|
|
119
|
+
? "설치: Agentlas 데스크탑 앱 또는 https://agentlas.cloud 안내를 따르세요. 공개 Hub 메뉴만이라면 지금도: agentlas workforce \"<요청>\""
|
|
120
|
+
: "Install Agentlas-OS (Desktop app or https://agentlas.cloud). For the public Hub menu only, this works today: agentlas workforce \"<request>\"");
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
const db = ctx.db();
|
|
124
|
+
const cwd = projectCwd();
|
|
125
|
+
const permission = resolvePermission(ctx);
|
|
126
|
+
const projectPath = ensureTerminalProjectForExecutionCli(db, cwd, permission, `terminal-${command}`) || cwd;
|
|
127
|
+
const { createLocalCoreHubTool } = require("../workforce/local-core-transport.cjs");
|
|
128
|
+
const { createLocalCoreWorkforceRuntime } = require("../workforce/deps.cjs");
|
|
129
|
+
const transport = createLocalCoreHubTool({ sourceScope, projectDir: projectPath, cwd });
|
|
130
|
+
try {
|
|
131
|
+
const runtime = createLocalCoreWorkforceRuntime({ lang: ctx.lang, out: ctx.out }, transport);
|
|
132
|
+
const result = await runtime.cmdWorkforce(db, rest, runtimeOverride, {
|
|
133
|
+
cwd,
|
|
134
|
+
projectPath,
|
|
135
|
+
permission,
|
|
136
|
+
sourceScope,
|
|
137
|
+
});
|
|
138
|
+
return result && result.ok ? 0 : 1;
|
|
139
|
+
} finally {
|
|
140
|
+
transport.close();
|
|
141
|
+
}
|
|
91
142
|
}
|
|
92
143
|
default: {
|
|
93
144
|
ctx.err(`unknown workforce command: ${command}`);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* core/desktop-core-fetch — 데스크탑 코어를 필요할 때만 내려받는다 (2026-08-06).
|
|
4
|
+
*
|
|
5
|
+
* 배경(오너: "코덱스는 어떻게 했냐"): 코덱스 CLI는 무거운 실행파일을 npm 패키지 안에 미리
|
|
6
|
+
* 담지 않는다 — 설치 시점(postinstall)에 그 사람 플랫폼용 파일만 GitHub Release 에서 내려받는다.
|
|
7
|
+
* 우리도 같은 패턴을 쓴다: 그래프 실행 커널(52MB, engine/vendor/desktop-core.cjs 가 재사용하는
|
|
8
|
+
* 데스크탑 코어)은 git 에 커밋하지 않고, `graph run` 을 실제로 쓸 때만 GitHub Release 자산을
|
|
9
|
+
* 내려받아 로컬 캐시(userDataDir()/desktop-core-cache/<version>/)에 푼다. 다음부턴 캐시를 쓴다.
|
|
10
|
+
*
|
|
11
|
+
* 안전: 무엇을 왜 받는지 사용자에게 **말하고** 받는다(조용히 안 받는다). sha256 체크섬으로
|
|
12
|
+
* 무결성을 확인한 뒤에만 푼다 — 받아온 걸 검증 없이 실행하지 않는다.
|
|
13
|
+
*
|
|
14
|
+
* 매니페스트(engine/vendor/desktop-core.manifest.json, git 커밋 — 이 파일만 작다)가
|
|
15
|
+
* {version, url, sha256, sizeBytes} 를 담는다. 실물(52MB)은 그 url 이 가리키는 곳에서 온다.
|
|
16
|
+
*/
|
|
17
|
+
const crypto = require("node:crypto");
|
|
18
|
+
const fs = require("node:fs");
|
|
19
|
+
const path = require("node:path");
|
|
20
|
+
const { spawnSync } = require("node:child_process");
|
|
21
|
+
const { userDataDir } = require("./paths.cjs");
|
|
22
|
+
|
|
23
|
+
// env 오버라이드는 테스트 전용(게이트가 로컬 서버를 가리키는 가짜 매니페스트로 전 경로를 잠근다).
|
|
24
|
+
function manifestPath() { return process.env.AGENTLAS_DESKTOP_CORE_MANIFEST || path.join(__dirname, "..", "vendor", "desktop-core.manifest.json"); }
|
|
25
|
+
|
|
26
|
+
function readManifest() {
|
|
27
|
+
try { return JSON.parse(fs.readFileSync(manifestPath(), "utf8")); } catch { return null; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function cacheDir(version) { return path.join(userDataDir(), "desktop-core-cache", String(version)); }
|
|
31
|
+
function cacheDistDir(version) { return path.join(cacheDir(version), "dist"); }
|
|
32
|
+
|
|
33
|
+
function sha256File(file) {
|
|
34
|
+
const hash = crypto.createHash("sha256");
|
|
35
|
+
hash.update(fs.readFileSync(file));
|
|
36
|
+
return hash.digest("hex");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 이미 캐시에 온전히 풀려 있으면 그 dist 경로, 아니면 null. */
|
|
40
|
+
function cachedCoreRoot(manifest = readManifest()) {
|
|
41
|
+
if (!manifest) return null;
|
|
42
|
+
const dist = cacheDistDir(manifest.version);
|
|
43
|
+
const marker = path.join(cacheDir(manifest.version), ".complete");
|
|
44
|
+
if (fs.existsSync(marker) && fs.existsSync(path.join(dist, "electron", "workflow", "run-graph.js"))) return dist;
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 매니페스트가 가리키는 데스크탑 코어를 내려받아 캐시에 푼다.
|
|
50
|
+
* onNotice(text): 사용자에게 보여줄 안내(무엇을·왜 받는지) — 조용히 받지 않는다.
|
|
51
|
+
* 반환: 성공 시 dist 경로, 실패 시 null(호출부가 정직하게 멈춘다).
|
|
52
|
+
*/
|
|
53
|
+
async function fetchDesktopCore({ onNotice } = {}) {
|
|
54
|
+
const manifest = readManifest();
|
|
55
|
+
if (!manifest || !manifest.url || !manifest.sha256) return null;
|
|
56
|
+
|
|
57
|
+
const existing = cachedCoreRoot(manifest);
|
|
58
|
+
if (existing) return existing;
|
|
59
|
+
|
|
60
|
+
const say = (t) => { if (typeof onNotice === "function") onNotice(t); };
|
|
61
|
+
say(`Downloading the graph-execution engine (${manifest.sizeBytes ? Math.round(manifest.sizeBytes / 1024 / 1024) + " MB" : "one-time"}) from ${manifest.url} …`);
|
|
62
|
+
|
|
63
|
+
const dir = cacheDir(manifest.version);
|
|
64
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
65
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
66
|
+
const tarPath = path.join(dir, "desktop-core.tar.gz");
|
|
67
|
+
|
|
68
|
+
const res = await fetch(manifest.url);
|
|
69
|
+
if (!res.ok) { say(`Download failed: HTTP ${res.status}`); return null; }
|
|
70
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
71
|
+
fs.writeFileSync(tarPath, buf);
|
|
72
|
+
|
|
73
|
+
const digest = sha256File(tarPath);
|
|
74
|
+
if (digest !== manifest.sha256) {
|
|
75
|
+
say(`Checksum mismatch (expected ${manifest.sha256.slice(0, 12)}…, got ${digest.slice(0, 12)}…) — refusing to use it.`);
|
|
76
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const extract = spawnSync("tar", ["-xzf", tarPath, "-C", dir], { encoding: "utf8" });
|
|
81
|
+
if (extract.status !== 0) {
|
|
82
|
+
say(`Extraction failed: ${extract.stderr || extract.error || "unknown error"}`);
|
|
83
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
fs.rmSync(tarPath);
|
|
87
|
+
|
|
88
|
+
if (!fs.existsSync(path.join(cacheDistDir(manifest.version), "electron", "workflow", "run-graph.js"))) {
|
|
89
|
+
say("Downloaded archive did not contain the expected engine files.");
|
|
90
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
fs.writeFileSync(path.join(dir, ".complete"), new Date().toISOString());
|
|
94
|
+
say("Engine ready.");
|
|
95
|
+
return cacheDistDir(manifest.version);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { readManifest, cachedCoreRoot, fetchDesktopCore, cacheDistDir };
|