@modootoday/datalab-extension-mcp 1.8.12 → 1.8.13

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.en.md CHANGED
@@ -58,7 +58,7 @@ One command finds **every** AI app on your computer and connects them automatica
58
58
  2. **Paste this command and press Enter.** The saved command names an exact bootstrap version. At startup and while idle, the connector checks the canonical release and promotes itself to the current secure, compatible version. Diagnostic environments can opt out with `--no-self-update`.
59
59
 
60
60
  ```
61
- npx -y @modootoday/datalab-extension-mcp@1.8.12 install
61
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 install
62
62
  ```
63
63
 
64
64
  Lines of English scrolling by is normal. This only downloads and runs the connector — it touches nothing else.
@@ -130,10 +130,10 @@ Apps it **only prints instructions for** (follow the on-screen guide): VS Code,
130
130
  Installing also places the bundled **writing skills**. An AI app reads the name and description of every installed skill on every start, so the more are installed the less room the conversation has. The default therefore places only the ones that work right away.
131
131
 
132
132
  ```
133
- npx -y @modootoday/datalab-extension-mcp@1.8.12 skills list
134
- npx -y @modootoday/datalab-extension-mcp@1.8.12 skills status
135
- npx -y @modootoday/datalab-extension-mcp@1.8.12 skills attach <name>
136
- npx -y @modootoday/datalab-extension-mcp@1.8.12 skills detach <name>
133
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 skills list
134
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 skills status
135
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 skills attach <name>
136
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 skills detach <name>
137
137
  ```
138
138
 
139
139
  - `list` shows everything in this version, name and description.
@@ -152,10 +152,10 @@ The **workbench** is this computer, where the connector runs. If you use more th
152
152
  Make the code from the panel with [등록 코드 만들기], or from the terminal.
153
153
 
154
154
  ```
155
- npx -y @modootoday/datalab-extension-mcp@1.8.12 browsers add "Work Chrome"
156
- npx -y @modootoday/datalab-extension-mcp@1.8.12 browsers list
157
- npx -y @modootoday/datalab-extension-mcp@1.8.12 browsers revoke <handle>
158
- npx -y @modootoday/datalab-extension-mcp@1.8.12 browsers repair
155
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers add "Work Chrome"
156
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers list
157
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers revoke <handle>
158
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers repair
159
159
  ```
160
160
 
161
161
  `add` **asks you to paste the management token** — the same value you used to
@@ -255,7 +255,7 @@ Start it again after a full quit and it picks the tools up. If they still aren't
255
255
  - **When the connection disappears** — run the very command you used at the start once more; it safely re-registers. (Some AI apps clean up their settings file and drop the connection.)
256
256
 
257
257
  ```
258
- npx -y @modootoday/datalab-extension-mcp@1.8.12 install
258
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 install
259
259
  ```
260
260
 
261
261
  - **`'npx' is not recognized...`** — Node.js isn't installed. The "No Node.js?" one-liner in Step 2 is the simplest fix.
@@ -285,7 +285,7 @@ Start it again after a full quit and it picks the tools up. If they still aren't
285
285
  1. Run this in a terminal (it removes the entries registered in your AI apps).
286
286
 
287
287
  ```
288
- npx -y @modootoday/datalab-extension-mcp@1.8.12 uninstall
288
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 uninstall
289
289
  ```
290
290
 
291
291
  2. Turn **MCP 연결** off in the extension settings. The panel is the bridge, so with it off a leftover config line can look nothing up.
package/README.md CHANGED
@@ -52,7 +52,7 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
52
52
  2. **아래 명령을 붙여넣고 Enter를 눌러요.** 저장되는 명령은 정확한 시작 버전을 적습니다. 커넥터는 시작할 때와 유휴 상태에서 정본 릴리스를 확인해 현재의 안전하고 호환되는 버전으로 승격합니다. 진단 환경에서는 `--no-self-update`로 승격을 끌 수 있어요.
53
53
 
54
54
  ```
55
- npx -y @modootoday/datalab-extension-mcp@1.8.12 install
55
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 install
56
56
  ```
57
57
 
58
58
  영어 글자가 여러 줄 올라오는 건 정상이에요. 이 명령은 커넥터를 내려받아 실행할 뿐, 다른 건 건드리지 않아요.
@@ -118,10 +118,10 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
118
118
  설치할 때 **글쓰기 도움말(스킬)** 도 함께 놓입니다. AI 앱은 켜질 때마다 설치된 도움말의 이름과 설명을 전부 읽기 때문에, 많이 깔수록 대화에 쓸 자리가 줄어들어요. 그래서 기본은 **바로 쓸 수 있는 것만** 놓습니다.
119
119
 
120
120
  ```
121
- npx -y @modootoday/datalab-extension-mcp@1.8.12 skills list
122
- npx -y @modootoday/datalab-extension-mcp@1.8.12 skills status
123
- npx -y @modootoday/datalab-extension-mcp@1.8.12 skills attach <이름>
124
- npx -y @modootoday/datalab-extension-mcp@1.8.12 skills detach <이름>
121
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 skills list
122
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 skills status
123
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 skills attach <이름>
124
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 skills detach <이름>
125
125
  ```
126
126
 
127
127
  - `list` 는 이 버전에 들어 있는 전부를 이름과 설명으로 보여 줘요.
@@ -140,10 +140,10 @@ npx -y @modootoday/datalab-extension-mcp@1.8.12 skills detach <이름>
140
140
  패널에서 [등록 코드 만들기]를 눌러도 되고, 터미널을 쓰셔도 돼요.
141
141
 
142
142
  ```
143
- npx -y @modootoday/datalab-extension-mcp@1.8.12 browsers add "회사 크롬"
144
- npx -y @modootoday/datalab-extension-mcp@1.8.12 browsers list
145
- npx -y @modootoday/datalab-extension-mcp@1.8.12 browsers revoke <handle>
146
- npx -y @modootoday/datalab-extension-mcp@1.8.12 browsers repair
143
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers add "회사 크롬"
144
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers list
145
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers revoke <handle>
146
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers repair
147
147
  ```
148
148
 
149
149
  `add` 를 실행하면 **관리 토큰을 붙여넣으라고 물어봐요** — 설치할 때와 같은 값이고, 패널의 `복사` 버튼으로 얻습니다. 그러면 브라우저 등록 코드가 **한 번만** 출력돼요. 커넥터는 그 코드를 저장하지 않고 지문만 남기므로 다시 보여 줄 수 없어요.
