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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.14 — 2026-07-29
|
|
4
|
+
|
|
5
|
+
- **Write-capable commands now prepare every project automatically.** The first
|
|
6
|
+
`run`, `storm`, `swarm`, or workforce execution in any folder installs the
|
|
7
|
+
same private, merge-only Agentlas project infrastructure through Core. This
|
|
8
|
+
is based on the folder the user opened, not on the Agentlas source checkout.
|
|
9
|
+
Read-only commands remain passive and do not create files.
|
|
10
|
+
- **Per-command help works before database startup.** Commands such as
|
|
11
|
+
`agentlas run --help` and `agentlas workforce --help` no longer require an
|
|
12
|
+
SQLite driver or open the project database just to print usage.
|
|
13
|
+
|
|
14
|
+
## 1.0.13 — 2026-07-28
|
|
15
|
+
|
|
16
|
+
- **MCP servers reach the chat again.** `runNativeTurn` only injects them at
|
|
17
|
+
`full` permission and that gate was right, but `sessions/session.cjs` never
|
|
18
|
+
put `mcpServers` in the request — zero callers — so no CLI ever received a
|
|
19
|
+
server. `agentlas mcp probe` printed "connected" while the turn that followed
|
|
20
|
+
could not use it. Turns now carry servers the user already consented to;
|
|
21
|
+
nothing new is asked mid-turn, because a turn is not a place a user can answer.
|
|
22
|
+
- **The shared database stops taking a write lock every turn.** Four
|
|
23
|
+
`CREATE TABLE IF NOT EXISTS`, five indexes and an `ALTER TABLE` ran on every
|
|
24
|
+
single turn against the file Desktop also uses. All idempotent, all taking the
|
|
25
|
+
lock; during a Desktop migration that burns the 15s busy timeout and every
|
|
26
|
+
call site swallowed the failure. Schema repair is once per connection now, and
|
|
27
|
+
the three copies of `ensureMemoryContextColumn` are one function.
|
|
28
|
+
- **Rows written here are checked for referential integrity.** `foreign_keys` is
|
|
29
|
+
a connection property, not a file property — Desktop opened with it ON and the
|
|
30
|
+
terminal did not, so terminal writes skipped the check on shared tables.
|
|
31
|
+
- **A zero-byte database file no longer blocks first run forever.** One stray
|
|
32
|
+
`sqlite3 <missing-path>` left an empty file, and every run after that read
|
|
33
|
+
"already exists" and died on `no such table` with nothing visible to explain it.
|
|
34
|
+
- **Bootstrap schema is current again** (v81; it was a v45 snapshot from
|
|
35
|
+
2026-07-07), and a mismatch with Desktop's migration target now fails a gate.
|
|
36
|
+
- **An engine update landing mid-run cannot mix two releases.** Core roots
|
|
37
|
+
resolve to their real path, so a long run keeps the modules it started with.
|
|
38
|
+
The next call still picks up the new release.
|
|
39
|
+
- `/ontology` says what it manages — this project's knowledge sources, not the
|
|
40
|
+
engine's knowledge runtime. The command itself is unchanged.
|
|
41
|
+
|
|
42
|
+
## 1.0.12 — 2026-07-28
|
|
43
|
+
|
|
44
|
+
- **Orchestrator/worker model roles now resolve from ordered candidate
|
|
45
|
+
pools.** The shared `model_role_members` table (Desktop schema v80) holds
|
|
46
|
+
n candidates per role in priority order; the terminal picks the first
|
|
47
|
+
member that is actually executable here, records every skipped member
|
|
48
|
+
with its reason, and never silently substitutes a lower-priority runtime
|
|
49
|
+
when all members are unavailable — the head member is used and the skip
|
|
50
|
+
list is preserved. An empty worker pool inherits the orchestrator pool,
|
|
51
|
+
matching the single-row inherit contract, and databases without pools
|
|
52
|
+
keep resolving through the v79 single-row and legacy `active_runtime`
|
|
53
|
+
ladders unchanged.
|
|
54
|
+
- **Workforce escalation is bounded and receipted end to end.** A worker
|
|
55
|
+
that violates the handoff output contract twice — or is named in two
|
|
56
|
+
consecutive verifier failures — gets exactly one orchestrator-role
|
|
57
|
+
retry, stamped with `escalated-after-failure`, the failure count, and
|
|
58
|
+
the attempt number; a failing escalation stops honestly instead of
|
|
59
|
+
looping or downgrading.
|
|
60
|
+
- **BYOK Anthropic calls mark the system prefix as a prompt-cache
|
|
61
|
+
breakpoint.** Real Anthropic endpoints receive the system prompt as one
|
|
62
|
+
`cache_control: ephemeral` block (~90% cheaper cached input on hits;
|
|
63
|
+
a silent no-op below the per-model minimum). Anthropic-compatible
|
|
64
|
+
endpoints (GLM/Kimi/DeepSeek) keep the plain string form they expect.
|
|
65
|
+
- Model-allocation receipts split "no allocation was provided"
|
|
66
|
+
(`allocation_not_provided`) from "the allocation was malformed"
|
|
67
|
+
(`invalid_ai_allocation`), and real invocations now retain the
|
|
68
|
+
provider-reported token usage per role instead of discarding it.
|
|
69
|
+
|
|
3
70
|
## 1.0.11 — 2026-07-27
|
|
4
71
|
|
|
5
72
|
- **The slash palette repaints in place instead of stacking copies of
|
package/README.md
CHANGED
|
@@ -110,9 +110,11 @@ agentlas connect <sub> # Telegram 등 플랫폼 연결 (무인자는 u
|
|
|
110
110
|
agentlas import <폴더> # 로컬 에이전트/팀 임포트
|
|
111
111
|
agentlas native prepare <agent> # 네이티브 CLI 문맥 파일 생성
|
|
112
112
|
agentlas list # 설치 에이전트/회사 + 활성 런타임
|
|
113
|
-
agentlas uninstall <agent>
|
|
113
|
+
agentlas uninstall <agent> [--yes] # 설치 에이전트 제거 (빌트인 거부).
|
|
114
|
+
# 대화 이력이 있으면 건수를 보여주고 --yes 없이는 거절한다
|
|
115
|
+
# (챗/메시지가 CASCADE로 함께 영구 삭제되기 때문).
|
|
114
116
|
agentlas experience <sub> # list|inspect|validate|save|publish|status|export|unpublish|withdraw
|
|
115
|
-
agentlas variant resolve
|
|
117
|
+
agentlas variant resolve --base-release <id> # 로컬 variant 호환성 프리뷰 (권위 없음, `agentlas variant help`)
|
|
116
118
|
```
|
|
117
119
|
|
|
118
120
|
### EXECUTE
|
package/bin/agentlas.cjs
CHANGED
|
@@ -117,9 +117,23 @@ function bootstrapDbIfMissing() {
|
|
|
117
117
|
const dir = path.dirname(p);
|
|
118
118
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
119
119
|
securePrivateMode(dir, 0o700);
|
|
120
|
+
// 존재 여부만 보면 **0바이트 파일이 부트스트랩을 영구히 막는다.** 실측 2026-07-28:
|
|
121
|
+
// `sqlite3 <없는경로> 'PRAGMA user_version;'` 같은 무해한 명령 하나가 빈 파일을 남기고,
|
|
122
|
+
// 그 뒤 모든 실행이 "이미 있음"으로 판단해 스키마 없이 진행하다 `no such table` 로
|
|
123
|
+
// 죽는다. 사용자에게는 원인이 전혀 안 보이는 상태다.
|
|
124
|
+
//
|
|
125
|
+
// SQLite 파일은 최소 한 페이지(512바이트)를 갖는다. 0바이트는 DB 가 아니라 자리만
|
|
126
|
+
// 잡힌 파일이므로 없는 것으로 취급해 정상 부트스트랩 경로를 태운다. 내용이 있는데
|
|
127
|
+
// 손상된 경우는 여기서 판단하지 않는다 — 그건 복구지 부트스트랩이 아니고, 멀쩡한
|
|
128
|
+
// DB 를 빈 것으로 오판해 덮어쓰는 위험이 훨씬 크다.
|
|
120
129
|
if (exists(p)) {
|
|
121
|
-
|
|
122
|
-
|
|
130
|
+
let empty = false;
|
|
131
|
+
try { empty = fs.statSync(p).size === 0; } catch { empty = false; }
|
|
132
|
+
if (!empty) {
|
|
133
|
+
securePrivateMode(p, 0o600);
|
|
134
|
+
return { created: false, path: p };
|
|
135
|
+
}
|
|
136
|
+
try { fs.rmSync(p, { force: true }); } catch { /* 지울 수 없으면 아래 link 가 EEXIST 로 알려준다 */ }
|
|
123
137
|
}
|
|
124
138
|
const schemaFile = path.join(PKG_ROOT, "engine", "bootstrap-schema.sql");
|
|
125
139
|
if (!exists(schemaFile)) {
|
|
@@ -172,7 +186,7 @@ function main() {
|
|
|
172
186
|
let error = null;
|
|
173
187
|
if (!engineFound) {
|
|
174
188
|
error = "Engine not found (engine/agentlas.cjs). Reinstall with: npm i -g agentlas";
|
|
175
|
-
} else if (!sqliteDriver) {
|
|
189
|
+
} else if (!sqliteDriver && !metadataOnly) {
|
|
176
190
|
error = `Node ${process.version} — no SQLite driver. Upgrade to Node 22.5+ or reinstall with 'npm i -g agentlas' to build better-sqlite3.`;
|
|
177
191
|
}
|
|
178
192
|
|
|
@@ -100,28 +100,35 @@ function mergePrefs(base, patch) {
|
|
|
100
100
|
return next;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/*
|
|
104
|
+
* Throws on failure (lock timeout, read-only data dir, ENOSPC …) — it used to
|
|
105
|
+
* swallow every error and return null, so `agentlas setup` announced "All set."
|
|
106
|
+
* and exited 0 while the user's language/runtime/permission were never written.
|
|
107
|
+
* A caller that cannot persist must be able to say so; use savePrefs for the
|
|
108
|
+
* best-effort boolean contract.
|
|
109
|
+
*/
|
|
103
110
|
function updatePrefs(userDataDir, patch) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
118
|
-
} catch {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
111
|
+
fs.mkdirSync(userDataDir, { recursive: true });
|
|
112
|
+
return withPrefsLock(userDataDir, () => {
|
|
113
|
+
const file = prefsPath(userDataDir);
|
|
114
|
+
const currentWasValid = Boolean(readPrefsFile(file));
|
|
115
|
+
const current = loadPrefs(userDataDir);
|
|
116
|
+
if (fs.existsSync(file) && !currentWasValid) {
|
|
117
|
+
try { fs.renameSync(file, `${file}.corrupt-${Date.now()}-${process.pid}`); } catch { /* recover from backup */ }
|
|
118
|
+
}
|
|
119
|
+
const next = mergePrefs(current, patch);
|
|
120
|
+
if (currentWasValid) atomicWrite(backupPath(userDataDir), current);
|
|
121
|
+
atomicWrite(file, next);
|
|
122
|
+
return next;
|
|
123
|
+
});
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
function savePrefs(userDataDir, prefs) {
|
|
124
|
-
|
|
127
|
+
try {
|
|
128
|
+
return Boolean(updatePrefs(userDataDir, prefs));
|
|
129
|
+
} catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
module.exports = { prefsPath, loadPrefs, savePrefs, updatePrefs, mergePrefs };
|
|
@@ -84,7 +84,20 @@ function resolveCoreRuntimeRootFromCandidates(candidateRoots, requiredMarkers =
|
|
|
84
84
|
const version = readCoreRuntimeVersion(root);
|
|
85
85
|
if (!version || compareSemVer(version, minVersion) < 0) continue;
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
// `~/.agentlas/runtime/current` 는 업데이터가 원자적으로 갈아 끼우는 심볼릭
|
|
88
|
+
// 링크다. 그 경로를 그대로 넘기면 Python 이 import 를 늦게 해석하는 특성상,
|
|
89
|
+
// 긴 실행 도중 업데이트가 일어나면 **옛 버전 모듈과 새 버전 모듈이 한 프로세스에
|
|
90
|
+
// 섞여 로드된다** — 어떤 버전에서도 시험된 적 없는 조합이고 버전 번호로는
|
|
91
|
+
// 재현조차 못 한다(감사 D3).
|
|
92
|
+
//
|
|
93
|
+
// 실경로로 고정해도 라이브 버전 선택은 그대로다: **다음** 호출이 다시 해석해
|
|
94
|
+
// 새 릴리스를 집는다. 없어지는 것은 실행 중 교체뿐이다.
|
|
95
|
+
try {
|
|
96
|
+
return fs.realpathSync(root);
|
|
97
|
+
} catch {
|
|
98
|
+
// 링크를 읽을 수 없다고 실행을 거부할 이유는 없다 — 이전 동작대로 경로를 쓴다.
|
|
99
|
+
return root;
|
|
100
|
+
}
|
|
88
101
|
} catch {
|
|
89
102
|
// Continue to the next installed/bundled root.
|
|
90
103
|
}
|
package/engine/agentlas-i18n.cjs
CHANGED
|
@@ -260,6 +260,7 @@ const STRINGS = {
|
|
|
260
260
|
"wiz.default": "(Enter default)",
|
|
261
261
|
"wiz.invalid": "Choose 1–%s.",
|
|
262
262
|
"wiz.saved": "All set. You can change any of this later with /runtime, /permission.",
|
|
263
|
+
"wiz.saveFailed": "Could not save your choices (%s) — they apply to this session only, and setup will ask again next launch.",
|
|
263
264
|
"wiz.changeLang": "Tip: re-run setup anytime with agentlas setup",
|
|
264
265
|
},
|
|
265
266
|
ko: {
|
|
@@ -505,6 +506,7 @@ const STRINGS = {
|
|
|
505
506
|
"wiz.default": "(Enter 기본값)",
|
|
506
507
|
"wiz.invalid": "1–%s 중 고르세요.",
|
|
507
508
|
"wiz.saved": "완료. 나중에 /runtime, /permission으로 언제든 바꿀 수 있어요.",
|
|
509
|
+
"wiz.saveFailed": "선택을 저장하지 못했습니다 (%s) — 이번 세션에만 적용되고, 다음 실행 때 다시 물어봅니다.",
|
|
508
510
|
"wiz.changeLang": "팁: 언제든 agentlas setup 으로 다시 설정",
|
|
509
511
|
},
|
|
510
512
|
};
|
|
@@ -298,7 +298,7 @@ const SLASH_COMMAND_META = [
|
|
|
298
298
|
{ command: "/route", description: "Preview which agent/pipeline would take a request", category: "Engine", usage: "/route <request>", detail: "Runs the Hephaestus router without executing — shows the selected agent, candidates, and reasons." },
|
|
299
299
|
{ command: "/research", description: "Run the Hephaestus Research Engine", category: "Engine", usage: "/research search \"query\"", detail: "status|gather|search|read|plan — evidence-grade web research from the terminal." },
|
|
300
300
|
{ command: "/search", description: "Discover agents in the Hub", category: "Hub", usage: "/search <what you need>", detail: "Search the Agentlas Hub + local for an agent that fits the task (hep-search)." },
|
|
301
|
-
{ command: "/install", description: "Install an agent from the Hub by slug", category: "Hub", usage: "/install <slug>", detail: "Install a marketplace agent into this terminal
|
|
301
|
+
{ command: "/install", description: "Install an agent from the Hub by slug", category: "Hub", usage: "/install <slug>", detail: "Install a marketplace agent into this terminal." },
|
|
302
302
|
{ command: "/network", description: "Staff and execute an ontology-grounded task force", category: "Engine", usage: "/network <request> [--benchmark]", detail: "The active top host LLM creates the work order, searches the Hub workforce ontology, selects exact releases, and executes a receipt-backed task force.", aliases: ["/taskforce", "/workforce"] },
|
|
303
303
|
{ command: "/legacy-network", description: "Run the compatibility Hephaestus network route", category: "Engine", usage: "/legacy-network <request>", detail: "Explicit compatibility escape hatch; never used as a fallback by /network." },
|
|
304
304
|
{ command: "/browser", description: "Real browser execution hardpoint", category: "Engine", usage: "/browser [sub]", detail: "Runs the Agentlas browser hardpoint (hep-browser)." },
|
|
@@ -582,6 +582,7 @@ function attachSlashPalette(rl, opts = {}) {
|
|
|
582
582
|
selected: 0,
|
|
583
583
|
selectedCommand: null,
|
|
584
584
|
visible: false,
|
|
585
|
+
navigated: false,
|
|
585
586
|
dismissedForLine: null,
|
|
586
587
|
};
|
|
587
588
|
|
|
@@ -596,8 +597,23 @@ function attachSlashPalette(rl, opts = {}) {
|
|
|
596
597
|
return rows().length > 0 && state.dismissedForLine !== (rl.line || "");
|
|
597
598
|
}
|
|
598
599
|
function replaceLine(value) {
|
|
600
|
+
const next = String(value || "");
|
|
601
|
+
/*
|
|
602
|
+
* keypress 리스너 안에서 rl.write(Ctrl-U) → rl.write(text)를 재진입시키면
|
|
603
|
+
* Node readline의 원래 Tab/Enter 핸들러가 아직 같은 키를 처리하는 중이라
|
|
604
|
+
* 기존 `/s` 뒤에 선택값을 붙였다(`/s/team`, `/s/skills` 실측). line/cursor는
|
|
605
|
+
* readline의 공개 관측 상태이고 `_refreshLine`은 그 상태를 그리는 유일한
|
|
606
|
+
* 부수효과라, 한 번에 교체해 재진입을 피한다. 구형 Node만 기존 키 시퀀스로
|
|
607
|
+
* 폴백한다.
|
|
608
|
+
*/
|
|
609
|
+
if (typeof rl._refreshLine === "function") {
|
|
610
|
+
rl.line = next;
|
|
611
|
+
rl.cursor = next.length;
|
|
612
|
+
rl._refreshLine();
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
599
615
|
rl.write(null, { ctrl: true, name: "u" });
|
|
600
|
-
rl.write(
|
|
616
|
+
rl.write(next);
|
|
601
617
|
}
|
|
602
618
|
/*
|
|
603
619
|
* 커서 복원은 상대 이동으로만 한다.
|
|
@@ -644,8 +660,19 @@ function attachSlashPalette(rl, opts = {}) {
|
|
|
644
660
|
});
|
|
645
661
|
if (!body) { clear(); return; }
|
|
646
662
|
const lines = body.split("\n");
|
|
647
|
-
|
|
648
|
-
|
|
663
|
+
/*
|
|
664
|
+
* 첫 줄바꿈은 프롬프트 아래로 내려가며, 자리가 없으면 여기서 화면이 한 번 밀린다.
|
|
665
|
+
* 그린 만큼 그대로 되올라오므로 이후 갱신은 제자리에서 일어난다.
|
|
666
|
+
*
|
|
667
|
+
* 복귀 후에는 커서 열만 맞춘다. readline 의 prompt(true) 로 프롬프트 줄을 다시
|
|
668
|
+
* 그리면 refreshLine 이 커서 아래를 지워 방금 그린 프레임까지 함께 날아간다
|
|
669
|
+
* (실측: 리사이즈 없이도 팔레트가 통째로 사라짐). 프롬프트 줄 자체는 지운 적이
|
|
670
|
+
* 없으므로 평상시에는 화면에 그대로 남아 있다.
|
|
671
|
+
*
|
|
672
|
+
* 남은 한계: 창 크기를 줄이면 터미널이 프롬프트 줄을 리플로우하며 지울 수 있고,
|
|
673
|
+
* 그때는 다음 입력 전까지 프롬프트가 보이지 않는다(40행→18행 축소에서 실측).
|
|
674
|
+
* 팔레트는 리사이즈를 구독하지 않는다.
|
|
675
|
+
*/
|
|
649
676
|
stream.write(`\r\n\x1b[0J${lines.join("\r\n")}\x1b[${lines.length}A\x1b[${promptColumn()}G`);
|
|
650
677
|
state.visible = true;
|
|
651
678
|
}
|
|
@@ -667,6 +694,7 @@ function attachSlashPalette(rl, opts = {}) {
|
|
|
667
694
|
if (!list.length) return false;
|
|
668
695
|
state.selected = (state.selected + delta + list.length) % list.length;
|
|
669
696
|
state.selectedCommand = list[state.selected].command;
|
|
697
|
+
state.navigated = true;
|
|
670
698
|
const query = rl.line || "";
|
|
671
699
|
setImmediate(() => {
|
|
672
700
|
if ((rl.line || "") !== query) replaceLine(query);
|
|
@@ -695,12 +723,38 @@ function attachSlashPalette(rl, opts = {}) {
|
|
|
695
723
|
move(name === "down" ? 1 : -1);
|
|
696
724
|
return;
|
|
697
725
|
}
|
|
698
|
-
//
|
|
699
|
-
//
|
|
700
|
-
if (active() &&
|
|
701
|
-
select()
|
|
726
|
+
// Tab 은 완성이다 — 강조된 항목으로 줄을 채운다.
|
|
727
|
+
// Shift-Tab 은 팔레트 확정 키가 아니다(호출자 REPL 의 권한 순환 단축키다).
|
|
728
|
+
if (active() && name === "tab" && !key.shift) {
|
|
729
|
+
if (select()) {
|
|
730
|
+
/*
|
|
731
|
+
* prependListener는 readline 자체 Tab 처리를 중단시키지 못한다. 선택을 먼저
|
|
732
|
+
* 반영한 뒤, 기본 완성기가 같은 키로 줄을 다시 바꿔도 다음 tick에 exact
|
|
733
|
+
* 선택을 한 번 재적용한다.
|
|
734
|
+
*/
|
|
735
|
+
const selected = state.selectedCommand;
|
|
736
|
+
setImmediate(() => {
|
|
737
|
+
if (selected) replaceLine(selected);
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
/*
|
|
743
|
+
* Enter 는 사용자가 실제로 목록을 훑었을 때만 강조 항목을 확정한다.
|
|
744
|
+
*
|
|
745
|
+
* 예전에는 팔레트가 떠 있기만 하면 Enter 가 무조건 select() 를 불렀고,
|
|
746
|
+
* 훑은 적이 없으면 state.selected 는 0이라 "목록 첫 줄"이 대신 실행됐다.
|
|
747
|
+
* `/s`(활성 세션 전환)를 치고 Enter 하면 `/sessions` 가 돌아간다 — 친 것과
|
|
748
|
+
* 다른 명령이 실행되는 것이다(pty 실측). 게다가 피해 대상은 팔레트 정렬의
|
|
749
|
+
* 함수라, 명령 목록을 손볼 때마다 어떤 명령이 가로채이는지가 조용히 바뀐다.
|
|
750
|
+
*/
|
|
751
|
+
if (active() && name === "return") {
|
|
752
|
+
if (state.navigated) select();
|
|
753
|
+
else clear();
|
|
702
754
|
return;
|
|
703
755
|
}
|
|
756
|
+
// 타이핑이 이어지면 "훑었다"는 사실은 무효가 된다 — 질의가 달라졌기 때문.
|
|
757
|
+
state.navigated = false;
|
|
704
758
|
state.dismissedForLine = null;
|
|
705
759
|
setImmediate(render);
|
|
706
760
|
}
|
|
@@ -163,7 +163,17 @@ function tableExists(db, name) {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
// 커넥션당 1회. 이전에는 `beginTurn`/`listScopedTimeline` 이 부를 때마다, 즉 **매 턴**
|
|
167
|
+
// 공유 DB 에 테이블 4개 + 인덱스 5개 DDL 과 `UPDATE … stale` 을 날렸다. 전부
|
|
168
|
+
// IF NOT EXISTS 라 결과는 멱등이지만 쓰기 락을 잡는 것은 매번이었고, 데스크탑
|
|
169
|
+
// 마이그레이션과 겹치면 15초 busy_timeout 을 소진했다(2026-07-28).
|
|
170
|
+
const { ensureOnce } = require("./core/schema-ensure.cjs");
|
|
171
|
+
|
|
166
172
|
function ensureGovernanceSchema(db) {
|
|
173
|
+
return ensureOnce(db, "terminal_memory.schema", (conn) => ensureGovernanceSchemaOnce(conn));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function ensureGovernanceSchemaOnce(db) {
|
|
167
177
|
db.exec(`
|
|
168
178
|
CREATE TABLE IF NOT EXISTS terminal_memory_turn_intents (
|
|
169
179
|
turn_id TEXT PRIMARY KEY,
|
|
@@ -7,8 +7,13 @@ const i18n = require("./agentlas-i18n.cjs");
|
|
|
7
7
|
const banner = require("./agentlas-banner.cjs");
|
|
8
8
|
const { visWidth, wrapWidth } = require("./agentlas-composer.cjs");
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/*
|
|
11
|
+
* req = { ui, rl, helpers, persist } → Promise<{ onboarded, saved, saveError, lang, runtime, permission }>
|
|
12
|
+
* `persist` writes the answers and must throw when it cannot. The wizard calls it
|
|
13
|
+
* itself, before announcing "All set." — the caller used to save afterwards, so a
|
|
14
|
+
* failed write (locked/read-only data dir) was announced as success and dropped.
|
|
15
|
+
*/
|
|
16
|
+
async function runOnboard({ ui, rl, helpers, persist }) {
|
|
12
17
|
const H = helpers;
|
|
13
18
|
const c = ui.c;
|
|
14
19
|
const contentWidth = () => Math.max(20, (ui.out.columns || 80) - 5);
|
|
@@ -149,10 +154,22 @@ async function runOnboard({ ui, rl, helpers }) {
|
|
|
149
154
|
const pi = await pickNum(permOpts.length);
|
|
150
155
|
const permission = permOpts[pi - 1].v;
|
|
151
156
|
|
|
157
|
+
// Save first, then report what actually happened — never the other way round.
|
|
158
|
+
let saveError = null;
|
|
159
|
+
try {
|
|
160
|
+
if (typeof persist !== "function") throw new Error("no persistence handler");
|
|
161
|
+
await persist({ lang, runtime, permission });
|
|
162
|
+
} catch (error) {
|
|
163
|
+
saveError = error;
|
|
164
|
+
}
|
|
152
165
|
ui.line("");
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
166
|
+
if (saveError) {
|
|
167
|
+
ui.error(ui.t("wiz.saveFailed", String((saveError && saveError.message) || saveError)));
|
|
168
|
+
} else {
|
|
169
|
+
printSaved(ui.t("wiz.saved"));
|
|
170
|
+
printIndented(ui.t("wiz.changeLang"), c.faint);
|
|
171
|
+
}
|
|
172
|
+
return { onboarded: true, saved: !saveError, saveError, lang, runtime, permission };
|
|
156
173
|
} finally {
|
|
157
174
|
rl.removeListener("line", onLine);
|
|
158
175
|
if (ownsSigint) rl.removeListener("SIGINT", onSigint);
|
|
@@ -6,15 +6,26 @@
|
|
|
6
6
|
// fail immediately with SQLITE_BUSY even when busy_timeout is configured.
|
|
7
7
|
const SQLITE_BUSY_TIMEOUT_MS = 15_000;
|
|
8
8
|
|
|
9
|
+
// `foreign_keys` 는 파일이 아니라 **커넥션** 속성이다. 데스크탑은
|
|
10
|
+
// `electron/store/db.ts` 에서 `foreign_keys = ON` 으로 열고, 터미널은 켜지 않아
|
|
11
|
+
// **같은 테이블인데 터미널이 쓰는 행만 참조 무결성 검사를 건너뛰고 있었다**
|
|
12
|
+
// (2026-07-28 확인). journal_mode 처럼 파일에 박히는 값이 아니라서 한쪽이 켠 것이
|
|
13
|
+
// 다른 쪽에 전파되지 않는다. 스키마 소유는 데스크탑이므로 그 규칙을 그대로 따른다.
|
|
14
|
+
const SQLITE_CONNECTION_PRAGMAS = [
|
|
15
|
+
`busy_timeout = ${SQLITE_BUSY_TIMEOUT_MS}`,
|
|
16
|
+
"foreign_keys = ON",
|
|
17
|
+
];
|
|
18
|
+
|
|
9
19
|
function configureSqliteConnection(db) {
|
|
10
20
|
if (!db) throw new TypeError("SQLite connection is required");
|
|
11
|
-
|
|
12
|
-
db.pragma
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
const applyPragma =
|
|
22
|
+
typeof db.pragma === "function"
|
|
23
|
+
? (statement) => db.pragma(statement)
|
|
24
|
+
: typeof db.exec === "function"
|
|
25
|
+
? (statement) => db.exec(`PRAGMA ${statement}`)
|
|
26
|
+
: null;
|
|
27
|
+
if (!applyPragma) throw new TypeError("SQLite connection must expose pragma() or exec()");
|
|
28
|
+
for (const statement of SQLITE_CONNECTION_PRAGMAS) applyPragma(statement);
|
|
18
29
|
return db;
|
|
19
30
|
}
|
|
20
31
|
|