@modootoday/datalab-extension-mcp 1.8.10 → 1.8.12

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
@@ -24,11 +24,11 @@ Set this up once per computer.
24
24
 
25
25
  ## Three steps
26
26
 
27
- | Step | What | Where |
28
- | ----------------- | --------------------------------------------------------------- | ------------------------ |
29
- | **1. Turn it on** | Flip a switch in the extension panel and note the pairing token | DataLab Tools side panel |
30
- | **2. Install** | Run one command and paste the token | Terminal |
31
- | **3. Verify** | Ask a test question in your AI app | Your AI app |
27
+ | Step | What | Where |
28
+ | ----------------- | ------------------------------------------------ | ------------------------ |
29
+ | **1. Turn it on** | Flip a switch and note the host management token | DataLab Tools side panel |
30
+ | **2. Install** | Run one command and paste the token | Terminal |
31
+ | **3. Verify** | Ask a test question in your AI app | Your AI app |
32
32
 
33
33
  Feel free to read [Safety](#safety) first if you'd like reassurance before starting.
34
34
 
@@ -42,32 +42,32 @@ Feel free to read [Safety](#safety) first if you'd like reassurance before start
42
42
  >
43
43
  > _(When a connected AI app asks, your Naver data is sent to that app and its AI provider's servers.)_
44
44
 
45
- 4. When the **"MCP 연결" (MCP connection) card** shows in the panel, you're set. You'll use the **pairing token** on it in the next step — press **[복사]** (copy) to put it on the clipboard now. ([보기] reveals it if you want to see it.)
45
+ 4. When the **"MCP 연결" (MCP connection) card** shows in the panel, you're set. You'll use the **MCP host management token** on it in the next step — press **[복사]** (copy) to put it on the clipboard now. ([보기] reveals it if you want to see it.)
46
46
 
47
47
  > "이 코드는 이 컴퓨터 전용 비밀번호예요. 다른 사람에게 보내지 마세요."
48
48
  >
49
49
  > _(This code is a password for this computer only. Don't send it to anyone.)_
50
50
 
51
- Avoid sharing a screenshot or the copied setup. If it may have leaked, press **[새 토큰 발급]** (issue a new token) on the card — the old code stops working immediately and a matching setup lands on your clipboard. Paste that into each AI app you had connected.
51
+ Avoid sharing a screenshot or the copied setup. This token belongs only in AI host configuration and workbench management. Browser registration uses separate one-time codes which are never shown again by the panel.
52
52
 
53
53
  ## Step 2 — Install
54
54
 
55
55
  One command finds **every** AI app on your computer and connects them automatically. Nothing to do per app.
56
56
 
57
57
  1. **Open a terminal** — on Windows type `cmd` in the search box → "Command Prompt"; on Mac press `⌘+Space` and type `Terminal`; on Linux open your terminal app (often `Ctrl+Alt+T`).
58
- 2. **Paste this command and press Enter.** (`@1.8.10` is the pinned version, so the connector never changes behind your back.)
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.10 install
61
+ npx -y @modootoday/datalab-extension-mcp@1.8.12 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.
65
65
 
66
- 3. **Paste the pairing token.** The helper asks:
66
+ 3. **Paste the management token.** The helper asks:
67
67
 
68
- > "복사한 연결 토큰을 여기에 붙여넣어 주세요:"
68
+ > "복사한 관리 토큰을 여기에 붙여넣어 주세요:"
69
69
  >
70
- > _(Paste the connection token you copied here:)_
70
+ > _(Paste the management token you copied here:)_
71
71
 
72
72
  Paste the token from the Step 1 card and press Enter.
73
73
 
@@ -105,7 +105,7 @@ If the command errors with `'npx' is not recognized`, Node.js isn't installed. T
105
105
  curl -fsSL https://app.datalab.tools/install/mcp.sh | sh
106
106
  ```
107
107
 
108
- When it asks for the pairing token, paste the one from the Step 1 card. If you have no AI app yet, you can also pick one to install right there (choosing is optional).
108
+ When it asks for the management token, paste the one from the Step 1 card. If you have no AI app yet, you can also pick one to install right there (choosing is optional).
109
109
 
110
110
  > Want to see what it does before running it? Open that URL in a browser — it serves the script itself, in plain text.
111
111
 
@@ -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 skills list
134
- npx -y @modootoday/datalab-extension-mcp skills status
135
- npx -y @modootoday/datalab-extension-mcp skills attach <name>
136
- npx -y @modootoday/datalab-extension-mcp skills detach <name>
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>
137
137
  ```
138
138
 
139
139
  - `list` shows everything in this version, name and description.
@@ -152,12 +152,13 @@ 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 browsers add "Work Chrome"
156
- npx -y @modootoday/datalab-extension-mcp browsers list
157
- npx -y @modootoday/datalab-extension-mcp browsers revoke <handle>
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
158
159
  ```
159
160
 
160
- `add` **asks you to paste the pairing token** — the same value you used to
161
+ `add` **asks you to paste the management token** — the same value you used to
161
162
  install, copied from the panel. It then prints a code **once**. The connector
162
163
  keeps only a fingerprint of it, so it cannot show it to you again.
163
164
 
@@ -168,6 +169,8 @@ Paste that code into the other browser's side panel, under **이 크롬을 워
168
169
  `connected`.
169
170
  - After `revoke`, that browser cannot rejoin. To let it back in, **revoke and
170
171
  then `add` again** — handing the same browser a new code alone is refused.
172
+ - If `list` asks for registry repair, run `repair`, then issue a fresh registration
173
+ code only for the affected browser. Healthy browser rows remain in place.
171
174
  - With the connector stopped, these commands do nothing and say so. Opening the
172
175
  AI app you paired brings it back.
173
176
 
@@ -205,7 +208,7 @@ The connector is running with the code its AI app handed it at startup. **[다
205
208
  2. Paste it into the AI app's settings
206
209
  3. **Fully quit that AI app and start it again** — not just close the window (Windows: tray icon → quit; Mac: `⌘+Q`; Linux: Quit from the app menu)
207
210
 
208
- Step 3 is what brings the connector back up with the new code. **Don't press [새 토큰 발급] (issue a new token) again** — that changes the code once more and puts you right back here.
211
+ Step 3 brings the connector back with the management token stored in the AI app. Browser registration codes are not changed by this procedure.
209
212
 
210
213
  ### "연결 통로(포트)를 다른 프로그램이 쓰고 있어요."
211
214
 
@@ -252,7 +255,7 @@ Start it again after a full quit and it picks the tools up. If they still aren't
252
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.)
253
256
 
254
257
  ```
255
- npx -y @modootoday/datalab-extension-mcp@1.8.10 install
258
+ npx -y @modootoday/datalab-extension-mcp@1.8.12 install
256
259
  ```
257
260
 
258
261
  - **`'npx' is not recognized...`** — Node.js isn't installed. The "No Node.js?" one-liner in Step 2 is the simplest fix.
@@ -272,9 +275,9 @@ Start it again after a full quit and it picks the tools up. If they still aren't
272
275
  - **Lookups just work; editing is asked for.** Looking data up is immediate. Changing your **Naver draft** or a photo/video editor needs **AI editing** allowed, in the side panel or in settings, and you pick how long that lasts: allow it for this connection and closing the panel turns it back off, or save **always allow** so you don't switch it on again (turned off in the same place). In the draft, that covers setting the title, inserting text, and undo. Replacing the whole body, inserting a photo, closing an editor window, and deleting anything are **not covered by "always allow"** — that setting does not open them. Anything that costs money is opened by no setting at all and is asked about **every single time**.
273
276
  - **It never publishes.** Posting or making a draft public is not connected — edits stay in the draft window, and publishing is yours to do.
274
277
  - **The connector stores no logins, passwords, or cookies.** It never contacts Naver directly. Every lookup happens inside your own logged-in browser.
275
- - **It works only inside your computer.** Nothing outside can connect, and it's double-locked: the connection token plus a check that the caller really is your extension.
278
+ - **It works only inside your computer.** Nothing outside can connect, and browser sessions use separate registered credentials plus extension identity checks.
276
279
  - **How data moves** — "연결된 AI 앱이 요청하면 네이버 데이터가 해당 앱과 그 AI 제공사 서버로 전달돼요." When the AI app asks, the result goes to that AI provider (e.g. Anthropic) — exactly what you agreed to when turning this on. Retention and training are governed by each provider's policy, so check the privacy policy of the AI app you use.
277
- - **The version is pinned and never changes behind your back.** When a new version ships, the panel tells you and the choice is yours.
280
+ - **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`.
278
281
  - **The full source is open.** That said, it can't stop malware already running under your own account — this connector's locks control remote and cross-origin access, not an already-compromised host.
279
282
 
280
283
  ## Removing it
@@ -282,7 +285,7 @@ Start it again after a full quit and it picks the tools up. If they still aren't
282
285
  1. Run this in a terminal (it removes the entries registered in your AI apps).
283
286
 
284
287
  ```
285
- npx -y @modootoday/datalab-extension-mcp@1.8.10 uninstall
288
+ npx -y @modootoday/datalab-extension-mcp@1.8.12 uninstall
286
289
  ```
287
290
 
288
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.
@@ -298,7 +301,7 @@ Deleting the extension also renders any leftover connector inert. A leftover lin
298
301
  - **Does the ChatGPT website work?** No. Only the installed desktop app connects.
299
302
  - **Do I keep it always on?** No. It connects only while the panel is open.
300
303
  - **What data does it read?** Blog stats, keywords, ads — what DataLab Tools shows. Scoped to the Naver account you're logged into in Chrome.
301
- - **Other computers?** Yes, but set it up once per computer; the pairing token is per-computer too.
304
+ - **Other computers?** Yes, but set it up once per computer; the management token is per-computer too.
302
305
  </details>
303
306
 
304
307
  ## Developer appendix
@@ -324,7 +327,7 @@ Deleting the extension also renders any leftover connector inert. A leftover lin
324
327
 
325
328
  **Architecture** — the connector is a relay with no credentials and no egress. The tool list is served by the extension; the canonical catalog is the store-reviewed extension code. Transport is loopback HTTP+SSE with dual auth (Origin check + token check), and the allowlist — both the read-only half and the tier table that gates every write — is enforced by the extension. Canonical allowlist (with per-item exclusion reasons): <https://github.com/modootoday/datalab-extension-mcp/blob/main/mcp-core/src/allowlist.ts>
326
329
 
327
- **Supply chain** — the public repo is the entire source. npm publishing uses OIDC trusted publishing + provenance, with no long-lived token (`npm audit signatures` verifies). Every install surface pins an exact version, so a compromised distribution path can't push new code to installed users; upgrades always go through the user's explicit choice.
330
+ **Supply chain** — the public repo is the entire source. npm publishing uses OIDC trusted publishing + provenance, with no long-lived token (`npm audit signatures` verifies). Install configuration records an exact bootstrap version, and canonical promotion uses the same attested npm package path.
328
331
 
329
332
  **License** — MIT ([LICENSE](./LICENSE)).
330
333
 
package/README.md CHANGED
@@ -24,7 +24,7 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
24
24
 
25
25
  | 단계 | 하는 일 | 어디서 |
26
26
  | ----------- | ------------------------------------------------ | ------------------------ |
27
- | **1. 켜기** | 확장 패널에서 스위치를 켜고 연결 토큰을 확인해요 | 데이터랩툴즈 사이드 패널 |
27
+ | **1. 켜기** | 확장 패널에서 스위치를 켜고 관리 토큰을 확인해요 | 데이터랩툴즈 사이드 패널 |
28
28
  | **2. 설치** | 명령 한 줄을 실행하고 토큰을 붙여넣어요 | 터미널 |
29
29
  | **3. 확인** | AI 앱에서 시험 질문을 해 봐요 | AI 앱 |
30
30
 
@@ -38,28 +38,28 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
38
38
 
39
39
  > "연결된 AI 앱이 요청하면 네이버 데이터가 해당 앱과 그 AI 제공사 서버로 전달돼요."
40
40
 
41
- 4. 패널에 **"MCP 연결" 카드**가 보이면 성공이에요. 다음 단계에서 여기 있는 **연결 토큰**을 씁니다. **[복사]** 버튼을 눌러 복사해 두세요(눈으로 확인하고 싶으면 [보기]).
41
+ 4. 패널에 **"MCP 연결" 카드**가 보이면 성공이에요. 다음 단계에서 여기 있는 **MCP 호스트 관리 토큰**을 씁니다. **[복사]** 버튼을 눌러 복사해 두세요(눈으로 확인하고 싶으면 [보기]).
42
42
 
43
43
  > "이 코드는 이 컴퓨터 전용 비밀번호예요. 다른 사람에게 보내지 마세요."
44
44
 
45
- 캡처 공유·채팅 전송은 피해 주세요. 혹시 새어 나간 것 같으면 카드의 **[새 토큰 발급]** 을 누르세요 — 옛 코드가 그 자리에서 무효가 되고, 새 설정이 클립보드에 담겨요. 연결해 둔 AI 앱마다 그 설정을 다시 넣어 주면 됩니다.
45
+ 캡처 공유·채팅 전송은 피해 주세요. 이 토큰은 AI 앱 설정과 워크벤치 관리에만 사용하며, 브라우저 등록 칸에는 붙여넣지 마세요. 브라우저마다 별도 등록 코드가 발급되고 그 코드는 패널에 다시 표시되지 않습니다.
46
46
 
47
47
  ## 2단계 — 설치
48
48
 
49
49
  명령 한 줄이면 컴퓨터에 있는 AI 앱을 **모두 자동으로** 찾아 연결해 줘요. 앱마다 따로 할 일은 없어요.
50
50
 
51
51
  1. **터미널을 열어요** — Windows는 검색창에 `cmd` → "명령 프롬프트", Mac은 `⌘+스페이스`로 `터미널`, Linux는 터미널 앱(보통 `Ctrl+Alt+T`)이에요.
52
- 2. **아래 명령을 붙여넣고 Enter를 눌러요.** (`@1.8.10`은 커넥터가 나도 모르게 바뀌지 않도록 고정한 버전이에요.)
52
+ 2. **아래 명령을 붙여넣고 Enter를 눌러요.** 저장되는 명령은 정확한 시작 버전을 적습니다. 커넥터는 시작할 때와 유휴 상태에서 정본 릴리스를 확인해 현재의 안전하고 호환되는 버전으로 승격합니다. 진단 환경에서는 `--no-self-update`로 승격을 끌 수 있어요.
53
53
 
54
54
  ```
55
- npx -y @modootoday/datalab-extension-mcp@1.8.10 install
55
+ npx -y @modootoday/datalab-extension-mcp@1.8.12 install
56
56
  ```
57
57
 
58
58
  영어 글자가 여러 줄 올라오는 건 정상이에요. 이 명령은 커넥터를 내려받아 실행할 뿐, 다른 건 건드리지 않아요.
59
59
 
60
- 3. **연결 토큰을 붙여넣어요.** 도우미가 이렇게 물어봐요.
60
+ 3. **관리 토큰을 붙여넣어요.** 도우미가 이렇게 물어봐요.
61
61
 
62
- > "복사한 연결 토큰을 여기에 붙여넣어 주세요:"
62
+ > "복사한 관리 토큰을 여기에 붙여넣어 주세요:"
63
63
 
64
64
  1단계 카드에서 복사한 토큰을 붙여넣고 Enter를 눌러요.
65
65
 
@@ -93,7 +93,7 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
93
93
  curl -fsSL https://app.datalab.tools/install/mcp.sh | sh
94
94
  ```
95
95
 
96
- 중간에 연결 토큰을 물어보면 1단계 카드에서 복사한 토큰을 붙여넣어요. 아직 AI 앱이 하나도 없으면, 원하는 AI 프로그램을 그 자리에서 골라 설치할 수도 있어요(고르는 건 자유예요).
96
+ 중간에 관리 토큰을 물어보면 1단계 카드에서 복사한 토큰을 붙여넣어요. 아직 AI 앱이 하나도 없으면, 원하는 AI 프로그램을 그 자리에서 골라 설치할 수도 있어요(고르는 건 자유예요).
97
97
 
98
98
  > 실행 전에 내용을 확인하고 싶으면 위 주소를 브라우저에 그대로 열어 보세요 — 받아서 실행할 스크립트 원문이 그대로 보여요.
99
99
 
@@ -118,10 +118,10 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
118
118
  설치할 때 **글쓰기 도움말(스킬)** 도 함께 놓입니다. AI 앱은 켜질 때마다 설치된 도움말의 이름과 설명을 전부 읽기 때문에, 많이 깔수록 대화에 쓸 자리가 줄어들어요. 그래서 기본은 **바로 쓸 수 있는 것만** 놓습니다.
119
119
 
120
120
  ```
121
- npx -y @modootoday/datalab-extension-mcp skills list
122
- npx -y @modootoday/datalab-extension-mcp skills status
123
- npx -y @modootoday/datalab-extension-mcp skills attach <이름>
124
- npx -y @modootoday/datalab-extension-mcp skills detach <이름>
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 <이름>
125
125
  ```
126
126
 
127
127
  - `list` 는 이 버전에 들어 있는 전부를 이름과 설명으로 보여 줘요.
@@ -140,17 +140,19 @@ npx -y @modootoday/datalab-extension-mcp skills detach <이름>
140
140
  패널에서 [등록 코드 만들기]를 눌러도 되고, 터미널을 쓰셔도 돼요.
141
141
 
142
142
  ```
143
- npx -y @modootoday/datalab-extension-mcp browsers add "회사 크롬"
144
- npx -y @modootoday/datalab-extension-mcp browsers list
145
- npx -y @modootoday/datalab-extension-mcp browsers revoke <handle>
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
146
147
  ```
147
148
 
148
- `add` 를 실행하면 **연결 토큰을 붙여넣으라고 물어봐요** — 설치할 때와 같은 값이고, 패널의 `복사` 버튼으로 얻습니다. 그러면 코드가 **한 번만** 출력돼요. 커넥터는 그 코드를 저장하지 않고 지문만 남기므로 다시 보여 줄 수 없어요.
149
+ `add` 를 실행하면 **관리 토큰을 붙여넣으라고 물어봐요** — 설치할 때와 같은 값이고, 패널의 `복사` 버튼으로 얻습니다. 그러면 브라우저 등록 코드가 **한 번만** 출력돼요. 커넥터는 그 코드를 저장하지 않고 지문만 남기므로 다시 보여 줄 수 없어요.
149
150
 
150
151
  만든 코드는 올릴 크롬의 사이드 패널 → **이 크롬을 워크벤치에 올리기** 에 붙여넣습니다. 그 브라우저는 자기 코드를 따로 만들지 않아요.
151
152
 
152
153
  - `list` 는 `waiting`(코드는 만들었지만 아직 아무도 안 붙음)과 `connected` 를 구분해서 보여 줘요.
153
154
  - `revoke` 뒤에는 그 브라우저가 다시 붙지 못해요. 다시 넣으려면 **`revoke` 한 뒤 새로 `add`** 하세요 — 같은 브라우저에 코드만 새로 줘도 거절돼요.
155
+ - `list` 가 등록부 복구를 안내하면 `repair`를 실행한 뒤, 영향받은 브라우저에 등록 코드를 새로 발급해 붙여넣으세요. 정상 등록은 유지됩니다.
154
156
  - 커넥터가 꺼져 있으면 이 명령은 아무것도 하지 않고 그렇게 말해요. 연결해 둔 AI 앱을 켜면 커넥터가 함께 살아납니다.
155
157
 
156
158
  ## 문제해결
@@ -179,7 +181,7 @@ AI 앱이 켜질 때 받은 옛 코드로 커넥터가 돌고 있어서 그래
179
181
  2. 그 내용을 AI 앱 설정에 붙여넣은 뒤
180
182
  3. **그 AI 앱을 완전히 종료했다가 다시 켜요** (창 닫기 말고 — Windows 트레이 종료 / Mac `⌘+Q` / Linux 앱 메뉴 Quit)
181
183
 
182
- 3번까지 해야 커넥터가 새 코드로 다시 떠요. **[새 토큰 발급]을 다시 누르지 마세요** — 코드가 또 바뀌어서 같은 상황이 반복돼요.
184
+ 3번까지 해야 커넥터가 AI 앱 설정에 저장된 관리 토큰으로 다시 떠요. 브라우저 등록 코드는 이 과정에서 바꾸지 않습니다.
183
185
 
184
186
  ### "연결 통로(포트)를 다른 프로그램이 쓰고 있어요."
185
187
 
@@ -220,7 +222,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
220
222
  - **연결이 사라졌을 때** — 처음 실행했던 그 명령을 한 번 더 실행하면 안전하게 다시 등록돼요. (일부 AI 앱이 설정 파일을 정리하며 연결을 지우기도 해요.)
221
223
 
222
224
  ```
223
- npx -y @modootoday/datalab-extension-mcp@1.8.10 install
225
+ npx -y @modootoday/datalab-extension-mcp@1.8.12 install
224
226
  ```
225
227
 
226
228
  - **`'npx'을(는) 내부 또는 외부 명령...`** — Node.js가 없는 거예요. 위 2단계의 "Node.js가 없다면" 한 줄 설치가 가장 간단해요.
@@ -240,9 +242,9 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
240
242
  - **조회는 그냥 되고, 고치는 건 허락받아요.** 데이터 조회는 바로 돼요. **네이버 글쓰기 창**과 사진·영상 편집기의 내용을 고치려면 사이드패널이나 설정에서 **AI 편집 허용**을 켜야 해요. 두 가지 중에 고르면 됩니다 — 이번 연결에만 켜면 패널을 닫을 때 다시 꺼지고, **항상 허용**으로 저장하면 매번 켜지 않아도 돼요(끄는 것도 같은 자리에서 합니다). 글쓰기 창에서는 제목·본문 넣기와 되돌리기가 열려요. 본문을 통째로 바꾸거나 사진을 넣는 것, 편집기 창을 닫는 것, 지우기는 **항상 허용에 포함되지 않아요** — 그 설정으로는 열리지 않습니다. 요금이 드는 기능은 어떤 설정으로도 열리지 않고, 실행 전에 **매번** 물어봐요.
241
243
  - **발행은 하지 않아요.** 글을 올리거나 공개하는 기능은 연결되지 않습니다 — 고친 내용은 글쓰기 창에 남고, 올릴지는 사용자가 직접 정해요.
242
244
  - **아이디·비밀번호·쿠키를 저장하지 않아요.** 네이버에 직접 접속하지도 않아요. 모든 조회는 내가 로그인해 둔 내 브라우저 안에서 일어나요.
243
- - **내 컴퓨터 안에서만 동작해요.** 밖에서는 접속할 수 없고, 연결 토큰과 확장 확인이라는 이중 잠금이 걸려 있어요.
245
+ - **내 컴퓨터 안에서만 동작해요.** 밖에서는 접속할 수 없고, 관리 토큰과 확장 확인이라는 이중 잠금이 걸려 있어요.
244
246
  - **데이터가 나가는 방식** — "연결된 AI 앱이 요청하면 네이버 데이터가 해당 앱과 그 AI 제공사 서버로 전달돼요." 이게 AI가 답하는 방식이라 처음 켤 때 미리 동의를 받아요. 전달된 데이터를 얼마나 보관·학습하는지는 각 AI 제공사 정책을 따르니, 쓰시는 AI 앱의 개인정보 정책을 확인해 주세요.
245
- - **버전은 고정되어 몰래 바뀌지 않아요.** 새 버전이 나오면 패널이 알려주고, 바꿀지는 사용자가 정해요.
247
+ - **설정에는 정확한 버전이 기록되고, 실행 시 안전한 최신 버전을 확인해요.** 커넥터는 시작할 때와 유휴 상태에서 정본 버전을 확인해 최신 보안·호환 버전으로 승격합니다. 특정 버전을 유지해야 하는 진단 환경은 `--no-self-update`를 사용할 수 있어요.
246
248
  - **소스가 전부 공개된 오픈소스예요.** 다만 이미 같은 권한으로 실행 중인 악성코드까지 막아 주지는 못해요 — 이 커넥터의 잠금은 원격·타 출처 접근을 막는 장치예요.
247
249
 
248
250
  ## 해지
@@ -250,7 +252,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
250
252
  1. 터미널에서 아래 명령을 실행해요(AI 앱에 등록된 설정을 지워 줘요).
251
253
 
252
254
  ```
253
- npx -y @modootoday/datalab-extension-mcp@1.8.10 uninstall
255
+ npx -y @modootoday/datalab-extension-mcp@1.8.12 uninstall
254
256
  ```
255
257
 
256
258
  2. 확장 설정에서 **MCP 연결을 꺼요.** 패널이 브릿지라, 꺼 두면 어딘가 설정 줄이 남아 있어도 AI 앱이 아무것도 조회할 수 없어요.
@@ -266,7 +268,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
266
268
  - **ChatGPT 웹사이트에서도 되나요?** 아니요. 컴퓨터에 설치한 데스크톱 앱만 연결돼요.
267
269
  - **항상 켜 둬야 하나요?** 아니요. 패널이 열려 있을 때만 연결돼요.
268
270
  - **어떤 데이터를 읽나요?** 블로그 통계·키워드·광고 등 데이터랩툴즈가 보여 주는 것들이에요. 기준은 지금 Chrome에 로그인해 둔 네이버 계정이에요.
269
- - **다른 컴퓨터에서도 되나요?** 네. 컴퓨터마다 한 번씩 설치하고, 연결 토큰도 컴퓨터마다 따로예요.
271
+ - **다른 컴퓨터에서도 되나요?** 네. 컴퓨터마다 한 번씩 설치하고, 관리 토큰도 컴퓨터마다 따로예요.
270
272
  </details>
271
273
 
272
274
  ## 개발자 부록
@@ -290,9 +292,9 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
290
292
 
291
293
  **실패 응답** — 도구 호출 실패는 프로토콜 오류가 아니라 `isError` 결과로 오고, 본문은 성공과 같은 JSON입니다(`ok` / `reason` / `message` / `retryable`, 기다리면 풀리는 경우 `retryAfterMs`). 실패 코드 전체 목록과 각각의 재시도 여부 정본: <https://github.com/modootoday/datalab-extension-mcp/blob/main/mcp-core/src/errors.ts>
292
294
 
293
- **아키텍처** — 커넥터는 자격증명도 egress도 없는 릴레이입니다. 도구 목록은 확장이 서빙하며 카탈로그 정본은 스토어 심사를 받는 확장 코드입니다. 전송은 루프백 HTTP+SSE이며 Origin 검증과 토큰 검증의 이중 인증을 거치고, 도구 등급 표(조회 / 편집 / 파괴적 / 유료 / 미개방)는 확장 쪽에서 집행됩니다 — 목록에 없는 이름은 거부되고, 파괴적·유료는 세션 허용으로 건너뛸 수 없습니다 — 사용자 절의 "확장 확인"과 "연결 토큰"이 각각에 해당합니다. allowlist 전문(제외 사유 포함)의 정본: <https://github.com/modootoday/datalab-extension-mcp/blob/main/mcp-core/src/allowlist.ts>
295
+ **아키텍처** — 커넥터는 자격증명도 egress도 없는 릴레이입니다. 도구 목록은 확장이 서빙하며 카탈로그 정본은 스토어 심사를 받는 확장 코드입니다. 전송은 루프백 HTTP+SSE이며 Origin 검증과 브라우저별 세션 검증을 거치고, 도구 등급 표(조회 / 편집 / 파괴적 / 유료 / 미개방)는 확장 쪽에서 집행됩니다 — 목록에 없는 이름은 거부되고, 파괴적·유료는 세션 허용으로 건너뛸 수 없습니다. allowlist 전문(제외 사유 포함)의 정본: <https://github.com/modootoday/datalab-extension-mcp/blob/main/mcp-core/src/allowlist.ts>
294
296
 
295
- **공급망** — 공개 저장소가 전체 소스입니다. npm 발행은 OIDC trusted publishing + provenance로 이루어지고 장기 발행 토큰은 쓰지 않습니다(`npm audit signatures`로 검증 가능). 모든 설치 표면은 정확 버전을 핀하므로, 배포 경로가 손상돼도 새 코드가 자동으로 흘러들지 않고 업그레이드는 항상 사용자의 명시적 선택을 거칩니다.
297
+ **공급망** — 공개 저장소가 전체 소스입니다. npm 발행은 OIDC trusted publishing + provenance로 이루어지고 장기 발행 토큰은 쓰지 않습니다(`npm audit signatures`로 검증 가능). 설치 설정은 정확한 bootstrap 버전을 기록하고, 커넥터는 정본 릴리스로 승격할 때도 같은 검증된 npm 패키지 경로를 사용합니다.
296
298
 
297
299
  **라이선스** — MIT ([LICENSE](./LICENSE)).
298
300
 
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  DEFAULT_HOST,
3
3
  DEFAULT_PORT
4
- } from "./chunk-DOPJ4OZ4.js";
4
+ } from "./chunk-YBNNWQMB.js";
5
5
  import {
6
6
  TOKEN_PROMPT_GUIDE,
7
7
  TOKEN_PROMPT_QUESTION,
8
8
  TOKEN_REQUIRED_NON_INTERACTIVE
9
- } from "./chunk-5S7RFXIU.js";
9
+ } from "./chunk-CQRDBJXM.js";
10
10
  import "./chunk-MLKGABMK.js";
11
11
 
12
12
  // src/browsers.ts
@@ -105,7 +105,7 @@ async function runBrowsers(argv, deps) {
105
105
  return 0;
106
106
  }
107
107
  for (const row of rows) {
108
- const state = row.browserId === null ? "waiting" : "connected";
108
+ const state = row.browserId === null ? "waiting" : row.connected === true ? "connected" : "detached";
109
109
  deps.out(`${row.handle} ${state.padEnd(9)} ${row.label}`);
110
110
  }
111
111
  return 0;
@@ -1109,16 +1109,16 @@ async function removeSkills(io, detectedIds, slugs) {
1109
1109
  var RESTART_NOTICE = "\uB9C8\uC9C0\uB9C9 \uD55C \uB2E8\uACC4: AI \uC571\uC744 \uC644\uC804\uD788 \uC885\uB8CC\uD588\uB2E4\uAC00 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.\n(Windows: \uC791\uC5C5 \uD45C\uC2DC\uC904 \uD2B8\uB808\uC774 \uC544\uC774\uCF58\uC5D0\uC11C \uC885\uB8CC)";
1110
1110
  var NOTHING_CHANGED = "\uC544\uBB34\uAC83\uB3C4 \uBC14\uAFB8\uC9C0 \uC54A\uC558\uC5B4\uC694.";
1111
1111
  var UNINSTALL_DONE = "\uC815\uB9AC\uAC00 \uB05D\uB0AC\uC5B4\uC694.";
1112
- var UNINSTALL_TOKEN_REMINDER = "\uC5F0\uACB0 \uD1A0\uD070\uAE4C\uC9C0 \uC5C6\uC560\uB824\uBA74 \uB370\uC774\uD130\uB7A9\uD234\uC988 \uD328\uB110\uC5D0\uC11C '\uC5F0\uB3D9 \uD574\uC81C' \uBC84\uD2BC\uC744 \uB20C\uB7EC \uC8FC\uC138\uC694. \uD3D0\uAE30\uB41C \uD1A0\uD070\uC740 \uC124\uC815\uC5D0 \uB0A8\uC740 \uD56D\uBAA9\uC744 \uC804\uBD80 \uBB34\uB825\uD654\uD574\uC694.";
1112
+ var UNINSTALL_TOKEN_REMINDER = "\uC774 \uBE0C\uB77C\uC6B0\uC800\uC758 \uB85C\uCEEC \uC5F0\uACB0 \uC815\uBCF4\uB3C4 \uC9C0\uC6B0\uB824\uBA74 \uB370\uC774\uD130\uB7A9\uD234\uC988 \uD328\uB110\uC5D0\uC11C '\uC774 \uBE0C\uB77C\uC6B0\uC800 \uC5F0\uACB0 \uB044\uAE30'\uB97C \uB20C\uB7EC \uC8FC\uC138\uC694. \uC6CC\uD06C\uBCA4\uCE58 \uB4F1\uB85D\uC740 \uBE0C\uB77C\uC6B0\uC800 \uBAA9\uB85D\uC5D0\uC11C \uBCC4\uB3C4\uB85C \uB0B4\uB9B4 \uC218 \uC788\uC5B4\uC694.";
1113
1113
  var PERMISSION_DENIED_HINT = "\uD30C\uC77C \uAD8C\uD55C \uB54C\uBB38\uC5D0 \uC124\uC815\uC744 \uC4F8 \uC218 \uC5C6\uC5C8\uC5B4\uC694. \uD30C\uC77C \uAD8C\uD55C\uC744 \uD655\uC778\uD55C \uB4A4 \uAC19\uC740 \uBA85\uB839\uC5B4\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
1114
1114
  var NO_HOSTS_DETECTED = "\uC5F0\uACB0\uD560 \uC218 \uC788\uB294 AI \uD504\uB85C\uADF8\uB7A8\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC5B4\uC694.";
1115
1115
  var SUPPORTED_APPS_HEADER = "\uC9C0\uC6D0\uD558\uB294 \uD504\uB85C\uADF8\uB7A8\uACFC \uB2E4\uC6B4\uB85C\uB4DC \uC8FC\uC18C\uC608\uC694:";
1116
1116
  var AFTER_INSTALL_RETRY = "\uD504\uB85C\uADF8\uB7A8\uC744 \uC124\uCE58\uD55C \uB4A4 \uAC19\uC740 \uBA85\uB839\uC5B4\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
1117
1117
  var ALREADY_INSTALLED_HINT = "\uC774\uBBF8 \uC124\uCE58\uD588\uB294\uB370 \uBAA9\uB85D\uC5D0 \uC5C6\uB2E4\uBA74, \uC124\uC815 \uD30C\uC77C\uC774 \uC544\uC9C1 \uB9CC\uB4E4\uC5B4\uC9C0\uC9C0 \uC54A\uC558\uC744 \uC218 \uC788\uC5B4\uC694. \uC124\uC815 \uD30C\uC77C\uC740 \uD504\uB85C\uADF8\uB7A8\uC744 \uC124\uCE58\uD560 \uB54C\uAC00 \uC544\uB2C8\uB77C \uCC98\uC74C \uC4F8 \uB54C \uB9CC\uB4E4\uC5B4\uC838\uC694:\n - ChatGPT \uB370\uC2A4\uD06C\uD1B1: \uC124\uC815 \u2192 MCP \uC11C\uBC84 \u2192 \uC11C\uBC84 \uCD94\uAC00\uB97C \uD55C \uBC88 \uC2E4\uD589\n - VS Code: \uBA85\uB839 \uD314\uB808\uD2B8(Ctrl+Shift+P) \u2192 'MCP: Open User Configuration' \uC2E4\uD589\n\uADF8 \uB4A4 \uAC19\uC740 \uBA85\uB839\uC5B4\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uBA74 \uCC3E\uC744 \uC218 \uC788\uC5B4\uC694.";
1118
- var TOKEN_PROMPT_GUIDE = "\uD06C\uB86C\uC5D0\uC11C \uB370\uC774\uD130\uB7A9\uD234\uC988 \uC0AC\uC774\uB4DC \uD328\uB110\uC744 \uC5F4\uACE0 'MCP \uC5F0\uACB0' \uCE74\uB4DC\uC758 \uC5F0\uACB0 \uD1A0\uD070\uC744 \uBCF5\uC0AC\uD574 \uC8FC\uC138\uC694.";
1119
- var TOKEN_PROMPT_QUESTION = "\uBCF5\uC0AC\uD55C \uC5F0\uACB0 \uD1A0\uD070\uC744 \uC5EC\uAE30\uC5D0 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694:";
1118
+ var TOKEN_PROMPT_GUIDE = "\uD06C\uB86C\uC5D0\uC11C \uB370\uC774\uD130\uB7A9\uD234\uC988 \uC0AC\uC774\uB4DC \uD328\uB110\uC744 \uC5F4\uACE0 'MCP \uC5F0\uACB0'\uC758 MCP \uD638\uC2A4\uD2B8 \uAD00\uB9AC \uD1A0\uD070\uC744 \uBCF5\uC0AC\uD574 \uC8FC\uC138\uC694.";
1119
+ var TOKEN_PROMPT_QUESTION = "\uBCF5\uC0AC\uD55C \uAD00\uB9AC \uD1A0\uD070\uC744 \uC5EC\uAE30\uC5D0 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694:";
1120
1120
  var TOKEN_PROMPT_RETRY = "\uD1A0\uD070 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC544\uC694. \uCE74\uB4DC\uC758 \uD1A0\uD070\uC744 \uB2E4\uC2DC \uBCF5\uC0AC\uD574\uC11C \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.";
1121
- var TOKEN_REQUIRED_NON_INTERACTIVE = "\uC5F0\uACB0 \uD1A0\uD070\uC774 \uD544\uC694\uD574\uC694. \uD06C\uB86C\uC5D0\uC11C \uB370\uC774\uD130\uB7A9\uD234\uC988 \uC0AC\uC774\uB4DC \uD328\uB110\uC744 \uC5F4\uACE0 'MCP \uC5F0\uACB0' \uCE74\uB4DC\uC758 [\uC124\uCE58 \uBA85\uB839 \uBCF5\uC0AC] \uBC84\uD2BC\uC744 \uB20C\uB7EC, \uBCF5\uC0AC\uB41C \uBA85\uB839\uC744 \uADF8\uB300\uB85C \uBD99\uC5EC\uB123\uC5B4 \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
1121
+ var TOKEN_REQUIRED_NON_INTERACTIVE = "\uAD00\uB9AC \uD1A0\uD070\uC774 \uD544\uC694\uD574\uC694. \uD06C\uB86C\uC5D0\uC11C \uB370\uC774\uD130\uB7A9\uD234\uC988 \uC0AC\uC774\uB4DC \uD328\uB110\uC744 \uC5F4\uACE0 'MCP \uC5F0\uACB0'\uC758 [\uC124\uCE58 \uBA85\uB839 \uBCF5\uC0AC] \uBC84\uD2BC\uC744 \uB20C\uB7EC, \uBCF5\uC0AC\uB41C \uBA85\uB839\uC744 \uADF8\uB300\uB85C \uBD99\uC5EC\uB123\uC5B4 \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
1122
1122
  var CLI_OFFER_INTRO = "\uC6D0\uD558\uC2DC\uBA74 \uC544\uB798 AI \uD504\uB85C\uADF8\uB7A8 \uC911 \uD558\uB098\uB97C \uC9C0\uAE08 \uBC14\uB85C \uC124\uCE58\uD560 \uC218 \uC788\uC5B4\uC694. Node.js \uAE30\uBC18\uC774\uB77C \uC774 \uC790\uB9AC\uC5D0\uC11C \uC124\uCE58\uB3FC\uC694 \u2014 \uC548 \uD558\uC154\uB3C4 \uAD1C\uCC2E\uC544\uC694.";
1123
1123
  var CLI_OFFER_SKIP_LABEL = "0. \uC9C0\uAE08\uC740 \uC124\uCE58\uD558\uC9C0 \uC54A\uAE30";
1124
1124
  var CLI_OFFER_QUESTION = "\uC124\uCE58\uD560 \uD504\uB85C\uADF8\uB7A8\uC758 \uBC88\uD638\uB97C \uC785\uB825\uD574 \uC8FC\uC138\uC694";
@@ -1,4 +1,4 @@
1
- // ../mcp-server/dist/chunk-Q4ALBPO7.js
1
+ // ../mcp-server/dist/chunk-XKX4SOFG.js
2
2
  import { createHash } from "crypto";
3
3
  import { createHash as createHash2, randomUUID } from "crypto";
4
4
  import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
@@ -14439,6 +14439,11 @@ var HelloSchema = external_exports.object({
14439
14439
  var BRIDGE_AUTH_CAPABILITY = "bridge-auth-bearer";
14440
14440
  var BROWSER_ROUTING_CAPABILITY = "browser-routing-v1";
14441
14441
  var LOCAL_FILE_REQUEST_CAPABILITY = "local-file-request-v1";
14442
+ var REQUIRED_BROWSER_CAPABILITIES = Object.freeze([
14443
+ BRIDGE_AUTH_CAPABILITY,
14444
+ BROWSER_ROUTING_CAPABILITY,
14445
+ LOCAL_FILE_REQUEST_CAPABILITY
14446
+ ]);
14442
14447
  function bearerToken(header) {
14443
14448
  if (typeof header !== "string") return null;
14444
14449
  const match = /^Bearer (.+)$/i.exec(header.trim());
@@ -14464,7 +14469,9 @@ var HelloNackReasonSchema = external_exports.enum([
14464
14469
  /** This browser already has an active event stream. */
14465
14470
  "session_active",
14466
14471
  /** Root authority cannot open a browser session. */
14467
- "browser_registration_required"
14472
+ "browser_registration_required",
14473
+ /** This panel predates mandatory browser routing and file permits. */
14474
+ "browser_capability_required"
14468
14475
  ]);
14469
14476
  var BrowserRoutingSchema = external_exports.object({
14470
14477
  targetBrowserId: external_exports.string(),
@@ -15024,6 +15031,7 @@ var REASON_POLICY = Object.freeze({
15024
15031
  // --- The request itself must change ---
15025
15032
  unknown_tool: Object.freeze({ retryable: false }),
15026
15033
  invalid_args: Object.freeze({ retryable: false }),
15034
+ browser_required: Object.freeze({ retryable: false }),
15027
15035
  tool_closed: Object.freeze({ retryable: false }),
15028
15036
  missing_in_build: Object.freeze({ retryable: false }),
15029
15037
  // --- A human must act; re-calling only nags them ---
@@ -15492,7 +15500,7 @@ var BRIDGE_USER_MESSAGES = Object.freeze({
15492
15500
  panelClosed: "\uD06C\uB86C\uC5D0\uC11C \uB370\uC774\uD130\uB7A9\uD234\uC988 \uD328\uB110\uC774 \uB2EB\uD600 \uC788\uC5B4\uC694. \uD06C\uB86C\uC744 \uC5F4\uACE0 \uB370\uC774\uD130\uB7A9\uD234\uC988 \uD328\uB110\uC744 \uC5F4\uC5B4 \uB450\uBA74 \uBC14\uB85C \uB3D9\uC791\uD574\uC694.",
15493
15501
  /** The panel accepted the call but never answered inside the deadline. */
15494
15502
  toolTimeout: "\uBE0C\uB77C\uC6B0\uC800 \uC751\uB2F5\uC774 \uB2A6\uC5B4\uC9C0\uACE0 \uC788\uC5B4\uC694. \uD06C\uB86C\uC5D0\uC11C \uB124\uC774\uBC84\uC5D0 \uB85C\uADF8\uC778\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778\uD558\uACE0 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
15495
- /** The session hit the per-session rate limit. */
15503
+ /** The daemon request budget is empty. */
15496
15504
  rateLimited: "\uC694\uCCAD\uC774 \uB108\uBB34 \uC7A6\uC544\uC694. \uC7A0\uAE50 \uAE30\uB2E4\uB838\uB2E4\uAC00 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
15497
15505
  /**
15498
15506
  * The local connector accepted nothing back inside its ceiling. Distinct
@@ -15504,7 +15512,7 @@ var BRIDGE_USER_MESSAGES = Object.freeze({
15504
15512
  var BRIDGE_AGENT_HINT = Object.freeze({
15505
15513
  /** Panel closed / not connected — the panel must be open for any tool to run. */
15506
15514
  notConnected: "The Datalab extension side panel is not open, so this tool cannot run right now. Ask the user to open the extension side panel in their browser, then call this tool again.",
15507
- /** Per-session rate limit tripped — transient. */
15515
+ /** Daemon-wide rate limit tripped — transient. */
15508
15516
  rateLimited: "This tool is temporarily rate-limited. Wait a few seconds, then call it again.",
15509
15517
  /** The local connector did not answer at all — retrying immediately will not help. */
15510
15518
  connectorStuck: "The local connector stopped responding, so this tool cannot run right now. Do not retry immediately; ask the user to turn the MCP connection off and on again in the extension side panel."
@@ -15634,7 +15642,6 @@ var Bridge = class {
15634
15642
  #idTag = daemonIdTag();
15635
15643
  #sessionSeq = 0;
15636
15644
  #defaultKey = null;
15637
- #retireSession = null;
15638
15645
  #bucketTokens = 0;
15639
15646
  #bucketRefilledAt = 0;
15640
15647
  // Last catalog the panel reported, kept across disconnects: many hosts cache
@@ -15643,12 +15650,9 @@ var Bridge = class {
15643
15650
  #cachedTools = [];
15644
15651
  constructor(deps) {
15645
15652
  this.#deps = deps;
15646
- }
15647
- bindRetireSession(retire) {
15648
- if (this.#retireSession !== null) {
15649
- throw new Error("bridge transport retirement is already bound");
15650
- }
15651
- this.#retireSession = retire;
15653
+ const limit = deps.rateLimit ?? DEFAULT_RATE_LIMIT;
15654
+ this.#bucketTokens = limit.capacity;
15655
+ this.#bucketRefilledAt = (deps.now ?? Date.now)();
15652
15656
  }
15653
15657
  /**
15654
15658
  * The session a request with no browser named goes to. Map preserves
@@ -15674,7 +15678,7 @@ var Bridge = class {
15674
15678
  */
15675
15679
  connectedBrowsers() {
15676
15680
  const keys = [...this.#sessions.values()].filter((entry) => entry.streamActive).map((entry) => entry.key);
15677
- const primary = this.#defaultKey === null ? this.#primary()?.key ?? null : this.#defaultKey;
15681
+ const primary = this.#primary()?.key ?? null;
15678
15682
  return keys.map((id) => ({
15679
15683
  id,
15680
15684
  name: this.#deps.browserLabels.get(id) ?? null,
@@ -15946,6 +15950,32 @@ var Bridge = class {
15946
15950
  message: idCheck.message
15947
15951
  };
15948
15952
  }
15953
+ const negotiated = negotiateProtocol(parsed.data.protocolVersions);
15954
+ if (!negotiated.ok) {
15955
+ return {
15956
+ t: "hello_nack",
15957
+ reason: negotiated.reason,
15958
+ supported: negotiated.supported,
15959
+ message: negotiated.message
15960
+ };
15961
+ }
15962
+ const bearerRequired = parsed.data.capabilities.includes(
15963
+ BRIDGE_AUTH_CAPABILITY
15964
+ );
15965
+ const routingRequired = parsed.data.capabilities.includes(
15966
+ BROWSER_ROUTING_CAPABILITY
15967
+ );
15968
+ const fileRequestBound = parsed.data.capabilities.includes(
15969
+ LOCAL_FILE_REQUEST_CAPABILITY
15970
+ );
15971
+ if (!bearerRequired || !routingRequired || !fileRequestBound) {
15972
+ return {
15973
+ t: "hello_nack",
15974
+ reason: "browser_capability_required",
15975
+ supported: [],
15976
+ message: "This browser panel is outdated. Close and reopen the side panel before reconnecting."
15977
+ };
15978
+ }
15949
15979
  const tokenHash = digestToken(parsed.data.token);
15950
15980
  const browserId = parsed.data.browserId;
15951
15981
  if (browserId !== void 0 && browserId.length > 0 && !this.#deps.claimedBrowsers.has(browserId)) {
@@ -15974,19 +16004,15 @@ var Bridge = class {
15974
16004
  message: "The root token is management-only. Register this browser with its own token."
15975
16005
  };
15976
16006
  }
15977
- const negotiated = negotiateProtocol(parsed.data.protocolVersions);
15978
- if (!negotiated.ok) {
15979
- return {
15980
- t: "hello_nack",
15981
- reason: negotiated.reason,
15982
- supported: negotiated.supported,
15983
- message: negotiated.message
15984
- };
15985
- }
15986
16007
  const key = credential.browserId;
15987
16008
  const held = this.#sessions.get(key);
15988
16009
  if (held?.streamActive) {
15989
- this.#retireSession?.(key, held.session.sessionId);
16010
+ return {
16011
+ t: "hello_nack",
16012
+ reason: "session_active",
16013
+ supported: [],
16014
+ message: "This browser already has an active side-panel session."
16015
+ };
15990
16016
  }
15991
16017
  const session = {
15992
16018
  // A counter, not a timestamp: a panel reload produces two handshakes
@@ -15996,28 +16022,17 @@ var Bridge = class {
15996
16022
  protocolVersion: negotiated.version,
15997
16023
  extensionVersion: parsed.data.extensionVersion
15998
16024
  };
15999
- const routingRequired = credential.kind === "claimed" && parsed.data.capabilities.includes(BROWSER_ROUTING_CAPABILITY);
16000
- const fileRequestBound = parsed.data.capabilities.includes(
16001
- LOCAL_FILE_REQUEST_CAPABILITY
16002
- );
16003
16025
  this.#installSession(key, session, {
16004
16026
  tokenHash,
16005
16027
  routingRequired,
16006
16028
  fileRequestBound
16007
16029
  });
16008
- const limit = this.#deps.rateLimit ?? DEFAULT_RATE_LIMIT;
16009
- this.#bucketTokens = limit.capacity;
16010
- this.#bucketRefilledAt = (this.#deps.now ?? Date.now)();
16011
16030
  return {
16012
16031
  t: "hello_ack",
16013
16032
  protocolVersion: negotiated.version,
16014
16033
  serverVersion: this.#deps.serverVersion,
16015
16034
  sessionId: session.sessionId,
16016
- capabilities: [
16017
- BRIDGE_AUTH_CAPABILITY,
16018
- ...routingRequired ? [BROWSER_ROUTING_CAPABILITY] : [],
16019
- ...fileRequestBound ? [LOCAL_FILE_REQUEST_CAPABILITY] : []
16020
- ]
16035
+ capabilities: [...REQUIRED_BROWSER_CAPABILITIES]
16021
16036
  };
16022
16037
  }
16023
16038
  /**
@@ -16209,7 +16224,7 @@ var Bridge = class {
16209
16224
  };
16210
16225
  }
16211
16226
  /**
16212
- * Refill-then-take on the session's token bucket. The refill is computed
16227
+ * Refill-then-take on the daemon's token bucket. The refill is computed
16213
16228
  * lazily on each take, so there is no timer to leak and an injected clock
16214
16229
  * keeps it testable without waiting real seconds.
16215
16230
  */
@@ -16823,7 +16838,15 @@ async function handleMcpRequest(bridge, body, accept, stage) {
16823
16838
  }
16824
16839
  if (params.name === BROWSERS_TOOL) {
16825
16840
  const registryStatus = bridge.browserRegistryStatus();
16826
- const { state: registryState, ...registryDetail } = registryStatus;
16841
+ const { state: registryState } = registryStatus;
16842
+ const registryDetail = registryStatus.state === "degraded" ? {
16843
+ error: registryStatus.error,
16844
+ quarantinedHandles: registryStatus.quarantinedHandles,
16845
+ recovery: registryStatus.recovery
16846
+ } : registryStatus.state === "disabled" ? {
16847
+ error: registryStatus.error,
16848
+ detail: registryStatus.detail
16849
+ } : {};
16827
16850
  const payload = {
16828
16851
  browsers: bridge.connectedBrowsers(),
16829
16852
  registryStatus: registryState,
@@ -17137,13 +17160,6 @@ function createHttpBridge(opts) {
17137
17160
  opts.bridge.disconnect(key);
17138
17161
  if (wasPrimary) scheduleToolsDrop();
17139
17162
  };
17140
- opts.bridge.bindRetireSession((key, sessionId) => {
17141
- const stream = streams.get(key);
17142
- if (!stream || stream.sessionId !== sessionId) return;
17143
- const wasPrimary = key === opts.bridge.sessionKey;
17144
- removeStream(key)?.end();
17145
- if (wasPrimary) scheduleToolsDrop();
17146
- });
17147
17163
  const server = createServer((req, res) => {
17148
17164
  const url2 = new URL(req.url ?? "/", `http://${host}`);
17149
17165
  const origin = req.headers.origin;
@@ -17276,6 +17292,7 @@ function createHttpBridge(opts) {
17276
17292
  json2(res, 404, { error: "not found" });
17277
17293
  return;
17278
17294
  }
17295
+ res.setHeader("cache-control", "no-store");
17279
17296
  void readJson(req, MAX_BODY_BYTES).then(async (body) => {
17280
17297
  const b = body;
17281
17298
  if (!opts.bridge.verifyRootToken(
@@ -17286,9 +17303,16 @@ function createHttpBridge(opts) {
17286
17303
  }
17287
17304
  const action = b?.["action"];
17288
17305
  if (action === "list") {
17306
+ const active = new Set(
17307
+ opts.bridge.connectedBrowsers().map((row) => row.id)
17308
+ );
17309
+ const browsers = (await registry2.list()).map((row) => ({
17310
+ ...row,
17311
+ connected: row.browserId !== null && active.has(row.browserId)
17312
+ }));
17289
17313
  json2(res, 200, {
17290
17314
  ok: true,
17291
- browsers: await registry2.list(),
17315
+ browsers,
17292
17316
  registryStatus: registry2.status()
17293
17317
  });
17294
17318
  return;
@@ -17310,6 +17334,20 @@ function createHttpBridge(opts) {
17310
17334
  json2(res, 200, { ok: true, ...await registry2.repair() });
17311
17335
  return;
17312
17336
  }
17337
+ if (action === "repair_registration") {
17338
+ const browserId = typeof b?.["browserId"] === "string" ? b["browserId"] : "";
17339
+ const label = typeof b?.["label"] === "string" ? b["label"] : void 0;
17340
+ const repaired = await registry2.repairRegistration(
17341
+ browserId,
17342
+ label
17343
+ );
17344
+ json2(res, 200, {
17345
+ ok: true,
17346
+ ...repaired,
17347
+ registryStatus: registry2.status()
17348
+ });
17349
+ return;
17350
+ }
17313
17351
  json2(res, 400, { error: "unknown action" });
17314
17352
  }).catch((err) => {
17315
17353
  const why = err instanceof Error ? err.message : String(err);
@@ -17854,24 +17892,54 @@ function matches(claim, handle) {
17854
17892
  function createBrowserRegistry(opts) {
17855
17893
  const now = opts.now ?? (() => /* @__PURE__ */ new Date());
17856
17894
  const revoked = /* @__PURE__ */ new Set();
17857
- const readCurrent = () => {
17858
- const read = readLedger(opts.path);
17859
- if (read.state === "unreadable") {
17860
- throw new Error(`browser registry is unreadable: ${read.reason}`);
17861
- }
17862
- const rows = read.state === "ok" ? [...read.rows] : [];
17863
- if (!opts.forbiddenTokenHash) return { rows, quarantined: [] };
17864
- const quarantined = rows.filter(
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(
17865
17906
  (row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
17866
17907
  );
17867
17908
  return {
17868
- rows: rows.filter(
17909
+ rows: source.filter(
17869
17910
  (row) => !timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
17870
17911
  ),
17871
17912
  quarantined
17872
17913
  };
17873
17914
  };
17874
- const current = () => readCurrent().rows;
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
+ };
17875
17943
  return {
17876
17944
  async add(label) {
17877
17945
  const token = mintToken();
@@ -17881,8 +17949,10 @@ function createBrowserRegistry(opts) {
17881
17949
  label,
17882
17950
  createdAt: now().toISOString()
17883
17951
  };
17884
- await writeLedger(opts.path, [...current(), claim]);
17885
- return { handle: handleOf(claim), token };
17952
+ return transact((snapshot) => ({
17953
+ next: [...snapshot, claim],
17954
+ result: { handle: handleOf(claim), token }
17955
+ }));
17886
17956
  },
17887
17957
  async list() {
17888
17958
  return current().map((claim) => ({
@@ -17893,6 +17963,7 @@ function createBrowserRegistry(opts) {
17893
17963
  }));
17894
17964
  },
17895
17965
  claim(browserId, tokenHash) {
17966
+ if (pendingMutations > 0) return false;
17896
17967
  if (opts.claimed.has(browserId)) return false;
17897
17968
  for (const held of opts.claimed.values()) {
17898
17969
  if (timingSafeEqual(held, tokenHash)) return false;
@@ -17907,50 +17978,128 @@ function createBrowserRegistry(opts) {
17907
17978
  (claim) => claim.browserId === null && timingSafeEqual(claim.tokenHash, tokenHash)
17908
17979
  );
17909
17980
  if (!row) return false;
17910
- row.browserId = browserId;
17981
+ const stored = rows.find((claim) => claim.tokenHash === row.tokenHash);
17982
+ if (!stored) return false;
17983
+ stored.browserId = browserId;
17911
17984
  opts.claimed.set(browserId, row.tokenHash);
17912
- void writeLedger(opts.path, claims).catch(() => {
17913
- });
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);
17914
17999
  return true;
17915
18000
  },
17916
18001
  async revoke(handle) {
17917
- const claims = current();
17918
- const kept = claims.filter((claim) => !matches(claim, handle));
17919
- if (kept.length === claims.length) return false;
17920
- await writeLedger(opts.path, kept);
17921
- for (const claim of claims) {
17922
- if (!matches(claim, handle)) continue;
17923
- revoked.add(claim.tokenHash);
17924
- if (claim.browserId !== null) {
17925
- opts.claimed.delete(claim.browserId);
17926
- opts.onRevoke?.(claim.browserId);
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 };
17927
18007
  }
17928
- }
17929
- return true;
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
+ });
17930
18023
  },
17931
18024
  async repair() {
17932
- const view = readCurrent();
17933
- if (view.quarantined.length === 0) return { removed: [] };
17934
- await writeLedger(opts.path, view.rows);
17935
- return { removed: view.quarantined.map(handleOf) };
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
+ });
17936
18078
  },
17937
18079
  status() {
17938
- const read = readLedger(opts.path);
17939
- if (read.state === "unreadable") {
18080
+ if (unreadable !== null) {
17940
18081
  return {
17941
18082
  state: "disabled",
17942
18083
  error: "browser_registry_unreadable",
17943
- detail: read.reason
18084
+ detail: unreadable
17944
18085
  };
17945
18086
  }
17946
- if (read.state === "ok" && opts.forbiddenTokenHash && ledgerOverlapsToken(read.rows, opts.forbiddenTokenHash)) {
18087
+ if (opts.forbiddenTokenHash && ledgerOverlapsToken(rows, opts.forbiddenTokenHash)) {
17947
18088
  return {
17948
18089
  state: "degraded",
17949
18090
  error: "browser_registry_root_authority_overlap",
17950
- quarantinedHandles: read.rows.filter(
18091
+ quarantinedHandles: rows.filter(
17951
18092
  (row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
17952
18093
  ).map(handleOf),
17953
- recovery: "browsers repair"
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"
17954
18103
  };
17955
18104
  }
17956
18105
  return { state: "ready" };
@@ -18016,20 +18165,7 @@ function runDaemon(env, deps = {}) {
18016
18165
  claimedBrowsers,
18017
18166
  browserLabels,
18018
18167
  browserRegistryStatus: () => registry2.status(),
18019
- claimBrowser: (browserId, tokenHash) => {
18020
- const took = registry2.claim(browserId, tokenHash);
18021
- if (took) {
18022
- const now = readLedger(ledgerPath(deps.home));
18023
- if (now.state === "ok") {
18024
- for (const row of now.rows) {
18025
- if (row.browserId === browserId) {
18026
- browserLabels.set(browserId, row.label);
18027
- }
18028
- }
18029
- }
18030
- }
18031
- return took;
18032
- },
18168
+ claimBrowser: (browserId, tokenHash) => registry2.claim(browserId, tokenHash),
18033
18169
  wasRevoked: (tokenHash) => registry2.wasRevoked(tokenHash),
18034
18170
  log
18035
18171
  });
@@ -18038,6 +18174,7 @@ function runDaemon(env, deps = {}) {
18038
18174
  const registry2 = createBrowserRegistry({
18039
18175
  path: ledgerPath(deps.home),
18040
18176
  claimed: claimedBrowsers,
18177
+ onClaim: (browserId, label) => browserLabels.set(browserId, label),
18041
18178
  forbiddenTokenHash: rootTokenHash,
18042
18179
  onRevoke: (browserId) => http.disconnect(browserId)
18043
18180
  });
@@ -4,7 +4,7 @@ import {
4
4
  mediaToolDescriptors,
5
5
  spawnDaemon,
6
6
  tryConnect
7
- } from "./chunk-DOPJ4OZ4.js";
7
+ } from "./chunk-YBNNWQMB.js";
8
8
  import {
9
9
  __export
10
10
  } from "./chunk-MLKGABMK.js";
@@ -14583,6 +14583,14 @@ var HelloSchema = external_exports.object({
14583
14583
  */
14584
14584
  capabilities: external_exports.array(external_exports.string()).default([])
14585
14585
  });
14586
+ var BRIDGE_AUTH_CAPABILITY = "bridge-auth-bearer";
14587
+ var BROWSER_ROUTING_CAPABILITY = "browser-routing-v1";
14588
+ var LOCAL_FILE_REQUEST_CAPABILITY = "local-file-request-v1";
14589
+ var REQUIRED_BROWSER_CAPABILITIES = Object.freeze([
14590
+ BRIDGE_AUTH_CAPABILITY,
14591
+ BROWSER_ROUTING_CAPABILITY,
14592
+ LOCAL_FILE_REQUEST_CAPABILITY
14593
+ ]);
14586
14594
  var HelloAckSchema = external_exports.object({
14587
14595
  t: external_exports.literal("hello_ack"),
14588
14596
  /** The single version both sides will use for this session. */
@@ -14603,7 +14611,9 @@ var HelloNackReasonSchema = external_exports.enum([
14603
14611
  /** This browser already has an active event stream. */
14604
14612
  "session_active",
14605
14613
  /** Root authority cannot open a browser session. */
14606
- "browser_registration_required"
14614
+ "browser_registration_required",
14615
+ /** This panel predates mandatory browser routing and file permits. */
14616
+ "browser_capability_required"
14607
14617
  ]);
14608
14618
  var BrowserRoutingSchema = external_exports.object({
14609
14619
  targetBrowserId: external_exports.string(),
@@ -15033,6 +15043,7 @@ var REASON_POLICY = Object.freeze({
15033
15043
  // --- The request itself must change ---
15034
15044
  unknown_tool: Object.freeze({ retryable: false }),
15035
15045
  invalid_args: Object.freeze({ retryable: false }),
15046
+ browser_required: Object.freeze({ retryable: false }),
15036
15047
  tool_closed: Object.freeze({ retryable: false }),
15037
15048
  missing_in_build: Object.freeze({ retryable: false }),
15038
15049
  // --- A human must act; re-calling only nags them ---
@@ -15446,7 +15457,7 @@ var BRIDGE_USER_MESSAGES = Object.freeze({
15446
15457
  panelClosed: "\uD06C\uB86C\uC5D0\uC11C \uB370\uC774\uD130\uB7A9\uD234\uC988 \uD328\uB110\uC774 \uB2EB\uD600 \uC788\uC5B4\uC694. \uD06C\uB86C\uC744 \uC5F4\uACE0 \uB370\uC774\uD130\uB7A9\uD234\uC988 \uD328\uB110\uC744 \uC5F4\uC5B4 \uB450\uBA74 \uBC14\uB85C \uB3D9\uC791\uD574\uC694.",
15447
15458
  /** The panel accepted the call but never answered inside the deadline. */
15448
15459
  toolTimeout: "\uBE0C\uB77C\uC6B0\uC800 \uC751\uB2F5\uC774 \uB2A6\uC5B4\uC9C0\uACE0 \uC788\uC5B4\uC694. \uD06C\uB86C\uC5D0\uC11C \uB124\uC774\uBC84\uC5D0 \uB85C\uADF8\uC778\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778\uD558\uACE0 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
15449
- /** The session hit the per-session rate limit. */
15460
+ /** The daemon request budget is empty. */
15450
15461
  rateLimited: "\uC694\uCCAD\uC774 \uB108\uBB34 \uC7A6\uC544\uC694. \uC7A0\uAE50 \uAE30\uB2E4\uB838\uB2E4\uAC00 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
15451
15462
  /**
15452
15463
  * The local connector accepted nothing back inside its ceiling. Distinct
@@ -15458,7 +15469,7 @@ var BRIDGE_USER_MESSAGES = Object.freeze({
15458
15469
  var BRIDGE_AGENT_HINT = Object.freeze({
15459
15470
  /** Panel closed / not connected — the panel must be open for any tool to run. */
15460
15471
  notConnected: "The Datalab extension side panel is not open, so this tool cannot run right now. Ask the user to open the extension side panel in their browser, then call this tool again.",
15461
- /** Per-session rate limit tripped — transient. */
15472
+ /** Daemon-wide rate limit tripped — transient. */
15462
15473
  rateLimited: "This tool is temporarily rate-limited. Wait a few seconds, then call it again.",
15463
15474
  /** The local connector did not answer at all — retrying immediately will not help. */
15464
15475
  connectorStuck: "The local connector stopped responding, so this tool cannot run right now. Do not retry immediately; ask the user to turn the MCP connection off and on again in the extension side panel."
package/dist/cli.js CHANGED
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  dispatchCli,
4
4
  runAdapter
5
- } from "./chunk-YB7L4SZC.js";
5
+ } from "./chunk-YBQ7ZLSQ.js";
6
6
  import {
7
7
  runDaemon
8
- } from "./chunk-DOPJ4OZ4.js";
8
+ } from "./chunk-YBNNWQMB.js";
9
9
  import "./chunk-MLKGABMK.js";
10
10
 
11
11
  // src/cli.ts
@@ -17,7 +17,7 @@ function log(message) {
17
17
  `);
18
18
  }
19
19
  async function runInstaller(sub, argv) {
20
- const { createNodeIo, runInstall, runUninstall } = await import("./dist-JICXADYR.js");
20
+ const { createNodeIo, runInstall, runUninstall } = await import("./dist-R3VRQN24.js");
21
21
  const opts = { version: VERSION };
22
22
  for (let i = 0; i < argv.length; i += 1) {
23
23
  const flag = argv[i];
@@ -62,8 +62,8 @@ 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-AGEUPMJA.js");
66
- const { createNodeIo } = await import("./dist-JICXADYR.js");
65
+ const { runBrowsers } = await import("./browsers-4L3WAODH.js");
66
+ const { createNodeIo } = await import("./dist-R3VRQN24.js");
67
67
  const io = createNodeIo();
68
68
  const code = await runBrowsers(argv, {
69
69
  env: process.env,
@@ -88,7 +88,7 @@ async function main() {
88
88
  attachSkill,
89
89
  detachSkill,
90
90
  skillsPayloadRoot
91
- } = await import("./dist-JICXADYR.js");
91
+ } = await import("./dist-R3VRQN24.js");
92
92
  const io = createNodeIo();
93
93
  const root = skillsPayloadRoot();
94
94
  const code = await runSkills(argv, {
@@ -49,7 +49,7 @@ import {
49
49
  upsertServerKey,
50
50
  validateInstallOptions,
51
51
  validateUninstallOptions
52
- } from "./chunk-5S7RFXIU.js";
52
+ } from "./chunk-CQRDBJXM.js";
53
53
  import "./chunk-MLKGABMK.js";
54
54
  export {
55
55
  BACKUP_KEEP,
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  dispatchCli,
4
4
  ensureDaemonRunning,
5
5
  runAdapter
6
- } from "./chunk-YB7L4SZC.js";
7
- import "./chunk-DOPJ4OZ4.js";
6
+ } from "./chunk-YBQ7ZLSQ.js";
7
+ import "./chunk-YBNNWQMB.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.10",
3
+ "version": "1.8.12",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",