@@ -222,7 +222,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
222
222
  - **연결이 사라졌을 때** — 처음 실행했던 그 명령을 한 번 더 실행하면 안전하게 다시 등록돼요. (일부 AI 앱이 설정 파일을 정리하며 연결을 지우기도 해요.)
223
223
 
224
224
  ```
225
- npx -y @modootoday/datalab-extension-mcp@1.8.12 install
225
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 install
226
226
  ```
227
227
 
228
228
  - **`'npx'을(는) 내부 또는 외부 명령...`** — Node.js가 없는 거예요. 위 2단계의 "Node.js가 없다면" 한 줄 설치가 가장 간단해요.
@@ -252,7 +252,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
252
252
  1. 터미널에서 아래 명령을 실행해요(AI 앱에 등록된 설정을 지워 줘요).
253
253
 
254
254
  ```
255
- npx -y @modootoday/datalab-extension-mcp@1.8.12 uninstall
255
+ npx -y @modootoday/datalab-extension-mcp@1.8.13 uninstall
256
256
  ```
257
257
 
258
258
  2. 확장 설정에서 **MCP 연결을 꺼요.** 패널이 브릿지라, 꺼 두면 어딘가 설정 줄이 남아 있어도 AI 앱이 아무것도 조회할 수 없어요.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DEFAULT_HOST,
3
3
  DEFAULT_PORT
4
- } from "./chunk-YBNNWQMB.js";
4
+ } from "./chunk-QHEJF6FK.js";
5
5
  import {
6
6
  TOKEN_PROMPT_GUIDE,
7
7
  TOKEN_PROMPT_QUESTION,
@@ -4,7 +4,7 @@ import {
4
4
  mediaToolDescriptors,
5
5
  spawnDaemon,
6
6
  tryConnect
7
- } from "./chunk-YBNNWQMB.js";
7
+ } from "./chunk-QHEJF6FK.js";
8
8
  import {
9
9
  __export
10
10
  } from "./chunk-MLKGABMK.js";
@@ -1,15 +1,5 @@
1
- // ../mcp-server/dist/chunk-XKX4SOFG.js
1
+ // ../mcp-server/dist/chunk-HS7SL22A.js
2
2
  import { createHash } from "crypto";
3
- import { createHash as createHash2, randomUUID } from "crypto";
4
- import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
5
- import { join } from "path";
6
- import {
7
- createServer
8
- } from "http";
9
- import { pipeline } from "stream/promises";
10
- import { constants } from "fs";
11
- import { lstat, open } from "fs/promises";
12
- import { basename, isAbsolute } from "path";
13
3
  import {
14
4
  appendFileSync,
15
5
  mkdirSync,
@@ -18,14 +8,24 @@ import {
18
8
  statSync
19
9
  } from "fs";
20
10
  import { homedir } from "os";
21
- import { join as join2 } from "path";
11
+ import { join } from "path";
12
+ import { randomBytes } from "crypto";
13
+ import { readFileSync } from "fs";
14
+ import { mkdir, rename, writeFile } from "fs/promises";
15
+ import { dirname, join as join2 } from "path";
16
+ import { createHash as createHash2, randomUUID } from "crypto";
17
+ import { mkdir as mkdir2, readFile, readdir, rm, writeFile as writeFile2 } from "fs/promises";
18
+ import { join as join3 } from "path";
19
+ import {
20
+ createServer
21
+ } from "http";
22
+ import { pipeline } from "stream/promises";
23
+ import { constants } from "fs";
24
+ import { lstat, open } from "fs/promises";
25
+ import { basename, isAbsolute } from "path";
22
26
  import { createConnection } from "net";
23
27
  import { spawn } from "child_process";
24
28
  import { closeSync } from "fs";
25
- import { randomBytes } from "crypto";
26
- import { readFileSync } from "fs";
27
- import { mkdir as mkdir2, rename, writeFile as writeFile2 } from "fs/promises";
28
- import { dirname, join as join3 } from "path";
29
29
  import { createRequire } from "module";
30
30
  import { join as join4 } from "path";
31
31
  var __defProp = Object.defineProperty;
@@ -16334,62 +16334,419 @@ var BridgeError = class extends Error {
16334
16334
  if (timing) this.timing = timing;
16335
16335
  }
16336
16336
  };
