agentlas 1.0.21 → 1.0.23
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 +20 -0
- package/README.md +9 -26
- package/engine/agentlas-banner.cjs +0 -1
- package/engine/agentlas-capabilities.cjs +8 -71
- package/engine/agentlas-core-harness.cjs +18 -11
- package/engine/agentlas-experience-exchange.cjs +11 -75
- package/engine/agentlas-i18n.cjs +21 -21
- package/engine/agentlas-judgment.cjs +0 -0
- package/engine/agentlas-native-host.cjs +4 -31
- package/engine/agentlas-ui.cjs +13 -1
- package/engine/agentlas-workload-routing.cjs +11 -24
- package/engine/agentlas.cjs +3 -13
- package/engine/agents/router.cjs +0 -1
- package/engine/architecture.data.json +5 -5
- package/engine/bootstrap-schema.sql +3 -3
- package/engine/commands/doctor.cjs +3 -3
- package/engine/commands/help.cjs +4 -7
- package/engine/commands/index.cjs +1 -6
- package/engine/experience/runtime.cjs +1 -0
- package/engine/sessions/orchestrator.cjs +4 -21
- package/engine/sessions/session.cjs +66 -5
- package/engine/sessions/sink.cjs +13 -3
- package/engine/sessions/store.cjs +2 -2
- package/engine/ui/palette.cjs +3 -12
- package/engine/ui/renderer.cjs +3 -6
- package/engine/ui/repl.cjs +41 -93
- package/engine/workforce/deps.cjs +9 -6
- package/package.json +4 -3
- package/engine/agentlas-doctor.cjs +0 -224
- package/engine/commands/chat.cjs +0 -12
- package/engine/commands/chats.cjs +0 -33
- package/engine/commands/open.cjs +0 -52
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.23 — 2026-08-01
|
|
4
|
+
|
|
5
|
+
- **Semantic routing stays with the connected model.** Image-capability and
|
|
6
|
+
installed-agent routing no longer retain regex hints, keyword glossaries,
|
|
7
|
+
deterministic role vetoes, or caller-authored fallback labels. If the model
|
|
8
|
+
cannot return a valid judgment, the decision remains unavailable.
|
|
9
|
+
- **Fresh Terminal databases match Desktop schema 86.** The retired
|
|
10
|
+
chat-level hired-agent roster is absent from the bootstrap schema; project
|
|
11
|
+
order and current-turn task-force targets own controller and helper binding.
|
|
12
|
+
|
|
13
|
+
## 1.0.22 — 2026-08-01
|
|
14
|
+
|
|
15
|
+
- **Task classes no longer come from a fixed keyword dictionary.** An explicit
|
|
16
|
+
user/project declaration remains authoritative; otherwise only the connected
|
|
17
|
+
model may classify the full request, and an unavailable or invalid judgment
|
|
18
|
+
stays unresolved.
|
|
19
|
+
- Preserve an explicit `declaredTaskClasses` value across the Desktop loadout
|
|
20
|
+
boundary so removing the dictionary fallback does not discard user-owned
|
|
21
|
+
structured intent.
|
|
22
|
+
|
|
3
23
|
## 1.0.21 — 2026-08-01
|
|
4
24
|
|
|
5
25
|
- Align the independent Terminal sentence with the exact cross-surface
|
package/README.md
CHANGED
|
@@ -54,33 +54,21 @@ agentlas
|
|
|
54
54
|
agentlas "Identify why repository tests are failing" # Uses the connected project's first agent as controller
|
|
55
55
|
agentlas run -p "Summarize the last 3 CHANGELOG entries" # Prints the project controller's final answer to stdout
|
|
56
56
|
git log --oneline -20 | agentlas run # Reads stdin; still requires a connected project
|
|
57
|
-
agentlas run <agent> "Advanced direct request" #
|
|
58
|
-
agentlas <agent> # Starts an interactive REPL with an exact agent
|
|
57
|
+
agentlas run <agent> "Advanced direct request" # Explicit one-turn exact-agent override
|
|
59
58
|
agentlas firm <firm> "Request" # Delegates to an exact firm CEO
|
|
60
59
|
```
|
|
61
60
|
|
|
62
61
|
Ordinary one-shot work is project-first: run it inside a folder connected to a Desktop Work project. The first agent in that project's ordered pool owns the task; remaining members are eligible task-scoped sub-agents. Missing projects, empty teams, and unavailable controllers stop without substituting another agent. The separate `agentlas route` preview is model-judged and stays unresolved when no exact installed agent can be justified.
|
|
63
62
|
|
|
64
|
-
**3. Resuming & Managing Conversations**
|
|
65
|
-
|
|
66
|
-
```sh
|
|
67
|
-
agentlas chats # List recent conversations (shares SQLite DB with Desktop app)
|
|
68
|
-
agentlas chats 30 # List last 30 conversations
|
|
69
|
-
agentlas open <chat-id> # Re-open REPL with the chat's assigned agent
|
|
70
|
-
```
|
|
71
|
-
|
|
72
63
|
## Command Reference
|
|
73
64
|
|
|
74
65
|
Run `agentlas help` for the authoritative command list. Below is the organized command structure:
|
|
75
66
|
|
|
76
|
-
###
|
|
67
|
+
### PROJECT WORK
|
|
77
68
|
```sh
|
|
78
|
-
agentlas
|
|
79
|
-
agentlas
|
|
80
|
-
agentlas run [agent] [prompt] # Single-run execution (-p, --runtime, --permission, stdin)
|
|
69
|
+
agentlas # Open the connected project's controller session
|
|
70
|
+
agentlas run [agent] [prompt] # Project-first run; optional agent is one explicit turn
|
|
81
71
|
agentlas firm <firm> [task] # Delegate task to firm CEO
|
|
82
|
-
agentlas chats [n] # List recent chat conversations
|
|
83
|
-
agentlas open <chat-id> # Resume conversation by ID
|
|
84
72
|
agentlas cd <agent> # Print folder path of an agent (cd "$(agentlas cd x)")
|
|
85
73
|
```
|
|
86
74
|
|
|
@@ -164,7 +152,7 @@ $ agentlas lst
|
|
|
164
152
|
Command list: agentlas help · To run this exact prompt: agentlas run -p "lst"
|
|
165
153
|
```
|
|
166
154
|
|
|
167
|
-
Desktop-only surface names (`site`, `trex`, `prompts`, `dashboard`, `marketplace`, `library`, `
|
|
155
|
+
Desktop-only surface names (`site`, `trex`, `prompts`, `dashboard`, `marketplace`, `library`, `settings`, `apps`, `quests`, `bookmarks`, `one`) also halt with typo guard guidance directing you to equivalent terminal commands. To run a single word as a prompt, wrap it in quotes or pass `run -p`.
|
|
168
156
|
|
|
169
157
|
### Permission Levels
|
|
170
158
|
|
|
@@ -176,9 +164,9 @@ Desktop-only surface names (`site`, `trex`, `prompts`, `dashboard`, `marketplace
|
|
|
176
164
|
|
|
177
165
|
*Note: Saved `full` permission is session-bound and automatically fails closed to `write` on subsequent executions. REPL shell execution (`!<command>`) runs strictly under `full` permission, enforced with 8MB output capping, secret masking, and process-group termination.*
|
|
178
166
|
|
|
179
|
-
##
|
|
167
|
+
## Project Work Execution Tree
|
|
180
168
|
|
|
181
|
-
Executing `agentlas` with no arguments
|
|
169
|
+
Executing `agentlas` with no arguments resolves the current folder to an Agentlas project and starts its first ordered agent as the task controller. The controller may create task-scoped worker runs; users can inspect the real execution tree, but cannot replace the controller or address workers around it.
|
|
182
170
|
|
|
183
171
|
### REPL Slash Commands
|
|
184
172
|
|
|
@@ -187,14 +175,9 @@ Executing `agentlas` with no arguments starts an interactive REPL session. Foreg
|
|
|
187
175
|
| `/help` | Display available commands and keybindings |
|
|
188
176
|
| `/sessions` · `/tree` | View session table or parent-child process tree |
|
|
189
177
|
| `/s <n>` · `/switch <n>` | Switch active session (replays tail logs & subscribes to live stream) |
|
|
190
|
-
| `/spawn <agent> [task]` | Spawn subagent background session (executes task immediately if supplied) |
|
|
191
|
-
| `/steer <n> <msg>` | Queue instruction for session `n`'s next turn |
|
|
192
178
|
| `/kill <n>` | Interrupt active turn in session `n` |
|
|
193
179
|
| `/rm <n>` | Remove session `n` |
|
|
194
|
-
| `/broadcast <msg>` | Broadcast instruction to all running sessions |
|
|
195
|
-
| `/use <agent>` | Change active agent in main session |
|
|
196
180
|
| `/agents` · `/list` | List installed agents |
|
|
197
|
-
| `/chats [n]` | View recent conversations |
|
|
198
181
|
| `/mcp` | List active MCP servers |
|
|
199
182
|
| `/doctor` | Run runtime & database diagnostics |
|
|
200
183
|
| `/runtime <kind>` | Set runtime for new sessions (`claude-code` \| `codex` \| `gemini`) |
|
|
@@ -242,9 +225,9 @@ The launcher (`bin/agentlas.cjs`) runs system Node against `engine/`. The defaul
|
|
|
242
225
|
| Windows | `%APPDATA%\Agentlas` |
|
|
243
226
|
| Linux | `$XDG_CONFIG_HOME/Agentlas` (default `~/.config/Agentlas`) |
|
|
244
227
|
|
|
245
|
-
The SQLite database file is `agentlas.sqlite` (`user_version=
|
|
228
|
+
The SQLite database file is `agentlas.sqlite` (`user_version=86`). When launched for the first time without an existing database, it bootstraps schemas using `engine/bootstrap-schema.sql`. Consequently, **projects, installed agents, task history, automation sessions, and MCP registrations are shared across Desktop and Terminal**. The first ordered project agent remains the controller; additional agents are task-scoped and are stored only as execution ledgers, never as global conversations or durable owners.
|
|
246
229
|
|
|
247
|
-
SQLite
|
|
230
|
+
SQLite driver priority: `better-sqlite3` (optional dependency native build), then Node 22+ `node:sqlite` when the first driver is unavailable.
|
|
248
231
|
|
|
249
232
|
### Hub Installation & Safety Policy
|
|
250
233
|
|
|
@@ -195,7 +195,6 @@ function renderBanner(ctx) {
|
|
|
195
195
|
const menu = [
|
|
196
196
|
[ui.t("banner.menu.help"), "/help"],
|
|
197
197
|
[ui.t("banner.menu.sessions"), "/sessions"],
|
|
198
|
-
[ui.t("banner.menu.chats"), "/chats"],
|
|
199
198
|
[ui.t("banner.menu.quit"), "/quit"],
|
|
200
199
|
];
|
|
201
200
|
const infoRoom = inner - CARD_PAD * 2;
|
|
@@ -40,58 +40,9 @@ function runtimeFromSpec(spec) {
|
|
|
40
40
|
return CLI_KINDS.includes(spec) ? { mode: "cli", kind: spec } : { mode: "api", backend: spec, model: null };
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
/image/i, /이미지/, /그림(?!자)/, /\bdesign\b/i, /디자인/, /쇼핑몰/, /상품\s*(사진|이미지|상세)/, /상세\s*페이지/,
|
|
47
|
-
/thumbnail/i, /썸네일/, /banner/i, /배너/, /poster/i, /포스터/, /visual/i, /비주얼/, /illustrat/i, /일러스트/,
|
|
48
|
-
/로고/, /\blogo\b/i, /사진/, /photo/i, /nano-?banana/i, /imagen/i, /이미지\s*생성/, /그래픽/, /graphic/i,
|
|
49
|
-
];
|
|
50
|
-
// 빌더/메타/조율/거버넌스 역할은 (이미지 에이전트를 *만들* 수는 있어도) 스스로 이미지를 생산하지 않는다.
|
|
51
|
-
// 이런 역할이 system_prompt에 "이미지/디자인"을 언급한다는 이유로 gemini로 끌려가면 코드/빌드 품질이 떨어진다.
|
|
52
|
-
const NON_IMAGE_ROLES = new Set(["meta", "builder", "orchestrator", "pm", "curator", "governance"]);
|
|
53
|
-
// 부정/거절 문장("이미지 생성 금지", "영상·이미지 생성은 하지 않는다")은 능력이 아니라
|
|
54
|
-
// 반(反)능력 선언이다 — 그 문장 안의 힌트는 세지 않는다. 단, "묻지 않고 바로 생성한다"처럼
|
|
55
|
-
// 긍정문에 흔한 보조 부정("않고","없다" 단독)은 잡지 않도록 강한 금지 구문만 매칭한다.
|
|
56
|
-
const IMAGE_NEGATION_RE = /(금지|하지\s*않|하지\s*마|말\s*것|거절|불가(?!피)|아니다|refuse|\bnever\b|\bdo(es)?\s+not\b|\bdon'?t\b)/i;
|
|
57
|
-
// 이미지 생성 도구 이름은 단독으로도 이미지 생산 역할의 강한 증거다.
|
|
58
|
-
const IMAGE_TOOL_MARKERS = [/nano-?banana/i, /\bimagen\b/i, /gpt-image/i, /grok\s*imagine/i];
|
|
59
|
-
// 사고(2026-07-12): appbridge CEO 프롬프트의 "코드/디자인/스토어 결정의 owner가 아니다" 속
|
|
60
|
-
// "디자인" 한 단어로 이미지 에이전트 판정 → PPT 요청 세션이 통째로 gemini로 전환됐다.
|
|
61
|
-
// 수리: 정체성 존(이름/태그라인)은 그대로 신뢰하되, 본문 단독으로는 "힌트를 포함한 긍정문
|
|
62
|
-
// 3문장 이상"을 요구한다. 문장 단위로 세므로 "상품 이미지 생성 금지" 같은 한 문장이
|
|
63
|
-
// 겹치는 정규식 여러 개를 동시에 때려도 1클러스터다.
|
|
64
|
-
const MIN_BODY_IMAGE_SENTENCES = 3;
|
|
65
|
-
const BODY_SCAN_CAP = 16000; // 로컬 임포트 상한과 동일 — 클라우드 무제한 프롬프트의 전문 스캔 방지
|
|
66
|
-
// 어휘 스코어 — IMAGE_HINTS 단어목록에 대한 참고용 스코어러. 하우스 룰(2026-07-25):
|
|
67
|
-
// 단어목록은 최종 결정을 내리지 않는다. resolveNeedsImage가 IMAGE_HINTS를 힌트로만 모델에
|
|
68
|
-
// 넘기고, 연결 모델이 없으면 이미지 여부를 판정하지 않는다(어휘 폴백 제거). 이 함수는 힌트
|
|
69
|
-
// 품질을 고정하는 참조 스코어러로만 남는다(회귀 테스트가 정밀도를 검증).
|
|
70
|
-
function needsImageLexical(agent) {
|
|
71
|
-
if (!agent) return false;
|
|
72
|
-
if (NON_IMAGE_ROLES.has(String(agent.role || "").toLowerCase())) return false;
|
|
73
|
-
// 정체성 존은 사용자가 선언한 이름/태그라인만 — slug는 폴더명에서 기계 파생되므로
|
|
74
|
-
// ("design-system" 리포 임포트 등) 단독 신뢰 대상이 아니다.
|
|
75
|
-
const identity = [agent.name, agent.name_en, agent.tagline, agent.tagline_en].filter(Boolean).join(" ");
|
|
76
|
-
if (IMAGE_HINTS.some((re) => re.test(identity))) return true;
|
|
77
|
-
// 팀 CEO 두뇌(부서 소개·위임 규칙)에서는 body 키워드가 역할 증거가 아니다 — vibecoder처럼
|
|
78
|
-
// "Design and Publishing HQ" 부서명이 10문장씩 나오는 조율용 프롬프트가 이미지 팀으로
|
|
79
|
-
// 오판되던 사례. entity_kind='team'은 사용자가 선언한 정체성 존만 신뢰한다.
|
|
80
|
-
if (String(agent.entity_kind || "").toLowerCase() === "team") return false;
|
|
81
|
-
const body = String(agent.system_prompt || "").slice(0, BODY_SCAN_CAP);
|
|
82
|
-
let clusters = 0;
|
|
83
|
-
for (const sentence of body.split(/[\n.!?。!?]+/)) {
|
|
84
|
-
if (!sentence || IMAGE_NEGATION_RE.test(sentence)) continue;
|
|
85
|
-
if (IMAGE_TOOL_MARKERS.some((re) => re.test(sentence))) return true;
|
|
86
|
-
if (IMAGE_HINTS.some((re) => re.test(sentence))) {
|
|
87
|
-
clusters += 1;
|
|
88
|
-
if (clusters >= MIN_BODY_IMAGE_SENTENCES) return true;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ── 상주 판정 서비스 배선 — 모델이 의미로 최종 판정, IMAGE_HINTS는 참고 힌트 ──────
|
|
43
|
+
// Image capability is judged only by the connected model from the complete
|
|
44
|
+
// agent identity and instructions. No regex, keyword glossary, role veto, or
|
|
45
|
+
// default runtime may make this semantic decision.
|
|
95
46
|
// needsImage 호출자(REPL 배지·autoRuntimeFor·routingNote)는 동기라서 warm-cache 패턴:
|
|
96
47
|
// 비동기 경로(resolveNeedsImage)가 먼저 판정해 캐시를 데우고, 동기 needsImage는 캐시만
|
|
97
48
|
// 읽는다. 캐시 미스 = "이미지 아님"으로 처리(어휘 폴백 없음) — 모델 판정만 이미지로 인정한다.
|
|
@@ -101,22 +52,13 @@ function imageJudgeInput(agent) {
|
|
|
101
52
|
const identity = [agent.slug, agent.name, agent.name_en, agent.tagline, agent.tagline_en].filter(Boolean).join(" | ");
|
|
102
53
|
return `${identity}\n---\n${String(agent.system_prompt || "").slice(0, 6000)}`;
|
|
103
54
|
}
|
|
104
|
-
// 역할/팀 베토는 보수적 하드 가드로 유지 — 조율 두뇌가 부서 소개 문장("Design HQ")으로
|
|
105
|
-
// 이미지 팀이 되던 사고(vibecoder/appbridge)는 모델 판정 대상에서 아예 뺀다.
|
|
106
|
-
function imageJudgeVetoed(agent) {
|
|
107
|
-
if (!agent) return true;
|
|
108
|
-
if (NON_IMAGE_ROLES.has(String(agent.role || "").toLowerCase())) return true;
|
|
109
|
-
if (String(agent.entity_kind || "").toLowerCase() === "team") return true;
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
55
|
// 이 에이전트의 직무가 이미지 생산인지를 연결 모델이 의미로 판정한다.
|
|
113
56
|
// 하우스 룰(2026-07-25): 연결 모델이 없으면 단어목록으로 이미지 여부를 결정하지 않는다.
|
|
114
57
|
// 러너 없음/타임아웃/정크 → source:"unavailable"(판정 불가)로 반환하고, 호출자는 안전
|
|
115
58
|
// 기본값(세션 런타임 유지, gemini/codex 하이재킹 금지)을 지킨다. 이미지 판정은 저위험
|
|
116
59
|
// 능력 추론이라 실패-닫힘이 아니라 "판정 안 함"으로 정직하게 둔다.
|
|
117
60
|
async function resolveNeedsImage(agent) {
|
|
118
|
-
|
|
119
|
-
if (imageJudgeVetoed(agent)) return { image: false, source: "deterministic" };
|
|
61
|
+
if (!agent) return { image: false, source: "unavailable", decided: false };
|
|
120
62
|
let judgment;
|
|
121
63
|
try {
|
|
122
64
|
judgment = require("./agentlas-judgment.cjs");
|
|
@@ -134,13 +76,11 @@ async function resolveNeedsImage(agent) {
|
|
|
134
76
|
labels: ["image", "not-image"],
|
|
135
77
|
multi: false,
|
|
136
78
|
input,
|
|
137
|
-
hints: { image: IMAGE_HINTS.map((re) => re.source) },
|
|
138
79
|
guidance:
|
|
139
80
|
"Judge the agent's role from its identity and instructions, in any language. Mentioning images is not " +
|
|
140
81
|
"producing them: builders, orchestrators, PMs, curators, and coordination brains that commission or " +
|
|
141
82
|
"delegate image work are 'not-image'. Refusals or prohibitions ('never generate images') declare the " +
|
|
142
83
|
"opposite of a capability.",
|
|
143
|
-
fallback: [],
|
|
144
84
|
});
|
|
145
85
|
// 모델이 판정을 못 냈다 → 어휘 폴백 없이 "판정 불가". 캐시하지 않아 이후 모델 연결 시 재판정한다.
|
|
146
86
|
if (verdict.source !== "llm" || !verdict.labels.length) return { image: false, source: "unavailable", decided: false };
|
|
@@ -155,18 +95,16 @@ async function resolveNeedsImage(agent) {
|
|
|
155
95
|
// Does this agent's job involve generating/handling images? Sync surface for badges and
|
|
156
96
|
// autoRuntimeFor. House rule (2026-07-25): only a warm MODEL verdict counts as "image".
|
|
157
97
|
// A cache miss (no model judgment / not warmed) is treated as "not specifically an image
|
|
158
|
-
// agent" so
|
|
159
|
-
// scorer (needsImageLexical) survives only as the hint source for the judge, not a decision.
|
|
98
|
+
// agent" so an unproven semantic guess can never hijack the runtime to gemini/codex.
|
|
160
99
|
function needsImage(agent) {
|
|
161
100
|
if (!agent) return false;
|
|
162
|
-
if (imageJudgeVetoed(agent)) return false;
|
|
163
101
|
const cached = imageVerdicts.get(imageJudgeInput(agent));
|
|
164
102
|
return cached ? cached.image : false;
|
|
165
103
|
}
|
|
166
|
-
// 라벨용 —
|
|
104
|
+
// 라벨용 — 모델 판정이 없으면 unavailable이며 다른 의미 판정으로 대체하지 않는다.
|
|
167
105
|
function imageJudgmentSource(agent) {
|
|
168
|
-
if (agent &&
|
|
169
|
-
return "
|
|
106
|
+
if (agent && imageVerdicts.has(imageJudgeInput(agent))) return "llm";
|
|
107
|
+
return "unavailable";
|
|
170
108
|
}
|
|
171
109
|
function clearImageJudgments() {
|
|
172
110
|
imageVerdicts.clear();
|
|
@@ -195,7 +133,6 @@ module.exports = {
|
|
|
195
133
|
specOf,
|
|
196
134
|
runtimeFromSpec,
|
|
197
135
|
needsImage,
|
|
198
|
-
needsImageLexical,
|
|
199
136
|
resolveNeedsImage,
|
|
200
137
|
imageJudgmentSource,
|
|
201
138
|
clearImageJudgments,
|
|
@@ -40,6 +40,8 @@ function runtimeRoots(explicitRoot) {
|
|
|
40
40
|
binRoot,
|
|
41
41
|
path.join(os.homedir(), ".agentlas", "runtime", "current"),
|
|
42
42
|
];
|
|
43
|
+
// Monorepo/development parity with Desktop's signed-bundle candidate.
|
|
44
|
+
roots.push(path.resolve(__dirname, "..", "..", "agentlas_desktop", "Hephaestus"));
|
|
43
45
|
if (process.resourcesPath) roots.push(path.join(process.resourcesPath, "Hephaestus"));
|
|
44
46
|
if (process.platform === "darwin") roots.push("/Applications/Agentlas.app/Contents/Resources/Hephaestus");
|
|
45
47
|
return unique(roots);
|
|
@@ -77,13 +79,12 @@ function resolveCoreRuntimeRootFromCandidates(candidateRoots, requiredMarkers =
|
|
|
77
79
|
const markers = [...CORE_RUNTIME_MARKERS, ...requiredMarkers];
|
|
78
80
|
const minVersion = options.minVersion ? normalizeSemVer(String(options.minVersion)) : null;
|
|
79
81
|
if (options.minVersion && !minVersion) throw new Error(`Invalid minimum Core version: ${options.minVersion}`);
|
|
82
|
+
const eligible = [];
|
|
80
83
|
for (const root of unique(candidateRoots)) {
|
|
81
84
|
try {
|
|
82
85
|
if (!markers.every((segments) => fs.existsSync(path.join(root, ...segments)))) continue;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (!version || compareSemVer(version, minVersion) < 0) continue;
|
|
86
|
-
}
|
|
86
|
+
const version = readCoreRuntimeVersion(root);
|
|
87
|
+
if (minVersion && (!version || compareSemVer(version, minVersion) < 0)) continue;
|
|
87
88
|
// `~/.agentlas/runtime/current` 는 업데이터가 원자적으로 갈아 끼우는 심볼릭
|
|
88
89
|
// 링크다. 그 경로를 그대로 넘기면 Python 이 import 를 늦게 해석하는 특성상,
|
|
89
90
|
// 긴 실행 도중 업데이트가 일어나면 **옛 버전 모듈과 새 버전 모듈이 한 프로세스에
|
|
@@ -92,17 +93,23 @@ function resolveCoreRuntimeRootFromCandidates(candidateRoots, requiredMarkers =
|
|
|
92
93
|
//
|
|
93
94
|
// 실경로로 고정해도 라이브 버전 선택은 그대로다: **다음** 호출이 다시 해석해
|
|
94
95
|
// 새 릴리스를 집는다. 없어지는 것은 실행 중 교체뿐이다.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// 링크를 읽을 수 없다고 실행을 거부할 이유는 없다 — 이전 동작대로 경로를 쓴다.
|
|
99
|
-
return root;
|
|
100
|
-
}
|
|
96
|
+
let pinned = root;
|
|
97
|
+
try { pinned = fs.realpathSync(root); } catch {}
|
|
98
|
+
eligible.push({ root: pinned, version });
|
|
101
99
|
} catch {
|
|
102
100
|
// Continue to the next installed/bundled root.
|
|
103
101
|
}
|
|
104
102
|
}
|
|
105
|
-
|
|
103
|
+
// Desktop and Terminal must attach to the same newest valid Core. Candidate
|
|
104
|
+
// order is only a tie-breaker; an older managed runtime may never shadow a
|
|
105
|
+
// newer signed Desktop bundle after an app update.
|
|
106
|
+
eligible.sort((left, right) => {
|
|
107
|
+
if (!left.version && !right.version) return 0;
|
|
108
|
+
if (!left.version) return 1;
|
|
109
|
+
if (!right.version) return -1;
|
|
110
|
+
return compareSemVer(right.version, left.version);
|
|
111
|
+
});
|
|
112
|
+
return eligible[0]?.root ?? null;
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
function resolveCoreRuntimeRoot(explicitRoot, requiredMarkers = [], options = {}) {
|
|
@@ -1469,41 +1469,6 @@ async function withdrawUpload(ref, options = {}) {
|
|
|
1469
1469
|
return { receipt, authoritative: "server", publicActivation: false };
|
|
1470
1470
|
}
|
|
1471
1471
|
|
|
1472
|
-
const TASK_CLASS_KEYWORDS = Object.freeze({
|
|
1473
|
-
research: ["research", "investigate", "literature review", "market research", "리서치", "연구", "자료 조사"],
|
|
1474
|
-
writing: ["writing", "write article", "write copy", "copywriting", "blog post", "essay", "글쓰기", "글 작성", "카피 작성", "원고 작성"],
|
|
1475
|
-
coding: ["coding", "code implementation", "implement code", "write code", "source code", "코딩", "코드 구현", "코드 작성", "프로그래밍"],
|
|
1476
|
-
debugging: ["debug", "debugging", "bug fix", "fix bug", "troubleshoot", "error", "exception", "failure", "failed", "디버깅", "버그 수정", "오류", "오류 수정", "실패"],
|
|
1477
|
-
design: ["design", "ui design", "ux design", "wireframe", "디자인", "와이어프레임", "화면 설계"],
|
|
1478
|
-
"image-generation": ["image generation", "generate image", "create image", "text to image", "이미지 생성", "그림 생성"],
|
|
1479
|
-
"video-production": ["video production", "create video", "video editing", "영상 제작", "비디오 제작", "영상 편집"],
|
|
1480
|
-
presentation: ["presentation", "slide deck", "powerpoint", "ppt", "프레젠테이션", "발표 자료", "슬라이드", "피피티"],
|
|
1481
|
-
document: ["document", "docx", "pdf document", "document editing", "문서", "문서 작성", "문서 편집"],
|
|
1482
|
-
"data-analysis": ["data analysis", "analyze data", "analytics", "데이터 분석", "통계 분석"],
|
|
1483
|
-
"browser-automation": ["browser automation", "automate browser", "playwright", "브라우저 자동화", "웹 자동화"],
|
|
1484
|
-
"social-publishing": ["social publishing", "publish social", "post to instagram", "post to tiktok", "sns 게시", "소셜 게시", "인스타 업로드", "틱톡 업로드"],
|
|
1485
|
-
marketing: ["marketing", "campaign", "seo", "마케팅", "캠페인"],
|
|
1486
|
-
sales: ["sales", "lead generation", "sales outreach", "영업", "리드 발굴"],
|
|
1487
|
-
"customer-support": ["customer support", "customer service", "support ticket", "고객 지원", "고객 문의", "cs 응대"],
|
|
1488
|
-
ecommerce: ["ecommerce", "e commerce", "online store", "shopify", "이커머스", "온라인 쇼핑몰", "스마트스토어"],
|
|
1489
|
-
"legal-review": ["legal review", "contract review", "legal analysis", "법률 검토", "계약 검토", "법무 검토"],
|
|
1490
|
-
finance: ["finance", "financial analysis", "investment analysis", "재무", "금융", "투자 분석"],
|
|
1491
|
-
"project-planning": ["project planning", "project plan", "roadmap", "프로젝트 계획", "로드맵", "일정 계획"],
|
|
1492
|
-
"agent-building": ["agent building", "build agent", "create agent", "에이전트 빌드", "에이전트 만들", "에이전트 생성"],
|
|
1493
|
-
"workflow-automation": ["workflow automation", "automate workflow", "automation workflow", "워크플로 자동화", "업무 자동화"],
|
|
1494
|
-
"file-operations": ["file operations", "move files", "rename files", "organize files", "파일 작업", "파일 이동", "파일 이름 변경", "파일 정리"],
|
|
1495
|
-
translation: ["translation", "translate", "localization", "번역", "현지화"],
|
|
1496
|
-
});
|
|
1497
|
-
|
|
1498
|
-
function normalizeClassificationText(value) {
|
|
1499
|
-
return String(value || "")
|
|
1500
|
-
.normalize("NFKC")
|
|
1501
|
-
.toLowerCase()
|
|
1502
|
-
.replace(/[^a-z0-9가-힣]+/gi, " ")
|
|
1503
|
-
.replace(/\s+/g, " ")
|
|
1504
|
-
.trim();
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
1472
|
function normalizedTaxonomyAtom(value) {
|
|
1508
1473
|
return typeof value === "string" ? value.normalize("NFKC").trim().toLowerCase() : "";
|
|
1509
1474
|
}
|
|
@@ -1525,20 +1490,7 @@ function isCanonicalTaskId(value) {
|
|
|
1525
1490
|
return typeof value === "string" && CANONICAL_TASK_ID_SET.has(value);
|
|
1526
1491
|
}
|
|
1527
1492
|
|
|
1528
|
-
function
|
|
1529
|
-
const keyword = normalizeClassificationText(rawKeyword);
|
|
1530
|
-
if (!keyword) return false;
|
|
1531
|
-
if (/[가-힣]/.test(keyword)) {
|
|
1532
|
-
// Korean has no word boundary, so a raw includes() matched inside longer compounds:
|
|
1533
|
-
// 번역 hit 번역기, 금융 hit compound finance words, 영업 hit 영업일 (business day).
|
|
1534
|
-
// Require the keyword not be glued to another Hangul syllable on either side.
|
|
1535
|
-
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1536
|
-
return new RegExp(`(?<![가-힣])${escaped}(?![가-힣])`).test(normalizedPrompt);
|
|
1537
|
-
}
|
|
1538
|
-
return ` ${normalizedPrompt} `.includes(` ${keyword} `);
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
function deriveCanonicalTaskClasses(prompt, options = {}) {
|
|
1493
|
+
function deriveCanonicalTaskClasses(_prompt, options = {}) {
|
|
1542
1494
|
const declaredRaw = options.declaredTaskClasses ?? options.declaredTaskClass;
|
|
1543
1495
|
if (declaredRaw != null && (Array.isArray(declaredRaw) ? declaredRaw.length : String(declaredRaw).trim())) {
|
|
1544
1496
|
const declared = (Array.isArray(declaredRaw) ? declaredRaw : [declaredRaw]).map(String);
|
|
@@ -1551,39 +1503,23 @@ function deriveCanonicalTaskClasses(prompt, options = {}) {
|
|
|
1551
1503
|
invalidDeclaredCount: invalidDeclared.length,
|
|
1552
1504
|
};
|
|
1553
1505
|
}
|
|
1554
|
-
|
|
1555
|
-
const matches = [];
|
|
1556
|
-
for (const slug of CANONICAL_TASK_SLUGS) {
|
|
1557
|
-
if ((TASK_CLASS_KEYWORDS[slug] || []).some((keyword) => keywordOccurs(normalizedPrompt, keyword))) {
|
|
1558
|
-
matches.push(`${CANONICAL_TASK_PREFIX}${slug}`);
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
return { taskIds: matches, source: "deterministic-keyword-map", matchedTaskClasses: matches, invalidDeclaredCount: 0 };
|
|
1506
|
+
return { taskIds: [], source: "unresolved", matchedTaskClasses: [], invalidDeclaredCount: 0 };
|
|
1562
1507
|
}
|
|
1563
1508
|
|
|
1564
1509
|
/**
|
|
1565
|
-
* Meaning-aware task classification.
|
|
1566
|
-
*
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
* An explicit declared task class still wins outright (it is data, not a guess), and with
|
|
1571
|
-
* no connected model the deterministic keyword prefilter is the fallback so classification
|
|
1572
|
-
* never stops working.
|
|
1510
|
+
* Meaning-aware task classification. An explicit declared class is accepted as
|
|
1511
|
+
* user/project data. Otherwise only the connected model may decide from the full
|
|
1512
|
+
* request. No regex, keyword list, glossary, or default class substitutes for a
|
|
1513
|
+
* missing or invalid model judgment.
|
|
1573
1514
|
*/
|
|
1574
1515
|
async function resolveCanonicalTaskClasses(prompt, options = {}) {
|
|
1575
1516
|
const declaredRaw = options.declaredTaskClasses ?? options.declaredTaskClass;
|
|
1576
1517
|
if (declaredRaw != null && (Array.isArray(declaredRaw) ? declaredRaw.length : String(declaredRaw).trim())) {
|
|
1577
1518
|
return deriveCanonicalTaskClasses(prompt, options);
|
|
1578
1519
|
}
|
|
1579
|
-
const prefilter = deriveCanonicalTaskClasses(prompt, options);
|
|
1580
1520
|
const judgment = require("./agentlas-judgment.cjs");
|
|
1581
|
-
if (!judgment.hasJudgmentRunner())
|
|
1582
|
-
|
|
1583
|
-
const hints = {};
|
|
1584
|
-
for (const slug of CANONICAL_TASK_SLUGS) {
|
|
1585
|
-
const words = TASK_CLASS_KEYWORDS[slug];
|
|
1586
|
-
if (Array.isArray(words) && words.length) hints[slug] = words;
|
|
1521
|
+
if (!judgment.hasJudgmentRunner()) {
|
|
1522
|
+
return { taskIds: [], source: "model-unavailable", matchedTaskClasses: [], invalidDeclaredCount: 0 };
|
|
1587
1523
|
}
|
|
1588
1524
|
const verdict = await judgment.judgeLabels({
|
|
1589
1525
|
kind: "experience-task-class",
|
|
@@ -1595,11 +1531,11 @@ async function resolveCanonicalTaskClasses(prompt, options = {}) {
|
|
|
1595
1531
|
"Return a label only when that kind of work is genuinely part of the request. A word inside an " +
|
|
1596
1532
|
"unrelated compound or a different sense of the word does not count. Return an empty list for " +
|
|
1597
1533
|
"content with no identifiable task (hashes, ids, random strings).",
|
|
1598
|
-
hints,
|
|
1599
|
-
fallback: [],
|
|
1600
1534
|
signal: options.signal,
|
|
1601
1535
|
});
|
|
1602
|
-
if (verdict.source !== "llm")
|
|
1536
|
+
if (verdict.source !== "llm") {
|
|
1537
|
+
return { taskIds: [], source: "model-unavailable", matchedTaskClasses: [], invalidDeclaredCount: 0 };
|
|
1538
|
+
}
|
|
1603
1539
|
const taskIds = CANONICAL_TASK_IDS.filter((id) =>
|
|
1604
1540
|
verdict.labels.some((slug) => id === `${CANONICAL_TASK_PREFIX}${slug}`));
|
|
1605
1541
|
return {
|
package/engine/agentlas-i18n.cjs
CHANGED
|
@@ -21,7 +21,7 @@ const STRINGS = {
|
|
|
21
21
|
"banner.location": "%s · / commands · Shift-Tab permissions",
|
|
22
22
|
"banner.menu.help": "Command reference",
|
|
23
23
|
"banner.menu.sessions": "Running sessions",
|
|
24
|
-
"banner.menu.chats": "
|
|
24
|
+
"banner.menu.chats": "Project runs",
|
|
25
25
|
"banner.menu.quit": "Quit",
|
|
26
26
|
"status.title": "Session",
|
|
27
27
|
"status.runtime": "runtime",
|
|
@@ -93,10 +93,10 @@ const STRINGS = {
|
|
|
93
93
|
"permFullConfirm": "FULL ACCESS ON FOR THIS SESSION — approvals and sandboxing are bypassed",
|
|
94
94
|
"sideUsage": "usage: /side <question>",
|
|
95
95
|
"sideNeedsSubject": "pick an agent first, or type the task normally so Agentlas can auto-route it",
|
|
96
|
-
"sideStart": "side question; this answer will not be saved to
|
|
97
|
-
"sideDone": "side question complete;
|
|
98
|
-
"sideCancelled": "side question cancelled;
|
|
99
|
-
"sideFailed": "side question
|
|
96
|
+
"sideStart": "side question; this answer will not be saved to the project task",
|
|
97
|
+
"sideDone": "side question complete; project task context unchanged",
|
|
98
|
+
"sideCancelled": "side question cancelled; project task context unchanged",
|
|
99
|
+
"sideFailed": "side question ended; project task context unchanged",
|
|
100
100
|
"noAgent": "no agent: %s",
|
|
101
101
|
"noCompany": "no company: %s",
|
|
102
102
|
"agentUsage": "usage: /agent <name>",
|
|
@@ -139,7 +139,7 @@ const STRINGS = {
|
|
|
139
139
|
"team.set": "%s → %s",
|
|
140
140
|
"routedImage": "routed to %s for image support",
|
|
141
141
|
"judge.source.llm": "judged by the connected model",
|
|
142
|
-
"judge.source.fallback": "
|
|
142
|
+
"judge.source.fallback": "unresolved (no model judgment)",
|
|
143
143
|
"usageBar": "session usage",
|
|
144
144
|
"config.title": "Engine auto engagement — explicit on/off (default: off)",
|
|
145
145
|
"config.storm": "Stormbreaker auto-engage on direct-routed real work",
|
|
@@ -150,7 +150,7 @@ const STRINGS = {
|
|
|
150
150
|
"config.autoEngage": "auto-engaging %s (opted in via /config — turn off with /config %s off)",
|
|
151
151
|
"help.config": "explicit on/off for Stormbreaker/Workforce Ontology auto engagement",
|
|
152
152
|
"guard.imageWarn": "this agent works with images, but %s can't generate them — try /runtime gemini (or codex), or /team to pin one",
|
|
153
|
-
"multimodal.title": "Multimodal
|
|
153
|
+
"multimodal.title": "Multimodal provider priority",
|
|
154
154
|
"multimodal.set": "multimodal %s → %s",
|
|
155
155
|
"multimodal.usage": "set with /multimodal set <image|video|audio> <provider-id>",
|
|
156
156
|
// help rows
|
|
@@ -177,12 +177,12 @@ const STRINGS = {
|
|
|
177
177
|
"help.memory": "show the memory being injected",
|
|
178
178
|
"help.careerGraph": "show/add source refs for the Career Graph routing index",
|
|
179
179
|
"help.ontology": "turn on/list/add project ontology sources with short natural commands",
|
|
180
|
-
"help.side": "ask a side question without saving it to
|
|
180
|
+
"help.side": "ask a side question without saving it to project task context",
|
|
181
181
|
"help.status": "show model/runtime, agent, permission, and directory",
|
|
182
|
-
"help.multimodal": "show/set image, video, and audio
|
|
182
|
+
"help.multimodal": "show/set image, video, and audio provider priorities",
|
|
183
183
|
"help.import": "import a local folder (agent or team)",
|
|
184
184
|
"help.market": "browse/install marketplace agents",
|
|
185
|
-
"help.clear": "clear the
|
|
185
|
+
"help.clear": "clear the terminal view and redraw",
|
|
186
186
|
"help.storm": "run a force-robust Stormbreaker pipeline on a goal",
|
|
187
187
|
"help.build": "build/repair/package an agent or team (Hephaestus)",
|
|
188
188
|
"help.route": "preview which agent/pipeline would take a request",
|
|
@@ -272,7 +272,7 @@ const STRINGS = {
|
|
|
272
272
|
"banner.location": "%s · / 명령 · Shift-Tab 권한",
|
|
273
273
|
"banner.menu.help": "명령 보기",
|
|
274
274
|
"banner.menu.sessions": "실행 중인 세션",
|
|
275
|
-
"banner.menu.chats": "
|
|
275
|
+
"banner.menu.chats": "프로젝트 실행",
|
|
276
276
|
"banner.menu.quit": "종료",
|
|
277
277
|
"status.title": "세션",
|
|
278
278
|
"status.runtime": "런타임",
|
|
@@ -342,10 +342,10 @@ const STRINGS = {
|
|
|
342
342
|
"permFullConfirm": "이 세션에서만 무제한 권한 켜짐 — 승인과 샌드박스를 우회함",
|
|
343
343
|
"sideUsage": "사용법: /side <질문>",
|
|
344
344
|
"sideNeedsSubject": "먼저 에이전트를 고르거나, 그냥 할 일을 입력해서 Agentlas가 자동 라우팅하게 하세요.",
|
|
345
|
-
"sideStart": "사이드 질문입니다. 이 답변은
|
|
346
|
-
"sideDone": "사이드 질문 완료.
|
|
347
|
-
"sideCancelled": "사이드 질문이 중단되었습니다.
|
|
348
|
-
"sideFailed": "사이드 질문이
|
|
345
|
+
"sideStart": "사이드 질문입니다. 이 답변은 프로젝트 작업에 저장하지 않습니다.",
|
|
346
|
+
"sideDone": "사이드 질문 완료. 프로젝트 작업 맥락은 그대로입니다.",
|
|
347
|
+
"sideCancelled": "사이드 질문이 중단되었습니다. 프로젝트 작업 맥락은 그대로입니다.",
|
|
348
|
+
"sideFailed": "사이드 질문이 종료되었습니다. 프로젝트 작업 맥락은 그대로입니다.",
|
|
349
349
|
"noAgent": "에이전트 없음: %s",
|
|
350
350
|
"noCompany": "회사 없음: %s",
|
|
351
351
|
"agentUsage": "사용법: /agent <이름>",
|
|
@@ -388,7 +388,7 @@ const STRINGS = {
|
|
|
388
388
|
"team.set": "%s → %s",
|
|
389
389
|
"routedImage": "이미지 지원을 위해 %s로 라우팅",
|
|
390
390
|
"judge.source.llm": "판정: 연결 모델",
|
|
391
|
-
"judge.source.fallback": "
|
|
391
|
+
"judge.source.fallback": "판정 미확정(모델 판단 없음)",
|
|
392
392
|
"usageBar": "세션 사용량",
|
|
393
393
|
"config.title": "엔진 자동 개입 — 명시적 on/off (기본: off)",
|
|
394
394
|
"config.storm": "직답 라우팅된 실작업에 Stormbreaker 자동 개입",
|
|
@@ -399,7 +399,7 @@ const STRINGS = {
|
|
|
399
399
|
"config.autoEngage": "%s 자동 개입 (/config로 켜져 있음 — 해제: /config %s off)",
|
|
400
400
|
"help.config": "Stormbreaker/Workforce Ontology 자동 개입 명시적 on/off",
|
|
401
401
|
"guard.imageWarn": "이 에이전트는 이미지 작업인데 %s는 이미지 생성 불가 — /runtime gemini(또는 codex), 또는 /team으로 고정",
|
|
402
|
-
"multimodal.title": "멀티모달
|
|
402
|
+
"multimodal.title": "멀티모달 제공자 우선순위",
|
|
403
403
|
"multimodal.set": "멀티모달 %s → %s",
|
|
404
404
|
"multimodal.usage": "/multimodal set <image|video|audio> <provider-id> 로 변경",
|
|
405
405
|
"help.talk": "현재 에이전트/회사와 대화 — 스트리밍 + 툴",
|
|
@@ -425,12 +425,12 @@ const STRINGS = {
|
|
|
425
425
|
"help.memory": "주입되는 메모리 보기",
|
|
426
426
|
"help.careerGraph": "경력 그래프 라우팅 색인의 원본 경로 보기/등록",
|
|
427
427
|
"help.ontology": "짧은 자연어로 프로젝트 온톨로지 켜기/목록/자료 등록",
|
|
428
|
-
"help.side": "
|
|
428
|
+
"help.side": "프로젝트 작업 맥락에 저장하지 않는 사이드 질문",
|
|
429
429
|
"help.status": "모델/런타임, 에이전트, 권한, 작업 폴더 보기",
|
|
430
|
-
"help.multimodal": "이미지·영상·음성
|
|
430
|
+
"help.multimodal": "이미지·영상·음성 제공자 우선순위 보기/변경",
|
|
431
431
|
"help.import": "로컬 폴더(에이전트/팀) 임포트",
|
|
432
432
|
"help.market": "마켓플레이스 에이전트 보기/설치",
|
|
433
|
-
"help.clear": "
|
|
433
|
+
"help.clear": "터미널 화면 비우고 다시 그리기",
|
|
434
434
|
"help.storm": "목표를 Stormbreaker 견고 파이프라인으로 실행",
|
|
435
435
|
"help.build": "에이전트/팀 빌드·수리·패키징 (Hephaestus)",
|
|
436
436
|
"help.route": "요청이 어떤 에이전트로 라우팅되는지 미리보기",
|
|
@@ -443,7 +443,7 @@ const STRINGS = {
|
|
|
443
443
|
"help.connect": "Telegram/플랫폼을 에이전트 팀에 연결",
|
|
444
444
|
"help.swarm": "목표에 창발형 에이전트 스웜 전개",
|
|
445
445
|
"help.doctor": "런타임/데이터 점검",
|
|
446
|
-
"help.compact": "오래된
|
|
446
|
+
"help.compact": "오래된 작업 맥락을 줄이고 최근 맥락 유지",
|
|
447
447
|
"help.keybindings": "터미널 단축키 보기",
|
|
448
448
|
"help.exit": "종료 (Ctrl-C: 턴 중단 / 유휴 시 종료)",
|
|
449
449
|
"help.talkKey": "(메시지 입력)",
|
|
Binary file
|
|
@@ -964,7 +964,6 @@ function runNativeTurn(req) {
|
|
|
964
964
|
return;
|
|
965
965
|
}
|
|
966
966
|
ui.stopSpinner();
|
|
967
|
-
ui.error(uiText(ui, "runtime.failed", kind, err.message));
|
|
968
967
|
finish({ text: "", session: st.session, error: err.message });
|
|
969
968
|
});
|
|
970
969
|
child.on("close", (code) => {
|
|
@@ -976,39 +975,13 @@ function runNativeTurn(req) {
|
|
|
976
975
|
ui.streamEnd();
|
|
977
976
|
ui.stopSpinner();
|
|
978
977
|
const text = (st.finalText || st.text || "").trim();
|
|
979
|
-
const errTail = stripAnsi(stderrBuf).replace(/\s+/g, " ").trim(); // ANSI 제거 + 한 줄로
|
|
980
|
-
if (st.error && !st.errorShown) {
|
|
981
|
-
// claude `result` is_error 등 — 이전에 표시되지 않은 에러를 노출
|
|
982
|
-
ui.error(String(st.error));
|
|
983
|
-
} else if (code !== 0) {
|
|
984
|
-
// Runtime Doctor — 아는 시스템 원인(미인증 OAuth MCP 플러그인 등)이면 즉시 수리하고
|
|
985
|
-
// 1회 자동 재시도한다(2026-07-08 notion@openai-curated가 codex 전멸시킨 사고).
|
|
986
|
-
if (!req._doctorRetried) {
|
|
987
|
-
try {
|
|
988
|
-
const { runRuntimeDoctor } = require("./agentlas-doctor.cjs");
|
|
989
|
-
const report = runRuntimeDoctor(`${kind} exited with code ${code}\n${stripAnsi(stderrBuf)}`);
|
|
990
|
-
if (report.repaired) {
|
|
991
|
-
ui.warn(uiText(ui, "runtime.doctor", report.summary));
|
|
992
|
-
for (const act of report.actions) ui.warn(` ${act.title} — ${act.detail}`);
|
|
993
|
-
ui.warn(uiText(ui, "runtime.doctorRetry"));
|
|
994
|
-
settled = true;
|
|
995
|
-
cleanup();
|
|
996
|
-
resolve(runNativeTurn({ ...req, _doctorRetried: true }));
|
|
997
|
-
return;
|
|
998
|
-
}
|
|
999
|
-
} catch {
|
|
1000
|
-
/* 닥터 실패는 원래 에러 표출을 막지 않는다 */
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
ui.error(uiText(ui, "runtime.exited", kind, String(code)) + (errTail ? `\n ${errTail.slice(-400)}` : ""));
|
|
1004
|
-
} else if (!text && !st.error) {
|
|
1005
|
-
// 정상 종료인데 출력이 비어 있음(거부/차단 등) — 무음 실패 방지
|
|
1006
|
-
ui.warn(uiText(ui, "runtime.noOutput", kind) + (errTail ? ` (${errTail.slice(-200)})` : ""));
|
|
1007
|
-
}
|
|
1008
978
|
if (st.usage) ui.cost(st.usage);
|
|
1009
979
|
const exitError = code === 0
|
|
1010
980
|
? st.error
|
|
1011
|
-
: st.error ||
|
|
981
|
+
: st.error || [
|
|
982
|
+
`${kind} exited with code ${code == null ? "unknown" : code}`,
|
|
983
|
+
stripAnsi(stderrBuf).slice(-4000),
|
|
984
|
+
].filter(Boolean).join("\n");
|
|
1012
985
|
finish({ text, session: st.session, usage: st.usage, error: exitError });
|
|
1013
986
|
});
|
|
1014
987
|
|