agentlas 1.0.11 → 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 +28 -0
- package/engine/agentlas-input.cjs +61 -7
- 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 +71 -9
- package/engine/workforce/capture.cjs +199 -14
- package/engine/workforce/deps.cjs +145 -29
- package/package.json +1 -1
|
@@ -13,7 +13,13 @@
|
|
|
13
13
|
*/
|
|
14
14
|
const { tableExists, columnExists } = require("../core/db.cjs");
|
|
15
15
|
const { RUNTIME_BIN, whichSync } = require("./detect.cjs");
|
|
16
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
resolveRuntime,
|
|
18
|
+
EXECUTABLE_KINDS,
|
|
19
|
+
CLI_EXECUTABLE_KINDS,
|
|
20
|
+
API_EXECUTABLE_KINDS,
|
|
21
|
+
} = require("./resolve.cjs");
|
|
22
|
+
const { pickRoleFromPool } = require("./roles.cjs");
|
|
17
23
|
|
|
18
24
|
const OVERRIDE_TABLE = "agent_runtime_overrides";
|
|
19
25
|
// 데스크탑 VALID_SCOPES 동형. v2 터미널 호출자는 주로 'agent'지만 firm/division도 읽을 수 있다.
|
|
@@ -78,7 +84,7 @@ function findRuntimeOverride(db, targets) {
|
|
|
78
84
|
|
|
79
85
|
/**
|
|
80
86
|
* 오버라이드를 얹은 런타임 해석.
|
|
81
|
-
* @param {object} p { db, prefs, explicit, agentId, targets?, deps? }
|
|
87
|
+
* @param {object} p { db, prefs, explicit, model?, effort?, role?, agentId, targets?, deps? }
|
|
82
88
|
* targets: [{scope,targetId}] — 주면 agentId 대신 이 우선순위로 오버라이드를 찾는다
|
|
83
89
|
* (firm 경로: agent > division > firm).
|
|
84
90
|
* deps: 테스트 주입 { which, resolve } — 상용 호출자는 사용하지 않는다.
|
|
@@ -86,10 +92,48 @@ function findRuntimeOverride(db, targets) {
|
|
|
86
92
|
* { ..., unavailableOverride } (오버라이드가 있으나 이 머신/v2에서 실행 불가 —
|
|
87
93
|
* 조용히 무시하지 않고 사유를 실어 보내며, 호출자가 note를 출력해야 한다)
|
|
88
94
|
*/
|
|
89
|
-
function resolveRuntimeForAgent({
|
|
95
|
+
function resolveRuntimeForAgent({
|
|
96
|
+
db,
|
|
97
|
+
prefs,
|
|
98
|
+
explicit,
|
|
99
|
+
model,
|
|
100
|
+
effort,
|
|
101
|
+
role = "orchestrator",
|
|
102
|
+
agentId,
|
|
103
|
+
targets,
|
|
104
|
+
deps,
|
|
105
|
+
} = {}) {
|
|
90
106
|
const resolveImpl = (deps && deps.resolve) || resolveRuntime;
|
|
107
|
+
const withPins = (runtime) => ({
|
|
108
|
+
...runtime,
|
|
109
|
+
...(cleanText(model) ? { model: cleanText(model) } : {}),
|
|
110
|
+
...(cleanText(effort) ? { effort: cleanText(effort) } : {}),
|
|
111
|
+
});
|
|
112
|
+
const selectedRuntime = (selection, source) => {
|
|
113
|
+
const kind = selection && selection.kind;
|
|
114
|
+
if (API_EXECUTABLE_KINDS.has(kind)) {
|
|
115
|
+
return {
|
|
116
|
+
kind,
|
|
117
|
+
backend: selection.backend || kind,
|
|
118
|
+
model: cleanText(model) || cleanText(selection.model) || undefined,
|
|
119
|
+
effort: cleanText(effort) || cleanText(selection.effort) || undefined,
|
|
120
|
+
source,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (!CLI_EXECUTABLE_KINDS.has(kind)) return null;
|
|
124
|
+
const which = (deps && deps.which) || whichSync;
|
|
125
|
+
const bin = which(RUNTIME_BIN[kind]);
|
|
126
|
+
if (!bin) return null;
|
|
127
|
+
return {
|
|
128
|
+
kind,
|
|
129
|
+
bin,
|
|
130
|
+
model: cleanText(model) || cleanText(selection.model) || undefined,
|
|
131
|
+
effort: cleanText(effort) || cleanText(selection.effort) || undefined,
|
|
132
|
+
source,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
91
135
|
// 명시(--runtime)가 항상 이긴다 — 오버라이드는 "사용자가 고르지 않았을 때"의 기본값이다.
|
|
92
|
-
if (explicit) return resolveImpl({ db, prefs, explicit });
|
|
136
|
+
if (explicit) return withPins(resolveImpl({ db, prefs, explicit }));
|
|
93
137
|
|
|
94
138
|
const override = targets && targets.length
|
|
95
139
|
? findRuntimeOverride(db, targets)
|
|
@@ -98,27 +142,43 @@ function resolveRuntimeForAgent({ db, prefs, explicit, agentId, targets, deps }
|
|
|
98
142
|
: null;
|
|
99
143
|
if (override) {
|
|
100
144
|
const kind = override.selection.kind;
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
// 조용히 다른 런타임으로 둔갑시키지 않고 unavailableOverride로 정직하게 알린다.
|
|
145
|
+
// CLI 스트리밍과 로컬 API(Ollama)는 같은 Session 경로를 쓴다. 아직 연결되지
|
|
146
|
+
// 않은 BYOK/kimi 등은 조용히 다른 런타임으로 둔갑시키지 않는다.
|
|
104
147
|
if (EXECUTABLE_KINDS.has(kind)) {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
148
|
+
const selected = selectedRuntime(override.selection, "agent-override");
|
|
149
|
+
if (selected) return { ...selected, override, role };
|
|
150
|
+
}
|
|
151
|
+
const resolved = withPins(resolveImpl({ db, prefs, explicit: null }));
|
|
152
|
+
return { ...resolved, unavailableOverride: override, role };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Role defaults sit above active_runtime/detected but below exact per-call
|
|
156
|
+
// pins and agent/firm/division overrides. v80 풀(순서=우선순위)이 있으면
|
|
157
|
+
// 이 Terminal에서 실행 가능한 첫 멤버를 쓰고, 스킵 내역은 결과에 남긴다.
|
|
158
|
+
// 풀이 없으면 pickRoleFromPool이 단일 행/레거시 해석으로 내려간다.
|
|
159
|
+
const roleSelection = pickRoleFromPool(db, role, (member) => {
|
|
160
|
+
if (!EXECUTABLE_KINDS.has(member.kind)) return false;
|
|
161
|
+
return Boolean(selectedRuntime(member, member.sourceLayer));
|
|
162
|
+
});
|
|
163
|
+
if (roleSelection && EXECUTABLE_KINDS.has(roleSelection.kind)) {
|
|
164
|
+
const selected = selectedRuntime(roleSelection, roleSelection.sourceLayer);
|
|
165
|
+
if (selected) {
|
|
166
|
+
return {
|
|
167
|
+
...selected,
|
|
168
|
+
role,
|
|
169
|
+
inheritedRole: roleSelection.inherit,
|
|
170
|
+
...(roleSelection.skipped?.length
|
|
171
|
+
? { rolePoolSkipped: roleSelection.skipped }
|
|
172
|
+
: {}),
|
|
173
|
+
};
|
|
117
174
|
}
|
|
118
|
-
const resolved = resolveImpl({ db, prefs, explicit: null });
|
|
119
|
-
return { ...resolved, unavailableOverride: override };
|
|
120
175
|
}
|
|
121
|
-
|
|
176
|
+
const resolved = withPins(resolveImpl({ db, prefs, explicit: null }));
|
|
177
|
+
return {
|
|
178
|
+
...resolved,
|
|
179
|
+
role,
|
|
180
|
+
...(roleSelection ? { unavailableRoleSelection: roleSelection } : {}),
|
|
181
|
+
};
|
|
122
182
|
}
|
|
123
183
|
|
|
124
184
|
/** 오버라이드 실행 불가 시 사용자에게 출력할 한 줄 (데스크탑 문구 동형). */
|
|
@@ -130,10 +190,19 @@ function unavailableOverrideNote(runtime, lang) {
|
|
|
130
190
|
: `Assigned runtime (${kind}) is unavailable here — using the default runtime (${runtime.kind}).`;
|
|
131
191
|
}
|
|
132
192
|
|
|
193
|
+
function unavailableRoleNote(runtime, lang) {
|
|
194
|
+
if (!runtime || !runtime.unavailableRoleSelection) return "";
|
|
195
|
+
const selected = runtime.unavailableRoleSelection;
|
|
196
|
+
return lang === "ko"
|
|
197
|
+
? `${selected.role} 기본 런타임(${selected.kind})을 이 Terminal에서 실행할 수 없어 ${runtime.kind}으로 실행합니다.`
|
|
198
|
+
: `${selected.role} default runtime (${selected.kind}) is unavailable in this Terminal — using ${runtime.kind}.`;
|
|
199
|
+
}
|
|
200
|
+
|
|
133
201
|
module.exports = {
|
|
134
202
|
readAgentRuntimeOverride,
|
|
135
203
|
readRuntimeOverride,
|
|
136
204
|
findRuntimeOverride,
|
|
137
205
|
resolveRuntimeForAgent,
|
|
138
206
|
unavailableOverrideNote,
|
|
207
|
+
unavailableRoleNote,
|
|
139
208
|
};
|
|
@@ -7,9 +7,25 @@
|
|
|
7
7
|
*/
|
|
8
8
|
const { RUNTIME_BIN, whichSync, listAvailableCliRuntimes, activeRuntimeRow } = require("./detect.cjs");
|
|
9
9
|
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
10
|
+
// Session이 실제 드라이버를 갖춘 런타임만 실행 대상으로 삼는다.
|
|
11
|
+
// CLI는 native-host, Ollama는 로컬 API loop를 쓴다. 다른 드라이버가 포팅되면
|
|
12
|
+
// 해당 집합에 추가한다(조용한 오폭 방지).
|
|
13
|
+
const CLI_EXECUTABLE_KINDS = new Set(["claude-code", "codex", "gemini"]);
|
|
14
|
+
const API_EXECUTABLE_KINDS = new Set(["ollama"]);
|
|
15
|
+
const EXECUTABLE_KINDS = new Set([
|
|
16
|
+
...CLI_EXECUTABLE_KINDS,
|
|
17
|
+
...API_EXECUTABLE_KINDS,
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
function apiRuntime(kind, model, source) {
|
|
21
|
+
if (!API_EXECUTABLE_KINDS.has(kind)) return null;
|
|
22
|
+
return {
|
|
23
|
+
kind,
|
|
24
|
+
backend: kind,
|
|
25
|
+
...(model ? { model } : {}),
|
|
26
|
+
source,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
13
29
|
|
|
14
30
|
class NoRuntimeError extends Error {
|
|
15
31
|
constructor(message) {
|
|
@@ -24,9 +40,11 @@ class NoRuntimeError extends Error {
|
|
|
24
40
|
*/
|
|
25
41
|
function resolveRuntime({ db, prefs, explicit }) {
|
|
26
42
|
if (explicit) {
|
|
43
|
+
const api = apiRuntime(explicit, null, "explicit");
|
|
44
|
+
if (api) return api;
|
|
27
45
|
const bin = RUNTIME_BIN[explicit];
|
|
28
46
|
if (!bin) throw new NoRuntimeError(`unknown runtime: ${explicit}`);
|
|
29
|
-
if (!
|
|
47
|
+
if (!CLI_EXECUTABLE_KINDS.has(explicit)) {
|
|
30
48
|
throw new NoRuntimeError(`runtime '${explicit}' has no v2 streaming driver yet (available: ${[...EXECUTABLE_KINDS].join(", ")})`);
|
|
31
49
|
}
|
|
32
50
|
const p = whichSync(bin);
|
|
@@ -34,18 +52,24 @@ function resolveRuntime({ db, prefs, explicit }) {
|
|
|
34
52
|
return { kind: explicit, bin: p, source: "explicit" };
|
|
35
53
|
}
|
|
36
54
|
const pref = prefs && prefs.runtime;
|
|
37
|
-
if (pref &&
|
|
55
|
+
if (pref && API_EXECUTABLE_KINDS.has(pref)) {
|
|
56
|
+
return apiRuntime(pref, null, "prefs");
|
|
57
|
+
}
|
|
58
|
+
if (pref && CLI_EXECUTABLE_KINDS.has(pref)) {
|
|
38
59
|
const p = whichSync(RUNTIME_BIN[pref]);
|
|
39
60
|
if (p) return { kind: pref, bin: p, source: "prefs" };
|
|
40
61
|
}
|
|
41
62
|
if (db) {
|
|
42
63
|
const active = activeRuntimeRow(db);
|
|
43
|
-
if (active &&
|
|
64
|
+
if (active && API_EXECUTABLE_KINDS.has(active.kind)) {
|
|
65
|
+
return apiRuntime(active.kind, active.model || undefined, "active");
|
|
66
|
+
}
|
|
67
|
+
if (active && CLI_EXECUTABLE_KINDS.has(active.kind)) {
|
|
44
68
|
const p = whichSync(RUNTIME_BIN[active.kind]);
|
|
45
69
|
if (p) return { kind: active.kind, bin: p, model: active.model || undefined, source: "active" };
|
|
46
70
|
}
|
|
47
71
|
}
|
|
48
|
-
const found = listAvailableCliRuntimes().filter((r) =>
|
|
72
|
+
const found = listAvailableCliRuntimes().filter((r) => CLI_EXECUTABLE_KINDS.has(r.kind));
|
|
49
73
|
if (found.length) return { kind: found[0].kind, bin: found[0].path, source: "detected" };
|
|
50
74
|
// 신규 사용자의 최빈 막다른 길: "설치하라"만 있고 방법이 없으면 여기서 이탈한다.
|
|
51
75
|
// 실제 설치 명령을 그대로 준다(데스크탑 온보딩의 "Claude Code 무료로 설치하기"와 동형).
|
|
@@ -61,4 +85,10 @@ function resolveRuntime({ db, prefs, explicit }) {
|
|
|
61
85
|
].join("\n"));
|
|
62
86
|
}
|
|
63
87
|
|
|
64
|
-
module.exports = {
|
|
88
|
+
module.exports = {
|
|
89
|
+
resolveRuntime,
|
|
90
|
+
NoRuntimeError,
|
|
91
|
+
EXECUTABLE_KINDS,
|
|
92
|
+
CLI_EXECUTABLE_KINDS,
|
|
93
|
+
API_EXECUTABLE_KINDS,
|
|
94
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Shared Desktop/Terminal model-role reader.
|
|
4
|
+
*
|
|
5
|
+
* Persistence is owned by Desktop migration v79. Terminal reads defensively:
|
|
6
|
+
* orchestrator row -> legacy active_runtime -> null
|
|
7
|
+
* worker direct row -> orchestrator row/legacy active_runtime
|
|
8
|
+
*
|
|
9
|
+
* The worker may inherit upward for quality; the orchestrator never falls
|
|
10
|
+
* downward to the worker row.
|
|
11
|
+
*/
|
|
12
|
+
const { tableExists, columnExists } = require("../core/db.cjs");
|
|
13
|
+
|
|
14
|
+
const MODEL_ROLE_TABLE = "model_roles";
|
|
15
|
+
const VALID_ROLES = new Set(["orchestrator", "worker"]);
|
|
16
|
+
|
|
17
|
+
function cleanText(value) {
|
|
18
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
19
|
+
return trimmed || null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function roleRow(db, role) {
|
|
23
|
+
if (!db || !VALID_ROLES.has(role) || !tableExists(db, MODEL_ROLE_TABLE)) return null;
|
|
24
|
+
for (const column of ["role", "kind", "inherit"]) {
|
|
25
|
+
if (!columnExists(db, MODEL_ROLE_TABLE, column)) return null;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return db.prepare("SELECT * FROM model_roles WHERE role=?").get(role) || null;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function legacyOrchestrator(db) {
|
|
35
|
+
if (!db || !tableExists(db, "active_runtime")) return null;
|
|
36
|
+
try {
|
|
37
|
+
const row = db
|
|
38
|
+
.prepare(
|
|
39
|
+
"SELECT kind, backend, source, model, long_context FROM active_runtime WHERE id=1",
|
|
40
|
+
)
|
|
41
|
+
.get();
|
|
42
|
+
if (!row || !cleanText(row.kind)) return null;
|
|
43
|
+
let effort = null;
|
|
44
|
+
if (tableExists(db, "meta")) {
|
|
45
|
+
effort = cleanText(
|
|
46
|
+
db.prepare("SELECT value FROM meta WHERE key='claude_effort'").get()
|
|
47
|
+
?.value,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
role: "orchestrator",
|
|
52
|
+
kind: cleanText(row.kind),
|
|
53
|
+
backend: cleanText(row.backend),
|
|
54
|
+
source: cleanText(row.source),
|
|
55
|
+
model: cleanText(row.model),
|
|
56
|
+
effort,
|
|
57
|
+
longContext: Boolean(row.long_context),
|
|
58
|
+
inherit: false,
|
|
59
|
+
updatedAt: null,
|
|
60
|
+
sourceLayer: "active-runtime",
|
|
61
|
+
};
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizedRow(row, role) {
|
|
68
|
+
if (!row || !cleanText(row.kind)) return null;
|
|
69
|
+
return {
|
|
70
|
+
role,
|
|
71
|
+
kind: cleanText(row.kind),
|
|
72
|
+
backend: cleanText(row.backend),
|
|
73
|
+
source: cleanText(row.source),
|
|
74
|
+
model: cleanText(row.model),
|
|
75
|
+
effort: cleanText(row.effort),
|
|
76
|
+
longContext: Boolean(row.long_context),
|
|
77
|
+
inherit: role === "worker" && Boolean(row.inherit),
|
|
78
|
+
updatedAt: cleanText(row.updated_at),
|
|
79
|
+
sourceLayer: "model-role",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const MODEL_ROLE_MEMBER_TABLE = "model_role_members";
|
|
84
|
+
|
|
85
|
+
/** Desktop v80 역할 풀(순서=우선순위). 테이블이 없거나 비면 []. */
|
|
86
|
+
function roleMembers(db, role) {
|
|
87
|
+
if (!db || !VALID_ROLES.has(role) || !tableExists(db, MODEL_ROLE_MEMBER_TABLE)) return [];
|
|
88
|
+
for (const column of ["role", "position", "kind"]) {
|
|
89
|
+
if (!columnExists(db, MODEL_ROLE_MEMBER_TABLE, column)) return [];
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
return db
|
|
93
|
+
.prepare("SELECT * FROM model_role_members WHERE role=? ORDER BY position ASC")
|
|
94
|
+
.all(role)
|
|
95
|
+
.map((row) => ({
|
|
96
|
+
...normalizedRow({ ...row, inherit: 0 }, role),
|
|
97
|
+
position: row.position,
|
|
98
|
+
sourceLayer: "model-role-pool",
|
|
99
|
+
}))
|
|
100
|
+
.filter((row) => row && row.kind);
|
|
101
|
+
} catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 풀에서 첫 가용 멤버를 고른다. isAvailable(member)가 없으면 순서 1위.
|
|
108
|
+
* 전원 불가면 조용한 대체 없이 1위를 그대로 쓰고 skipped를 남긴다.
|
|
109
|
+
* worker 풀이 비면 오케스트레이터 풀을 상속하며, 풀 자체가 없으면
|
|
110
|
+
* 기존 단일 행 해석(resolvedModelRole)으로 내려간다.
|
|
111
|
+
*/
|
|
112
|
+
function pickRoleFromPool(db, role = "orchestrator", isAvailable = null) {
|
|
113
|
+
if (!VALID_ROLES.has(role)) throw new TypeError(`unknown model role: ${role}`);
|
|
114
|
+
const own = roleMembers(db, role);
|
|
115
|
+
const inherited = role === "worker" && own.length === 0;
|
|
116
|
+
const members = inherited ? roleMembers(db, "orchestrator") : own;
|
|
117
|
+
const skipped = [];
|
|
118
|
+
for (const member of members) {
|
|
119
|
+
if (typeof isAvailable === "function" && !isAvailable(member)) {
|
|
120
|
+
skipped.push({ position: member.position, kind: member.kind, reason: "runtime-unavailable" });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
return { ...member, role, inherit: inherited, skipped };
|
|
124
|
+
}
|
|
125
|
+
if (members.length > 0) {
|
|
126
|
+
return { ...members[0], role, inherit: inherited, skipped };
|
|
127
|
+
}
|
|
128
|
+
const single = resolvedModelRole(db, role);
|
|
129
|
+
return single ? { ...single, position: null, skipped } : null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function resolvedModelRole(db, role = "orchestrator") {
|
|
133
|
+
if (!VALID_ROLES.has(role)) throw new TypeError(`unknown model role: ${role}`);
|
|
134
|
+
if (role === "orchestrator") {
|
|
135
|
+
return normalizedRow(roleRow(db, "orchestrator"), role) || legacyOrchestrator(db);
|
|
136
|
+
}
|
|
137
|
+
const worker = normalizedRow(roleRow(db, "worker"), "worker");
|
|
138
|
+
if (worker && !worker.inherit) return worker;
|
|
139
|
+
const orchestrator = resolvedModelRole(db, "orchestrator");
|
|
140
|
+
if (!orchestrator) return null;
|
|
141
|
+
return {
|
|
142
|
+
...orchestrator,
|
|
143
|
+
role: "worker",
|
|
144
|
+
inherit: true,
|
|
145
|
+
updatedAt: worker?.updatedAt || orchestrator.updatedAt,
|
|
146
|
+
sourceLayer:
|
|
147
|
+
worker?.sourceLayer === "model-role"
|
|
148
|
+
? "model-role-inherit"
|
|
149
|
+
: "active-runtime-inherit",
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
MODEL_ROLE_TABLE,
|
|
155
|
+
MODEL_ROLE_MEMBER_TABLE,
|
|
156
|
+
VALID_ROLES,
|
|
157
|
+
roleRow,
|
|
158
|
+
roleMembers,
|
|
159
|
+
pickRoleFromPool,
|
|
160
|
+
legacyOrchestrator,
|
|
161
|
+
resolvedModelRole,
|
|
162
|
+
};
|
|
@@ -43,7 +43,7 @@ class Orchestrator extends EventEmitter {
|
|
|
43
43
|
* 세션 생성(+선택적 즉시 실행). parentKey를 주면 그 세션의 서브에이전트
|
|
44
44
|
* (division 챗)로 붙는다.
|
|
45
45
|
*/
|
|
46
|
-
spawn({ agent, runtime, permission, cwd, title, parentKey, activate = true, spawnImpl, timeoutConfig, chatId }) {
|
|
46
|
+
spawn({ agent, runtime, permission, cwd, title, parentKey, activate = true, spawnImpl, apiTurnImpl, timeoutConfig, chatId }) {
|
|
47
47
|
const parent = parentKey ? this.sessions.get(parentKey) || null : null;
|
|
48
48
|
const session = new Session({
|
|
49
49
|
db: this.db,
|
|
@@ -55,6 +55,7 @@ class Orchestrator extends EventEmitter {
|
|
|
55
55
|
parent,
|
|
56
56
|
title,
|
|
57
57
|
spawnImpl,
|
|
58
|
+
apiTurnImpl,
|
|
58
59
|
timeoutConfig,
|
|
59
60
|
chatId,
|
|
60
61
|
});
|
|
@@ -21,7 +21,7 @@ const RING_LIMIT = 2000;
|
|
|
21
21
|
class Session extends EventEmitter {
|
|
22
22
|
/**
|
|
23
23
|
* @param {object} opts
|
|
24
|
-
* db, agent {id,slug,name,systemPrompt}, runtime {kind,bin,model?},
|
|
24
|
+
* db, agent {id,slug,name,systemPrompt}, runtime {kind,bin,model?,effort?},
|
|
25
25
|
* permission, cwd, lang, parent (Session|null), title, chatId?(재개)
|
|
26
26
|
*/
|
|
27
27
|
constructor(opts) {
|
|
@@ -46,7 +46,9 @@ class Session extends EventEmitter {
|
|
|
46
46
|
this._turnPromise = null;
|
|
47
47
|
// 계약 테스트용 spawn 주입(runNativeTurn의 req.spawn). 프로덕션 경로에선 null.
|
|
48
48
|
this._spawnImpl = opts.spawnImpl || null;
|
|
49
|
+
this._apiTurnImpl = opts.apiTurnImpl || null;
|
|
49
50
|
this._timeoutConfig = opts.timeoutConfig || null;
|
|
51
|
+
this._apiAbort = null;
|
|
50
52
|
|
|
51
53
|
this.chatId = opts.chatId || store.createChat(this.db, {
|
|
52
54
|
agentId: this.agent.id,
|
|
@@ -154,26 +156,55 @@ class Session extends EventEmitter {
|
|
|
154
156
|
}, true, prompt);
|
|
155
157
|
} catch { /* 프롬프트 증강 실패는 턴을 막지 않는다 — 원 프롬프트로 진행 */ }
|
|
156
158
|
|
|
157
|
-
const req = {
|
|
158
|
-
kind: this.runtime.kind,
|
|
159
|
-
bin: this.runtime.bin,
|
|
160
|
-
ui: this._sink,
|
|
161
|
-
cwd: this.cwd,
|
|
162
|
-
prompt,
|
|
163
|
-
systemPrompt,
|
|
164
|
-
permission: this.permission,
|
|
165
|
-
session: { ...this.runtimeSession },
|
|
166
|
-
model: this.runtime.model,
|
|
167
|
-
onSpawn: (child) => { this._child = child; },
|
|
168
|
-
};
|
|
169
|
-
if (this._spawnImpl) req.spawn = this._spawnImpl;
|
|
170
|
-
if (this._timeoutConfig) req.timeoutConfig = this._timeoutConfig;
|
|
171
|
-
|
|
172
159
|
let res;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
160
|
+
if (this.runtime.kind === "ollama") {
|
|
161
|
+
const ctrl = new AbortController();
|
|
162
|
+
this._apiAbort = ctrl;
|
|
163
|
+
const apiTurn = this._apiTurnImpl || require("../agentlas-api-agent.cjs").runApiTurn;
|
|
164
|
+
try {
|
|
165
|
+
const history = store.chatHistory(this.db, this.chatId).map((row) => ({
|
|
166
|
+
role: row.role,
|
|
167
|
+
content: row.text,
|
|
168
|
+
}));
|
|
169
|
+
res = await apiTurn({
|
|
170
|
+
backend: "ollama",
|
|
171
|
+
model: this.runtime.model || "llama3.1",
|
|
172
|
+
system: systemPrompt,
|
|
173
|
+
messages: history,
|
|
174
|
+
ctx: {
|
|
175
|
+
cwd: this.cwd,
|
|
176
|
+
permission: this.permission,
|
|
177
|
+
env: process.env,
|
|
178
|
+
},
|
|
179
|
+
ui: this._sink,
|
|
180
|
+
signal: ctrl.signal,
|
|
181
|
+
});
|
|
182
|
+
} catch (e) {
|
|
183
|
+
res = { text: "", error: (e && e.message) || String(e) };
|
|
184
|
+
} finally {
|
|
185
|
+
this._apiAbort = null;
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
const req = {
|
|
189
|
+
kind: this.runtime.kind,
|
|
190
|
+
bin: this.runtime.bin,
|
|
191
|
+
ui: this._sink,
|
|
192
|
+
cwd: this.cwd,
|
|
193
|
+
prompt,
|
|
194
|
+
systemPrompt,
|
|
195
|
+
permission: this.permission,
|
|
196
|
+
session: { ...this.runtimeSession },
|
|
197
|
+
model: this.runtime.model,
|
|
198
|
+
effort: this.runtime.effort,
|
|
199
|
+
onSpawn: (child) => { this._child = child; },
|
|
200
|
+
};
|
|
201
|
+
if (this._spawnImpl) req.spawn = this._spawnImpl;
|
|
202
|
+
if (this._timeoutConfig) req.timeoutConfig = this._timeoutConfig;
|
|
203
|
+
try {
|
|
204
|
+
res = await nativeHost.runNativeTurn(req);
|
|
205
|
+
} catch (e) {
|
|
206
|
+
res = { text: "", session: req.session, error: (e && e.message) || String(e) };
|
|
207
|
+
}
|
|
177
208
|
}
|
|
178
209
|
this._child = null;
|
|
179
210
|
|
|
@@ -233,6 +264,11 @@ class Session extends EventEmitter {
|
|
|
233
264
|
/** 실행 중 턴을 중단한다. 큐는 비운다. */
|
|
234
265
|
kill() {
|
|
235
266
|
this.queue.length = 0;
|
|
267
|
+
if (this._apiAbort) {
|
|
268
|
+
this.status = "killed";
|
|
269
|
+
try { this._apiAbort.abort(new Error("session killed")); } catch { /* already aborted */ }
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
236
272
|
if (this._child) {
|
|
237
273
|
this.status = "killed";
|
|
238
274
|
try { nativeHost.terminateNativeChild(this._child); } catch { /* already dead */ }
|
package/engine/storm/swarm.cjs
CHANGED
|
@@ -191,7 +191,7 @@ function create(deps) {
|
|
|
191
191
|
return typeof text === "string" ? text : (text && text.text) || "";
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
function recordAllocation(task, stage, decision, resolution, parentTaskId = null) {
|
|
194
|
+
function recordAllocation(task, stage, decision, resolution, parentTaskId = null, usage = null) {
|
|
195
195
|
const receipt = workloadRouting.createDecisionReceipt({
|
|
196
196
|
taskId: `${stage}-${task.id || "synthesis"}`,
|
|
197
197
|
parentTaskId,
|
|
@@ -199,6 +199,7 @@ function create(deps) {
|
|
|
199
199
|
stage,
|
|
200
200
|
decision,
|
|
201
201
|
resolution,
|
|
202
|
+
usage,
|
|
202
203
|
});
|
|
203
204
|
try {
|
|
204
205
|
workloadRouting.appendDecisionReceipt(
|
|
@@ -221,9 +222,9 @@ function create(deps) {
|
|
|
221
222
|
availableModels: ctx.availableModels,
|
|
222
223
|
maxTier: ctx.maxTier || process.env.AGENTLAS_MODEL_MAX_TIER,
|
|
223
224
|
});
|
|
224
|
-
recordAllocation(task, stage, task.allocation, resolution, parentTaskId);
|
|
225
225
|
// 할당 거부 후 CLI 기본 모델로 조용히 실행 금지 — fail-closed (계약 테스트 고정).
|
|
226
226
|
if (!resolution.ok) {
|
|
227
|
+
recordAllocation(task, stage, task.allocation, resolution, parentTaskId);
|
|
227
228
|
throw new Error(`model allocation failed closed: ${resolution.fallbackReason || "no compliant live model"}`);
|
|
228
229
|
}
|
|
229
230
|
if (resolution.fallbackReason) {
|
|
@@ -238,17 +239,32 @@ function create(deps) {
|
|
|
238
239
|
source: resolution.source,
|
|
239
240
|
fallbackReason: resolution.fallbackReason || null,
|
|
240
241
|
};
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
242
|
+
let observed;
|
|
243
|
+
try {
|
|
244
|
+
observed = selectedRuntime.mode === "cli"
|
|
245
|
+
? await D.captureRuntime(selectedRuntime.kind, system, prompt, {
|
|
246
|
+
cwd,
|
|
247
|
+
env,
|
|
248
|
+
permission,
|
|
249
|
+
model: resolution.model,
|
|
250
|
+
effort: resolution.effort,
|
|
251
|
+
envelope: true,
|
|
252
|
+
})
|
|
253
|
+
: await D.runApi(
|
|
254
|
+
selectedRuntime.backend,
|
|
255
|
+
resolution.model || selectedRuntime.model,
|
|
256
|
+
system,
|
|
257
|
+
prompt,
|
|
258
|
+
{ envelope: true },
|
|
259
|
+
);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
recordAllocation(task, stage, task.allocation, resolution, parentTaskId);
|
|
262
|
+
throw error;
|
|
249
263
|
}
|
|
250
|
-
const text =
|
|
251
|
-
|
|
264
|
+
const text = typeof observed === "string" ? observed : (observed && observed.text) || "";
|
|
265
|
+
const usage = observed && typeof observed === "object" ? observed.usage : null;
|
|
266
|
+
recordAllocation(task, stage, task.allocation, resolution, parentTaskId, usage);
|
|
267
|
+
return text;
|
|
252
268
|
}
|
|
253
269
|
|
|
254
270
|
const label = runtime.mode === "cli" ? runtime.kind : runtime.backend;
|
package/engine/ui/palette.cjs
CHANGED
|
@@ -29,6 +29,8 @@ const SLASH_COMMANDS = [
|
|
|
29
29
|
{ command: "/mcp", args: "", ko: "MCP 서버 목록", en: "MCP servers" },
|
|
30
30
|
{ command: "/doctor", args: "", ko: "런타임·데이터 점검", en: "Health check" },
|
|
31
31
|
{ command: "/runtime", args: "<kind>", ko: "새 세션 런타임 지정", en: "Set runtime for new sessions" },
|
|
32
|
+
{ command: "/model", args: "<id|default>", ko: "새 세션 모델 지정", en: "Set model for new sessions" },
|
|
33
|
+
{ command: "/effort", args: "<level|none>", ko: "새 세션 추론 강도 지정", en: "Set effort for new sessions" },
|
|
32
34
|
{ command: "/permission", args: "<level>", ko: "새 세션 권한 지정", en: "Set permission for new sessions" },
|
|
33
35
|
{ command: "/login", args: "", ko: "Agentlas Cloud 로그인", en: "Sign in to Agentlas Cloud" },
|
|
34
36
|
{ command: "/whoami", args: "", ko: "로그인 상태", en: "Signed-in account" },
|
|
@@ -40,12 +42,49 @@ const SLASH_COMMANDS = [
|
|
|
40
42
|
{ command: "/storm", args: "<goal>", ko: "Goal+UltraCode 하니스", en: "Goal+UltraCode harness" },
|
|
41
43
|
{ command: "/swarm", args: "<goal>", ko: "에이전트 스웜", en: "Agent swarm" },
|
|
42
44
|
{ command: "/network", args: "<request>", ko: "Workforce 라우트", en: "Workforce route" },
|
|
45
|
+
{ command: "/workforce", args: "<request>", ko: "Workforce 라우트", en: "Workforce route" },
|
|
46
|
+
{ command: "/taskforce", args: "<request>", ko: "임시 태스크포스 편성", en: "Assemble a task force" },
|
|
47
|
+
{ command: "/build", args: "\"<request>\"", ko: "에이전트·팀 제작/수리/패키징", en: "Build, repair or package an agent or team" },
|
|
48
|
+
{ command: "/call", args: "\"a,b\" \"<ctx>\"", ko: "지정 에이전트 호출", en: "Call named agents" },
|
|
49
|
+
{ command: "/route", args: "\"<req>\"", ko: "최적 에이전트 라우팅", en: "Route to the best agent" },
|
|
50
|
+
{ command: "/browser", args: "[sub]", ko: "브라우저 하드포인트", en: "Browser hardpoint" },
|
|
51
|
+
{ command: "/connect", args: "<target>", ko: "에이전트·팀 연결", en: "Connect an agent or team" },
|
|
52
|
+
{ command: "/research", args: "<sub>", ko: "리서치", en: "Research" },
|
|
53
|
+
{ command: "/upload", args: "<path>", ko: "Agent Cloud에 저장·발행", en: "Save to Agent Cloud or publish" },
|
|
54
|
+
{ command: "/cloud", args: "<sub>", ko: "클라우드 자산 관리", en: "Cloud assets" },
|
|
55
|
+
{ command: "/import", args: "<path>", ko: "로컬 폴더 에이전트 가져오기", en: "Import a local folder agent" },
|
|
56
|
+
{ command: "/cd", args: "[path]", ko: "작업 폴더 이동", en: "Change working folder" },
|
|
57
|
+
{ command: "/native", args: "prepare <agent>", ko: "네이티브 CLI 컨텍스트 생성", en: "Prepare native CLI context" },
|
|
58
|
+
{ command: "/plugin", args: "<sub>", ko: "Hub 플러그인(MCP)", en: "Hub plugins (MCP servers)" },
|
|
59
|
+
{ command: "/plugins", args: "", ko: "설치된 플러그인", en: "Installed plugins" },
|
|
60
|
+
{ command: "/experience", args: "<sub>", ko: "이식 가능한 Experience", en: "Portable Experience" },
|
|
61
|
+
{ command: "/variant", args: "resolve", ko: "로컬 변형 선택", en: "Local variant selection" },
|
|
62
|
+
{ command: "/memory", args: "<sub>", ko: "메모리", en: "Memory" },
|
|
63
|
+
{ command: "/evolve", args: "", ko: "프롬프트 진화 제안", en: "Prompt-evolution proposals" },
|
|
64
|
+
{ command: "/ontology", args: "", ko: "프로젝트 지식", en: "Project knowledge" },
|
|
65
|
+
{ command: "/career-graph", args: "", ko: "소스 라우팅 그래프", en: "Source routing graph" },
|
|
66
|
+
{ command: "/journal", args: "<sub>", ko: "Stormbreaker 실행 일지", en: "Stormbreaker run journal" },
|
|
67
|
+
{ command: "/project", args: "[status|init]", ko: ".agentlas 프로젝트 상태", en: "Private project state" },
|
|
68
|
+
{ command: "/context", args: "<sub>", ko: "의존성 맵", en: "Dependency map" },
|
|
69
|
+
{ command: "/creds", args: "<sub>", ko: "자격증명", en: "Credentials" },
|
|
70
|
+
{ command: "/env", args: "", ko: "공유 환경 키", en: "Shared env keys" },
|
|
71
|
+
{ command: "/multimodal", args: "", ko: "이미지·영상·음성 설정", en: "Image/video/audio providers" },
|
|
72
|
+
{ command: "/telegram", args: "[sub]", ko: "텔레그램 연결", en: "Telegram bindings" },
|
|
73
|
+
{ command: "/oberon", args: "[sub]", ko: "AI 필름", en: "AI film" },
|
|
74
|
+
{ command: "/film", args: "<sub>", ko: "필름 렌더", en: "Film render" },
|
|
75
|
+
{ command: "/hep", args: "<sub…>", ko: "Hephaestus 패스스루", en: "Hephaestus passthrough" },
|
|
76
|
+
{ command: "/netadmin", args: "[sub]", ko: "로컬 네트워크 관리", en: "Local network admin" },
|
|
77
|
+
{ command: "/update", args: "", ko: "npm 업데이트 확인", en: "npm update check" },
|
|
78
|
+
{ command: "/version", args: "", ko: "버전", en: "Version" },
|
|
79
|
+
{ command: "/logout", args: "", ko: "로그아웃", en: "Sign out" },
|
|
80
|
+
{ command: "/uninstall", args: "<slug>", ko: "에이전트 제거", en: "Uninstall an agent" },
|
|
43
81
|
{ command: "/quit", args: "", ko: "종료", en: "Quit" },
|
|
44
82
|
{ command: "/exit", args: "", ko: "종료", en: "Quit" },
|
|
45
83
|
];
|
|
46
84
|
|
|
47
85
|
const SLASH_NAMES = SLASH_COMMANDS.map((c) => c.command);
|
|
48
86
|
const RUNTIME_KINDS = ["claude-code", "codex", "gemini"];
|
|
87
|
+
const EFFORT_LEVELS = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
49
88
|
const PERM_LEVELS = ["read", "write", "full"];
|
|
50
89
|
// 세션 인자를 받는 명령 — 완성 후보를 살아있는 세션 키(s1, s2…)로 채운다.
|
|
51
90
|
const SESSION_ARG_COMMANDS = new Set(["/s", "/switch", "/steer", "/kill", "/rm"]);
|
|
@@ -83,6 +122,7 @@ function makeCompleter(ctx = {}) {
|
|
|
83
122
|
|
|
84
123
|
const cmd = tokens[0];
|
|
85
124
|
if (cmd === "/runtime") return [uniqStartsWith(RUNTIME_KINDS, last), last];
|
|
125
|
+
if (cmd === "/effort") return [uniqStartsWith(EFFORT_LEVELS, last), last];
|
|
86
126
|
if (cmd === "/permission") return [uniqStartsWith(PERM_LEVELS, last), last];
|
|
87
127
|
if (SESSION_ARG_COMMANDS.has(cmd) && tokens.length === 2) return [uniqStartsWith(getSessions(), last), last];
|
|
88
128
|
if (AGENT_ARG_COMMANDS.has(cmd) && tokens.length === 2) {
|