16337
- var MAX_STAGE_BYTES = 10 * 1024 * 1024;
16338
- var DEFAULT_TOKEN_TTL_MS = 30 * 60 * 1e3;
16339
- var STAGE_CAPABILITY = "datalab-mcp-stage-v1";
16340
- function refuse(reason, retryable, message) {
16341
- return { state: "REFUSED", reason, retryable, message };
16337
+ var LOG_MAX_BYTES = 1024 * 1024;
16338
+ var DIR_NAME = ".datalab-mcp";
16339
+ var FILE_NAME = "connector.log";
16340
+ function logDir(home = homedir()) {
16341
+ return join(home, DIR_NAME);
16342
16342
  }
16343
- function segment(...parts) {
16344
- return createHash2("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
16343
+ function logPath(home = homedir()) {
16344
+ return join(logDir(home), FILE_NAME);
16345
16345
  }
16346
- function createMediaStage(opts) {
16347
- const now = opts.now ?? Date.now;
16348
- const ttl = opts.tokenTtlMs ?? DEFAULT_TOKEN_TTL_MS;
16349
- const ledgerDir = join(opts.root, "ledger");
16350
- const blobDir = join(opts.root, "blob");
16351
- const tokens = /* @__PURE__ */ new Map();
16352
- const active = /* @__PURE__ */ new Map();
16353
- const keyOf = (j) => segment(j.blogId, j.runId, j.actionId, j.jobId);
16354
- const ledgerPath2 = (key) => join(ledgerDir, `${key}.json`);
16355
- const blobPath = (key) => join(blobDir, key);
16356
- async function readRow(key) {
16357
- try {
16358
- const raw = await readFile(ledgerPath2(key), "utf8");
16359
- return JSON.parse(raw);
16360
- } catch (err) {
16361
- if (err.code === "ENOENT") return null;
16362
- return "unreadable";
16346
+ function rolledLogPath(home = homedir()) {
16347
+ return `${logPath(home)}.1`;
16348
+ }
16349
+ function rollIfLarge(home = homedir(), maxBytes = LOG_MAX_BYTES) {
16350
+ try {
16351
+ const size = statSync(logPath(home)).size;
16352
+ if (size < maxBytes) return false;
16353
+ renameSync(logPath(home), rolledLogPath(home));
16354
+ return true;
16355
+ } catch {
16356
+ return false;
16357
+ }
16358
+ }
16359
+ function openLogFd(home = homedir()) {
16360
+ try {
16361
+ mkdirSync(logDir(home), { recursive: true });
16362
+ rollIfLarge(home);
16363
+ return openSync(logPath(home), "a");
16364
+ } catch {
16365
+ return null;
16366
+ }
16367
+ }
16368
+ var FILE_NAME2 = "browsers.json";
16369
+ function ledgerPath(home) {
16370
+ return join2(logDir(home), FILE_NAME2);
16371
+ }
16372
+ function isRegistration(row) {
16373
+ if (typeof row !== "object" || row === null) return false;
16374
+ const r = row;
16375
+ const id = r["browserId"];
16376
+ const hash2 = r["tokenHash"];
16377
+ return (id === null || typeof id === "string" && id.length > 0) && typeof hash2 === "string" && /^[a-f0-9]{64}$/.test(hash2) && typeof r["label"] === "string" && typeof r["createdAt"] === "string";
16378
+ }
16379
+ function readLedger(path) {
16380
+ let raw;
16381
+ try {
16382
+ raw = readFileSync(path, "utf8");
16383
+ } catch (err) {
16384
+ if (err.code === "ENOENT") {
16385
+ return { state: "absent" };
16363
16386
  }
16387
+ return { state: "unreadable", reason: describe3(err) };
16364
16388
  }
16365
- async function writeRow(key, row) {
16366
- await mkdir(ledgerDir, { recursive: true });
16367
- await writeFile(ledgerPath2(key), JSON.stringify(row), "utf8");
16389
+ let parsed;
16390
+ try {
16391
+ parsed = JSON.parse(raw);
16392
+ } catch (err) {
16393
+ return { state: "unreadable", reason: describe3(err) };
16394
+ }
16395
+ if (!Array.isArray(parsed)) {
16396
+ return { state: "unreadable", reason: "not a list of browsers" };
16397
+ }
16398
+ if (!parsed.every(isRegistration)) {
16399
+ return { state: "unreadable", reason: "a row is not a browser" };
16400
+ }
16401
+ const browserIds = /* @__PURE__ */ new Set();
16402
+ const tokenHashes = /* @__PURE__ */ new Set();
16403
+ for (const row of parsed) {
16404
+ if (tokenHashes.has(row.tokenHash) || row.browserId !== null && browserIds.has(row.browserId)) {
16405
+ return { state: "unreadable", reason: "duplicate browser authority" };
16406
+ }
16407
+ tokenHashes.add(row.tokenHash);
16408
+ if (row.browserId !== null) browserIds.add(row.browserId);
16409
+ }
16410
+ return { state: "ok", rows: parsed };
16411
+ }
16412
+ function ledgerOverlapsToken(rows, tokenHash) {
16413
+ return rows.some((row) => row.tokenHash === tokenHash);
16414
+ }
16415
+ async function writeLedger(path, claims) {
16416
+ const queued = (writing.get(path) ?? Promise.resolve()).then(
16417
+ () => writeOnce(path, claims),
16418
+ () => writeOnce(path, claims)
16419
+ );
16420
+ writing.set(path, queued);
16421
+ try {
16422
+ await queued;
16423
+ } finally {
16424
+ if (writing.get(path) === queued) writing.delete(path);
16425
+ }
16426
+ }
16427
+ var writing = /* @__PURE__ */ new Map();
16428
+ async function writeOnce(path, claims) {
16429
+ const tmp = `${path}.${process.pid}.tmp`;
16430
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
16431
+ await writeFile(tmp, `${JSON.stringify(claims, null, 2)}
16432
+ `, {
16433
+ mode: 384
16434
+ });
16435
+ await rename(tmp, path);
16436
+ }
16437
+ function claimedDigestsFrom(claims) {
16438
+ const pairs = [];
16439
+ for (const claim of claims) {
16440
+ if (claim.browserId !== null && claim.browserId.length > 0) {
16441
+ pairs.push([claim.browserId, claim.tokenHash]);
16442
+ }
16443
+ }
16444
+ return pairs;
16445
+ }
16446
+ function describe3(err) {
16447
+ return err instanceof Error ? err.message : String(err);
16448
+ }
16449
+ var HANDLE_LENGTH = 8;
16450
+ var BrowserRegistryError = class extends Error {
16451
+ code;
16452
+ constructor(code) {
16453
+ super(code);
16454
+ this.name = "BrowserRegistryError";
16455
+ this.code = code;
16368
16456
  }
16457
+ };
16458
+ function mintToken() {
16459
+ return randomBytes(32).toString("hex");
16460
+ }
16461
+ function handleOf(claim) {
16462
+ return claim.tokenHash.slice(0, HANDLE_LENGTH);
16463
+ }
16464
+ function matches(claim, handle) {
16465
+ return handleOf(claim) === handle || claim.browserId === handle;
16466
+ }
16467
+ function createBrowserRegistry(opts) {
16468
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
16469
+ const revoked = /* @__PURE__ */ new Set();
16470
+ const initial = readLedger(opts.path);
16471
+ const unreadable = initial.state === "unreadable" ? initial.reason : null;
16472
+ let rows = initial.state === "ok" ? initial.rows.map((row) => ({ ...row })) : [];
16473
+ let mutationTail = Promise.resolve();
16474
+ let pendingMutations = 0;
16475
+ const view = (source = rows) => {
16476
+ if (unreadable !== null) {
16477
+ throw new Error(`browser registry is unreadable: ${unreadable}`);
16478
+ }
16479
+ if (!opts.forbiddenTokenHash) return { rows: [...source], quarantined: [] };
16480
+ const quarantined = source.filter(
16481
+ (row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
16482
+ );
16483
+ return {
16484
+ rows: source.filter(
16485
+ (row) => !timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
16486
+ ),
16487
+ quarantined
16488
+ };
16489
+ };
16490
+ const current = () => view().rows;
16491
+ const serialize = (work) => {
16492
+ pendingMutations += 1;
16493
+ const result = mutationTail.then(work, work);
16494
+ mutationTail = result.then(
16495
+ () => {
16496
+ pendingMutations -= 1;
16497
+ },
16498
+ () => {
16499
+ pendingMutations -= 1;
16500
+ }
16501
+ );
16502
+ return result;
16503
+ };
16504
+ const transact = (propose) => {
16505
+ if (unreadable !== null) {
16506
+ return Promise.reject(
16507
+ new Error(`browser registry is unreadable: ${unreadable}`)
16508
+ );
16509
+ }
16510
+ return serialize(async () => {
16511
+ const proposal = propose(rows.map((row) => ({ ...row })));
16512
+ await writeLedger(opts.path, proposal.next);
16513
+ rows = proposal.next;
16514
+ proposal.commit?.();
16515
+ return proposal.result;
16516
+ });
16517
+ };
16369
16518
  return {
16370
- async begin(req) {
16371
- const key = keyOf(req);
16372
- const row = await readRow(key);
16373
- if (row === "unreadable") {
16374
- return refuse("uploads_unproven", true, "\uC791\uC5C5 \uC0C1\uD0DC\uB97C \uC77D\uC9C0 \uBABB\uD588\uC5B4\uC694.");
16519
+ async add(label) {
16520
+ const token = mintToken();
16521
+ const claim = {
16522
+ browserId: null,
16523
+ tokenHash: digestToken(token),
16524
+ label,
16525
+ createdAt: now().toISOString()
16526
+ };
16527
+ return transact((snapshot) => ({
16528
+ next: [...snapshot, claim],
16529
+ result: { handle: handleOf(claim), token }
16530
+ }));
16531
+ },
16532
+ async list() {
16533
+ return current().map((claim) => ({
16534
+ handle: handleOf(claim),
16535
+ label: claim.label,
16536
+ browserId: claim.browserId,
16537
+ createdAt: claim.createdAt
16538
+ }));
16539
+ },
16540
+ claim(browserId, tokenHash) {
16541
+ if (pendingMutations > 0) return false;
16542
+ if (opts.claimed.has(browserId)) return false;
16543
+ for (const held of opts.claimed.values()) {
16544
+ if (timingSafeEqual(held, tokenHash)) return false;
16375
16545
  }
16376
- if (row) {
16377
- if (req.fence < row.fence) {
16378
- return refuse(
16379
- "stale_fence",
16380
- false,
16381
- "\uB354 \uC0C8\uB85C\uC6B4 \uC2DC\uB3C4\uAC00 \uC774\uBBF8 \uC9C4\uD589\uB410\uC5B4\uC694."
16382
- );
16383
- }
16384
- if (row.state === "SEALED") {
16385
- return refuse("already_sealed", false, "\uC774\uBBF8 \uBD09\uC778\uB41C \uC790\uC0B0\uC774\uC5D0\uC694.");
16386
- }
16387
- if (row.state === "OPEN") {
16388
- for (const [token2, held] of tokens) {
16389
- if (held.key === key && held.expiresAt > now()) {
16390
- return {
16391
- state: "OPEN",
16392
- jobId: req.jobId,
16546
+ let claims;
16547
+ try {
16548
+ claims = current();
16549
+ } catch {
16550
+ return false;
16551
+ }
16552
+ const row = claims.find(
16553
+ (claim) => claim.browserId === null && timingSafeEqual(claim.tokenHash, tokenHash)
16554
+ );
16555
+ if (!row) return false;
16556
+ const stored = rows.find((claim) => claim.tokenHash === row.tokenHash);
16557
+ if (!stored) return false;
16558
+ stored.browserId = browserId;
16559
+ opts.claimed.set(browserId, row.tokenHash);
16560
+ opts.onClaim?.(browserId, stored.label);
16561
+ const snapshot = rows.map((entry) => ({ ...entry }));
16562
+ void serialize(async () => {
16563
+ try {
16564
+ await writeLedger(opts.path, snapshot);
16565
+ } catch (error51) {
16566
+ if (stored.browserId === browserId) stored.browserId = null;
16567
+ if (opts.claimed.get(browserId) === row.tokenHash) {
16568
+ opts.claimed.delete(browserId);
16569
+ opts.onRevoke?.(browserId);
16570
+ }
16571
+ throw error51;
16572
+ }
16573
+ }).catch(() => void 0);
16574
+ return true;
16575
+ },
16576
+ async revoke(handle) {
16577
+ return transact((snapshot) => {
16578
+ const claims = view(snapshot).rows;
16579
+ const removed = claims.filter((claim) => matches(claim, handle));
16580
+ if (removed.length === 0) {
16581
+ return { next: snapshot, result: false };
16582
+ }
16583
+ const removedHashes = new Set(removed.map((claim) => claim.tokenHash));
16584
+ return {
16585
+ next: snapshot.filter((claim) => !removedHashes.has(claim.tokenHash)),
16586
+ result: true,
16587
+ commit: () => {
16588
+ for (const claim of removed) {
16589
+ revoked.add(claim.tokenHash);
16590
+ if (claim.browserId !== null) {
16591
+ opts.claimed.delete(claim.browserId);
16592
+ opts.onRevoke?.(claim.browserId);
16593
+ }
16594
+ }
16595
+ }
16596
+ };
16597
+ });
16598
+ },
16599
+ async repair() {
16600
+ return transact((snapshot) => {
16601
+ const currentView = view(snapshot);
16602
+ const removed = currentView.quarantined.map(handleOf);
16603
+ const removedHashes = new Set(
16604
+ currentView.quarantined.map((row) => row.tokenHash)
16605
+ );
16606
+ return {
16607
+ next: snapshot.filter((row) => !removedHashes.has(row.tokenHash)),
16608
+ result: { removed }
16609
+ };
16610
+ });
16611
+ },
16612
+ async repairRegistration(browserId, requestedLabel) {
16613
+ if (browserId.length === 0) {
16614
+ throw new BrowserRegistryError("registration_not_found");
16615
+ }
16616
+ return transact((snapshot) => {
16617
+ const currentView = view(snapshot);
16618
+ if (currentView.rows.some((row) => row.browserId === browserId)) {
16619
+ throw new BrowserRegistryError("registration_already_valid");
16620
+ }
16621
+ const quarantined = currentView.quarantined.filter(
16622
+ (row) => row.browserId === browserId
16623
+ );
16624
+ if (quarantined.length === 0) {
16625
+ throw new BrowserRegistryError(
16626
+ currentView.quarantined.length > 0 ? "registration_not_quarantined" : "registration_not_found"
16627
+ );
16628
+ }
16629
+ const token = mintToken();
16630
+ const tokenHash = digestToken(token);
16631
+ const label = requestedLabel?.trim() || quarantined[0]?.label || "\uC774 \uBE0C\uB77C\uC6B0\uC800";
16632
+ const removedHashes = new Set(quarantined.map((row) => row.tokenHash));
16633
+ const next = {
16634
+ browserId: null,
16635
+ tokenHash,
16636
+ label,
16637
+ createdAt: now().toISOString()
16638
+ };
16639
+ return {
16640
+ next: [
16641
+ ...snapshot.filter((row) => !removedHashes.has(row.tokenHash)),
16642
+ next
16643
+ ],
16644
+ result: {
16645
+ outcome: "repaired",
16646
+ removed: quarantined.map(handleOf),
16647
+ registration: { handle: handleOf(next), label, token }
16648
+ },
16649
+ commit: () => {
16650
+ for (const row of quarantined) {
16651
+ revoked.add(row.tokenHash);
16652
+ if (row.browserId !== null && opts.claimed.get(row.browserId) === row.tokenHash) {
16653
+ opts.claimed.delete(row.browserId);
16654
+ opts.onRevoke?.(row.browserId);
16655
+ }
16656
+ }
16657
+ }
16658
+ };
16659
+ });
16660
+ },
16661
+ status() {
16662
+ if (unreadable !== null) {
16663
+ return {
16664
+ state: "disabled",
16665
+ error: "browser_registry_unreadable",
16666
+ detail: unreadable
16667
+ };
16668
+ }
16669
+ if (opts.forbiddenTokenHash && ledgerOverlapsToken(rows, opts.forbiddenTokenHash)) {
16670
+ return {
16671
+ state: "degraded",
16672
+ error: "browser_registry_root_authority_overlap",
16673
+ quarantinedHandles: rows.filter(
16674
+ (row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
16675
+ ).map(handleOf),
16676
+ quarantined: rows.filter(
16677
+ (row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
16678
+ ).map((row) => ({
16679
+ handle: handleOf(row),
16680
+ label: row.label,
16681
+ browserId: row.browserId,
16682
+ createdAt: row.createdAt
16683
+ })),
16684
+ recovery: "repair_registration"
16685
+ };
16686
+ }
16687
+ return { state: "ready" };
16688
+ },
16689
+ wasRevoked(tokenHash) {
16690
+ return revoked.has(tokenHash);
16691
+ }
16692
+ };
16693
+ }
16694
+ var MAX_STAGE_BYTES = 10 * 1024 * 1024;
16695
+ var DEFAULT_TOKEN_TTL_MS = 30 * 60 * 1e3;
16696
+ var STAGE_CAPABILITY = "datalab-mcp-stage-v1";
16697
+ function refuse(reason, retryable, message) {
16698
+ return { state: "REFUSED", reason, retryable, message };
16699
+ }
16700
+ function segment(...parts) {
16701
+ return createHash2("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
16702
+ }
16703
+ function createMediaStage(opts) {
16704
+ const now = opts.now ?? Date.now;
16705
+ const ttl = opts.tokenTtlMs ?? DEFAULT_TOKEN_TTL_MS;
16706
+ const ledgerDir = join3(opts.root, "ledger");
16707
+ const blobDir = join3(opts.root, "blob");
16708
+ const tokens = /* @__PURE__ */ new Map();
16709
+ const active = /* @__PURE__ */ new Map();
16710
+ const keyOf = (j) => segment(j.blogId, j.runId, j.actionId, j.jobId);
16711
+ const ledgerPath2 = (key) => join3(ledgerDir, `${key}.json`);
16712
+ const blobPath = (key) => join3(blobDir, key);
16713
+ async function readRow(key) {
16714
+ try {
16715
+ const raw = await readFile(ledgerPath2(key), "utf8");
16716
+ return JSON.parse(raw);
16717
+ } catch (err) {
16718
+ if (err.code === "ENOENT") return null;
16719
+ return "unreadable";
16720
+ }
16721
+ }
16722
+ async function writeRow(key, row) {
16723
+ await mkdir2(ledgerDir, { recursive: true });
16724
+ await writeFile2(ledgerPath2(key), JSON.stringify(row), "utf8");
16725
+ }
16726
+ return {
16727
+ async begin(req) {
16728
+ const key = keyOf(req);
16729
+ const row = await readRow(key);
16730
+ if (row === "unreadable") {
16731
+ return refuse("uploads_unproven", true, "\uC791\uC5C5 \uC0C1\uD0DC\uB97C \uC77D\uC9C0 \uBABB\uD588\uC5B4\uC694.");
16732
+ }
16733
+ if (row) {
16734
+ if (req.fence < row.fence) {
16735
+ return refuse(
16736
+ "stale_fence",
16737
+ false,
16738
+ "\uB354 \uC0C8\uB85C\uC6B4 \uC2DC\uB3C4\uAC00 \uC774\uBBF8 \uC9C4\uD589\uB410\uC5B4\uC694."
16739
+ );
16740
+ }
16741
+ if (row.state === "SEALED") {
16742
+ return refuse("already_sealed", false, "\uC774\uBBF8 \uBD09\uC778\uB41C \uC790\uC0B0\uC774\uC5D0\uC694.");
16743
+ }
16744
+ if (row.state === "OPEN") {
16745
+ for (const [token2, held] of tokens) {
16746
+ if (held.key === key && held.expiresAt > now()) {
16747
+ return {
16748
+ state: "OPEN",
16749
+ jobId: req.jobId,
16393
16750
  uploadToken: token2,
16394
16751
  expiresAt: held.expiresAt,
16395
16752
  capabilityVersion: STAGE_CAPABILITY
@@ -16424,7 +16781,7 @@ function createMediaStage(opts) {
16424
16781
  }
16425
16782
  active.set(held.key, (active.get(held.key) ?? 0) + 1);
16426
16783
  try {
16427
- await mkdir(blobDir, { recursive: true });
16784
+ await mkdir2(blobDir, { recursive: true });
16428
16785
  const chunks = [];
16429
16786
  let total = 0;
16430
16787
  for await (const chunk of body) {
@@ -16435,7 +16792,7 @@ function createMediaStage(opts) {
16435
16792
  }
16436
16793
  chunks.push(buf);
16437
16794
  }
16438
- await writeFile(blobPath(held.key), Buffer.concat(chunks));
16795
+ await writeFile2(blobPath(held.key), Buffer.concat(chunks));
16439
16796
  return { ok: true, bytes: total };
16440
16797
  } finally {
16441
16798
  const left = (active.get(held.key) ?? 1) - 1;
@@ -16568,7 +16925,7 @@ function createMediaStage(opts) {
16568
16925
  const out = [];
16569
16926
  for (const dir of [ledgerDir, blobDir]) {
16570
16927
  try {
16571
- for (const name of await readdir(dir)) out.push(join(dir, name));
16928
+ for (const name of await readdir(dir)) out.push(join3(dir, name));
16572
16929
  } catch {
16573
16930
  }
16574
16931
  }
@@ -17026,6 +17383,11 @@ async function openLocalImage(path) {
17026
17383
  };
17027
17384
  }
17028
17385
  }
17386
+ var MCP_SERVER_HEALTH_NAME = "datalab-extension-mcp-server";
17387
+ var MCP_SERVER_HEALTH_SCHEMA = "datalab-extension-mcp-health-v1";
17388
+ var MCP_MANAGEMENT_CAPABILITIES = Object.freeze([
17389
+ "browser-registration-repair-v1"
17390
+ ]);
17029
17391
  var MAX_BODY_BYTES = 64 * 1024;
17030
17392
  var DEFAULT_TOOLS_CHANGED_DEBOUNCE_MS = 600;
17031
17393
  var MAX_MCP_BODY_BYTES2 = 256 * 1024;
@@ -17196,8 +17558,9 @@ function createHttpBridge(opts) {
17196
17558
  }
17197
17559
  if (req.method === "GET" && url2.pathname === "/bridge/health") {
17198
17560
  json2(res, 200, {
17199
- name: opts.identity.name,
17200
- version: opts.identity.version
17561
+ name: MCP_SERVER_HEALTH_NAME,
17562
+ version: opts.identity.version,
17563
+ schema: MCP_SERVER_HEALTH_SCHEMA
17201
17564
  });
17202
17565
  return;
17203
17566
  }
@@ -17313,7 +17676,8 @@ function createHttpBridge(opts) {
17313
17676
  json2(res, 200, {
17314
17677
  ok: true,
17315
17678
  browsers,
17316
- registryStatus: registry2.status()
17679
+ registryStatus: registry2.status(),
17680
+ capabilities: [...MCP_MANAGEMENT_CAPABILITIES]
17317
17681
  });
17318
17682
  return;
17319
17683
  }
@@ -17344,7 +17708,8 @@ function createHttpBridge(opts) {
17344
17708
  json2(res, 200, {
17345
17709
  ok: true,
17346
17710
  ...repaired,
17347
- registryStatus: registry2.status()
17711
+ registryStatus: registry2.status(),
17712
+ capabilities: [...MCP_MANAGEMENT_CAPABILITIES]
17348
17713
  });
17349
17714
  return;
17350
17715
  }
@@ -17352,6 +17717,14 @@ function createHttpBridge(opts) {
17352
17717
  }).catch((err) => {
17353
17718
  const why = err instanceof Error ? err.message : String(err);
17354
17719
  opts.log?.(`browsers route failed: ${why}`);
17720
+ if (err instanceof BrowserRegistryError) {
17721
+ json2(res, err.code === "registration_not_found" ? 404 : 409, {
17722
+ error: err.code,
17723
+ detail: err.code,
17724
+ capabilities: [...MCP_MANAGEMENT_CAPABILITIES]
17725
+ });
17726
+ return;
17727
+ }
17355
17728
  json2(res, 400, { error: "bad request", detail: why });
17356
17729
  });
17357
17730
  return;
@@ -17653,37 +18026,6 @@ function parsePort(raw) {
17653
18026
  if (!Number.isInteger(n) || n < 1 || n > 65535) return null;
17654
18027
  return n;
17655
18028
  }
17656
- var LOG_MAX_BYTES = 1024 * 1024;
17657
- var DIR_NAME = ".datalab-mcp";
17658
- var FILE_NAME = "connector.log";
17659
- function logDir(home = homedir()) {
17660
- return join2(home, DIR_NAME);
17661
- }
17662
- function logPath(home = homedir()) {
17663
- return join2(logDir(home), FILE_NAME);
17664
- }
17665
- function rolledLogPath(home = homedir()) {
17666
- return `${logPath(home)}.1`;
17667
- }
17668
- function rollIfLarge(home = homedir(), maxBytes = LOG_MAX_BYTES) {
17669
- try {
17670
- const size = statSync(logPath(home)).size;
17671
- if (size < maxBytes) return false;
17672
- renameSync(logPath(home), rolledLogPath(home));
17673
- return true;
17674
- } catch {
17675
- return false;
17676
- }
17677
- }
17678
- function openLogFd(home = homedir()) {
17679
- try {
17680
- mkdirSync(logDir(home), { recursive: true });
17681
- rollIfLarge(home);
17682
- return openSync(logPath(home), "a");
17683
- } catch {
17684
- return null;
17685
- }
17686
- }
17687
18029
  function tryConnect(host, port, timeoutMs, deps = {}) {
17688
18030
  const connect = deps.createConnection ?? createConnection;
17689
18031
  return new Promise((resolve) => {
@@ -17798,318 +18140,7 @@ var Lifecycle = class {
17798
18140
  }
17799
18141
  }
17800
18142
  };
17801
- var FILE_NAME2 = "browsers.json";
17802
- function ledgerPath(home) {
17803
- return join3(logDir(home), FILE_NAME2);
17804
- }
17805
- function isRegistration(row) {
17806
- if (typeof row !== "object" || row === null) return false;
17807
- const r = row;
17808
- const id = r["browserId"];
17809
- const hash2 = r["tokenHash"];
17810
- return (id === null || typeof id === "string" && id.length > 0) && typeof hash2 === "string" && /^[a-f0-9]{64}$/.test(hash2) && typeof r["label"] === "string" && typeof r["createdAt"] === "string";
17811
- }
17812
- function readLedger(path) {
17813
- let raw;
17814
- try {
17815
- raw = readFileSync(path, "utf8");
17816
- } catch (err) {
17817
- if (err.code === "ENOENT") {
17818
- return { state: "absent" };
17819
- }
17820
- return { state: "unreadable", reason: describe3(err) };
17821
- }
17822
- let parsed;
17823
- try {
17824
- parsed = JSON.parse(raw);
17825
- } catch (err) {
17826
- return { state: "unreadable", reason: describe3(err) };
17827
- }
17828
- if (!Array.isArray(parsed)) {
17829
- return { state: "unreadable", reason: "not a list of browsers" };
17830
- }
17831
- if (!parsed.every(isRegistration)) {
17832
- return { state: "unreadable", reason: "a row is not a browser" };
17833
- }
17834
- const browserIds = /* @__PURE__ */ new Set();
17835
- const tokenHashes = /* @__PURE__ */ new Set();
17836
- for (const row of parsed) {
17837
- if (tokenHashes.has(row.tokenHash) || row.browserId !== null && browserIds.has(row.browserId)) {
17838
- return { state: "unreadable", reason: "duplicate browser authority" };
17839
- }
17840
- tokenHashes.add(row.tokenHash);
17841
- if (row.browserId !== null) browserIds.add(row.browserId);
17842
- }
17843
- return { state: "ok", rows: parsed };
17844
- }
17845
- function ledgerOverlapsToken(rows, tokenHash) {
17846
- return rows.some((row) => row.tokenHash === tokenHash);
17847
- }
17848
- async function writeLedger(path, claims) {
17849
- const queued = (writing.get(path) ?? Promise.resolve()).then(
17850
- () => writeOnce(path, claims),
17851
- () => writeOnce(path, claims)
17852
- );
17853
- writing.set(path, queued);
17854
- try {
17855
- await queued;
17856
- } finally {
17857
- if (writing.get(path) === queued) writing.delete(path);
17858
- }
17859
- }
17860
- var writing = /* @__PURE__ */ new Map();
17861
- async function writeOnce(path, claims) {
17862
- const tmp = `${path}.${process.pid}.tmp`;
17863
- await mkdir2(dirname(path), { recursive: true, mode: 448 });
17864
- await writeFile2(tmp, `${JSON.stringify(claims, null, 2)}
17865
- `, {
17866
- mode: 384
17867
- });
17868
- await rename(tmp, path);
17869
- }
17870
- function claimedDigestsFrom(claims) {
17871
- const pairs = [];
17872
- for (const claim of claims) {
17873
- if (claim.browserId !== null && claim.browserId.length > 0) {
17874
- pairs.push([claim.browserId, claim.tokenHash]);
17875
- }
17876
- }
17877
- return pairs;
17878
- }
17879
- function describe3(err) {
17880
- return err instanceof Error ? err.message : String(err);
17881
- }
17882
- var HANDLE_LENGTH = 8;
17883
- function mintToken() {
17884
- return randomBytes(32).toString("hex");
17885
- }
17886
- function handleOf(claim) {
17887
- return claim.tokenHash.slice(0, HANDLE_LENGTH);
17888
- }
17889
- function matches(claim, handle) {
17890
- return handleOf(claim) === handle || claim.browserId === handle;
17891
- }
17892
- function createBrowserRegistry(opts) {
17893
- const now = opts.now ?? (() => /* @__PURE__ */ new Date());
17894
- const revoked = /* @__PURE__ */ new Set();
17895
- const initial = readLedger(opts.path);
17896
- const unreadable = initial.state === "unreadable" ? initial.reason : null;
17897
- let rows = initial.state === "ok" ? initial.rows.map((row) => ({ ...row })) : [];
17898
- let mutationTail = Promise.resolve();
17899
- let pendingMutations = 0;
17900
- const view = (source = rows) => {
17901
- if (unreadable !== null) {
17902
- throw new Error(`browser registry is unreadable: ${unreadable}`);
17903
- }
17904
- if (!opts.forbiddenTokenHash) return { rows: [...source], quarantined: [] };
17905
- const quarantined = source.filter(
17906
- (row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
17907
- );
17908
- return {
17909
- rows: source.filter(
17910
- (row) => !timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
17911
- ),
17912
- quarantined
17913
- };
17914
- };
17915
- const current = () => view().rows;
17916
- const serialize = (work) => {
17917
- pendingMutations += 1;
17918
- const result = mutationTail.then(work, work);
17919
- mutationTail = result.then(
17920
- () => {
17921
- pendingMutations -= 1;
17922
- },
17923
- () => {
17924
- pendingMutations -= 1;
17925
- }
17926
- );
17927
- return result;
17928
- };
17929
- const transact = (propose) => {
17930
- if (unreadable !== null) {
17931
- return Promise.reject(
17932
- new Error(`browser registry is unreadable: ${unreadable}`)
17933
- );
17934
- }
17935
- return serialize(async () => {
17936
- const proposal = propose(rows.map((row) => ({ ...row })));
17937
- await writeLedger(opts.path, proposal.next);
17938
- rows = proposal.next;
17939
- proposal.commit?.();
17940
- return proposal.result;
17941
- });
17942
- };
17943
- return {
17944
- async add(label) {
17945
- const token = mintToken();
17946
- const claim = {
17947
- browserId: null,
17948
- tokenHash: digestToken(token),
17949
- label,
17950
- createdAt: now().toISOString()
17951
- };
17952
- return transact((snapshot) => ({
17953
- next: [...snapshot, claim],
17954
- result: { handle: handleOf(claim), token }
17955
- }));
17956
- },
17957
- async list() {
17958
- return current().map((claim) => ({
17959
- handle: handleOf(claim),
17960
- label: claim.label,
17961
- browserId: claim.browserId,
17962
- createdAt: claim.createdAt
17963
- }));
17964
- },
17965
- claim(browserId, tokenHash) {
17966
- if (pendingMutations > 0) return false;
17967
- if (opts.claimed.has(browserId)) return false;
17968
- for (const held of opts.claimed.values()) {
17969
- if (timingSafeEqual(held, tokenHash)) return false;
17970
- }
17971
- let claims;
17972
- try {
17973
- claims = current();
17974
- } catch {
17975
- return false;
17976
- }
17977
- const row = claims.find(
17978
- (claim) => claim.browserId === null && timingSafeEqual(claim.tokenHash, tokenHash)
17979
- );
17980
- if (!row) return false;
17981
- const stored = rows.find((claim) => claim.tokenHash === row.tokenHash);
17982
- if (!stored) return false;
17983
- stored.browserId = browserId;
17984
- opts.claimed.set(browserId, row.tokenHash);
17985
- opts.onClaim?.(browserId, stored.label);
17986
- const snapshot = rows.map((entry) => ({ ...entry }));
17987
- void serialize(async () => {
17988
- try {
17989
- await writeLedger(opts.path, snapshot);
17990
- } catch (error51) {
17991
- if (stored.browserId === browserId) stored.browserId = null;
17992
- if (opts.claimed.get(browserId) === row.tokenHash) {
17993
- opts.claimed.delete(browserId);
17994
- opts.onRevoke?.(browserId);
17995
- }
17996
- throw error51;
17997
- }
17998
- }).catch(() => void 0);
17999
- return true;
18000
- },
18001
- async revoke(handle) {
18002
- return transact((snapshot) => {
18003
- const claims = view(snapshot).rows;
18004
- const removed = claims.filter((claim) => matches(claim, handle));
18005
- if (removed.length === 0) {
18006
- return { next: snapshot, result: false };
18007
- }
18008
- const removedHashes = new Set(removed.map((claim) => claim.tokenHash));
18009
- return {
18010
- next: snapshot.filter((claim) => !removedHashes.has(claim.tokenHash)),
18011
- result: true,
18012
- commit: () => {
18013
- for (const claim of removed) {
18014
- revoked.add(claim.tokenHash);
18015
- if (claim.browserId !== null) {
18016
- opts.claimed.delete(claim.browserId);
18017
- opts.onRevoke?.(claim.browserId);
18018
- }
18019
- }
18020
- }
18021
- };
18022
- });
18023
- },
18024
- async repair() {
18025
- return transact((snapshot) => {
18026
- const currentView = view(snapshot);
18027
- const removed = currentView.quarantined.map(handleOf);
18028
- const removedHashes = new Set(
18029
- currentView.quarantined.map((row) => row.tokenHash)
18030
- );
18031
- return {
18032
- next: snapshot.filter((row) => !removedHashes.has(row.tokenHash)),
18033
- result: { removed }
18034
- };
18035
- });
18036
- },
18037
- async repairRegistration(browserId, requestedLabel) {
18038
- if (browserId.length === 0) throw new Error("browser id is required");
18039
- const token = mintToken();
18040
- const tokenHash = digestToken(token);
18041
- return transact((snapshot) => {
18042
- const currentView = view(snapshot);
18043
- if (currentView.rows.some((row) => row.browserId === browserId)) {
18044
- throw new Error("registration_already_valid");
18045
- }
18046
- const quarantined = currentView.quarantined.filter(
18047
- (row) => row.browserId === browserId
18048
- );
18049
- const label = requestedLabel?.trim() || quarantined[0]?.label || "\uC774 \uBE0C\uB77C\uC6B0\uC800";
18050
- const removedHashes = new Set(quarantined.map((row) => row.tokenHash));
18051
- const next = {
18052
- browserId: null,
18053
- tokenHash,
18054
- label,
18055
- createdAt: now().toISOString()
18056
- };
18057
- return {
18058
- next: [
18059
- ...snapshot.filter((row) => !removedHashes.has(row.tokenHash)),
18060
- next
18061
- ],
18062
- result: {
18063
- outcome: quarantined.length > 0 ? "repaired" : "created",
18064
- removed: quarantined.map(handleOf),
18065
- registration: { handle: handleOf(next), label, token }
18066
- },
18067
- commit: () => {
18068
- for (const row of quarantined) {
18069
- revoked.add(row.tokenHash);
18070
- if (row.browserId !== null && opts.claimed.get(row.browserId) === row.tokenHash) {
18071
- opts.claimed.delete(row.browserId);
18072
- opts.onRevoke?.(row.browserId);
18073
- }
18074
- }
18075
- }
18076
- };
18077
- });
18078
- },
18079
- status() {
18080
- if (unreadable !== null) {
18081
- return {
18082
- state: "disabled",
18083
- error: "browser_registry_unreadable",
18084
- detail: unreadable
18085
- };
18086
- }
18087
- if (opts.forbiddenTokenHash && ledgerOverlapsToken(rows, opts.forbiddenTokenHash)) {
18088
- return {
18089
- state: "degraded",
18090
- error: "browser_registry_root_authority_overlap",
18091
- quarantinedHandles: rows.filter(
18092
- (row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
18093
- ).map(handleOf),
18094
- quarantined: rows.filter(
18095
- (row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
18096
- ).map((row) => ({
18097
- handle: handleOf(row),
18098
- label: row.label,
18099
- browserId: row.browserId,
18100
- createdAt: row.createdAt
18101
- })),
18102
- recovery: "repair_registration"
18103
- };
18104
- }
18105
- return { state: "ready" };
18106
- },
18107
- wasRevoked(tokenHash) {
18108
- return revoked.has(tokenHash);
18109
- }
18110
- };
18111
- }
18112
- var NAME = "datalab-extension-mcp-server";
18143
+ var NAME = MCP_SERVER_HEALTH_NAME;
18113
18144
  function readVersion() {
18114
18145
  return createRequire(import.meta.url)("../package.json").version;
18115
18146
  }
package/dist/cli.js CHANGED
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  dispatchCli,
4
4
  runAdapter
5
- } from "./chunk-YBQ7ZLSQ.js";
5
+ } from "./chunk-M24B42VT.js";
6
6
  import {
7
7
  runDaemon
8
- } from "./chunk-YBNNWQMB.js";
8
+ } from "./chunk-QHEJF6FK.js";
9
9
  import "./chunk-MLKGABMK.js";
10
10
 
11
11
  // src/cli.ts
@@ -62,7 +62,7 @@ async function main() {
62
62
  // Loaded lazily, like the installer: the adapter path is what an MCP host
63
63
  // spawns and must not pay for a command only an operator types.
64
64
  browsers: async (argv) => {
65
- const { runBrowsers } = await import("./browsers-4L3WAODH.js");
65
+ const { runBrowsers } = await import("./browsers-M2DRBQB6.js");
66
66
  const { createNodeIo } = await import("./dist-R3VRQN24.js");
67
67
  const io = createNodeIo();
68
68
  const code = await runBrowsers(argv, {
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  dispatchCli,
4
4
  ensureDaemonRunning,
5
5
  runAdapter
6
- } from "./chunk-YBQ7ZLSQ.js";
7
- import "./chunk-YBNNWQMB.js";
6
+ } from "./chunk-M24B42VT.js";
7
+ import "./chunk-QHEJF6FK.js";
8
8
  import "./chunk-MLKGABMK.js";
9
9
  export {
10
10
  createAdapterServer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modootoday/datalab-extension-mcp",
3
- "version": "1.8.12",
3
+ "version": "1.8.13",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",