agentlas 1.0.12 → 1.0.15
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 +53 -0
- package/README.md +4 -2
- package/bin/agentlas.cjs +17 -3
- package/engine/agentlas-cloud-runtime.cjs +8 -2
- package/engine/agentlas-config.cjs +25 -18
- package/engine/agentlas-core-harness.cjs +14 -1
- package/engine/agentlas-i18n.cjs +2 -0
- package/engine/agentlas-input.cjs +1 -1
- package/engine/agentlas-memory-governance.cjs +10 -0
- package/engine/agentlas-onboard.cjs +22 -5
- package/engine/agentlas-sqlite-policy.cjs +18 -7
- package/engine/agentlas-workforce.cjs +136 -19
- package/engine/agentlas-workload-routing.cjs +32 -4
- package/engine/agentlas.cjs +8 -0
- package/engine/automation/daemon.cjs +67 -30
- package/engine/automation/schedule.cjs +16 -0
- package/engine/automation/store.cjs +70 -12
- package/engine/bootstrap-schema.sql +788 -53
- package/engine/cloud-assets/cas.cjs +7 -2
- package/engine/cloud-assets/commands.cjs +2 -0
- package/engine/cloud-assets/package.cjs +49 -1
- package/engine/cloud-assets/state.cjs +35 -0
- package/engine/commands/automation.cjs +8 -0
- package/engine/commands/chats.cjs +6 -1
- package/engine/commands/firm.cjs +7 -0
- package/engine/commands/help.cjs +23 -2
- package/engine/commands/hep-cloud.cjs +31 -0
- package/engine/commands/hep-hub.cjs +30 -0
- package/engine/commands/hep-local.cjs +32 -0
- package/engine/commands/hep-network.cjs +43 -0
- package/engine/commands/index.cjs +38 -7
- package/engine/commands/open.cjs +5 -1
- package/engine/commands/run.cjs +12 -2
- package/engine/commands/setup.cjs +12 -11
- package/engine/commands/storm.cjs +5 -9
- package/engine/commands/swarm.cjs +4 -4
- package/engine/commands/uninstall.cjs +36 -2
- package/engine/commands/version.cjs +29 -0
- package/engine/commands/workforce.cjs +10 -10
- package/engine/core/schema-ensure.cjs +75 -0
- package/engine/experience/variant.cjs +46 -2
- package/engine/hephaestus/runtime.cjs +7 -0
- package/engine/memory-cli/curate.cjs +3 -7
- package/engine/project/memory-context.cjs +3 -7
- package/engine/project/state.cjs +7 -6
- package/engine/sessions/orchestrator.cjs +41 -3
- package/engine/sessions/prompt.cjs +4 -7
- package/engine/sessions/session.cjs +32 -0
- package/engine/storm/swarm.cjs +12 -0
- package/engine/ui/palette.cjs +14 -2
- package/engine/ui/renderer.cjs +37 -0
- package/engine/ui/repl.cjs +7 -1
- package/engine/workforce/concurrency.cjs +41 -0
- package/package.json +1 -1
|
@@ -42,9 +42,14 @@ function cloudCasResponseError(response, label) {
|
|
|
42
42
|
let message = `${label} failed with HTTP ${response.status}`;
|
|
43
43
|
if (response.status === 412 && code === "cloud_agent_revision_conflict") {
|
|
44
44
|
const current = body && body.current ? body.current : body && body.conflict && body.conflict.current;
|
|
45
|
+
// 서버가 알려준 사실만 사람 말로 전달한다. 내부 개념(precondition, base, 연결)은
|
|
46
|
+
// 사용자에게 아무 의미가 없다 — 기준은 "이 이름이 내 계정 것인가" 하나다.
|
|
45
47
|
message = current
|
|
46
|
-
?
|
|
47
|
-
|
|
48
|
+
? `업로드하지 않았습니다. "${current.slug || "이 이름"}"의 서버 버전이 지금 보낸 것과 맞지 않습니다` +
|
|
49
|
+
`${current.updatedAt ? ` (서버 쪽 마지막 저장: ${current.updatedAt})` : ""}.\n` +
|
|
50
|
+
` 내 계정 자산이면: agentlas cloud restore ${current.slug || "<slug>"} 로 받아서 확인한 뒤 다시 저장하세요.\n` +
|
|
51
|
+
` (서버 cloudId ${current.cloudId || "?"} · revision ${current.revision || "?"})`
|
|
52
|
+
: "업로드하지 않았습니다. 이 이름의 자산이 삭제됐거나 다른 계정 소유입니다. `agentlas cloud list`로 내 자산을 확인하세요.";
|
|
48
53
|
} else if (response.status === 428 && code === "client_upgrade_required") {
|
|
49
54
|
message = "No base revision is available to safely update the existing Cloud asset. The server revision will not be copied automatically. Check `agentlas cloud list`, restore with `agentlas cloud restore <slug>`, then save again.";
|
|
50
55
|
} else if (response.status === 503 && code === "cloud_mutations_maintenance") {
|
|
@@ -103,6 +103,7 @@ const CLOUD_HELP = [
|
|
|
103
103
|
" publish <path> [--dry-run] [--slug name]",
|
|
104
104
|
" explicitly publish to the public Agentlas Hub",
|
|
105
105
|
" package <path> [--json] [--visibility private-link|marketplace]",
|
|
106
|
+
" --overwrite 서버에 더 새 버전이 있어도 지금 폴더 내용으로 덮어쓰기",
|
|
106
107
|
" package only; defaults to private-save checks",
|
|
107
108
|
" list [--json] list packages in your private Agent Cloud",
|
|
108
109
|
" restore <slug> [--json] restore an owned Cloud package on this machine",
|
|
@@ -241,6 +242,7 @@ async function runCloud(ctx, args) {
|
|
|
241
242
|
slug: typeof flags.slug === "string" ? flags.slug : undefined,
|
|
242
243
|
visibility,
|
|
243
244
|
llmReview: Boolean(flags["llm-review"]),
|
|
245
|
+
overwriteRemote: Boolean(flags.overwrite),
|
|
244
246
|
dryRun,
|
|
245
247
|
});
|
|
246
248
|
if (flags.json) {
|
|
@@ -33,10 +33,12 @@ const {
|
|
|
33
33
|
cloudPortablePathConflict,
|
|
34
34
|
cloudPackageHashVersion,
|
|
35
35
|
cloudHashPackage,
|
|
36
|
+
normalizeCloudAssetDescriptor,
|
|
36
37
|
} = require("../hub/install.cjs");
|
|
37
38
|
const { SECRET_PATTERNS } = require("../agentlas-secret-patterns.cjs");
|
|
38
39
|
const { userDataDir } = require("../core/paths.cjs");
|
|
39
40
|
const state = require("./state.cjs");
|
|
41
|
+
const { cargoSearchAgents } = require("./cargo.cjs");
|
|
40
42
|
const cas = require("./cas.cjs");
|
|
41
43
|
|
|
42
44
|
const CLOUD_TEXT_EXTS = new Set([".cfg", ".cjs", ".conf", ".config", ".css", ".csv", ".env", ".html", ".ini", ".js", ".json", ".jsonl", ".md", ".mjs", ".properties", ".ps1", ".psd1", ".psm1", ".py", ".sh", ".toml", ".ts", ".tsx", ".txt", ".xml", ".yaml", ".yml"]);
|
|
@@ -748,6 +750,28 @@ function cloudSecuritySummary(findings) {
|
|
|
748
750
|
|
|
749
751
|
// ── 메인: 패키지(+등록) ──
|
|
750
752
|
|
|
753
|
+
/**
|
|
754
|
+
* 이 계정이 소유한 자산의 현재 리비전을 서버에서 조회한다.
|
|
755
|
+
* 로컬 관측 기록이 전혀 없는 새 PC/새 클론에서 갱신을 가능하게 하는 유일한 경로다.
|
|
756
|
+
* 소유자 세션이 없거나 자산이 없으면 null — 조용한 폴백은 만들지 않는다.
|
|
757
|
+
*/
|
|
758
|
+
async function lookupOwnedCloudDescriptor(slug, scope) {
|
|
759
|
+
let response;
|
|
760
|
+
try {
|
|
761
|
+
response = await cargoSearchAgents({ q: slug, limit: 20 });
|
|
762
|
+
} catch {
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
const rows = Array.isArray(response && response.results) ? response.results : [];
|
|
766
|
+
const row = rows.find((item) => item && item.slug === slug && item.scope === scope);
|
|
767
|
+
if (!row) return null;
|
|
768
|
+
try {
|
|
769
|
+
return normalizeCloudAssetDescriptor(row, "owner cloud search result");
|
|
770
|
+
} catch {
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
751
775
|
async function packageCloudAgent(db, root, opts = {}) {
|
|
752
776
|
const requestedRoot = path.resolve(root);
|
|
753
777
|
let st;
|
|
@@ -796,7 +820,7 @@ async function packageCloudAgent(db, root, opts = {}) {
|
|
|
796
820
|
const name = cloudReadName(snapshot, path.basename(rootPath));
|
|
797
821
|
const slug = cloudSlug(opts.slug || cloudReadStableSlug(snapshot) || name || path.basename(rootPath));
|
|
798
822
|
const scope = cas.cloudScopeForVisibility(visibility);
|
|
799
|
-
|
|
823
|
+
let baseDescriptor = state.cloudBaseDescriptorForSource(scan.localPackageMarker, rootPath, slug, scope);
|
|
800
824
|
const packageHashVersion = CLOUD_PACKAGE_HASH_V2;
|
|
801
825
|
const packageHash = cloudHashPackage(scan.included, packageHashVersion);
|
|
802
826
|
const manifest = {
|
|
@@ -844,6 +868,30 @@ async function packageCloudAgent(db, root, opts = {}) {
|
|
|
844
868
|
let registration = null;
|
|
845
869
|
let status = blocked ? "blocked" : opts.dryRun ? "dry-run" : "ready";
|
|
846
870
|
if (!blocked && !opts.dryRun) {
|
|
871
|
+
// 자산의 정체성은 (로그인 계정, slug)다 — 어느 폴더에서 올리는지는 중요하지 않다.
|
|
872
|
+
// 쓰기 전에 서버가 들고 있는 내 자산의 현재 버전을 조회해서:
|
|
873
|
+
// · 이 폴더에 기록이 없으면(새 PC, 새 클론) 그 버전을 기준으로 업데이트한다.
|
|
874
|
+
// · 이 폴더 기록이 서버보다 오래됐으면(다른 곳에서 먼저 올림) 덮어쓰기 전에 멈춘다.
|
|
875
|
+
// 이것은 412 뒤의 자동 재시도가 아니라 쓰기 전 refresh다. 등록 자체는 여전히
|
|
876
|
+
// 조회한 정확한 리비전에 대한 조건부 쓰기(If-Match)라 경합은 서버가 막는다.
|
|
877
|
+
const remote = await lookupOwnedCloudDescriptor(slug, scope);
|
|
878
|
+
if (!baseDescriptor && remote) {
|
|
879
|
+
baseDescriptor = state.rememberCloudAssetDescriptor(remote, { sourceRoot: rootPath });
|
|
880
|
+
} else if (baseDescriptor && remote && baseDescriptor.revision !== remote.revision) {
|
|
881
|
+
if (opts.overwriteRemote) {
|
|
882
|
+
baseDescriptor = state.rememberCloudAssetDescriptor(remote, { sourceRoot: rootPath });
|
|
883
|
+
} else {
|
|
884
|
+
const error = new Error(
|
|
885
|
+
`업로드하지 않았습니다. "${slug}"에 더 새 버전이 있습니다 (${remote.updatedAt || "시각 미상"}에 다른 곳에서 저장됨).\n` +
|
|
886
|
+
` 지금 폴더 내용으로 그 버전을 덮어쓰려면: --overwrite\n` +
|
|
887
|
+
` 먼저 그 버전을 받아서 비교하려면: agentlas cloud restore ${slug}\n` +
|
|
888
|
+
` (서버 cloudId ${remote.cloudId} · revision ${remote.revision})`,
|
|
889
|
+
);
|
|
890
|
+
error.code = "cloud_agent_revision_conflict";
|
|
891
|
+
error.current = remote;
|
|
892
|
+
throw error;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
847
895
|
registration = await cas.registerCloudAgent(manifest, bundlePath, review, visibility, { baseDescriptor });
|
|
848
896
|
let descriptor;
|
|
849
897
|
try {
|
|
@@ -174,6 +174,39 @@ function cloudBaseDescriptorFromMarker(marker, slug, scope) {
|
|
|
174
174
|
* 톰스톤에 걸린 마커 디스크립터는 무시(삭제 후 재저장은 새 생성이어야 한다).
|
|
175
175
|
* 둘 다 있으면 같은 cloudId에서 더 최신 관측을 채택한다.
|
|
176
176
|
*/
|
|
177
|
+
/**
|
|
178
|
+
* 이 slug/scope의 관측된 리비전은 있는데, 이 소스 루트에는 아직 연결되지 않은
|
|
179
|
+
* 경우를 돌려준다.
|
|
180
|
+
*
|
|
181
|
+
* 이 구분이 없으면 base 조회가 null을 돌려주고, 호출부는 그것을 "새 자산"으로
|
|
182
|
+
* 읽어 create precondition을 보낸다. 서버에는 자산이 멀쩡히 존재하므로 412로
|
|
183
|
+
* 거절되고, 사용자는 "다른 PC에서 변경됨 / restore 하세요"라는 엉뚱한 안내를
|
|
184
|
+
* 받는다. restore는 디스크립터를 설치 경로에 묶으므로 소스 루트에서 발행하는
|
|
185
|
+
* 한 영원히 해소되지 않는다. 모름을 그럴듯한 값으로 메꾸지 않기 위한 분기다.
|
|
186
|
+
*/
|
|
187
|
+
function cloudUnboundDescriptorForSource(rootPath, slug, scope) {
|
|
188
|
+
const state = readCloudAssetState();
|
|
189
|
+
const entry = state.assets[`${scope}:${slug}`];
|
|
190
|
+
if (!entry) return null;
|
|
191
|
+
return entry.sourceRoots.includes(path.resolve(rootPath)) ? null : entry.descriptor;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** 이 소스 루트를 이미 관측된 클라우드 자산에 명시적으로 연결한다. */
|
|
195
|
+
function bindCloudAssetSourceRoot(rootPath, slug, scope) {
|
|
196
|
+
const state = readCloudAssetState();
|
|
197
|
+
const key = `${scope}:${slug}`;
|
|
198
|
+
const entry = state.assets[key];
|
|
199
|
+
if (!entry) throw new Error(`No observed Cloud revision for ${key} to bind.`);
|
|
200
|
+
const normalizedRoot = path.resolve(rootPath);
|
|
201
|
+
const roots = [...new Set([...entry.sourceRoots, normalizedRoot])].slice(0, 32);
|
|
202
|
+
state.assets[key] = { descriptor: entry.descriptor, sourceRoots: roots };
|
|
203
|
+
state.deletedBases = state.deletedBases.filter(
|
|
204
|
+
(item) => !(item.rootPath === normalizedRoot && item.slug === slug && item.scope === scope),
|
|
205
|
+
);
|
|
206
|
+
writeCloudAssetState(state);
|
|
207
|
+
return entry.descriptor;
|
|
208
|
+
}
|
|
209
|
+
|
|
177
210
|
function cloudBaseDescriptorForSource(marker, rootPath, slug, scope) {
|
|
178
211
|
const state = readCloudAssetState();
|
|
179
212
|
const normalizedRoot = path.resolve(rootPath);
|
|
@@ -263,6 +296,8 @@ module.exports = {
|
|
|
263
296
|
cloudMarkerDescriptors,
|
|
264
297
|
cloudBaseDescriptorFromMarker,
|
|
265
298
|
cloudBaseDescriptorForSource,
|
|
299
|
+
cloudUnboundDescriptorForSource,
|
|
300
|
+
bindCloudAssetSourceRoot,
|
|
266
301
|
writeCloudSourceMarker,
|
|
267
302
|
readCloudSourceMarker,
|
|
268
303
|
};
|
|
@@ -92,6 +92,14 @@ async function run(ctx, args) {
|
|
|
92
92
|
targetId = f.id;
|
|
93
93
|
targetLabel = f.name;
|
|
94
94
|
}
|
|
95
|
+
// 잘못된 IANA 존이면 nextCronRun 이 cron 파싱 실패와 똑같이 null 을 돌려준다.
|
|
96
|
+
// 그대로 두면 멀쩡한 cron 을 범인으로 지목해서, 사용자가 cron 만 계속 고쳐 쓰며
|
|
97
|
+
// 매번 같은 실패를 본다. 존을 먼저 검증해 틀린 필드를 정확히 지목한다.
|
|
98
|
+
if (flags.tz && !schedule.isValidTimezone(flags.tz)) {
|
|
99
|
+
return fail(ko
|
|
100
|
+
? `타임존을 해석하지 못했습니다: "${flags.tz}" (IANA 형식이 필요합니다 — 예: Asia/Seoul, UTC, America/New_York)`
|
|
101
|
+
: `Could not parse timezone: "${flags.tz}" (needs IANA format — e.g. Asia/Seoul, UTC, America/New_York)`);
|
|
102
|
+
}
|
|
95
103
|
const next = schedule.nextCronRun(flags.cron, new Date(), flags.tz || null);
|
|
96
104
|
if (!next) {
|
|
97
105
|
return fail(ko
|
|
@@ -20,8 +20,13 @@ function run(ctx, args) {
|
|
|
20
20
|
}
|
|
21
21
|
for (const r of rows) {
|
|
22
22
|
const when = String(r.updated_at || "").replace("T", " ").slice(0, 16);
|
|
23
|
-
|
|
23
|
+
// id 앞 8자를 먼저 찍는다 — `open <id 앞부분>`이 유일한 재개 경로인데
|
|
24
|
+
// 목록에 id가 없으면 SQLite를 직접 열지 않는 한 재개가 불가능했다.
|
|
25
|
+
// 8자는 open.cjs의 모호성 안내(id.slice(0,8))와 같은 규약.
|
|
26
|
+
const id = String(r.id || "").slice(0, 8);
|
|
27
|
+
ctx.out(` ${ctx.ui.bold(id)} ${ctx.ui.dim(when)} ${ctx.ui.accent((r.agent_slug || "?").padEnd(20))} ${r.title}`);
|
|
24
28
|
}
|
|
29
|
+
ctx.out(ctx.ui.dim(ctx.lang === "en" ? " resume: agentlas open <id>" : " 재개: agentlas open <id>"));
|
|
25
30
|
return 0;
|
|
26
31
|
}
|
|
27
32
|
|
package/engine/commands/firm.cjs
CHANGED
|
@@ -73,6 +73,13 @@ async function run(ctx, args) {
|
|
|
73
73
|
const { EFFORTS, TIERS } = require("../agentlas-workload-routing.cjs");
|
|
74
74
|
const { Orchestrator } = require("../sessions/orchestrator.cjs");
|
|
75
75
|
const permissions = require("../agentlas-permissions.cjs");
|
|
76
|
+
// 플래그 검증은 CEO 계획 턴을 부르기 전에. normalize는 모르는 값을 read로 fail-closed
|
|
77
|
+
// 강등하는데, firm은 그 권한을 CEO·본부 전 세션·종합 턴까지 그대로 물려주므로 조용히
|
|
78
|
+
// 강등하면 full을 요청한 사용자가 쓰기 차단된 채 3-tier 전체를 돌린 줄 모른다 (run.cjs 동일 가드).
|
|
79
|
+
if (flags.permission && !permissions.LEVELS.includes(String(flags.permission))) {
|
|
80
|
+
ctx.err(`unknown --permission ${flags.permission} (use: ${permissions.LEVELS.join(" | ")})`);
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
76
83
|
if (flags.effort && !EFFORTS.includes(String(flags.effort))) {
|
|
77
84
|
ctx.err(`unknown --effort ${flags.effort} (use: ${EFFORTS.join(" | ")})`);
|
|
78
85
|
return 1;
|
package/engine/commands/help.cjs
CHANGED
|
@@ -22,12 +22,13 @@ AGENTS & HUB
|
|
|
22
22
|
import <path> · cd · native prepare local folder agents
|
|
23
23
|
list installed agents/companies + orchestrator/worker runtimes
|
|
24
24
|
experience <sub> portable Experience: list|inspect|validate|save|publish|status|export|unpublish
|
|
25
|
-
variant resolve
|
|
25
|
+
variant resolve --base-release <id> local variant selection (variant help)
|
|
26
26
|
|
|
27
27
|
EXECUTE
|
|
28
28
|
storm <goal> Goal+UltraCode harness: plan → allocate → execute → verify [--research]
|
|
29
29
|
swarm <goal> emergent agent swarm [--parallel N]
|
|
30
30
|
workforce | network <request> Agent Workforce Ontology route
|
|
31
|
+
hep-local | hep-cloud | hep-hub "<request>" same, restricted to one source scope
|
|
31
32
|
call "a,b" "<ctx>" · browser · route "<req>" [--json] · research <sub>
|
|
32
33
|
|
|
33
34
|
KNOWLEDGE
|
|
@@ -65,4 +66,24 @@ function run(ctx) {
|
|
|
65
66
|
return 0;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
function runForCommand(ctx, command) {
|
|
70
|
+
const name = String(command || "").trim();
|
|
71
|
+
const rows = HELP.split("\n")
|
|
72
|
+
.map((line) => line.trim())
|
|
73
|
+
.filter((line) => line && !line.endsWith(":"))
|
|
74
|
+
.filter((line) => {
|
|
75
|
+
const commandColumn = line.split(/\s{2,}/, 1)[0];
|
|
76
|
+
return commandColumn
|
|
77
|
+
.split(/\s*·\s*|\s*\|\s*/)
|
|
78
|
+
.some((entry) => entry === name || entry.startsWith(`${name} `));
|
|
79
|
+
});
|
|
80
|
+
ctx.out(`Usage: agentlas ${name} [options]`);
|
|
81
|
+
if (rows.length > 0) {
|
|
82
|
+
for (const row of rows) ctx.out(` ${row}`);
|
|
83
|
+
} else {
|
|
84
|
+
ctx.out(` See "agentlas help" for the full command list.`);
|
|
85
|
+
}
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { run, runForCommand, HELP };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* hep-cloud — 로그인한 오너의 Agent Cloud 에이전트만으로 임시 태스크포스 편성.
|
|
4
|
+
*
|
|
5
|
+
* WHY 이 파일이 따로 있는가 (2026-07-28 수리):
|
|
6
|
+
* COMMAND_ALIASES 가 `hep-cloud → cloud` 로 접혀 있었다. `cloud` 는 이름만 같은
|
|
7
|
+
* 전혀 다른 명령 — 클라우드 자산 보관함(save|publish|package|list|restore|
|
|
8
|
+
* install|delete|field-test)이다. 그래서 `agentlas hep-cloud "<과제>"` 는
|
|
9
|
+
* 과제를 서브커맨드로 읽고 `usage: agentlas cloud <save|…>` 를 뱉으며 exit 1 —
|
|
10
|
+
* hep-cloud 를 한 글자도 언급하지 않는 에러라 사용자에게 다음 수가 없었다.
|
|
11
|
+
* 스코프(오너 Cloud 한정)를 실제로 지키는 표면은 Hephaestus 네이티브 런타임
|
|
12
|
+
* (`hephaestus hep-cloud`)뿐이므로 build/call/legacy-network 와 동일한
|
|
13
|
+
* 패스스루 계약으로 그쪽에 넘긴다. 자산 보관함은 계속 `agentlas cloud …` 다.
|
|
14
|
+
*
|
|
15
|
+
* v1 인자 가드 그대로: help 토큰 → usage 0, 무인자 → usage 실패 exit 1.
|
|
16
|
+
*/
|
|
17
|
+
const { create, usageFor, isHelpToken } = require("../hephaestus/runtime.cjs");
|
|
18
|
+
|
|
19
|
+
async function run(ctx, args) {
|
|
20
|
+
if (args.some(isHelpToken)) {
|
|
21
|
+
ctx.out(usageFor("hep-cloud", ctx.lang));
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
if (!args.length) {
|
|
25
|
+
ctx.err("✖ " + usageFor("hep-cloud", ctx.lang));
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
return create(ctx).cmdHep(["hep-cloud", ...args]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { run };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* hep-hub — 공개 Agentlas Hub 에이전트만으로 임시 태스크포스 편성.
|
|
4
|
+
*
|
|
5
|
+
* WHY 이 파일이 따로 있는가 (2026-07-28 수리):
|
|
6
|
+
* COMMAND_ALIASES 가 `hep-hub → search` 로 접혀 있었다. search 는 스태핑이 아니라
|
|
7
|
+
* 마켓플레이스 디렉터리 나열이고(그마저 Cloud+Hub 혼합 표면이다), 실행은 하지
|
|
8
|
+
* 않는다. 그래서 `agentlas hep-hub "<과제>"` 는 과제를 검색어로 읽고 슬러그
|
|
9
|
+
* 목록만 찍은 뒤 exit 0 — 아무것도 실행하지 않고 성공한 척했다(가짜 성공 금지).
|
|
10
|
+
* 공개 Hub 한정 스태핑을 실제로 수행하는 표면은 Hephaestus 네이티브 런타임
|
|
11
|
+
* (`hephaestus hep-hub`)이므로 그쪽에 그대로 넘긴다. 후보만 보고 싶으면
|
|
12
|
+
* 기존 `agentlas search`(=hep-search)가 그대로 남아 있다.
|
|
13
|
+
*
|
|
14
|
+
* v1 인자 가드 그대로: help 토큰 → usage 0, 무인자 → usage 실패 exit 1.
|
|
15
|
+
*/
|
|
16
|
+
const { create, usageFor, isHelpToken } = require("../hephaestus/runtime.cjs");
|
|
17
|
+
|
|
18
|
+
async function run(ctx, args) {
|
|
19
|
+
if (args.some(isHelpToken)) {
|
|
20
|
+
ctx.out(usageFor("hep-hub", ctx.lang));
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
if (!args.length) {
|
|
24
|
+
ctx.err("✖ " + usageFor("hep-hub", ctx.lang));
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
return create(ctx).cmdHep(["hep-hub", ...args]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { run };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* hep-local — 이 머신에 등록된 Local 에이전트만으로 임시 태스크포스 편성.
|
|
4
|
+
*
|
|
5
|
+
* WHY 이 파일이 따로 있는가 (2026-07-28 수리):
|
|
6
|
+
* COMMAND_ALIASES 가 `hep-local → workforce` 로 접혀 있었다. 터미널 workforce 는
|
|
7
|
+
* 공개 Hub 메뉴로 스태핑하는 Agent Workforce Ontology 경로이고(엔드포인트:
|
|
8
|
+
* AGENTLAS_MCP_BASE_URL, search_candidates 스키마에 sourceScope 자체가 없음),
|
|
9
|
+
* cmdWorkforce 는 스코프 플래그를 받지 않는다. 즉 "등록된 로컬만" 이라고
|
|
10
|
+
* 문서화된 명령이 조용히 Local+Cloud+공개 Hub 전체로 넓혀 실행됐다 —
|
|
11
|
+
* 사용자가 의도하지 않은 Hub 크레딧 소모까지 포함해서. 스코프는 이름의 전부다.
|
|
12
|
+
* 스코프를 지킬 수 있는 표면은 Hephaestus 네이티브 런타임(`hephaestus hep-local`)
|
|
13
|
+
* 뿐이므로, build/call/legacy-network 와 동일한 패스스루 계약으로 그쪽에 넘긴다.
|
|
14
|
+
*
|
|
15
|
+
* v1 인자 가드 그대로: help 토큰 → usage 0, 무인자 → usage 실패 exit 1
|
|
16
|
+
* (요청 문자열 없는 호출이 자연어 라우팅으로 새는 것 방지).
|
|
17
|
+
*/
|
|
18
|
+
const { create, usageFor, isHelpToken } = require("../hephaestus/runtime.cjs");
|
|
19
|
+
|
|
20
|
+
async function run(ctx, args) {
|
|
21
|
+
if (args.some(isHelpToken)) {
|
|
22
|
+
ctx.out(usageFor("hep-local", ctx.lang));
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
if (!args.length) {
|
|
26
|
+
ctx.err("✖ " + usageFor("hep-local", ctx.lang));
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
29
|
+
return create(ctx).cmdHep(["hep-local", ...args]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { run };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* hep-network — 등록된 Local + 오너 Cloud + 공개 Hub 를 연합해 임시 태스크포스 편성.
|
|
4
|
+
*
|
|
5
|
+
* WHY 이 파일이 따로 있는가 (2026-07-28 수리):
|
|
6
|
+
* COMMAND_ALIASES 가 `hep-network → workforce` 로 접혀 있었다. hep-local/hep-cloud/
|
|
7
|
+
* hep-hub 와 정확히 같은 결함이다 — 별칭은 이름만 바꿔주고 스코프는 어디에도
|
|
8
|
+
* 전달하지 않는다. 터미널 workforce(cmdWorkforce)는 callHubTool 로
|
|
9
|
+
* AGENTLAS_MCP_BASE_URL(기본 https://agentlas.cloud/api/mcp/v1)을 직접 치고
|
|
10
|
+
* `{workOrder}` 만 보낸다(engine/agentlas-workforce.cjs:3043). 그 서버는
|
|
11
|
+
* sourceScope 가 없으면 "hub" 로 기본값을 잡는다
|
|
12
|
+
* (agentlas/AgentsAtlas/app/src/lib/mcp/workforce.ts:228). 결과:
|
|
13
|
+
*
|
|
14
|
+
* - 로컬 에이전트와 오너 Cloud 에이전트는 후보 집합에 들어간 적이 없다.
|
|
15
|
+
* - 그런데 영수증의 로스터 핀은 전부 source:"hub" 로 기록된다
|
|
16
|
+
* (engine/workforce/deps.cjs). "네트워크 전량을 봤다" 로 읽힌다.
|
|
17
|
+
*
|
|
18
|
+
* 연합(federation)은 Core 가 소유한다: 세 소스 메뉴를 각각 받아 출처·계보를
|
|
19
|
+
* 증명하고 하나의 CandidateSet 으로 합친 뒤, 선택은 호스트 LLM 이 한다. 터미널이
|
|
20
|
+
* Hub 를 직접 치는 경로에는 그 계층이 통째로 없다. 그래서 build/call/hep-local
|
|
21
|
+
* 과 동일한 패스스루 계약으로 Hephaestus 네이티브 표면에 넘긴다.
|
|
22
|
+
*
|
|
23
|
+
* `agentlas workforce` 자체는 남겨둔다 — 공개 Hub 스태핑을 그 이름으로 명시해
|
|
24
|
+
* 부르는 것은 정직한 사용이다. 이름이 소스 스코프를 약속하는 hep-* 만 옮긴다.
|
|
25
|
+
*
|
|
26
|
+
* v1 인자 가드 그대로: help 토큰 → usage 0, 무인자 → usage 실패 exit 1
|
|
27
|
+
* (요청 문자열 없는 호출이 자연어 라우팅으로 새는 것 방지).
|
|
28
|
+
*/
|
|
29
|
+
const { create, usageFor, isHelpToken } = require("../hephaestus/runtime.cjs");
|
|
30
|
+
|
|
31
|
+
async function run(ctx, args) {
|
|
32
|
+
if (args.some(isHelpToken)) {
|
|
33
|
+
ctx.out(usageFor("hep-network", ctx.lang));
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
if (!args.length) {
|
|
37
|
+
ctx.err("✖ " + usageFor("hep-network", ctx.lang));
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
return create(ctx).cmdHep(["hep-network", ...args]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { run };
|
|
@@ -47,6 +47,12 @@ const COMMANDS = {
|
|
|
47
47
|
evolve: () => require("./evolve.cjs"),
|
|
48
48
|
variant: () => require("./variant.cjs"),
|
|
49
49
|
hep: () => require("./hep.cjs"),
|
|
50
|
+
// 소스 스코프 스태핑 3종은 1급 명령이다 — 별칭으로 접으면 스코프가 사라진다.
|
|
51
|
+
// (아래 COMMAND_ALIASES 주석의 2026-07-28 수리 참조.)
|
|
52
|
+
"hep-network": () => require("./hep-network.cjs"),
|
|
53
|
+
"hep-local": () => require("./hep-local.cjs"),
|
|
54
|
+
"hep-cloud": () => require("./hep-cloud.cjs"),
|
|
55
|
+
"hep-hub": () => require("./hep-hub.cjs"),
|
|
50
56
|
build: () => require("./build.cjs"),
|
|
51
57
|
connect: () => require("./connect.cjs"),
|
|
52
58
|
call: () => require("./call.cjs"),
|
|
@@ -106,10 +112,25 @@ const GUARDED_NO_ARG = new Set(["search", "install", "upload"]);
|
|
|
106
112
|
// 플랫폼 간 이름 통일(오너 결정 2026-07-27): 클로드코드/코덱스에서 부르는 hep-*
|
|
107
113
|
// 스킬명과 터미널 명령이 서로 다르면 사용자가 어느 표면에 있는지에 따라 이름을
|
|
108
114
|
// 바꿔 써야 한다. 같은 기능은 어디서든 같은 이름으로 부른다.
|
|
115
|
+
//
|
|
116
|
+
// 불변식(2026-07-28 수리): 별칭은 "같은 기능"에만 건다. 소스 스코프를 이름에
|
|
117
|
+
// 달고 있는 hep-local / hep-cloud / hep-hub 는 여기 넣으면 안 된다 — 별칭은
|
|
118
|
+
// 이름만 바꿔주고 스코프는 어디에도 전달되지 않기 때문이다. 실제로 그랬다:
|
|
119
|
+
// hep-cloud → cloud : 자산 보관함 명령. 과제 문자열을 서브커맨드로 읽어
|
|
120
|
+
// `usage: agentlas cloud <save|…>` + exit 1.
|
|
121
|
+
// hep-local → workforce : cmdWorkforce 는 스코프 플래그를 받지 않는다(전량
|
|
122
|
+
// 공개 Hub 메뉴 스태핑). "로컬 전용"이 조용히 넓어짐.
|
|
123
|
+
// hep-hub → search : 디렉터리 나열만 하고 아무것도 실행하지 않음.
|
|
124
|
+
// 세 명령은 스코프를 실제로 지키는 Hephaestus 네이티브 표면으로 가는 1급 명령
|
|
125
|
+
// (COMMANDS 의 hep-local/hep-cloud/hep-hub)으로 승격했다.
|
|
126
|
+
//
|
|
127
|
+
// 같은 이유로 hep-network 도 별칭에서 뺐다(2026-07-28). 이름은 "Local + owner
|
|
128
|
+
// Cloud + public Hub"인데 cmdWorkforce 는 스코프를 어디에도 싣지 않고
|
|
129
|
+
// agentlas.cloud/api/mcp/v1 을 직접 친다. 그 서버는 sourceScope 가 없으면 "hub"
|
|
130
|
+
// 로 기본값을 잡으므로(agentlas/.../lib/mcp/workforce.ts:228) 로컬·클라우드
|
|
131
|
+
// 에이전트는 후보에 들어간 적이 없는데 결과는 네트워크 전량을 본 것처럼 남았다.
|
|
132
|
+
// 연합은 Core 가 소유한다 — 네이티브 표면으로 넘긴다.
|
|
109
133
|
const COMMAND_ALIASES = {
|
|
110
|
-
"hep-network": "workforce",
|
|
111
|
-
network: "workforce",
|
|
112
|
-
"hep-cloud": "cloud",
|
|
113
134
|
"hep-build": "build",
|
|
114
135
|
"hep-call": "call",
|
|
115
136
|
"hep-search": "search",
|
|
@@ -117,8 +138,6 @@ const COMMAND_ALIASES = {
|
|
|
117
138
|
"hep-storm": "storm",
|
|
118
139
|
"hep-browser": "browser",
|
|
119
140
|
"hep-connect": "connect",
|
|
120
|
-
"hep-local": "workforce",
|
|
121
|
-
"hep-hub": "search",
|
|
122
141
|
};
|
|
123
142
|
|
|
124
143
|
function resolveCommandName(cmd) {
|
|
@@ -139,8 +158,20 @@ function dispatch(ctx, argv) {
|
|
|
139
158
|
return 1;
|
|
140
159
|
}
|
|
141
160
|
|
|
142
|
-
|
|
143
|
-
|
|
161
|
+
/*
|
|
162
|
+
* 인자 유무와 무관하게 막는다. `rest.length === 0` 조건이 붙어 있던 동안은
|
|
163
|
+
* 단어 하나만 더 붙이면(`agentlas settings theme`, `marketplace browse`)
|
|
164
|
+
* 가드를 그냥 지나쳐 dispatch가 undefined를 반환했고, 엔진은 그걸 프롬프트로
|
|
165
|
+
* 보고 실제 에이전트를 띄웠다 — 제품이 "데스크탑 전용"이라고 선언한 화면
|
|
166
|
+
* 이름에 토큰이 청구되고 에이전트가 사용자 저장소에서 셸까지 돌렸다(실사용
|
|
167
|
+
* 실증: settings theme → System Optimizer가 Bash 실행, marketplace browse →
|
|
168
|
+
* 20883 토큰 소진). agentlas.cjs의 오타 가드는 `normalized.length === 1`
|
|
169
|
+
* 이라 이 경로를 못 받는다. 여기가 유일한 차단 지점이므로 arity를 보지 않는다.
|
|
170
|
+
* 진짜 작업이면 안내대로 따옴표로 묶어 하나의 프롬프트로 넘긴다.
|
|
171
|
+
*/
|
|
172
|
+
if (DESKTOP_ONLY_SURFACES[cmd]) {
|
|
173
|
+
const asTask = [rawCmd, ...rest].join(" ");
|
|
174
|
+
ctx.err(`${DESKTOP_ONLY_SURFACES[cmd]}\nIt was not run as a prompt — rerun with quotes if you meant a task: agentlas "${asTask}"`);
|
|
144
175
|
return 1;
|
|
145
176
|
}
|
|
146
177
|
|
package/engine/commands/open.cjs
CHANGED
|
@@ -25,7 +25,11 @@ function run(ctx, args) {
|
|
|
25
25
|
return 1;
|
|
26
26
|
}
|
|
27
27
|
if (rows.length > 1) {
|
|
28
|
-
|
|
28
|
+
// 안내 접두사는 사용자가 입력한 것보다 반드시 길어야 한다 — chats 목록이
|
|
29
|
+
// 8자를 찍으므로, 8자로 고정하면 8자 충돌 시 같은 문자열 두 개를 돌려주는
|
|
30
|
+
// 막다른 길이 된다("더 긴 접두사"를 만들 방법이 없음).
|
|
31
|
+
const hintLen = Math.max(8, prefix.length + 4);
|
|
32
|
+
ctx.err(ko ? `모호합니다 — 더 긴 접두사를 쓰세요 (${rows.map((r) => r.id.slice(0, hintLen)).join(", ")})` : `Ambiguous — use a longer prefix (${rows.map((r) => r.id.slice(0, hintLen)).join(", ")})`);
|
|
29
33
|
return 1;
|
|
30
34
|
}
|
|
31
35
|
const chat = rows[0];
|
package/engine/commands/run.cjs
CHANGED
|
@@ -26,6 +26,8 @@ const { Orchestrator } = require("../sessions/orchestrator.cjs");
|
|
|
26
26
|
const { Renderer } = require("../ui/renderer.cjs");
|
|
27
27
|
const permissions = require("../agentlas-permissions.cjs");
|
|
28
28
|
const { EFFORTS, TIERS } = require("../agentlas-workload-routing.cjs");
|
|
29
|
+
const { projectCwd } = require("../project/paths.cjs");
|
|
30
|
+
const { ensureTerminalProjectForExecutionCli } = require("../project/state.cjs");
|
|
29
31
|
|
|
30
32
|
function readStdin() {
|
|
31
33
|
return new Promise((resolve) => {
|
|
@@ -164,12 +166,20 @@ async function runOnce(ctx, args) {
|
|
|
164
166
|
}
|
|
165
167
|
}
|
|
166
168
|
|
|
169
|
+
const permission = permissions.normalize(parsed.permission || (ctx.prefs && ctx.prefs.permission) || "write");
|
|
170
|
+
const cwd = projectCwd();
|
|
171
|
+
try {
|
|
172
|
+
ensureTerminalProjectForExecutionCli(db, cwd, permission, "terminal-run");
|
|
173
|
+
} catch (e) {
|
|
174
|
+
ctx.err(String((e && e.message) || e));
|
|
175
|
+
return 1;
|
|
176
|
+
}
|
|
167
177
|
const orch = new Orchestrator({ db, lang: ctx.lang });
|
|
168
178
|
const session = orch.spawn({
|
|
169
179
|
agent,
|
|
170
180
|
runtime,
|
|
171
|
-
permission
|
|
172
|
-
cwd
|
|
181
|
+
permission,
|
|
182
|
+
cwd,
|
|
173
183
|
title: prompt.slice(0, 60),
|
|
174
184
|
});
|
|
175
185
|
|
|
@@ -10,20 +10,20 @@ const { userDataDir } = require("../core/paths.cjs");
|
|
|
10
10
|
const { RUNTIME_BIN, whichSync } = require("../runtimes/detect.cjs");
|
|
11
11
|
|
|
12
12
|
async function runWizard(ctx, rl) {
|
|
13
|
-
|
|
13
|
+
// 저장을 마법사 "밖"에서 하면 이미 "완료" 문구가 찍힌 뒤다. 저장 함수를 넘겨
|
|
14
|
+
// 성공 문구보다 먼저 쓰게 하고, updatePrefs가 던지는 실패를 그대로 올려보낸다
|
|
15
|
+
// (예전엔 반환값을 버려서 잠금 경합·읽기전용 디렉터리에서도 exit 0으로 거짓말).
|
|
16
|
+
return runOnboard({
|
|
14
17
|
ui: ctx.uiInstance,
|
|
15
18
|
rl,
|
|
16
19
|
helpers: { RUNTIME_BIN, which: whichSync },
|
|
17
|
-
|
|
18
|
-
if (result && result.onboarded) {
|
|
19
|
-
updatePrefs(userDataDir(), {
|
|
20
|
+
persist: (choices) => updatePrefs(userDataDir(), {
|
|
20
21
|
onboarded: true,
|
|
21
|
-
...(
|
|
22
|
-
...(
|
|
23
|
-
...(
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
return result;
|
|
22
|
+
...(choices.lang ? { language: choices.lang } : {}),
|
|
23
|
+
...(choices.runtime ? { runtime: choices.runtime } : {}),
|
|
24
|
+
...(choices.permission ? { permission: choices.permission } : {}),
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
async function run(ctx) {
|
|
@@ -36,7 +36,8 @@ async function run(ctx) {
|
|
|
36
36
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
37
37
|
try {
|
|
38
38
|
const result = await runWizard(ctx, rl);
|
|
39
|
-
|
|
39
|
+
// 저장까지 성공해야 0 — 설정이 디스크에 없으면 사용자에겐 아무것도 안 한 것과 같다.
|
|
40
|
+
return result && result.onboarded && result.saved ? 0 : 1;
|
|
40
41
|
} finally {
|
|
41
42
|
rl.close();
|
|
42
43
|
}
|
|
@@ -10,16 +10,12 @@
|
|
|
10
10
|
* stormbreaker-core-harness-unavailable 로 멈춘다 — 하네스의 로컬 모조 실행 금지.
|
|
11
11
|
* 런타임 부재는 resolveRuntime의 code="no_runtime" throw가 그대로 전파돼 exit 1.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* 경로를 만들었다. 그 프로젝트-상태 기계는 아직 v2로 포팅되지 않았다. storm/swarm 이
|
|
16
|
-
* projectPath 를 쓰는 곳은 buildChildEnv 의 프로젝트 dotenv 스코핑뿐이고 부재 시
|
|
17
|
-
* fail-closed 의존이 없다 — 그래서 여기서는 cwd 를 projectPath 로 넘긴다
|
|
18
|
-
* (commands/workforce.cjs 와 동일한 결정·동일한 근거). 프로젝트-상태 모듈이 v2에
|
|
19
|
-
* 착륙하면 이 자리를 그 결과로 교체한다.
|
|
13
|
+
* Write-capable first contact now runs the canonical Core project bootstrap
|
|
14
|
+
* for the exact current folder. Read-only execution remains passive.
|
|
20
15
|
*/
|
|
21
16
|
const { buildStormDeps } = require("../storm/deps.cjs");
|
|
22
17
|
const { projectCwd } = require("../workforce/capture.cjs");
|
|
18
|
+
const { ensureTerminalProjectForExecutionCli } = require("../project/state.cjs");
|
|
23
19
|
|
|
24
20
|
function assertPermissionFlag(value) {
|
|
25
21
|
if (value && !["read", "write", "full"].includes(String(value))) {
|
|
@@ -62,11 +58,11 @@ async function run(ctx, args) {
|
|
|
62
58
|
const db = ctx.db();
|
|
63
59
|
const cwd = projectCwd();
|
|
64
60
|
const permission = resolvePermission(ctx);
|
|
61
|
+
const projectPath = ensureTerminalProjectForExecutionCli(db, cwd, permission, "terminal-storm") || cwd;
|
|
65
62
|
const storm = require("../storm/storm.cjs").create(buildStormDeps(ctx));
|
|
66
63
|
const r = await storm.cmdStorm(db, rest, runtimeOverride, {
|
|
67
64
|
cwd,
|
|
68
|
-
|
|
69
|
-
projectPath: cwd,
|
|
65
|
+
projectPath,
|
|
70
66
|
permission,
|
|
71
67
|
});
|
|
72
68
|
return r && r.ok ? 0 : 1;
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
* agentlas-workload-routing 이 검증만 하고, 거부 시 fail-closed 다.
|
|
12
12
|
* 런타임 부재는 resolveRuntime의 code="no_runtime" throw가 그대로 전파돼 exit 1.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* commands/storm.cjs 파일 머리 주석과 동일한 결정·동일한 근거로 cwd 를 넘긴다.
|
|
14
|
+
* Write-capable first contact bootstraps the exact current project through Core.
|
|
16
15
|
*/
|
|
17
16
|
const { buildStormDeps } = require("../storm/deps.cjs");
|
|
18
17
|
const { projectCwd } = require("../workforce/capture.cjs");
|
|
18
|
+
const { ensureTerminalProjectForExecutionCli } = require("../project/state.cjs");
|
|
19
19
|
|
|
20
20
|
function assertPermissionFlag(value) {
|
|
21
21
|
if (value && !["read", "write", "full"].includes(String(value))) {
|
|
@@ -62,11 +62,11 @@ async function run(ctx, args) {
|
|
|
62
62
|
const db = ctx.db();
|
|
63
63
|
const cwd = projectCwd();
|
|
64
64
|
const permission = resolvePermission(ctx);
|
|
65
|
+
const projectPath = ensureTerminalProjectForExecutionCli(db, cwd, permission, "terminal-swarm") || cwd;
|
|
65
66
|
const swarm = require("../storm/swarm.cjs").create(buildStormDeps(ctx));
|
|
66
67
|
const r = await swarm.cmdSwarm(db, rest, runtimeOverride, {
|
|
67
68
|
cwd,
|
|
68
|
-
|
|
69
|
-
projectPath: cwd,
|
|
69
|
+
projectPath,
|
|
70
70
|
permission,
|
|
71
71
|
});
|
|
72
72
|
return r && r.ok ? 0 : 1;
|