agentlas 1.0.10 → 1.0.12
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 +48 -0
- package/engine/agentlas-input.cjs +121 -20
- package/engine/agentlas-workforce.cjs +859 -101
- package/engine/agentlas-workload-routing.cjs +29 -3
- package/engine/automation/daemon.cjs +9 -0
- package/engine/automation/store.cjs +22 -0
- package/engine/bootstrap-schema.sql +24 -0
- package/engine/commands/doctor.cjs +23 -1
- package/engine/commands/firm.cjs +59 -4
- package/engine/commands/help.cjs +8 -5
- package/engine/commands/list.cjs +25 -1
- package/engine/commands/run.cjs +65 -11
- package/engine/firms/orchestrate.cjs +10 -3
- package/engine/runtimes/overrides.cjs +91 -22
- package/engine/runtimes/resolve.cjs +38 -8
- package/engine/runtimes/roles.cjs +162 -0
- package/engine/sessions/orchestrator.cjs +2 -1
- package/engine/sessions/session.cjs +56 -20
- package/engine/storm/swarm.cjs +28 -12
- package/engine/ui/palette.cjs +40 -0
- package/engine/ui/repl.cjs +83 -17
- package/engine/workforce/capture.cjs +199 -14
- package/engine/workforce/deps.cjs +145 -29
- package/package.json +1 -1
|
@@ -397,10 +397,32 @@ function resolveAllocationAcrossRuntimes(options = {}) {
|
|
|
397
397
|
return { ...resolution, runtime, runtimeId, requestedRuntimeId: requestedId || null };
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
function
|
|
400
|
+
function modelRoleForStage(stage) {
|
|
401
|
+
return ["plan", "planner", "leader", "verify", "verifier", "synthesize", "synthesis", "route", "clarify"]
|
|
402
|
+
.includes(cleanText(stage, 80).toLowerCase())
|
|
403
|
+
? "orchestrator"
|
|
404
|
+
: "worker";
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function normalizeObservedUsage(value) {
|
|
408
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
409
|
+
const inputTokens = value.inputTokens;
|
|
410
|
+
const outputTokens = value.outputTokens;
|
|
411
|
+
return Number.isInteger(inputTokens) && inputTokens >= 0
|
|
412
|
+
&& Number.isInteger(outputTokens) && outputTokens >= 0
|
|
413
|
+
? { inputTokens, outputTokens }
|
|
414
|
+
: null;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function createDecisionReceipt({ taskId, stage, decision, resolution, role, usage }) {
|
|
401
418
|
const normalized = normalizeAllocation(decision);
|
|
402
419
|
const validationIssues = [];
|
|
403
|
-
|
|
420
|
+
const decisionProvided = decision != null;
|
|
421
|
+
if (!decisionProvided) validationIssues.push("allocation_not_provided");
|
|
422
|
+
else if (!normalized) validationIssues.push("invalid_ai_allocation");
|
|
423
|
+
const resolvedRole = role === "worker" || role === "orchestrator"
|
|
424
|
+
? role
|
|
425
|
+
: modelRoleForStage(stage);
|
|
404
426
|
const resolutionCodes = cleanText(resolution && resolution.fallbackReason, 500)
|
|
405
427
|
.split(",")
|
|
406
428
|
.map((code) => cleanText(code, 120))
|
|
@@ -411,12 +433,13 @@ function createDecisionReceipt({ taskId, stage, decision, resolution }) {
|
|
|
411
433
|
])].slice(0, 32);
|
|
412
434
|
const featurePayload = JSON.stringify(normalized ? {
|
|
413
435
|
phase: normalized.phase,
|
|
436
|
+
role: resolvedRole,
|
|
414
437
|
tier: normalized.tier,
|
|
415
438
|
effort: normalized.effort,
|
|
416
439
|
reasonCodes: normalized.reasonCodes,
|
|
417
440
|
requiredCapabilities: normalized.requiredCapabilities,
|
|
418
441
|
estimatedContextTokens: normalized.estimatedContextTokens,
|
|
419
|
-
} : { phase: cleanText(stage, 80) || null, allocation: null });
|
|
442
|
+
} : { phase: cleanText(stage, 80) || null, role: resolvedRole, allocation: null });
|
|
420
443
|
const featureHash = `sha256:${crypto.createHash("sha256").update(featurePayload, "utf8").digest("hex")}`;
|
|
421
444
|
const source = resolution && resolution.source;
|
|
422
445
|
const hasResolvedCurrent = Boolean(
|
|
@@ -439,6 +462,7 @@ function createDecisionReceipt({ taskId, stage, decision, resolution }) {
|
|
|
439
462
|
? normalized.decisionId
|
|
440
463
|
: `terminal:model-allocation:${featureHash.slice("sha256:".length, "sha256:".length + 24)}`,
|
|
441
464
|
packetId: cleanText(taskId, 255) || null,
|
|
465
|
+
role: resolvedRole,
|
|
442
466
|
status,
|
|
443
467
|
requested: {
|
|
444
468
|
tier: normalized ? normalized.tier : null,
|
|
@@ -460,6 +484,7 @@ function createDecisionReceipt({ taskId, stage, decision, resolution }) {
|
|
|
460
484
|
selectorVersion: normalized ? normalized.selectorVersion : "deterministic-host-fallback",
|
|
461
485
|
independentVerificationRequired:
|
|
462
486
|
riskCodes.has("high-risk") || riskCodes.has("critical-risk") || riskCodes.has("independent-verification"),
|
|
487
|
+
usage: normalizeObservedUsage(usage),
|
|
463
488
|
validationIssues,
|
|
464
489
|
privacy: { rawPromptIncluded: false, rawTranscriptIncluded: false },
|
|
465
490
|
};
|
|
@@ -522,6 +547,7 @@ module.exports = {
|
|
|
522
547
|
resolveAllocation,
|
|
523
548
|
resolveAllocationAcrossRuntimes,
|
|
524
549
|
createDecisionReceipt,
|
|
550
|
+
modelRoleForStage,
|
|
525
551
|
appendDecisionReceipt,
|
|
526
552
|
defaultReceiptPath,
|
|
527
553
|
plannerSystemPrompt,
|
|
@@ -167,6 +167,14 @@ async function runAutomationOnce(ctx, db, row, opts = {}) {
|
|
|
167
167
|
return { ok: false, skipped: true, reason: "lease" };
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// 데스크탑 스케줄러는 60초마다 리스를 갱신한다. 여기는 한 번 잡고 끝이라
|
|
171
|
+
// TTL(15분)을 넘긴 실행은 프로세스가 살아 있어도 회수 대상이 됐다 — 같은
|
|
172
|
+
// 자동화가 두 실행기에서 겹쳐 도는 경로. 같은 주기로 심장박동을 보낸다.
|
|
173
|
+
const leaseHeartbeat = setInterval(() => {
|
|
174
|
+
try { store.renewAutomationLease(db, row.id); } catch { /* best-effort */ }
|
|
175
|
+
}, 60_000);
|
|
176
|
+
if (typeof leaseHeartbeat.unref === "function") leaseHeartbeat.unref();
|
|
177
|
+
|
|
170
178
|
try {
|
|
171
179
|
// ── raw-row 실행 계약 게이트 (데스크탑 automation-scheduler.ts:538-549 동형) ──
|
|
172
180
|
// 손상된 계약 값으로는 무인 실행하지 않는다 — 문구까지 데스크탑과 동일.
|
|
@@ -246,6 +254,7 @@ async function runAutomationOnce(ctx, db, row, opts = {}) {
|
|
|
246
254
|
store.advanceAfterRun(db, row, { ok: false, advanceSchedule: !!opts.advanceSchedule });
|
|
247
255
|
return { ok: false, error: msg };
|
|
248
256
|
} finally {
|
|
257
|
+
clearInterval(leaseHeartbeat);
|
|
249
258
|
store.releaseAutomation(db, row.id);
|
|
250
259
|
}
|
|
251
260
|
}
|
|
@@ -105,6 +105,27 @@ function claimAutomation(db, id, now = new Date(), owner = LEASE_OWNER) {
|
|
|
105
105
|
return (result.changes ?? result.rowsAffected ?? 0) > 0;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* 이 프로세스가 아직 들고 있는 리스의 claimed_at 을 현재로 민다.
|
|
110
|
+
*
|
|
111
|
+
* claimAutomation 은 한 번만 부르고 갱신이 없었다. 데스크탑 스케줄러는 60초마다
|
|
112
|
+
* 갱신하므로, TTL(15분)을 넘긴 CLI 실행은 프로세스가 멀쩡히 살아 있는데도
|
|
113
|
+
* 회수 대상이 된다 — 에이전트 세션에서 15분은 평범하고, 그러면 같은 자동화가
|
|
114
|
+
* 두 실행기에서 겹쳐 돈다. 소유자가 나일 때만 갱신하므로, 이미 남에게 넘어간
|
|
115
|
+
* 리스를 되빼앗지는 않는다.
|
|
116
|
+
*/
|
|
117
|
+
function renewAutomationLease(db, id, now = new Date(), owner = LEASE_OWNER) {
|
|
118
|
+
if (!leaseSupported(db)) return false;
|
|
119
|
+
try {
|
|
120
|
+
const result = db
|
|
121
|
+
.prepare("UPDATE automations SET claimed_at = ? WHERE id = ? AND lease_owner = ?")
|
|
122
|
+
.run(now.toISOString(), id, owner);
|
|
123
|
+
return (result.changes ?? result.rowsAffected ?? 0) > 0;
|
|
124
|
+
} catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
108
129
|
function releaseAutomation(db, id) {
|
|
109
130
|
try {
|
|
110
131
|
db.prepare("UPDATE automations SET claimed_at = NULL, lease_owner = NULL WHERE id = ?").run(id);
|
|
@@ -201,6 +222,7 @@ module.exports = {
|
|
|
201
222
|
setEnabled,
|
|
202
223
|
removeAutomation,
|
|
203
224
|
claimAutomation,
|
|
225
|
+
renewAutomationLease,
|
|
204
226
|
releaseAutomation,
|
|
205
227
|
recordRun,
|
|
206
228
|
listRuns,
|
|
@@ -5,6 +5,30 @@ CREATE TABLE active_runtime (
|
|
|
5
5
|
id INTEGER PRIMARY KEY CHECK(id = 1),
|
|
6
6
|
kind TEXT NOT NULL
|
|
7
7
|
, backend TEXT, source TEXT, model TEXT, long_context INTEGER NOT NULL DEFAULT 0);
|
|
8
|
+
CREATE TABLE model_roles (
|
|
9
|
+
role TEXT PRIMARY KEY CHECK(role IN ('orchestrator','worker')),
|
|
10
|
+
kind TEXT NOT NULL,
|
|
11
|
+
backend TEXT,
|
|
12
|
+
source TEXT,
|
|
13
|
+
model TEXT,
|
|
14
|
+
effort TEXT,
|
|
15
|
+
long_context INTEGER NOT NULL DEFAULT 0 CHECK(long_context IN (0,1)),
|
|
16
|
+
inherit INTEGER NOT NULL DEFAULT 0 CHECK(inherit IN (0,1)),
|
|
17
|
+
updated_at TEXT NOT NULL,
|
|
18
|
+
CHECK(role = 'worker' OR inherit = 0)
|
|
19
|
+
);
|
|
20
|
+
CREATE TABLE model_role_members (
|
|
21
|
+
role TEXT NOT NULL CHECK(role IN ('orchestrator','worker')),
|
|
22
|
+
position INTEGER NOT NULL CHECK(position >= 1),
|
|
23
|
+
kind TEXT NOT NULL,
|
|
24
|
+
backend TEXT,
|
|
25
|
+
source TEXT,
|
|
26
|
+
model TEXT,
|
|
27
|
+
effort TEXT,
|
|
28
|
+
long_context INTEGER NOT NULL DEFAULT 0 CHECK(long_context IN (0,1)),
|
|
29
|
+
updated_at TEXT NOT NULL,
|
|
30
|
+
PRIMARY KEY(role, position)
|
|
31
|
+
);
|
|
8
32
|
CREATE TABLE installed_agents (
|
|
9
33
|
id TEXT PRIMARY KEY,
|
|
10
34
|
slug TEXT UNIQUE NOT NULL,
|
|
@@ -9,6 +9,19 @@ const fs = require("node:fs");
|
|
|
9
9
|
const path = require("node:path");
|
|
10
10
|
const { dbPath, userDataDir } = require("../core/paths.cjs");
|
|
11
11
|
const { listAvailableCliRuntimes, activeRuntimeRow } = require("../runtimes/detect.cjs");
|
|
12
|
+
const { resolvedModelRole } = require("../runtimes/roles.cjs");
|
|
13
|
+
|
|
14
|
+
function roleDetail(selection, role, en) {
|
|
15
|
+
if (!selection) return en ? "not set" : "미설정";
|
|
16
|
+
const provider = selection.kind === "byok" ? selection.backend || "byok" : selection.kind;
|
|
17
|
+
return [
|
|
18
|
+
`${role}=${provider}${selection.model ? `/${selection.model}` : ""}`,
|
|
19
|
+
selection.effort ? `effort=${selection.effort}` : null,
|
|
20
|
+
role === "worker" && selection.inherit
|
|
21
|
+
? (en ? "inherits orchestrator" : "오케스트레이터 상속")
|
|
22
|
+
: null,
|
|
23
|
+
].filter(Boolean).join(" · ");
|
|
24
|
+
}
|
|
12
25
|
|
|
13
26
|
function run(ctx) {
|
|
14
27
|
const en = ctx.lang === "en";
|
|
@@ -42,8 +55,17 @@ function run(ctx) {
|
|
|
42
55
|
ctx.out(ctx.ui.dim(" npm i -g @anthropic-ai/claude-code · @openai/codex · @google/gemini-cli"));
|
|
43
56
|
}
|
|
44
57
|
try {
|
|
45
|
-
const
|
|
58
|
+
const db = ctx.db();
|
|
59
|
+
const active = activeRuntimeRow(db);
|
|
46
60
|
if (active) ok(en ? "active runtime" : "활성 런타임", `${active.kind}${active.model ? ` (${active.model})` : ""}`);
|
|
61
|
+
const orchestrator = resolvedModelRole(db, "orchestrator");
|
|
62
|
+
const worker = resolvedModelRole(db, "worker");
|
|
63
|
+
if (orchestrator && worker) {
|
|
64
|
+
ok(
|
|
65
|
+
en ? "model roles" : "모델 역할",
|
|
66
|
+
`${roleDetail(orchestrator, "orchestrator", en)} · ${roleDetail(worker, "worker", en)}`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
47
69
|
} catch { /* db issue already reported */ }
|
|
48
70
|
|
|
49
71
|
// 3) 로그인 상태 (세션 파일 관측만 — 네트워크 호출 없음)
|
package/engine/commands/firm.cjs
CHANGED
|
@@ -50,11 +50,14 @@ async function run(ctx, args) {
|
|
|
50
50
|
}
|
|
51
51
|
const ceo = rowToAgent(ceoRow);
|
|
52
52
|
|
|
53
|
-
// 간단 플래그 파싱
|
|
53
|
+
// 간단 플래그 파싱 — 명령끼리 참조 금지 규칙상 run.cjs 미차용.
|
|
54
54
|
const rest = args.slice(1);
|
|
55
|
-
const flags = { runtime: null, permission: null, task: [] };
|
|
55
|
+
const flags = { runtime: null, model: null, effort: null, tier: null, permission: null, task: [] };
|
|
56
56
|
for (let i = 0; i < rest.length; i++) {
|
|
57
57
|
if (rest[i] === "--runtime") flags.runtime = rest[++i];
|
|
58
|
+
else if (rest[i] === "--model") flags.model = rest[++i];
|
|
59
|
+
else if (rest[i] === "--effort") flags.effort = rest[++i];
|
|
60
|
+
else if (rest[i] === "--tier") flags.tier = rest[++i];
|
|
58
61
|
else if (rest[i] === "--permission") flags.permission = rest[++i];
|
|
59
62
|
else flags.task.push(rest[i]);
|
|
60
63
|
}
|
|
@@ -62,26 +65,64 @@ async function run(ctx, args) {
|
|
|
62
65
|
if (task) {
|
|
63
66
|
// 3-tier 위임 실행 — PLAN → DELEGATE → SYNTHESIZE (firms/orchestrate.cjs).
|
|
64
67
|
const { runFirmTurn } = require("../firms/orchestrate.cjs");
|
|
65
|
-
const {
|
|
68
|
+
const {
|
|
69
|
+
resolveRuntimeForAgent,
|
|
70
|
+
unavailableOverrideNote,
|
|
71
|
+
unavailableRoleNote,
|
|
72
|
+
} = require("../runtimes/overrides.cjs");
|
|
73
|
+
const { EFFORTS, TIERS } = require("../agentlas-workload-routing.cjs");
|
|
66
74
|
const { Orchestrator } = require("../sessions/orchestrator.cjs");
|
|
67
75
|
const permissions = require("../agentlas-permissions.cjs");
|
|
76
|
+
if (flags.effort && !EFFORTS.includes(String(flags.effort))) {
|
|
77
|
+
ctx.err(`unknown --effort ${flags.effort} (use: ${EFFORTS.join(" | ")})`);
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
if (flags.tier && !TIERS.includes(String(flags.tier))) {
|
|
81
|
+
ctx.err(`unknown --tier ${flags.tier} (use: ${TIERS.join(" | ")})`);
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
if (flags.tier && !flags.model) {
|
|
85
|
+
ctx.err("--tier requires --model: Terminal never guesses a provider model id from a cost tier");
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
68
88
|
let runtime;
|
|
89
|
+
let workerRuntime;
|
|
69
90
|
try {
|
|
70
|
-
//
|
|
91
|
+
// CEO는 orchestrator, 본부는 worker. 명시 핀은 기존 firm 호출과의 호환을 위해
|
|
92
|
+
// 두 역할 모두에 적용하고, 미지정 시 각각 model_roles 기본값을 사용한다.
|
|
71
93
|
runtime = resolveRuntimeForAgent({
|
|
72
94
|
db,
|
|
73
95
|
prefs: ctx.prefs,
|
|
74
96
|
explicit: flags.runtime,
|
|
97
|
+
model: flags.model,
|
|
98
|
+
effort: flags.effort,
|
|
99
|
+
role: "orchestrator",
|
|
75
100
|
targets: [
|
|
76
101
|
{ scope: "agent", targetId: ceo.id },
|
|
77
102
|
{ scope: "firm", targetId: firm.id },
|
|
78
103
|
],
|
|
79
104
|
});
|
|
105
|
+
workerRuntime = resolveRuntimeForAgent({
|
|
106
|
+
db,
|
|
107
|
+
prefs: ctx.prefs,
|
|
108
|
+
explicit: flags.runtime,
|
|
109
|
+
model: flags.model,
|
|
110
|
+
effort: flags.effort,
|
|
111
|
+
role: "worker",
|
|
112
|
+
targets: [{ scope: "firm", targetId: firm.id }],
|
|
113
|
+
});
|
|
114
|
+
if (flags.tier) {
|
|
115
|
+
runtime.modelTier = flags.tier;
|
|
116
|
+
workerRuntime.modelTier = flags.tier;
|
|
117
|
+
}
|
|
80
118
|
} catch (e) {
|
|
81
119
|
ctx.err(String((e && e.message) || e));
|
|
82
120
|
return 1;
|
|
83
121
|
}
|
|
84
122
|
if (runtime.unavailableOverride) ctx.err(ctx.ui.dim(unavailableOverrideNote(runtime, ctx.lang)));
|
|
123
|
+
if (runtime.unavailableRoleSelection) ctx.err(ctx.ui.dim(unavailableRoleNote(runtime, ctx.lang)));
|
|
124
|
+
if (workerRuntime.unavailableOverride) ctx.err(ctx.ui.dim(unavailableOverrideNote(workerRuntime, ctx.lang)));
|
|
125
|
+
if (workerRuntime.unavailableRoleSelection) ctx.err(ctx.ui.dim(unavailableRoleNote(workerRuntime, ctx.lang)));
|
|
85
126
|
const orch = new Orchestrator({ db, lang: ctx.lang });
|
|
86
127
|
const dim = ctx.ui.dim;
|
|
87
128
|
const result = await runFirmTurn({
|
|
@@ -91,6 +132,20 @@ async function run(ctx, args) {
|
|
|
91
132
|
ceoAgent: ceo,
|
|
92
133
|
task,
|
|
93
134
|
runtime,
|
|
135
|
+
workerRuntime,
|
|
136
|
+
resolveWorkerRuntime: flags.runtime || flags.model || flags.effort
|
|
137
|
+
? null
|
|
138
|
+
: (node) => resolveRuntimeForAgent({
|
|
139
|
+
db,
|
|
140
|
+
prefs: ctx.prefs,
|
|
141
|
+
explicit: null,
|
|
142
|
+
role: "worker",
|
|
143
|
+
targets: [
|
|
144
|
+
{ scope: "agent", targetId: node.agent.id },
|
|
145
|
+
{ scope: "division", targetId: `${firm.id}:${node.role}` },
|
|
146
|
+
{ scope: "firm", targetId: firm.id },
|
|
147
|
+
],
|
|
148
|
+
}),
|
|
94
149
|
permission: permissions.normalize(flags.permission || (ctx.prefs && ctx.prefs.permission) || "write"),
|
|
95
150
|
cwd: process.cwd(),
|
|
96
151
|
onEvent: (ev) => {
|
package/engine/commands/help.cjs
CHANGED
|
@@ -9,8 +9,8 @@ const HELP = `agentlas — the operating system for agents, in your terminal
|
|
|
9
9
|
|
|
10
10
|
TALK & RUN
|
|
11
11
|
<agent> · chat <agent> jump into a chat with one agent
|
|
12
|
-
run [agent] [prompt] one-shot (-p
|
|
13
|
-
firm <firm> [task] delegate to a
|
|
12
|
+
run [agent] [prompt] one-shot (-p · --runtime · --model · --effort · --permission)
|
|
13
|
+
firm <firm> [task] delegate to a CEO (--runtime · --model · --effort)
|
|
14
14
|
chats [n] · open <id> recent conversations · resume one
|
|
15
15
|
|
|
16
16
|
AGENTS & HUB
|
|
@@ -20,7 +20,7 @@ AGENTS & HUB
|
|
|
20
20
|
build "<request>" build/repair/package an agent or team
|
|
21
21
|
upload <path> save owner-private in Agent Cloud (--visibility marketplace to publish)
|
|
22
22
|
import <path> · cd · native prepare local folder agents
|
|
23
|
-
list installed agents/companies +
|
|
23
|
+
list installed agents/companies + orchestrator/worker runtimes
|
|
24
24
|
experience <sub> portable Experience: list|inspect|validate|save|publish|status|export|unpublish
|
|
25
25
|
variant resolve local variant selection
|
|
26
26
|
|
|
@@ -51,10 +51,13 @@ ACCOUNT & OPS
|
|
|
51
51
|
|
|
52
52
|
IN-REPL (agentlas → interactive, Orca multi-session)
|
|
53
53
|
/spawn <agent> [task] · /sessions · /tree · /s <n> · /steer <n> <msg> ·
|
|
54
|
-
/kill <n> · /rm <n> · /broadcast <msg> · /use · /runtime · /permission
|
|
54
|
+
/kill <n> · /rm <n> · /broadcast <msg> · /use · /runtime · /model · /effort · /permission
|
|
55
55
|
typing during a running turn queues steering; ctrl-c interrupts the turn
|
|
56
56
|
|
|
57
|
-
Options: -p|--print · --runtime claude-code|codex|gemini · --
|
|
57
|
+
Options: -p|--print · --runtime claude-code|codex|gemini · --model <exact-id> ·
|
|
58
|
+
--effort none|minimal|low|medium|high|xhigh|max ·
|
|
59
|
+
--tier economy|balanced|frontier (requires --model) ·
|
|
60
|
+
--permission read|write|full
|
|
58
61
|
`;
|
|
59
62
|
|
|
60
63
|
function run(ctx) {
|
package/engine/commands/list.cjs
CHANGED
|
@@ -5,8 +5,25 @@
|
|
|
5
5
|
* 데스크탑과 동일하게 목록에서 숨긴다.
|
|
6
6
|
*/
|
|
7
7
|
const { activeRuntimeRow, listAvailableCliRuntimes } = require("../runtimes/detect.cjs");
|
|
8
|
+
const { resolvedModelRole } = require("../runtimes/roles.cjs");
|
|
8
9
|
const { listAgents } = require("../agents/registry.cjs");
|
|
9
10
|
|
|
11
|
+
function roleRuntimeLabel(selection, role, en) {
|
|
12
|
+
if (!selection) return en ? "(not set)" : "(미설정)";
|
|
13
|
+
const provider = selection.kind === "byok"
|
|
14
|
+
? selection.backend || "byok"
|
|
15
|
+
: selection.kind;
|
|
16
|
+
const bits = [
|
|
17
|
+
provider,
|
|
18
|
+
selection.model ? `(${selection.model})` : "",
|
|
19
|
+
selection.effort ? `· effort ${selection.effort}` : "",
|
|
20
|
+
].filter(Boolean);
|
|
21
|
+
if (role === "worker" && selection.inherit) {
|
|
22
|
+
bits.push(en ? "· inherits orchestrator" : "· 오케스트레이터 상속");
|
|
23
|
+
}
|
|
24
|
+
return bits.join(" ");
|
|
25
|
+
}
|
|
26
|
+
|
|
10
27
|
function run(ctx) {
|
|
11
28
|
const db = ctx.db();
|
|
12
29
|
// 프라이버시 정책(웹 전용/백그라운드 제외)은 registry가 소유한다 — 직접 SQL 금지.
|
|
@@ -36,7 +53,14 @@ function run(ctx) {
|
|
|
36
53
|
const active = activeRuntimeRow(db);
|
|
37
54
|
const clis = listAvailableCliRuntimes();
|
|
38
55
|
ctx.out("");
|
|
39
|
-
ctx.out(ctx.ui.bold(en ? "
|
|
56
|
+
ctx.out(ctx.ui.bold(en ? "Model roles" : "모델 역할"));
|
|
57
|
+
const orchestrator = resolvedModelRole(db, "orchestrator");
|
|
58
|
+
const worker = resolvedModelRole(db, "worker");
|
|
59
|
+
ctx.out(` orchestrator: ${roleRuntimeLabel(orchestrator, "orchestrator", en)}`);
|
|
60
|
+
ctx.out(` worker: ${roleRuntimeLabel(worker, "worker", en)}`);
|
|
61
|
+
|
|
62
|
+
ctx.out("");
|
|
63
|
+
ctx.out(ctx.ui.bold(en ? "Legacy runtime compatibility" : "레거시 런타임 호환"));
|
|
40
64
|
if (active) {
|
|
41
65
|
ctx.out(` active: ${active.kind}${active.model ? ` (${active.model})` : ""}${active.backend ? ` via ${active.backend}` : ""}`);
|
|
42
66
|
} else {
|
package/engine/commands/run.cjs
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* run — 원샷 실행: agentlas run [agent] [prompt…]
|
|
4
4
|
* -p | --print 최종 답만 stdout에 (스트리밍 UI 없음)
|
|
5
5
|
* --runtime <kind> claude-code | codex | gemini
|
|
6
|
+
* --model <id> exact provider model id
|
|
7
|
+
* --effort <level> none | minimal | low | medium | high | xhigh | max
|
|
8
|
+
* --tier <tier> economy | balanced | frontier (requires --model)
|
|
6
9
|
* --permission <level> read | write | full
|
|
7
10
|
* 프롬프트가 없고 stdin이 TTY가 아니면 stdin을 읽는다.
|
|
8
11
|
*
|
|
@@ -10,15 +13,19 @@
|
|
|
10
13
|
* 판정(resolveAutoRoute)이 최적 에이전트를 고른다 — 어휘 점수는 후보 모집 전용이다.
|
|
11
14
|
* 판정 런타임이 없으면 기본 에이전트로 정직 폴백하되 note를 stderr에 반드시 출력한다
|
|
12
15
|
* (조용한 오라우팅/폴백 금지 — 오너 결정).
|
|
13
|
-
* 런타임 사다리: 명시
|
|
14
|
-
*
|
|
16
|
+
* 런타임 사다리: 명시 핀 > 에이전트별 오버라이드(agent_runtime_overrides) >
|
|
17
|
+
* model_roles[orchestrator] > active_runtime > detected (runtimes/overrides.cjs).
|
|
15
18
|
*/
|
|
16
19
|
const { findAgent, listAgents } = require("../agents/registry.cjs");
|
|
17
|
-
const {
|
|
18
|
-
|
|
20
|
+
const {
|
|
21
|
+
resolveRuntimeForAgent,
|
|
22
|
+
unavailableOverrideNote,
|
|
23
|
+
unavailableRoleNote,
|
|
24
|
+
} = require("../runtimes/overrides.cjs");
|
|
19
25
|
const { Orchestrator } = require("../sessions/orchestrator.cjs");
|
|
20
26
|
const { Renderer } = require("../ui/renderer.cjs");
|
|
21
27
|
const permissions = require("../agentlas-permissions.cjs");
|
|
28
|
+
const { EFFORTS, TIERS } = require("../agentlas-workload-routing.cjs");
|
|
22
29
|
|
|
23
30
|
function readStdin() {
|
|
24
31
|
return new Promise((resolve) => {
|
|
@@ -30,11 +37,22 @@ function readStdin() {
|
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
function parseArgs(args) {
|
|
33
|
-
const out = {
|
|
40
|
+
const out = {
|
|
41
|
+
print: false,
|
|
42
|
+
runtime: null,
|
|
43
|
+
model: null,
|
|
44
|
+
effort: null,
|
|
45
|
+
tier: null,
|
|
46
|
+
permission: null,
|
|
47
|
+
rest: [],
|
|
48
|
+
};
|
|
34
49
|
for (let i = 0; i < args.length; i++) {
|
|
35
50
|
const a = args[i];
|
|
36
51
|
if (a === "-p" || a === "--print") out.print = true;
|
|
37
52
|
else if (a === "--runtime") out.runtime = args[++i];
|
|
53
|
+
else if (a === "--model") out.model = args[++i];
|
|
54
|
+
else if (a === "--effort") out.effort = args[++i];
|
|
55
|
+
else if (a === "--tier") out.tier = args[++i];
|
|
38
56
|
else if (a === "--permission") out.permission = args[++i];
|
|
39
57
|
else out.rest.push(a);
|
|
40
58
|
}
|
|
@@ -49,6 +67,18 @@ async function runOnce(ctx, args) {
|
|
|
49
67
|
ctx.err(`unknown --permission ${parsed.permission} (use: read | write | full)`);
|
|
50
68
|
return 1;
|
|
51
69
|
}
|
|
70
|
+
if (parsed.effort && !EFFORTS.includes(String(parsed.effort))) {
|
|
71
|
+
ctx.err(`unknown --effort ${parsed.effort} (use: ${EFFORTS.join(" | ")})`);
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
if (parsed.tier && !TIERS.includes(String(parsed.tier))) {
|
|
75
|
+
ctx.err(`unknown --tier ${parsed.tier} (use: ${TIERS.join(" | ")})`);
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
if (parsed.tier && !parsed.model) {
|
|
79
|
+
ctx.err("--tier requires --model: Terminal never guesses a provider model id from a cost tier");
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
52
82
|
const db = ctx.db();
|
|
53
83
|
|
|
54
84
|
let agent = null;
|
|
@@ -70,7 +100,15 @@ async function runOnce(ctx, args) {
|
|
|
70
100
|
// 기본 런타임을 먼저 확정한다 — 판정 러너(자동 라우팅)도 이 런타임을 쓴다.
|
|
71
101
|
let runtime;
|
|
72
102
|
try {
|
|
73
|
-
runtime =
|
|
103
|
+
runtime = resolveRuntimeForAgent({
|
|
104
|
+
db,
|
|
105
|
+
prefs: ctx.prefs,
|
|
106
|
+
explicit: parsed.runtime,
|
|
107
|
+
model: parsed.model,
|
|
108
|
+
effort: parsed.effort,
|
|
109
|
+
role: "orchestrator",
|
|
110
|
+
});
|
|
111
|
+
if (parsed.tier) runtime.modelTier = parsed.tier;
|
|
74
112
|
} catch (e) {
|
|
75
113
|
ctx.err(String((e && e.message) || e));
|
|
76
114
|
return 1;
|
|
@@ -103,13 +141,27 @@ async function runOnce(ctx, args) {
|
|
|
103
141
|
if (choice && choice.note) ctx.err(ctx.uiInstance.c.dim(choice.note));
|
|
104
142
|
}
|
|
105
143
|
|
|
106
|
-
// 에이전트가 확정된 뒤 에이전트별
|
|
107
|
-
|
|
144
|
+
// 에이전트가 확정된 뒤 에이전트별 오버라이드를 포함한 전체 사다리를 다시 해석한다.
|
|
145
|
+
// 명시 runtime/model/effort 핀은 항상 최상단이라 에이전트 기본값보다 우선한다.
|
|
146
|
+
if (agent && agent.id) {
|
|
108
147
|
try {
|
|
109
|
-
const layered = resolveRuntimeForAgent({
|
|
148
|
+
const layered = resolveRuntimeForAgent({
|
|
149
|
+
db,
|
|
150
|
+
prefs: ctx.prefs,
|
|
151
|
+
explicit: parsed.runtime,
|
|
152
|
+
model: parsed.model,
|
|
153
|
+
effort: parsed.effort,
|
|
154
|
+
role: "orchestrator",
|
|
155
|
+
agentId: agent.id,
|
|
156
|
+
});
|
|
110
157
|
if (layered.unavailableOverride) ctx.err(ctx.uiInstance.c.dim(unavailableOverrideNote(layered, ctx.lang)));
|
|
158
|
+
if (layered.unavailableRoleSelection) ctx.err(ctx.uiInstance.c.dim(unavailableRoleNote(layered, ctx.lang)));
|
|
159
|
+
if (parsed.tier) layered.modelTier = parsed.tier;
|
|
111
160
|
runtime = layered;
|
|
112
|
-
} catch {
|
|
161
|
+
} catch (e) {
|
|
162
|
+
ctx.err(String((e && e.message) || e));
|
|
163
|
+
return 1;
|
|
164
|
+
}
|
|
113
165
|
}
|
|
114
166
|
|
|
115
167
|
const orch = new Orchestrator({ db, lang: ctx.lang });
|
|
@@ -125,7 +177,9 @@ async function runOnce(ctx, args) {
|
|
|
125
177
|
if (!parsed.print) {
|
|
126
178
|
renderer = new Renderer(ctx.uiInstance);
|
|
127
179
|
renderer.attach(session, { replay: false });
|
|
128
|
-
ctx.err(ctx.uiInstance.c.dim(
|
|
180
|
+
ctx.err(ctx.uiInstance.c.dim(
|
|
181
|
+
`${agent.slug} · ${runtime.kind}${runtime.model ? ` · ${runtime.model}` : ""}${runtime.effort ? ` · ${runtime.effort}` : ""}`,
|
|
182
|
+
));
|
|
129
183
|
}
|
|
130
184
|
|
|
131
185
|
const res = await session.send(prompt);
|
|
@@ -204,13 +204,15 @@ function turnText(res) {
|
|
|
204
204
|
* 회사 1태스크 실행: PLAN → DELEGATE → SYNTHESIZE.
|
|
205
205
|
* @param {object} p
|
|
206
206
|
* db, orch(Orchestrator), firm(firms 행), ceoAgent(rowToAgent 결과),
|
|
207
|
-
* task, runtime,
|
|
207
|
+
* task, runtime(orchestrator), workerRuntime?, resolveWorkerRuntime?(division),
|
|
208
|
+
* permission, cwd, onEvent?({phase,...}),
|
|
208
209
|
* spawnImplFor?({kind:'ceo'|'division', role?}) — 계약 테스트 전용 fake spawn 주입,
|
|
209
210
|
* timeoutConfig? — 테스트 전용.
|
|
210
211
|
* @returns {ok, text, chatId, plan:{text,delegations}, divisions:[{role,name,ok,text,chatId}]}
|
|
211
212
|
*/
|
|
212
213
|
async function runFirmTurn(p) {
|
|
213
214
|
const { db, orch, firm, ceoAgent, task, runtime, permission, cwd } = p;
|
|
215
|
+
const workerRuntime = p.workerRuntime || runtime;
|
|
214
216
|
const onEvent = typeof p.onEvent === "function" ? p.onEvent : () => {};
|
|
215
217
|
const parseDelegations = loadDelegateParser();
|
|
216
218
|
const divisions = resolveDivisions(db, firm, ceoAgent);
|
|
@@ -269,15 +271,20 @@ async function runFirmTurn(p) {
|
|
|
269
271
|
// kind='division' + parent_chat_id(CEO 챗)로 영속된다. 한 본부의 실패는 격리한다.
|
|
270
272
|
onEvent({ phase: "delegate", targets: matched.map((m) => ({ role: m.node.role, name: m.node.name, brief: m.brief })) });
|
|
271
273
|
const divisionResults = await parallelCap(matched, maxParallel(), async (m) => {
|
|
274
|
+
const divisionRuntime = typeof p.resolveWorkerRuntime === "function"
|
|
275
|
+
? p.resolveWorkerRuntime(m.node)
|
|
276
|
+
: workerRuntime;
|
|
272
277
|
const session = orch.spawn({
|
|
273
278
|
agent: m.node.agent,
|
|
274
|
-
runtime,
|
|
279
|
+
runtime: divisionRuntime,
|
|
275
280
|
permission,
|
|
276
281
|
cwd,
|
|
277
282
|
title: `division: ${m.node.role}`,
|
|
278
283
|
parentKey: ceoSession.key,
|
|
279
284
|
activate: false,
|
|
280
|
-
spawnImpl: p.spawnImplFor
|
|
285
|
+
spawnImpl: p.spawnImplFor
|
|
286
|
+
? p.spawnImplFor({ kind: "division", role: m.node.role, runtime: divisionRuntime })
|
|
287
|
+
: undefined,
|
|
281
288
|
timeoutConfig: p.timeoutConfig,
|
|
282
289
|
});
|
|
283
290
|
// 본부 세션도 generic 자동 위임을 끈다 — 터미널 firm 은 아직 tier-3(전문가) 배선이
|