agentlas 1.0.48 → 1.0.50

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/README.md CHANGED
@@ -228,7 +228,9 @@ The launcher (`bin/agentlas.cjs`) runs system Node against `engine/`. The defaul
228
228
  | Windows | `%APPDATA%\Agentlas` |
229
229
  | Linux | `$XDG_CONFIG_HOME/Agentlas` (default `~/.config/Agentlas`) |
230
230
 
231
- 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.
231
+ The SQLite database file is `agentlas.sqlite` (`user_version=97`). 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.
232
+
233
+ **Single migration authority.** The Desktop app owns the schema migration ladder; the CLI never migrates the shared database. `engine/bootstrap-schema.sql` is generated by running that ladder to completion against an empty database, so a CLI-created store already sits at the ladder head and has nothing left for Desktop to upgrade. If the CLI opens a store older than the version it knows, it refuses with an actionable message instead of migrating or silently proceeding — a second migrator on this lock-free file is what corrupted the store once before. On a machine with no Desktop app, an operator can set `AGENTLAS_STORE_MIGRATION_ROLE=owner` for a single deliberate upgrade run with every other Agentlas process closed.
232
234
 
233
235
  SQLite driver priority: `better-sqlite3` (optional dependency native build), then Node 22+ `node:sqlite` when the first driver is unavailable.
234
236
 
@@ -289,7 +291,7 @@ Engine source code resides in `engine/*.cjs`.
289
291
  ```sh
290
292
  sh test/smoke.sh # Runs surface tests, guard tests, fresh DB tests, contract tests & parity gates
291
293
  npm run smoke # Equivalent to npm run test:release-contracts
292
- sh scripts/gen-bootstrap-schema.sh [db-path] # Regenerates engine/bootstrap-schema.sql
294
+ node scripts/gen-bootstrap-schema.cjs # Regenerates engine/bootstrap-schema.sql from the Desktop ladder (never reads a live store)
293
295
  ```
294
296
 
295
297
  Smoke tests run isolated inside a temporary `AGENTLAS_USER_DATA_DIR` and do not touch local user data.
package/bin/agentlas.cjs CHANGED
@@ -155,12 +155,22 @@ function bootstrapDbIfMissing() {
155
155
  throw new Error(`Bootstrap schema not found: ${schemaFile}`);
156
156
  }
157
157
  const sql = fs.readFileSync(schemaFile, "utf8");
158
+ // ★새 저장소는 태어날 때부터 WAL 이어야 한다 (2026-08-18 실측).
159
+ // journal_mode 는 파일에 박히는 값이고, delete→WAL 전환은 **배타 락**을 요구하며
160
+ // busy_timeout 이 걸려 있어도 즉시 SQLITE_BUSY 로 실패할 수 있다. 그래서 터미널이
161
+ // 만든 DB 를 delete 모드로 남기면, 데스크탑과 터미널이 그 파일을 처음 동시에 여는
162
+ // 순간 한쪽이 "database is locked" 로 죽는다 — 첫 부팅에서만 나는, 재현이 어려운
163
+ // 결함이다. 여기서 한 번 켜 두면 그 전환 자체가 존재하지 않는다.
164
+ const WAL_PRAGMA = "PRAGMA journal_mode=WAL;\n";
158
165
  // DB를 정식 경로에서 직접 만들면 두 첫 실행이 모두 exists=false를 본 뒤 한 프로세스의
159
166
  // 실패 cleanup이 다른 프로세스의 정상 DB까지 지울 수 있다. 각자 같은 볼륨의 임시 DB를
160
167
  // 완성하고 hard-link(EEXIST=다른 프로세스 승리)로만 정식 이름을 원자 획득한다.
161
168
  const temp = `${p}.bootstrap-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.tmp`;
162
169
  const db = openSqlite(temp);
163
170
  try {
171
+ // 임시 파일에서 켠다. journal_mode 는 DB 헤더에 박히므로 hard-link 로 정식 이름을
172
+ // 얻은 뒤에도 유지된다(닫을 때 -wal 은 체크포인트되고 사라진다).
173
+ db.exec(WAL_PRAGMA);
164
174
  db.exec(sql);
165
175
  } catch (e) {
166
176
  db.close();
@@ -4,6 +4,15 @@
4
4
  // lock waits bounded, and acquire writer authority before a transaction reads
5
5
  // state that it may subsequently update. A deferred read-to-write upgrade can
6
6
  // fail immediately with SQLITE_BUSY even when busy_timeout is configured.
7
+ //
8
+ // ★This value must stay identical to `STORE_BUSY_TIMEOUT_MS` in
9
+ // agentlas_desktop/electron/store/db.ts. Until 2026-08-18 the Desktop waited 5s
10
+ // and the terminal 15s on the very same file, so under contention the Desktop
11
+ // was always the first to give up with SQLITE_BUSY — even when the terminal was
12
+ // the slow writer. That asymmetry made shared-file contention look like a
13
+ // Desktop-only bug. 15s is the agreed value: nothing holds a transaction on this
14
+ // file for long (the longest writer is the migration ladder), so it is a ceiling
15
+ // that is essentially never reached rather than added latency.
7
16
  const SQLITE_BUSY_TIMEOUT_MS = 15_000;
8
17
 
9
18
  // `foreign_keys` 는 파일이 아니라 **커넥션** 속성이다. 데스크탑은
@@ -485,14 +485,18 @@ function createDecisionReceipt({ taskId, stage, decision, resolution, role, usag
485
485
  modelId: normalized && normalized.exactModelId
486
486
  ? normalized.exactModelId
487
487
  : source === "user-pin" ? cleanText(resolution && resolution.model, 255) || null : null,
488
- effort: normalized ? normalized.effort : "none",
488
+ // "none" is an effort level; no request is not one. Rendering the
489
+ // absence as a level made the receipt claim a decision nobody made —
490
+ // measured on the desktop twin, 17 of 46 receipts said resolved.effort
491
+ // "none" while carrying no effort-* reason code at all.
492
+ effort: normalized ? normalized.effort : null,
489
493
  },
490
494
  resolved: {
491
495
  tier: resolution && resolution.tier ? cleanText(resolution.tier, 32) : normalized ? normalized.tier : null,
492
496
  provider: cleanText(resolution && resolution.provider, 80) || null,
493
497
  modelId: cleanText(resolution && resolution.model, 255) || null,
494
498
  sessionId: cleanText(resolution && resolution.runtimeId, 255) || null,
495
- effort: cleanText(resolution && resolution.effort, 16) || "none",
499
+ effort: cleanText(resolution && resolution.effort, 16) || null,
496
500
  },
497
501
  reasonCodes,
498
502
  inputFeatureHash: normalized && normalized.inputFeatureHash ? normalized.inputFeatureHash : featureHash,