agentlas 1.0.37 → 1.0.38
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 +23 -0
- package/engine/agentlas-banner.cjs +1 -1
- package/engine/agentlas-onboard.cjs +2 -2
- package/engine/agentlas-ui.cjs +22 -7
- package/engine/agentlas-workforce.cjs +9 -3
- package/engine/commands/doctor.cjs +36 -7
- package/engine/commands/workforce.cjs +1 -1
- package/engine/graph/interview.cjs +55 -11
- package/engine/hephaestus/runtime.cjs +7 -1
- package/engine/storm/deps.cjs +2 -0
- package/engine/storm/storm.cjs +8 -2
- package/engine/storm/swarm.cjs +11 -5
- package/engine/ui/pitui-shell.cjs +295 -0
- package/engine/ui/repl.cjs +30 -7
- package/engine/ui/width.cjs +172 -0
- package/engine/workforce/deps.cjs +21 -0
- package/package.json +6 -3
- package/engine/agentlas-composer.cjs +0 -532
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.38 — 2026-08-11
|
|
4
|
+
|
|
5
|
+
Silence was the worst failure mode — this release makes failures speak.
|
|
6
|
+
|
|
7
|
+
- The presentation boundary (`Ui.error`) now has conditions. Machine-coded
|
|
8
|
+
messages (usage guidance, honest stops, server relays) pass through; only
|
|
9
|
+
uncoded raw provider text is still replaced by the neutral recovery line.
|
|
10
|
+
Previously every failure — including usage help for eight argless slash
|
|
11
|
+
commands — collapsed into the same "One is recovering" sentence.
|
|
12
|
+
- `/s` `/switch` `/kill` `/rm` `/runtime` `/model` `/effort` `/permission`
|
|
13
|
+
without arguments now print their usage line instead of a recovery notice.
|
|
14
|
+
- Workforce sign-in expiry is relayed honestly: the server's `auth_required`
|
|
15
|
+
guidance reaches the user (run `agentlas login`), instead of being
|
|
16
|
+
misreported as an invalid continuity receipt and then swallowed.
|
|
17
|
+
- `doctor` verifies the cloud session against the server instead of only
|
|
18
|
+
checking that a session file exists. Expired sessions are reported as a
|
|
19
|
+
warning with the login hint; offline is reported as "unverified", never as
|
|
20
|
+
a false all-clear. `--json` output remains observation-only.
|
|
21
|
+
- Authored guidance in storm/swarm/workforce flows (planner refusals, unknown
|
|
22
|
+
options, persisted receipt issues) is no longer swallowed by the boundary.
|
|
23
|
+
- `npm run sync:architecture` works again — the script was restored to
|
|
24
|
+
`scripts/` where its relative paths are correct.
|
|
25
|
+
|
|
3
26
|
## 1.0.37 — 2026-08-10
|
|
4
27
|
|
|
5
28
|
Agentlas One can now carry its owner-bound identity and curated memory tickets
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const path = require("node:path");
|
|
6
6
|
const os = require("node:os");
|
|
7
7
|
const permissions = require("./agentlas-permissions.cjs");
|
|
8
|
-
const { truncateWidth, visWidth } = require("./
|
|
8
|
+
const { truncateWidth, visWidth } = require("./ui/width.cjs");
|
|
9
9
|
|
|
10
10
|
// AGENTLAS wordmark (block letters). Rendered with a brand gradient across columns.
|
|
11
11
|
const WORDMARK = [
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
const i18n = require("./agentlas-i18n.cjs");
|
|
7
7
|
const banner = require("./agentlas-banner.cjs");
|
|
8
|
-
const { visWidth, wrapWidth } = require("./
|
|
8
|
+
const { visWidth, wrapWidth } = require("./ui/width.cjs");
|
|
9
9
|
|
|
10
10
|
/*
|
|
11
11
|
* req = { ui, rl, helpers, persist } → Promise<{ onboarded, saved, saveError, lang, runtime, permission }>
|
|
@@ -184,7 +184,7 @@ async function runOnboard({ ui, rl, helpers, persist }) {
|
|
|
184
184
|
}
|
|
185
185
|
ui.line("");
|
|
186
186
|
if (saveError) {
|
|
187
|
-
ui.error(ui.t("wiz.saveFailed", String((saveError && saveError.message) || saveError)));
|
|
187
|
+
ui.error(ui.t("wiz.saveFailed", String((saveError && saveError.message) || saveError)), { reveal: true });
|
|
188
188
|
} else {
|
|
189
189
|
printSaved(ui.t("wiz.saved"));
|
|
190
190
|
printIndented(ui.t("wiz.changeLang"), c.faint);
|
package/engine/agentlas-ui.cjs
CHANGED
|
@@ -574,7 +574,7 @@ class Ui {
|
|
|
574
574
|
this.updateSpinner(msg);
|
|
575
575
|
}
|
|
576
576
|
_message(prefix, prefixPaint, messagePaint, msg) {
|
|
577
|
-
const { wrapWidth } = require("./
|
|
577
|
+
const { wrapWidth } = require("./ui/width.cjs");
|
|
578
578
|
const columns = Math.max(8, Number(this.out.columns) || 80);
|
|
579
579
|
const prefixWidth = visibleWidth(prefix);
|
|
580
580
|
const lines = wrapWidth(stripAnsi(String(msg ?? "")), Math.max(2, columns - prefixWidth));
|
|
@@ -594,13 +594,28 @@ class Ui {
|
|
|
594
594
|
this.stopSpinner();
|
|
595
595
|
this._message("! ", this.c.amber, this.c.text, msg);
|
|
596
596
|
}
|
|
597
|
-
error(msg) {
|
|
597
|
+
error(msg, opts = {}) {
|
|
598
598
|
this.stopSpinner();
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
599
|
+
/*
|
|
600
|
+
* 표시 경계 — 조건이 있어야 경계다 (2026-08-11, 존폐 판단 규칙 2).
|
|
601
|
+
* 무조건 삼키면 기계 코드·사용법·안내문까지 전부 같은 복구 한 줄이 되어
|
|
602
|
+
* 진단 자체가 불가능해진다(실사고: 죽은 명령 0인데 "안 돌아간다"로 보임).
|
|
603
|
+
* 통과 조건 — 저자가 의도한 메시지라는 기계 신호가 있을 때만:
|
|
604
|
+
* · Error 이고 code 또는 honestStop 이 있다 (usage/정직정지/서버 중계)
|
|
605
|
+
* · 호출자가 opts.reveal === true 로 명시했다 (storm/swarm 안내문 등)
|
|
606
|
+
* 신호 없는 문자열/Error 는 provider 날것으로 간주해 기존 계약대로 삼킨다
|
|
607
|
+
* (recovery-presentation-contract 가 이 삼킴을 계속 잠근다).
|
|
608
|
+
*/
|
|
609
|
+
const isErrorLike = msg instanceof Error || (msg !== null && typeof msg === "object" && "message" in msg);
|
|
610
|
+
const machineSignal = isErrorLike && Boolean(msg.code || msg.honestStop);
|
|
611
|
+
if (opts.reveal === true || machineSignal) {
|
|
612
|
+
const raw = String((isErrorLike ? msg.message : msg) ?? "").trim();
|
|
613
|
+
const text = redactCommandSecrets(stripAnsi(raw)).slice(0, 800);
|
|
614
|
+
if (text) {
|
|
615
|
+
this._message("✖ ", this.c.amber, this.c.text, text);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
604
619
|
this._message(
|
|
605
620
|
"◆ ",
|
|
606
621
|
this.c.amber,
|
|
@@ -1978,7 +1978,13 @@ function buildPrompts(task, identity) {
|
|
|
1978
1978
|
function create(deps = {}) {
|
|
1979
1979
|
const D = deps;
|
|
1980
1980
|
|
|
1981
|
+
/*
|
|
1982
|
+
* pi-tui 이행 정지작업 (D3 Phase 1-2, 2026-08-11): ctx가 준 Ui가 있으면 그것을
|
|
1983
|
+
* 쓴다. 자체 생성 Ui는 렌더러 교체 시 구 코드가 stdout에 직접 써 프레임을
|
|
1984
|
+
* 찢는 병렬 경로였다. 생성은 주입이 없을 때의 폴백으로만 남긴다.
|
|
1985
|
+
*/
|
|
1981
1986
|
function newUi(lang) {
|
|
1987
|
+
if (D.uiInstance) return D.uiInstance;
|
|
1982
1988
|
return new Ui({ lang: lang || (typeof D.prefsLang === "function" ? D.prefsLang() : "en") });
|
|
1983
1989
|
}
|
|
1984
1990
|
|
|
@@ -4549,10 +4555,10 @@ const TRANSIENT_MODEL_ERROR_RE = /Connection closed mid-response|"terminal_reaso
|
|
|
4549
4555
|
const detailText = otherDetails && (typeof otherDetails !== "object" || Object.keys(otherDetails).length)
|
|
4550
4556
|
? ` — ${JSON.stringify(otherDetails).slice(0, 1_200)}`
|
|
4551
4557
|
: "";
|
|
4552
|
-
ui.error(`${receipt.failure.code}: ${receipt.failure.message}${detailText}
|
|
4558
|
+
ui.error(`${receipt.failure.code}: ${receipt.failure.message}${detailText}`, { reveal: true });
|
|
4553
4559
|
if (issues) {
|
|
4554
|
-
for (const issue of issues.slice(0, 16)) ui.error(` - ${String(issue).slice(0, 400)}
|
|
4555
|
-
if (issues.length > 16) ui.error(` … ${issues.length - 16} more issues in the persisted receipt
|
|
4560
|
+
for (const issue of issues.slice(0, 16)) ui.error(` - ${String(issue).slice(0, 400)}`, { reveal: true });
|
|
4561
|
+
if (issues.length > 16) ui.error(` … ${issues.length - 16} more issues in the persisted receipt`, { reveal: true });
|
|
4556
4562
|
}
|
|
4557
4563
|
// 실패한 실행이야말로 토큰이 어디로 갔는지 알아야 하는 순간이다. issues 유무와
|
|
4558
4564
|
// 무관하게 낸다 — 첫 배선이 이 블록 안에 들어가는 바람에 issues 없는 실패에서는
|
|
@@ -24,7 +24,7 @@ function roleDetail(selection, role, en) {
|
|
|
24
24
|
].filter(Boolean).join(" · ");
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function run(ctx, args = []) {
|
|
27
|
+
async function run(ctx, args = []) {
|
|
28
28
|
const en = ctx.lang === "en";
|
|
29
29
|
// clig.dev: 스크립트 소비자를 위한 기계 계약. 사람용 줄과 같은 사실만 담는다.
|
|
30
30
|
if (ctx.output?.format === "json" || args.includes("--json")) {
|
|
@@ -124,14 +124,43 @@ function run(ctx, args = []) {
|
|
|
124
124
|
}
|
|
125
125
|
} catch { /* db issue already reported */ }
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
/*
|
|
128
|
+
* 3) 로그인 상태 — 자격 존재 + 실제 세션 검증.
|
|
129
|
+
* 파일 존재만 보고 all clear를 내면 만료 세션에서 편성이 죽는데 doctor는
|
|
130
|
+
* 초록불이었다(2026-08-11 존폐 판단 결함 5 — 사용자가 가장 먼저 칠 진단이
|
|
131
|
+
* 거짓말을 했다). 검증 실패는 종류를 가른다: 만료=경고(로그인 안내),
|
|
132
|
+
* 네트워크 불가=검증불가 표기(오프라인이 doctor를 적색으로 만들면 안 된다).
|
|
133
|
+
* --json 경로는 기존대로 관측만 한다(스크립트 소비자를 네트워크에 묶지 않는다).
|
|
134
|
+
*/
|
|
128
135
|
const sessionFile = path.join(userDataDir(), "auth", "cli-session.v1.json");
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
const auth = require("../cloud/auth.cjs");
|
|
137
|
+
const sessionCookie = auth.cloudSessionCookie();
|
|
138
|
+
const credentialLabel = process.env.AGENTLAS_SESSION
|
|
139
|
+
? "AGENTLAS_SESSION env"
|
|
140
|
+
: (fs.existsSync(sessionFile) ? sessionFile : null);
|
|
141
|
+
if (!sessionCookie || !credentialLabel) {
|
|
134
142
|
ctx.out(` ${ctx.ui.dim("·")} ${en ? "cloud session" : "클라우드 세션"}${ctx.ui.dim(en ? " — not signed in (agentlas login)" : " — 로그인 안 됨 (agentlas login)")}`);
|
|
143
|
+
} else {
|
|
144
|
+
try {
|
|
145
|
+
const meta = await auth.fetchSessionMeta(sessionCookie);
|
|
146
|
+
const who = meta && (meta.email || meta?.user?.email || meta?.account?.email || null);
|
|
147
|
+
ok(
|
|
148
|
+
en ? "cloud session" : "클라우드 세션",
|
|
149
|
+
`${credentialLabel}${who ? ` · ${who}` : ""} · ${en ? "verified" : "검증됨"}`,
|
|
150
|
+
);
|
|
151
|
+
} catch (sessionError) {
|
|
152
|
+
const status = /returned (\d{3})/.exec(String(sessionError?.message || ""))?.[1];
|
|
153
|
+
if (status === "401" || status === "403") {
|
|
154
|
+
warn(
|
|
155
|
+
en ? "cloud session" : "클라우드 세션",
|
|
156
|
+
en ? "expired — run `agentlas login`" : "만료됨 — `agentlas login`으로 다시 로그인",
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
ctx.out(` ${ctx.ui.dim("·")} ${en ? "cloud session" : "클라우드 세션"}${ctx.ui.dim(en
|
|
160
|
+
? ` — ${credentialLabel} · unverified (network unreachable)`
|
|
161
|
+
: ` — ${credentialLabel} · 검증 불가 (네트워크 연결 안 됨)`)}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
135
164
|
}
|
|
136
165
|
|
|
137
166
|
ctx.out("");
|
|
@@ -75,7 +75,7 @@ async function dispatch(ctx, command, args) {
|
|
|
75
75
|
permission,
|
|
76
76
|
`terminal-${command}`,
|
|
77
77
|
) || cwd;
|
|
78
|
-
const runtime = workforceRuntime({ lang: ctx.lang, out: ctx.out });
|
|
78
|
+
const runtime = workforceRuntime({ lang: ctx.lang, out: ctx.out, uiInstance: ctx.uiInstance });
|
|
79
79
|
const result = await runtime.cmdWorkforce(db, rest, runtimeOverride, {
|
|
80
80
|
cwd,
|
|
81
81
|
projectPath,
|
|
@@ -110,7 +110,7 @@ const RULES = [
|
|
|
110
110
|
"",
|
|
111
111
|
"Return ONLY compact JSON, one of these two shapes:",
|
|
112
112
|
' {"ask":[{"id":"<stable-id>","question":"...","why":"...","choices":["...","..."]}]}',
|
|
113
|
-
` {"blueprint":{"schema":"${BLUEPRINT_SCHEMA}","name":"...","goal":"...","trigger":{...},"steps":[...],"branches":[...]}}`,
|
|
113
|
+
` {"blueprint":{"schema":"${BLUEPRINT_SCHEMA}","name":"...","goal":"...","trigger":{...},"steps":[...],"branches":[...],"checks":[...]}}`,
|
|
114
114
|
"",
|
|
115
115
|
'trigger is either {"kind":"cron","schedule":"daily-08:00"} (24h, or a 5-field cron string)',
|
|
116
116
|
'or {"kind":"input","label":"<what to ask the person>","varName":"<one word, a-z>"}.',
|
|
@@ -226,8 +226,13 @@ function buildInterviewPrompt(state, locale = "ko") {
|
|
|
226
226
|
lines.push(
|
|
227
227
|
"",
|
|
228
228
|
"Your previous blueprint could NOT be built. Fix exactly these problems and return a",
|
|
229
|
-
"corrected blueprint.
|
|
230
|
-
"
|
|
229
|
+
"corrected blueprint. EVERY fix below is ADDITIVE: add the missing top-level checks[] entry",
|
|
230
|
+
"(each problem message gives you the exact entry to add). Keep EVERY step, the trigger, and",
|
|
231
|
+
"every produces/consumes exactly as they are — never delete, merge, or shrink a step to make",
|
|
232
|
+
"a problem disappear: that removes what the person asked for and just triggers a different",
|
|
233
|
+
"error. More steps and more checks is the right direction, never fewer. Do not repeat the",
|
|
234
|
+
"same mistake, and do not ask the person — these are format problems on your side,",
|
|
235
|
+
"not missing information:",
|
|
231
236
|
);
|
|
232
237
|
for (const a of attempts) for (const problem of a.problems) lines.push(` · ${problem}`);
|
|
233
238
|
}
|
|
@@ -250,6 +255,39 @@ function triggerQuestion() {
|
|
|
250
255
|
};
|
|
251
256
|
}
|
|
252
257
|
|
|
258
|
+
/**
|
|
259
|
+
* ★출력값 검증 check를 **코드가 채운다**(데스크탑 graph-blueprint.ts autofillOutputChecks와 동일).
|
|
260
|
+
* 바깥으로 나가는 단계가 소비하는 '앞에서 만든 값'에 check가 없으면, 검증기가 아는 그대로
|
|
261
|
+
* 표준 check를 넣어 **완전한 그래프를 완성**한다. 모델에 되물어 진동시키지 않고, 단계를 깎거나
|
|
262
|
+
* 캔버스로 떠넘기지도 않는다. 사람은 저장 확인 화면에서 항목을 보고 고칠 수 있다.
|
|
263
|
+
*/
|
|
264
|
+
function autofillOutputChecks(bp) {
|
|
265
|
+
if (!bp || !Array.isArray(bp.steps)) return bp;
|
|
266
|
+
const checks = Array.isArray(bp.checks) ? [...bp.checks] : [];
|
|
267
|
+
const checked = new Set(checks.map((c) => (c.subject || "").trim()).filter(Boolean));
|
|
268
|
+
bp.steps.forEach((step, index) => {
|
|
269
|
+
if (step.effect !== "mutation") return;
|
|
270
|
+
for (const value of Array.isArray(step.consumes) ? step.consumes : []) {
|
|
271
|
+
const name = String(value == null ? "" : value).trim();
|
|
272
|
+
if (!name || checked.has(name)) continue;
|
|
273
|
+
const madeAt = bp.steps.findIndex((s, i) => i < index && (s.produces || "").trim() === name);
|
|
274
|
+
if (madeAt < 0) continue;
|
|
275
|
+
checks.push({
|
|
276
|
+
afterStep: madeAt,
|
|
277
|
+
subject: name,
|
|
278
|
+
criteria: `${name}이(가) 비어있지 않고 요청대로 채워졌다`,
|
|
279
|
+
produces: `${name}_ok`,
|
|
280
|
+
items: [
|
|
281
|
+
{ text: `${name}이(가) 실제 내용으로 채워졌다`, kind: "must" },
|
|
282
|
+
{ text: "빈 값·자리표시자·지어낸 값이 아니다", kind: "mustNot" },
|
|
283
|
+
],
|
|
284
|
+
});
|
|
285
|
+
checked.add(name);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
return { ...bp, checks };
|
|
289
|
+
}
|
|
290
|
+
|
|
253
291
|
/** 청사진이 그래프로 지어질 수 있는지. 모자란 곳은 기본값이 아니라 질문으로 돌려준다. */
|
|
254
292
|
function validateBlueprint(bp, ctx = {}) {
|
|
255
293
|
const problems = [];
|
|
@@ -431,12 +469,15 @@ function validateBlueprint(bp, ctx = {}) {
|
|
|
431
469
|
for (const value of consumes) {
|
|
432
470
|
const name = String(value == null ? "" : value).trim();
|
|
433
471
|
if (!name || checkedSubjects.has(name)) continue;
|
|
434
|
-
const
|
|
435
|
-
if (
|
|
472
|
+
const madeAt = steps.findIndex((s, i) => i < index && (s.produces || "").trim() === name);
|
|
473
|
+
if (madeAt < 0) continue;
|
|
436
474
|
push(
|
|
437
475
|
`"${step.title || `${index + 1}번째 단계`}"는 바깥으로 나가는데, 그 앞에서 만든 `
|
|
438
|
-
+ `"${name}" 값이 쓸 만한지 확인하는 단계가 없습니다. `
|
|
439
|
-
+ `checks[]에
|
|
476
|
+
+ `"${name}" 값이 쓸 만한지 확인하는 단계가 없습니다. 단계는 하나도 지우지 말고, `
|
|
477
|
+
+ `top-level checks[]에 이 항목을 그대로 추가하세요: `
|
|
478
|
+
+ `{"afterStep":${madeAt},"subject":"${name}","criteria":"${name}이(가) 비어있지 않고 요청대로 채워졌다",`
|
|
479
|
+
+ `"produces":"${name}_ok","items":[{"text":"${name}이(가) 실제 내용으로 채워졌다","kind":"must"},`
|
|
480
|
+
+ `{"text":"빈 값·자리표시자·지어낸 값이 아니다","kind":"mustNot"}]}`,
|
|
440
481
|
);
|
|
441
482
|
}
|
|
442
483
|
});
|
|
@@ -924,7 +965,8 @@ function parseInterviewTurn(text, state) {
|
|
|
924
965
|
|
|
925
966
|
const blueprint = parsed.blueprint;
|
|
926
967
|
if (!blueprint || typeof blueprint !== "object") return unreadable(text);
|
|
927
|
-
|
|
968
|
+
// ★출력값 검증 check는 코드가 채운다 — 부탁받은 완전한 그래프를 완성한다(깎지도 떠넘기지도 않음).
|
|
969
|
+
const normalized = autofillOutputChecks({ ...blueprint, schema: BLUEPRINT_SCHEMA });
|
|
928
970
|
const problems = validateBlueprint(normalized);
|
|
929
971
|
if (problems.length === 0) {
|
|
930
972
|
// 검증은 통과했다. 그런데 **앞 시도보다 작아졌으면** 문제를 지워서 고친 것이다 —
|
|
@@ -949,12 +991,14 @@ function parseInterviewTurn(text, state) {
|
|
|
949
991
|
};
|
|
950
992
|
}
|
|
951
993
|
|
|
952
|
-
/** 모델이 스스로 고쳐 볼 기회의 상한. 데스크탑과 같은 값.
|
|
953
|
-
|
|
994
|
+
/** 모델이 스스로 고쳐 볼 기회의 상한. 데스크탑과 같은 값.
|
|
995
|
+
* 2→4: 출력검증 문제는 사람에게 못 묻고 모델 단독 교정만 가능한데, 이제 메시지가 정확한
|
|
996
|
+
* checks[] 항목을 그대로 주므로(추가만 하면 됨) 몇 번 더 주면 대개 수렴한다. */
|
|
997
|
+
const MAX_SELF_CORRECTIONS = 4;
|
|
954
998
|
|
|
955
999
|
module.exports = {
|
|
956
1000
|
BLUEPRINT_SCHEMA, MAX_QUESTIONS_PER_TURN, MAX_INTERVIEW_ROUNDS, MAX_REPEATS,
|
|
957
1001
|
startInterview, recordAnswers, buildInterviewPrompt, parseInterviewTurn, humanSchedule,
|
|
958
1002
|
MAX_SELF_CORRECTIONS, weakenedAgainstLastAttempt,
|
|
959
|
-
validateBlueprint, buildGraphFromBlueprint, branchLabel, describeBranches,
|
|
1003
|
+
validateBlueprint, autofillOutputChecks, buildGraphFromBlueprint, branchLabel, describeBranches,
|
|
960
1004
|
};
|
|
@@ -27,7 +27,7 @@ const path = require("node:path");
|
|
|
27
27
|
const fs = require("node:fs");
|
|
28
28
|
const { spawn } = require("node:child_process");
|
|
29
29
|
const { Ui } = require("../agentlas-ui.cjs");
|
|
30
|
-
const { truncateWidth, visWidth, wrapWidth } = require("../
|
|
30
|
+
const { truncateWidth, visWidth, wrapWidth } = require("../ui/width.cjs");
|
|
31
31
|
const coreHarness = require("../agentlas-core-harness.cjs");
|
|
32
32
|
const { userDataDir } = require("../core/paths.cjs");
|
|
33
33
|
|
|
@@ -194,7 +194,13 @@ function create(ctx, deps = {}) {
|
|
|
194
194
|
const spawnCoreModule = deps.spawnCoreModule || coreHarness.spawnCoreModule;
|
|
195
195
|
const lang = () => (ctx && ctx.lang) || "en";
|
|
196
196
|
|
|
197
|
+
/*
|
|
198
|
+
* pi-tui 이행 정지작업 (D3 Phase 1-2, 2026-08-11): ctx가 준 Ui가 있으면 그것을
|
|
199
|
+
* 쓴다. 자체 생성 Ui는 렌더러 교체 시 구 코드가 stdout에 직접 써 프레임을
|
|
200
|
+
* 찢는 병렬 경로였다. 생성은 주입이 없을 때의 폴백으로만 남긴다.
|
|
201
|
+
*/
|
|
197
202
|
function newUi(uiLang) {
|
|
203
|
+
if (ctx && ctx.uiInstance) return ctx.uiInstance;
|
|
198
204
|
return new Ui({ lang: uiLang || lang() });
|
|
199
205
|
}
|
|
200
206
|
|
package/engine/storm/deps.cjs
CHANGED
|
@@ -73,6 +73,8 @@ function modelRoutingReceiptPath() {
|
|
|
73
73
|
function buildStormDeps(ctx = {}) {
|
|
74
74
|
return {
|
|
75
75
|
prefsLang: () => ctx.lang || "en",
|
|
76
|
+
// Phase 1-2: 주입 Ui 관통 (자체 생성 방지)
|
|
77
|
+
uiInstance: ctx.uiInstance || null,
|
|
76
78
|
out: typeof ctx.out === "function" ? ctx.out : (s) => process.stdout.write(`${s}\n`),
|
|
77
79
|
resolveRuntime: resolveWorkforceRuntime,
|
|
78
80
|
listAvailableRuntimes,
|
package/engine/storm/storm.cjs
CHANGED
|
@@ -36,7 +36,13 @@ function create(deps) {
|
|
|
36
36
|
const D = deps;
|
|
37
37
|
const { swarmRun } = require("./swarm.cjs").create(D);
|
|
38
38
|
|
|
39
|
+
/*
|
|
40
|
+
* pi-tui 이행 정지작업 (D3 Phase 1-2, 2026-08-11): ctx가 준 Ui가 있으면 그것을
|
|
41
|
+
* 쓴다. 자체 생성 Ui는 렌더러 교체 시 구 코드가 stdout에 직접 써 프레임을
|
|
42
|
+
* 찢는 병렬 경로였다. 생성은 주입이 없을 때의 폴백으로만 남긴다.
|
|
43
|
+
*/
|
|
39
44
|
function newUi(lang) {
|
|
45
|
+
if (D.uiInstance) return D.uiInstance;
|
|
40
46
|
return new Ui({ lang: lang || D.prefsLang() });
|
|
41
47
|
}
|
|
42
48
|
|
|
@@ -78,7 +84,7 @@ function create(deps) {
|
|
|
78
84
|
return { ok: false };
|
|
79
85
|
}
|
|
80
86
|
if (goal.startsWith("-")) {
|
|
81
|
-
ui.error("goal cannot start with '-'.");
|
|
87
|
+
ui.error("goal cannot start with '-'.", { reveal: true });
|
|
82
88
|
return { ok: false };
|
|
83
89
|
}
|
|
84
90
|
const cwd = ctx.cwd || (typeof D.projectCwd === "function" ? D.projectCwd() : D.runCwd());
|
|
@@ -86,7 +92,7 @@ function create(deps) {
|
|
|
86
92
|
try {
|
|
87
93
|
executionHarness = await loadCoreStormbreakerHarness(cwd);
|
|
88
94
|
} catch (error) {
|
|
89
|
-
ui.error(`Stormbreaker Core harness unavailable: ${String((error && error.message) || error).slice(0, 400)}
|
|
95
|
+
ui.error(`Stormbreaker Core harness unavailable: ${String((error && error.message) || error).slice(0, 400)}`, { reveal: true });
|
|
90
96
|
return { ok: false, error: "stormbreaker-core-harness-unavailable" };
|
|
91
97
|
}
|
|
92
98
|
const args = ["route", goal, "--project", cwd, "--runtime", "terminal"];
|
package/engine/storm/swarm.cjs
CHANGED
|
@@ -127,7 +127,13 @@ function parseSwarmOutput(text) {
|
|
|
127
127
|
function create(deps) {
|
|
128
128
|
const D = deps;
|
|
129
129
|
|
|
130
|
+
/*
|
|
131
|
+
* pi-tui 이행 정지작업 (D3 Phase 1-2, 2026-08-11): ctx가 준 Ui가 있으면 그것을
|
|
132
|
+
* 쓴다. 자체 생성 Ui는 렌더러 교체 시 구 코드가 stdout에 직접 써 프레임을
|
|
133
|
+
* 찢는 병렬 경로였다. 생성은 주입이 없을 때의 폴백으로만 남긴다.
|
|
134
|
+
*/
|
|
130
135
|
function newUi(lang) {
|
|
136
|
+
if (D.uiInstance) return D.uiInstance;
|
|
131
137
|
return new Ui({ lang: lang || D.prefsLang() });
|
|
132
138
|
}
|
|
133
139
|
|
|
@@ -146,7 +152,7 @@ function create(deps) {
|
|
|
146
152
|
// Core 하네스 없이 stormbreaker 모드 진입 금지 — 로컬 모조 프롬프트로 대체하는
|
|
147
153
|
// 것은 계약 위반이다(모델 실행 전에 실패해야 한다).
|
|
148
154
|
if (stormbreaker && (!executionHarness || typeof executionHarness.system_prompt !== "string")) {
|
|
149
|
-
ui.error("Stormbreaker requires the canonical Goal + UltraCode harness from Agentlas Core.");
|
|
155
|
+
ui.error("Stormbreaker requires the canonical Goal + UltraCode harness from Agentlas Core.", { reveal: true });
|
|
150
156
|
return { ok: false, error: "stormbreaker-core-harness-unavailable" };
|
|
151
157
|
}
|
|
152
158
|
const coreHarnessPrompt = executionHarness && executionHarness.system_prompt;
|
|
@@ -307,7 +313,7 @@ function create(deps) {
|
|
|
307
313
|
if (!planned) {
|
|
308
314
|
ui.error(ui.lang === "ko"
|
|
309
315
|
? "플래너가 유효한 실행 계획(JSON)을 내지 못했습니다 — 정지합니다 (조용한 폴백 금지)."
|
|
310
|
-
: "The planner did not produce a valid plan JSON — stopping (no silent fallback).");
|
|
316
|
+
: "The planner did not produce a valid plan JSON — stopping (no silent fallback).", { reveal: true });
|
|
311
317
|
return { ok: false, reason: "invalid_plan_json" };
|
|
312
318
|
}
|
|
313
319
|
if (planned) {
|
|
@@ -376,7 +382,7 @@ function create(deps) {
|
|
|
376
382
|
ui.line("");
|
|
377
383
|
ui.info(`tasks: ${tasks.length} · done: ${done.length} · failed: ${failed}`);
|
|
378
384
|
if (!done.length) {
|
|
379
|
-
ui.error(ui.lang === "ko" ? "스웜이 완료한 작업이 없습니다." : "The swarm completed no work.");
|
|
385
|
+
ui.error(ui.lang === "ko" ? "스웜이 완료한 작업이 없습니다." : "The swarm completed no work.", { reveal: true });
|
|
380
386
|
return { ok: false };
|
|
381
387
|
}
|
|
382
388
|
|
|
@@ -411,7 +417,7 @@ function create(deps) {
|
|
|
411
417
|
);
|
|
412
418
|
} catch (e) {
|
|
413
419
|
ui.stopSpinner();
|
|
414
|
-
ui.error("Synthesis failed: " + String((e && e.message) || e).slice(0, 200));
|
|
420
|
+
ui.error("Synthesis failed: " + String((e && e.message) || e).slice(0, 200), { reveal: true });
|
|
415
421
|
finalText = pieces;
|
|
416
422
|
}
|
|
417
423
|
ui.stopSpinner();
|
|
@@ -435,7 +441,7 @@ function create(deps) {
|
|
|
435
441
|
const strayFlag = rest.find((token) => String(token).startsWith("-"));
|
|
436
442
|
if (strayFlag) {
|
|
437
443
|
const ui = executionContext.ui || newUi();
|
|
438
|
-
ui.error(`unknown option ${strayFlag} — swarm accepts: --parallel N | -n N, --runtime <kind
|
|
444
|
+
ui.error(`unknown option ${strayFlag} — swarm accepts: --parallel N | -n N, --runtime <kind>`, { reveal: true });
|
|
439
445
|
process.exitCode = 1;
|
|
440
446
|
return { ok: false, error: "unknown-option" };
|
|
441
447
|
}
|