agentlas 1.0.11 → 1.0.14
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 +67 -0
- package/README.md +4 -2
- package/bin/agentlas.cjs +17 -3
- 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 +62 -8
- 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 +989 -114
- package/engine/agentlas-workload-routing.cjs +61 -7
- package/engine/agentlas.cjs +8 -0
- package/engine/automation/daemon.cjs +76 -30
- package/engine/automation/schedule.cjs +16 -0
- package/engine/automation/store.cjs +92 -12
- package/engine/bootstrap-schema.sql +788 -29
- package/engine/commands/automation.cjs +8 -0
- package/engine/commands/chats.cjs +6 -1
- package/engine/commands/doctor.cjs +23 -1
- package/engine/commands/firm.cjs +66 -4
- package/engine/commands/help.cjs +31 -7
- 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/list.cjs +25 -1
- package/engine/commands/open.cjs +5 -1
- package/engine/commands/run.cjs +77 -13
- 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/firms/orchestrate.cjs +10 -3
- 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/runtimes/overrides.cjs +91 -22
- package/engine/runtimes/resolve.cjs +38 -8
- package/engine/runtimes/roles.cjs +162 -0
- package/engine/sessions/orchestrator.cjs +43 -4
- package/engine/sessions/prompt.cjs +4 -7
- package/engine/sessions/session.cjs +88 -20
- package/engine/storm/swarm.cjs +40 -12
- package/engine/ui/palette.cjs +52 -0
- package/engine/ui/renderer.cjs +37 -0
- package/engine/ui/repl.cjs +78 -10
- package/engine/workforce/capture.cjs +199 -14
- package/engine/workforce/concurrency.cjs +41 -0
- package/engine/workforce/deps.cjs +145 -29
- package/package.json +1 -1
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;
|
|
@@ -411,6 +427,18 @@ function create(deps) {
|
|
|
411
427
|
if (args[i] === "--parallel" || args[i] === "-n") concurrency = Number(args[++i]);
|
|
412
428
|
else rest.push(args[i]);
|
|
413
429
|
}
|
|
430
|
+
// storm.cjs stormRun 의 leading-dash 가드와 동일 계약. 여기서 안 막으면 미지원·
|
|
431
|
+
// 오타 플래그(--permission, --model …)가 rest 에 남아 rest.join(" ") 로 목표가
|
|
432
|
+
// 되어버린다: 유료 플래너가 플래그 텍스트를 목표로 실제 실행되고, 플래그가
|
|
433
|
+
// 의도한 동작(예: 권한 상승)은 조용히 일어나지 않는다. 목표는 프롬프트까지
|
|
434
|
+
// 오염된다. 그래서 소비되지 않은 대시 토큰은 실행 전에 fail-closed 로 거절한다.
|
|
435
|
+
const strayFlag = rest.find((token) => String(token).startsWith("-"));
|
|
436
|
+
if (strayFlag) {
|
|
437
|
+
const ui = executionContext.ui || newUi();
|
|
438
|
+
ui.error(`unknown option ${strayFlag} — swarm accepts: --parallel N | -n N, --runtime <kind>`);
|
|
439
|
+
process.exitCode = 1;
|
|
440
|
+
return { ok: false, error: "unknown-option" };
|
|
441
|
+
}
|
|
414
442
|
const r = await swarmRun(db, rest.join(" "), { ...executionContext, concurrency, runtimeOverride });
|
|
415
443
|
if (!r.ok) process.exitCode = 1;
|
|
416
444
|
return r;
|
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,61 @@ 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
|
+
// 소스 스코프가 이름에 붙은 스태핑 3종. 스코프를 문장에 적는다 — 예전에는
|
|
48
|
+
// 별칭이 스코프를 버려 /hep-cloud 가 자산 보관함으로, /hep-hub 가 검색으로
|
|
49
|
+
// 갔다(2026-07-28 수리). 팔레트 문구가 곧 사용자에게 하는 약속이다.
|
|
50
|
+
{ command: "/hep-network", args: "\"<request>\"", ko: "로컬+오너 클라우드+공개 Hub 연합 편성", en: "Staff across Local + owner Cloud + public Hub" },
|
|
51
|
+
{ command: "/hep-local", args: "\"<request>\"", ko: "등록된 로컬 에이전트만으로 편성", en: "Staff from registered Local agents only" },
|
|
52
|
+
{ command: "/hep-cloud", args: "\"<request>\"", ko: "오너 Agent Cloud만으로 편성", en: "Staff from owner Agent Cloud only" },
|
|
53
|
+
{ command: "/hep-hub", args: "\"<request>\"", ko: "공개 Hub 에이전트만으로 편성", en: "Staff from public Hub agents only" },
|
|
54
|
+
{ command: "/build", args: "\"<request>\"", ko: "에이전트·팀 제작/수리/패키징", en: "Build, repair or package an agent or team" },
|
|
55
|
+
{ command: "/call", args: "\"a,b\" \"<ctx>\"", ko: "지정 에이전트 호출", en: "Call named agents" },
|
|
56
|
+
{ command: "/route", args: "\"<req>\"", ko: "최적 에이전트 라우팅", en: "Route to the best agent" },
|
|
57
|
+
{ command: "/browser", args: "[sub]", ko: "브라우저 하드포인트", en: "Browser hardpoint" },
|
|
58
|
+
{ command: "/connect", args: "<target>", ko: "에이전트·팀 연결", en: "Connect an agent or team" },
|
|
59
|
+
{ command: "/research", args: "<sub>", ko: "리서치", en: "Research" },
|
|
60
|
+
{ command: "/upload", args: "<path>", ko: "Agent Cloud에 저장·발행", en: "Save to Agent Cloud or publish" },
|
|
61
|
+
{ command: "/cloud", args: "<sub>", ko: "클라우드 자산 관리", en: "Cloud assets" },
|
|
62
|
+
{ command: "/import", args: "<path>", ko: "로컬 폴더 에이전트 가져오기", en: "Import a local folder agent" },
|
|
63
|
+
{ command: "/cd", args: "[path]", ko: "작업 폴더 이동", en: "Change working folder" },
|
|
64
|
+
{ command: "/native", args: "prepare <agent>", ko: "네이티브 CLI 컨텍스트 생성", en: "Prepare native CLI context" },
|
|
65
|
+
{ command: "/plugin", args: "<sub>", ko: "Hub 플러그인(MCP)", en: "Hub plugins (MCP servers)" },
|
|
66
|
+
{ command: "/plugins", args: "", ko: "설치된 플러그인", en: "Installed plugins" },
|
|
67
|
+
{ command: "/experience", args: "<sub>", ko: "이식 가능한 Experience", en: "Portable Experience" },
|
|
68
|
+
{ command: "/variant", args: "resolve", ko: "로컬 변형 선택", en: "Local variant selection" },
|
|
69
|
+
{ command: "/memory", args: "<sub>", ko: "메모리", en: "Memory" },
|
|
70
|
+
{ command: "/evolve", args: "", ko: "프롬프트 진화 제안", en: "Prompt-evolution proposals" },
|
|
71
|
+
// 데스크탑의 `ontology` 는 Core 의 지식·메모리 **런타임**(임베딩 포함)이고, 터미널의
|
|
72
|
+
// 이것은 **이 프로젝트의 지식 소스 등록부**다. 서로 다른 것이 같은 이름을 쓰고 있어
|
|
73
|
+
// (감사 D6) 라벨이라도 정확해야 한다 — 명령 이름은 사용자 습관과 스크립트가 걸려
|
|
74
|
+
// 있어 바꾸지 않는다. Core 의 지식 런타임은 터미널에 아직 미노출이다(결함 아님).
|
|
75
|
+
{ command: "/ontology", args: "", ko: "프로젝트 지식 소스 등록", en: "Project knowledge sources" },
|
|
76
|
+
{ command: "/career-graph", args: "", ko: "소스 라우팅 그래프", en: "Source routing graph" },
|
|
77
|
+
{ command: "/journal", args: "<sub>", ko: "Stormbreaker 실행 일지", en: "Stormbreaker run journal" },
|
|
78
|
+
{ command: "/project", args: "[status|init]", ko: ".agentlas 프로젝트 상태", en: "Private project state" },
|
|
79
|
+
{ command: "/context", args: "<sub>", ko: "의존성 맵", en: "Dependency map" },
|
|
80
|
+
{ command: "/creds", args: "<sub>", ko: "자격증명", en: "Credentials" },
|
|
81
|
+
{ command: "/env", args: "", ko: "공유 환경 키", en: "Shared env keys" },
|
|
82
|
+
{ command: "/multimodal", args: "", ko: "이미지·영상·음성 설정", en: "Image/video/audio providers" },
|
|
83
|
+
{ command: "/telegram", args: "[sub]", ko: "텔레그램 연결", en: "Telegram bindings" },
|
|
84
|
+
{ command: "/oberon", args: "[sub]", ko: "AI 필름", en: "AI film" },
|
|
85
|
+
{ command: "/film", args: "<sub>", ko: "필름 렌더", en: "Film render" },
|
|
86
|
+
{ command: "/hep", args: "<sub…>", ko: "Hephaestus 패스스루", en: "Hephaestus passthrough" },
|
|
87
|
+
{ command: "/netadmin", args: "[sub]", ko: "로컬 네트워크 관리", en: "Local network admin" },
|
|
88
|
+
{ command: "/update", args: "", ko: "npm 업데이트 확인", en: "npm update check" },
|
|
89
|
+
{ command: "/version", args: "", ko: "버전", en: "Version" },
|
|
90
|
+
{ command: "/logout", args: "", ko: "로그아웃", en: "Sign out" },
|
|
91
|
+
// 대화가 있으면 --yes 없이는 거절한다(챗/메시지 CASCADE 삭제) — 팔레트에도 노출.
|
|
92
|
+
{ command: "/uninstall", args: "<slug> [--yes]", ko: "에이전트 제거", en: "Uninstall an agent" },
|
|
43
93
|
{ command: "/quit", args: "", ko: "종료", en: "Quit" },
|
|
44
94
|
{ command: "/exit", args: "", ko: "종료", en: "Quit" },
|
|
45
95
|
];
|
|
46
96
|
|
|
47
97
|
const SLASH_NAMES = SLASH_COMMANDS.map((c) => c.command);
|
|
48
98
|
const RUNTIME_KINDS = ["claude-code", "codex", "gemini"];
|
|
99
|
+
const EFFORT_LEVELS = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
49
100
|
const PERM_LEVELS = ["read", "write", "full"];
|
|
50
101
|
// 세션 인자를 받는 명령 — 완성 후보를 살아있는 세션 키(s1, s2…)로 채운다.
|
|
51
102
|
const SESSION_ARG_COMMANDS = new Set(["/s", "/switch", "/steer", "/kill", "/rm"]);
|
|
@@ -83,6 +134,7 @@ function makeCompleter(ctx = {}) {
|
|
|
83
134
|
|
|
84
135
|
const cmd = tokens[0];
|
|
85
136
|
if (cmd === "/runtime") return [uniqStartsWith(RUNTIME_KINDS, last), last];
|
|
137
|
+
if (cmd === "/effort") return [uniqStartsWith(EFFORT_LEVELS, last), last];
|
|
86
138
|
if (cmd === "/permission") return [uniqStartsWith(PERM_LEVELS, last), last];
|
|
87
139
|
if (SESSION_ARG_COMMANDS.has(cmd) && tokens.length === 2) return [uniqStartsWith(getSessions(), last), last];
|
|
88
140
|
if (AGENT_ARG_COMMANDS.has(cmd) && tokens.length === 2) {
|
package/engine/ui/renderer.cjs
CHANGED
|
@@ -77,6 +77,43 @@ class Renderer {
|
|
|
77
77
|
case "task-result": ui.applyTaskResult(ev.name, ev.result, ev.id); return;
|
|
78
78
|
case "cost": ui.cost(ev.usage); return;
|
|
79
79
|
case "queued": ui.line(ui.c.dim(` ↳ queued for next turn: ${ev.text}`)); return;
|
|
80
|
+
|
|
81
|
+
/* ── 펜스 영수증 (apply-fences.cjs) ──────────────────────────────────
|
|
82
|
+
* WHY: 펜스 블록은 cleanText 에서 통째로 제거되므로, 이 case 들이 없으면
|
|
83
|
+
* default 로 떨어져 화면에 아무 흔적도 남지 않는다. 그 결과 이미 enabled
|
|
84
|
+
* 상태로 등록된 반복 자동화(데몬/데스크탑 스케줄러가 실제로 실행한다)나
|
|
85
|
+
* 스폰된 위임 세션이 사용자 모르게 생기고, 에이전트의 확인 질문(ask)은
|
|
86
|
+
* 본문에서 삭제된 채 영영 묻지 않는다. 부작용에는 반드시 영수증이 따른다 —
|
|
87
|
+
* 새 펜스 이벤트를 apply-fences 에 추가하면 여기에도 case 를 추가할 것. */
|
|
88
|
+
case "ask": {
|
|
89
|
+
const p = ev.payload || {};
|
|
90
|
+
ui.ensureNl();
|
|
91
|
+
if (p.header) ui.line(ui.c.dim(` ${p.header}`));
|
|
92
|
+
ui.warn(p.question || "(question)");
|
|
93
|
+
(p.options || []).forEach((o, i) => {
|
|
94
|
+
const desc = o && o.description ? ` — ${o.description}` : "";
|
|
95
|
+
ui.line(ui.c.dim(` ${i + 1}. ${(o && o.label) || ""}${desc}`));
|
|
96
|
+
});
|
|
97
|
+
ui.line(ui.c.dim(` ↳ 답을 그대로 입력하세요${p.multiSelect ? " (복수 선택 가능)" : ""}`));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
case "automation-registered": {
|
|
101
|
+
const steps = ev.stepsIgnored ? ` · steps ${ev.stepsIgnored}개 무시(터미널은 그래프 합성 없음)` : "";
|
|
102
|
+
ui.ok(`automation registered: ${ev.name} · ${ev.schedule} · next ${ev.nextRunAt}${steps}`);
|
|
103
|
+
// 취소 경로를 함께 제시한다. 실제 서브커맨드는 off (commands/automation.cjs:125).
|
|
104
|
+
ui.line(ui.c.dim(` ↳ agentlas automation list · agentlas automation off ${String(ev.id || "").slice(0, 8)}`));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
case "automation-refused": ui.warn(`automation refused: ${ev.name} — ${ev.reason}`); return;
|
|
108
|
+
case "delegate-spawned":
|
|
109
|
+
ui.ok(`delegate spawned: ${ev.target} → ${ev.key}`);
|
|
110
|
+
return;
|
|
111
|
+
case "delegate-refused": ui.warn(`delegate refused: ${ev.target} — ${ev.reason}`); return;
|
|
112
|
+
case "fence-error": ui.error(`fence: ${ev.text}`); return;
|
|
113
|
+
case "memory-curated":
|
|
114
|
+
ui.line(ui.c.dim(` ↳ memory: ${ev.written}/${ev.candidates} written (permission: ${ev.permission})`));
|
|
115
|
+
return;
|
|
116
|
+
|
|
80
117
|
default: return;
|
|
81
118
|
}
|
|
82
119
|
}
|
package/engine/ui/repl.cjs
CHANGED
|
@@ -20,7 +20,8 @@ const { renderBanner, readVersion } = require("../agentlas-banner.cjs");
|
|
|
20
20
|
const { Orchestrator, maxParallel } = require("../sessions/orchestrator.cjs");
|
|
21
21
|
const { Renderer } = require("./renderer.cjs");
|
|
22
22
|
const { findAgent, listAgents } = require("../agents/registry.cjs");
|
|
23
|
-
const {
|
|
23
|
+
const { resolveRuntimeForAgent } = require("../runtimes/overrides.cjs");
|
|
24
|
+
const { EFFORTS } = require("../agentlas-workload-routing.cjs");
|
|
24
25
|
const permissions = require("../agentlas-permissions.cjs");
|
|
25
26
|
const i18n = require("../agentlas-i18n.cjs");
|
|
26
27
|
const { tokenizeCommandLine } = require("../agentlas-input.cjs");
|
|
@@ -133,8 +134,18 @@ async function startRepl(ctx, opts = {}) {
|
|
|
133
134
|
const renderer = new Renderer(ui);
|
|
134
135
|
let permission = permissions.normalize(opts.permission || (ctx.prefs && ctx.prefs.permission) || "write");
|
|
135
136
|
let runtimeOverride = opts.runtime || null;
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
let modelOverride = opts.model || null;
|
|
138
|
+
let effortOverride = opts.effort || null;
|
|
139
|
+
|
|
140
|
+
const resolveRt = (agentId = null) => resolveRuntimeForAgent({
|
|
141
|
+
db,
|
|
142
|
+
prefs: ctx.prefs,
|
|
143
|
+
explicit: runtimeOverride,
|
|
144
|
+
model: modelOverride,
|
|
145
|
+
effort: effortOverride,
|
|
146
|
+
role: "orchestrator",
|
|
147
|
+
agentId,
|
|
148
|
+
});
|
|
138
149
|
|
|
139
150
|
let resumeChatId = opts.chatId || null;
|
|
140
151
|
const ensureMainSession = (agentToken) => {
|
|
@@ -146,7 +157,14 @@ async function startRepl(ctx, opts = {}) {
|
|
|
146
157
|
}
|
|
147
158
|
const active = orch.active();
|
|
148
159
|
if (active && active.agent.id === agent.id) return active;
|
|
149
|
-
const session = orch.spawn({
|
|
160
|
+
const session = orch.spawn({
|
|
161
|
+
agent,
|
|
162
|
+
runtime: resolveRt(agent.id),
|
|
163
|
+
permission,
|
|
164
|
+
cwd: process.cwd(),
|
|
165
|
+
activate: true,
|
|
166
|
+
chatId: resumeChatId,
|
|
167
|
+
});
|
|
150
168
|
resumeChatId = null; // 재개는 첫 세션에만 적용
|
|
151
169
|
renderer.attach(session, { replay: false });
|
|
152
170
|
return session;
|
|
@@ -318,7 +336,18 @@ async function startRepl(ctx, opts = {}) {
|
|
|
318
336
|
|
|
319
337
|
if (input.startsWith("/")) {
|
|
320
338
|
try {
|
|
321
|
-
const quit = handleSlash(ctx, input.slice(1), {
|
|
339
|
+
const quit = handleSlash(ctx, input.slice(1), {
|
|
340
|
+
orch,
|
|
341
|
+
renderer,
|
|
342
|
+
ensureMainSession,
|
|
343
|
+
resolveRt,
|
|
344
|
+
track: trackCommand,
|
|
345
|
+
setPermission: (p) => { permission = p; },
|
|
346
|
+
getPermission: () => permission,
|
|
347
|
+
setRuntime: (r) => { runtimeOverride = r; },
|
|
348
|
+
setModel: (model) => { modelOverride = model; },
|
|
349
|
+
setEffort: (effort) => { effortOverride = effort; },
|
|
350
|
+
});
|
|
322
351
|
if (quit === "quit") { rl.close(); return; }
|
|
323
352
|
} catch (e) {
|
|
324
353
|
ui.error(String((e && e.message) || e));
|
|
@@ -482,6 +511,18 @@ function printSessions(ctx, orch) {
|
|
|
482
511
|
}
|
|
483
512
|
}
|
|
484
513
|
|
|
514
|
+
/*
|
|
515
|
+
* 세션 키 파싱. 인자가 없으면 사용법을 낸다 — 예전에는 `s${undefined}` 가 그대로
|
|
516
|
+
* 조립돼 `/kill` 이 "no such session: sundefined" 를, `/steer` 는 rest[0].length 에서
|
|
517
|
+
* 날 TypeError 를 냈다. 팔레트가 `/steer <n> <msg>` 라고 안내하므로 인자 없이 Enter 를
|
|
518
|
+
* 눌러 사용법을 보려는 것은 정상적인 탐색이다.
|
|
519
|
+
*/
|
|
520
|
+
function sessionKeyArg(rest, usage) {
|
|
521
|
+
const token = rest[0];
|
|
522
|
+
if (!token) throw new Error(usage);
|
|
523
|
+
return String(token).startsWith("s") ? token : `s${token}`;
|
|
524
|
+
}
|
|
525
|
+
|
|
485
526
|
function handleSlash(ctx, cmdline, api) {
|
|
486
527
|
const en = ctx.lang === "en";
|
|
487
528
|
const ui = ctx.uiInstance;
|
|
@@ -524,7 +565,7 @@ function handleSlash(ctx, cmdline, api) {
|
|
|
524
565
|
case "sessions": case "tree": printSessions(ctx, orch); return;
|
|
525
566
|
|
|
526
567
|
case "s": case "switch": {
|
|
527
|
-
const key =
|
|
568
|
+
const key = sessionKeyArg(rest, `Usage: /${cmd} <n>`);
|
|
528
569
|
const session = orch.setActive(key);
|
|
529
570
|
renderer.attach(session, { replay: true });
|
|
530
571
|
return;
|
|
@@ -552,7 +593,7 @@ function handleSlash(ctx, cmdline, api) {
|
|
|
552
593
|
}
|
|
553
594
|
|
|
554
595
|
case "steer": {
|
|
555
|
-
const key =
|
|
596
|
+
const key = sessionKeyArg(rest, `Usage: /${cmd} <n> <message>`);
|
|
556
597
|
const msg = restStr.slice(rest[0].length).trim();
|
|
557
598
|
if (!msg) throw new Error("Usage: /steer <n> <message>");
|
|
558
599
|
orch.sendTo(key, msg);
|
|
@@ -561,12 +602,12 @@ function handleSlash(ctx, cmdline, api) {
|
|
|
561
602
|
}
|
|
562
603
|
|
|
563
604
|
case "kill": {
|
|
564
|
-
const key =
|
|
605
|
+
const key = sessionKeyArg(rest, `Usage: /${cmd} <n>`);
|
|
565
606
|
orch.kill(key);
|
|
566
607
|
return;
|
|
567
608
|
}
|
|
568
609
|
case "rm": {
|
|
569
|
-
const key =
|
|
610
|
+
const key = sessionKeyArg(rest, `Usage: /${cmd} <n>`);
|
|
570
611
|
orch.remove(key);
|
|
571
612
|
const active = orch.active();
|
|
572
613
|
if (active) renderer.attach(active, { replay: false });
|
|
@@ -575,8 +616,14 @@ function handleSlash(ctx, cmdline, api) {
|
|
|
575
616
|
|
|
576
617
|
case "broadcast": {
|
|
577
618
|
if (!restStr) throw new Error("Usage: /broadcast <message>");
|
|
578
|
-
|
|
619
|
+
/*
|
|
620
|
+
* broadcast 는 {sent, skipped} 를 준다. 전달된 목록을 먼저 찍고 못 보낸 세션은
|
|
621
|
+
* 사유와 함께 경고로 덧붙인다 — 예전엔 상한 throw 가 이 catch 로 떨어져 에러 한
|
|
622
|
+
* 줄만 보였고, 이미 지시를 받아 돌기 시작한 세션이 화면에 전혀 안 나왔다.
|
|
623
|
+
*/
|
|
624
|
+
const { sent, skipped } = orch.broadcast(restStr);
|
|
579
625
|
ctx.out(ui.c.dim(`→ ${sent.join(", ") || "(none)"}`));
|
|
626
|
+
for (const s of skipped) ui.warn(`${s.key} ${en ? "not sent" : "미전송"}: ${s.error}`);
|
|
580
627
|
return;
|
|
581
628
|
}
|
|
582
629
|
|
|
@@ -594,6 +641,27 @@ function handleSlash(ctx, cmdline, api) {
|
|
|
594
641
|
ctx.out(ui.c.dim(`runtime: ${rest[0]} (${en ? "applies to new sessions" : "새 세션부터 적용"})`));
|
|
595
642
|
return;
|
|
596
643
|
}
|
|
644
|
+
case "model": {
|
|
645
|
+
const model = String(rest[0] || "").trim();
|
|
646
|
+
if (!model) throw new Error("Usage: /model <provider-model-id|default>");
|
|
647
|
+
const next = ["default", "inherit"].includes(model.toLowerCase()) ? null : model;
|
|
648
|
+
api.setModel(next);
|
|
649
|
+
ctx.out(ui.c.dim(
|
|
650
|
+
`model: ${next || "default"} (${en ? "applies to new sessions" : "새 세션부터 적용"})`,
|
|
651
|
+
));
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
case "effort": {
|
|
655
|
+
const effort = String(rest[0] || "").trim().toLowerCase();
|
|
656
|
+
if (!EFFORTS.includes(effort)) {
|
|
657
|
+
throw new Error(`Usage: /effort ${EFFORTS.join("|")}`);
|
|
658
|
+
}
|
|
659
|
+
api.setEffort(effort === "none" ? null : effort);
|
|
660
|
+
ctx.out(ui.c.dim(
|
|
661
|
+
`effort: ${effort} (${en ? "applies to new sessions" : "새 세션부터 적용"})`,
|
|
662
|
+
));
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
597
665
|
case "permission": {
|
|
598
666
|
if (!["read", "write", "full"].includes(String(rest[0] || ""))) {
|
|
599
667
|
throw new Error("Usage: /permission read|write|full");
|