@modootoday/datalab-extension-mcp 1.4.1 → 1.4.3

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.
@@ -2,7 +2,7 @@ import {
2
2
  __export
3
3
  } from "./chunk-MLKGABMK.js";
4
4
 
5
- // ../mcp-server/dist/chunk-4Q6PTXC5.js
5
+ // ../mcp-server/dist/chunk-NRS76HC5.js
6
6
  import {
7
7
  createServer
8
8
  } from "http";
@@ -14391,17 +14391,14 @@ var BridgeToolDescriptorSchema = external_exports.object({
14391
14391
  /** JSON Schema for the arguments, passed to the host verbatim. */
14392
14392
  inputSchema: external_exports.record(external_exports.string(), external_exports.unknown()),
14393
14393
  /**
14394
- * MCP tool annotations, derived from the tool's tier. OPTIONAL by ABI rule.
14394
+ * MCP tool annotations, derived from the tool's tier. Optional by ABI rule.
14395
14395
  *
14396
- * Typed loosely on purpose. This end only forwards it — the shape is owned
14397
- * by `annotations.ts` on the extension side, and pinning it here would make
14398
- * every future hint a wire-contract change on a schema that binaries we
14399
- * cannot reach must keep parsing.
14396
+ * Typed loosely because this end only forwards it; pinning the shape here
14397
+ * would make every future hint a wire-contract change.
14400
14398
  *
14401
- * 🔴 A server built before this field existed strips it (z.object() without
14402
- * .strict()), so annotations simply do not arrive there. That degradation is
14403
- * silent and acceptable; a version marker that must survive old servers goes
14404
- * INSIDE `inputSchema`, which is forwarded verbatim.
14399
+ * 🔴 A server predating this field strips it, so annotations never arrive
14400
+ * there. Anything that must survive such a server goes inside the input
14401
+ * schema, which is forwarded verbatim.
14405
14402
  */
14406
14403
  annotations: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
14407
14404
  });
@@ -14414,19 +14411,15 @@ var HelloSchema = external_exports.object({
14414
14411
  /** Pairing token. Not auto-attached by the browser (unlike a cookie). */
14415
14412
  token: external_exports.string(),
14416
14413
  /**
14417
- * The extension's own id (`chrome.runtime.id`). ADDITIVE: older servers
14418
- * ignore it and identify the caller from the `Origin` header — but an
14419
- * extension service worker's privileged loopback fetch sends no `Origin`, so
14420
- * newer servers verify identity from this field when the header is absent.
14421
- * Optional so every binary that predates it still parses (see the ABI note).
14414
+ * The extension's own id. Additive: an extension service worker's privileged
14415
+ * loopback fetch sends no Origin header, so servers verify identity from this
14416
+ * field when the header is absent.
14422
14417
  */
14423
14418
  extensionId: external_exports.string().optional(),
14424
14419
  /**
14425
- * Feature flags this panel build understands, e.g. a future
14426
- * "catalog-push". Additive evolution happens HERE, not via a protocol
14427
- * bump: a capability an old peer never sent is simply absent, and code
14428
- * must treat absence as "do not use". Optional with a default so every
14429
- * binary that predates the field still parses.
14420
+ * Feature flags this panel build understands. Additive evolution happens
14421
+ * HERE, not via a protocol bump: a capability an old peer never sent is
14422
+ * simply absent, and code must treat absence as "do not use".
14430
14423
  */
14431
14424
  capabilities: external_exports.array(external_exports.string()).default([])
14432
14425
  });
@@ -14610,7 +14603,7 @@ function checkExtensionIdentity(origin, claimedExtensionId, extensionId) {
14610
14603
  };
14611
14604
  }
14612
14605
  var READ_ONLY_TOOLS = [
14613
- // --- Search-ad estimates. Uses your own advertiser API credentials. ---
14606
+ // --- Search-ad estimates. Uses the user's own advertiser API credentials. ---
14614
14607
  "ad_average_position_bid",
14615
14608
  "ad_competition_density",
14616
14609
  "ad_estimate_bid",
@@ -14665,7 +14658,7 @@ var READ_ONLY_TOOLS = [
14665
14658
  "local_job_categories",
14666
14659
  "local_region_trend",
14667
14660
  "local_regions",
14668
- // --- Your OWN channel analytics ---
14661
+ // --- The user's own channel analytics ---
14669
14662
  "daily_trend",
14670
14663
  "my_audience",
14671
14664
  "my_blog_summary",
@@ -14694,7 +14687,7 @@ var READ_ONLY_TOOLS = [
14694
14687
  "my_soaring",
14695
14688
  "my_top_content",
14696
14689
  "my_traffic_series",
14697
- // --- Public place data, and reads of a place you own ---
14690
+ // --- Public place data, and reads of a place the user owns ---
14698
14691
  "place_ai_briefing",
14699
14692
  "place_announcements",
14700
14693
  "place_autocomplete",
@@ -14726,8 +14719,8 @@ var READ_ONLY_TOOLS = [
14726
14719
  "search_influencer",
14727
14720
  "search_kin",
14728
14721
  "search_news",
14729
- // `search_total` is referenced by search_blog's description — leaving it out
14730
- // made that reference a broken link for MCP hosts (2026-07-17 audit).
14722
+ // Referenced by search_blog's description, so omitting it would leave a
14723
+ // dangling reference for hosts.
14731
14724
  "search_total",
14732
14725
  "search_video",
14733
14726
  "search_web",
@@ -14751,47 +14744,37 @@ var READ_ONLY_TOOLS = [
14751
14744
  // Its siblings that suggest titles, outlines, or specs all call a language
14752
14745
  // model, and are excluded for that reason.
14753
14746
  "seo_scorecard",
14754
- // --- 모델이 필요하지만 MCP 에서는 우리가 모델을 부르지 않는 도구들 ---
14755
- // 🔴 이 계열은 "이중과금" 을 이유로 통째로 제외돼 있었다. 호스트는 이 도구를 부르기로
14756
- // 결정하려고 **이미 자기 모델을 돌렸는데**, 우리가 사용자의 모델을 또 부르면 한 턴에
14757
- // 두 번 청구된다. 이제 `llmJson` 이 호출자를 보고 재료(프롬프트)만 돌려주므로 우리 쪽
14758
- // 모델 호출이 0 이다 — 제외의 근거가 사라졌다.
14759
- //
14760
- // 읽고 제안만 하므로 여기(읽기 전용)가 맞다. 되돌릴 것이 없다.
14747
+ // --- Planning tools that need a model, but never call one from here ---
14748
+ // 🔴 The calling host already ran its own model, so calling the user's too
14749
+ // would bill one turn twice. These return prompt material and make no model
14750
+ // call, which is what lets them read and suggest here, undoing nothing.
14761
14751
  "outline_suggest",
14762
14752
  "seo_aeo_geo_spec",
14763
- // 🔴 `write_draft` 는 2단(분석 → 초안)이라 한 번에 재료를 다 줄 수 없다. 먼저 오는
14764
- // 단계의 프롬프트를 주고, 호스트가 결과를 `structure` 로 되넘기면 다음 단계를 준다.
14765
- // 문체 프로필은 **객체가 아니라 프롬프트 안에 문장으로** 들어가고, 그 빌더는 문체 세
14766
- // 항목만 쓰므로 blogId·sourcePostIds·sourceTitles 는 나가지 않는다.
14753
+ // Two-stage (analyse, then draft), so it returns the first stage's prompt and
14754
+ // the second only once the host hands the result back. The
14755
+ // style profile travels as prose inside that prompt, not as an object.
14767
14756
  "write_draft",
14768
14757
  "benchmark_gap",
14769
14758
  "content_calendar",
14770
14759
  "internal_links",
14771
14760
  // --- Grouped with the writing tools, but a pure collector. ---
14772
- // `run_research` is categorised alongside tools that call a model, yet calls
14773
- // none itself — it only gathers public data. Its label is misleading; it was
14774
- // admitted after checking what it actually does. Classify by behaviour, not
14775
- // by category.
14761
+ // Calls no model itself — it only gathers public data. Admitted after
14762
+ // checking behaviour rather than trusting the category label.
14776
14763
  "run_research"
14777
14764
  ];
14778
14765
  var ALLOWED = Object.freeze(new Set(READ_ONLY_TOOLS));
14779
14766
  var EXCLUDED_TOOLS = Object.freeze({
14780
- // 🔴 editor_* 는 더 이상 여기 없다 — 등급 표(TOOL_TIERS)로 옮겼다. 제외였던
14781
- // 이유는 "확인을 받을 방법이 없고 되돌릴 수단도 없다" 였는데, 둘 다 생겼다:
14782
- // 세션 동의 + 호출별 확인이 서고, 주입 전후로 임시저장을 부르며 그것이
14783
- // 거절될 때를 위해 문서 사본을 따로 뜬다.
14784
14767
  generate_images: "writes to your post, needs confirmation, and spends your AI credits",
14785
- // 🔴 editor_read / editor_read_structure 도 더 이상 여기 없다. 보류 사유는 "탭이
14786
- // 열려 있어야 하는데 외부 에이전트에겐 열 탭이 없다" 였지만, 그것은 제외할 이유가
14787
- // 아니라 **읽고 나서 알게 될 사실**이다 — 탭이 없으면 도구가 그렇게 답하면 된다.
14788
- // 지금은 TOOL_TIERS 에서 read 등급으로 노출된다.
14789
- // Spend your own AI credits on a turn the calling host already paid for.
14790
14768
  // Side effect outside the request/response contract.
14791
- export_report: "triggers a file download"
14769
+ export_report: "triggers a file download",
14770
+ // 🔴 A host that can generate its own image has a free way onto the canvas —
14771
+ // gallery_image_add. Spending credits for what the caller already has is the
14772
+ // charge nobody meant to make.
14773
+ photo_ai_image_add: "spends your AI credits for an image your own app can make and hand over",
14774
+ video_image_realign: "spends your AI credits per photo to reach what the free scene tools already reach"
14792
14775
  });
14793
14776
  var TOOL_TIERS = Object.freeze({
14794
- // ── 사진 편집기 (우리 표면) ──────────────────────────────────────
14777
+ // ── Photo editor (our own surface) ──────────────────────────────
14795
14778
  photo_canvas_get: "read",
14796
14779
  photo_gallery_list: "read",
14797
14780
  photo_text_add: "write",
@@ -14808,13 +14791,21 @@ var TOOL_TIERS = Object.freeze({
14808
14791
  photo_node_remove: "destructive",
14809
14792
  photo_page_remove: "destructive",
14810
14793
  photo_template_apply: "destructive",
14811
- photo_ai_image_add: "paid",
14812
- // ── 영상 편집기 (우리 표면) ──────────────────────────────────────
14794
+ // ── Gallery: the free way in ────────────────────────────────────
14795
+ //
14796
+ // 🔴 Destructive because it reads a path off the user's disk, the same
14797
+ // capability editor_insert_image carries. A session toggle would open the
14798
+ // whole filesystem once and stay open.
14799
+ gallery_image_add: "destructive",
14800
+ // ── Video editor (our own surface) ──────────────────────────────
14813
14801
  video_timeline_get: "read",
14814
14802
  video_sources_list: "read",
14815
14803
  video_scene_add: "write",
14816
14804
  video_subtitle_set: "write",
14817
14805
  video_scene_image_set: "write",
14806
+ // Brings a gallery picture into this video — an add, like video_image_add.
14807
+ // The disk read already happened at gallery_image_add, under its own confirm.
14808
+ video_image_add_from_gallery: "write",
14818
14809
  video_scene_update: "write",
14819
14810
  video_background_set: "write",
14820
14811
  video_canvas_resize: "write",
@@ -14826,52 +14817,39 @@ var TOOL_TIERS = Object.freeze({
14826
14817
  video_node_remove: "destructive",
14827
14818
  video_scene_remove: "destructive",
14828
14819
  video_narration_generate: "paid",
14829
- video_image_realign: "paid",
14830
- // ── 스마트에디터 읽기 ────────────────────────────────────────────
14820
+ // ── Post editor: reads ──────────────────────────────────────────
14831
14821
  editor_read: "read",
14832
14822
  editor_read_structure: "read",
14833
- // ── 스마트에디터 쓰기 ────────────────────────────────────────────
14823
+ // ── Post editor: writes ─────────────────────────────────────────
14834
14824
  //
14835
- // 🔴 안전망이 선 뒤에 열었다. 주입 전후로 벤더 임시저장을 부르고, 그것이
14836
- // 거절되는 세 경우(빈 문서 · 길이 초과 · 충돌)를 위해 문서 사본을 따로 뜬다.
14837
- // 그전에는 되돌릴 수단이 에디터 undo 한 스텝뿐이었다 — 그 상태로 외부
14838
- // 프로세스에 본문 쓰기를 열 수는 없었다.
14825
+ // 🔴 Every injection is bracketed by a vendor autosave, plus a separate copy
14826
+ // of the document for the cases the vendor refuses one. Without that
14827
+ // recovery point, editor undo would be the only way back.
14839
14828
  //
14840
- // 🔴 `editor_replace` 만 파괴적이다. 나머지는 넣거나 덧붙이는 것이라 임시저장
14841
- // 지점으로 돌아갈 수 있지만, 본문 전체 교체는 그 지점 자체를 갈아치운다.
14829
+ // 🔴 Only whole-body replacement is destructive: the others append, so the
14830
+ // autosave point still holds, while replacing the body overwrites it.
14842
14831
  editor_insert_draft: "write",
14843
- // 🔴 창을 여는 것은 문서를 고치는 것이 아니다. 그래도 `write` 로 둔다 — 사용자의
14844
- // 브라우저에 탭을 만드는 일이고, 문서 편집을 켜지 않은 세션에서 창만 열어 두는
14845
- // 것은 어차피 할 수 있는 일이 없다. 등급이 하나면 사용자가 켜고 끄는 것도 하나다.
14832
+ // Opening a window edits no document, but it does create a tab in the user's
14833
+ // browser, and a session that has not enabled editing can do nothing with the
14834
+ // window anyway — one tier keeps it one thing for the user to allow.
14846
14835
  editor_open_window: "write",
14847
- // 🔴 창을 닫는 것은 되돌릴 수 없다 — 사용자가 쓰던 원고가 그 안에 있을 수 있으므로
14848
- // 호출마다 묻는다. 세션 동의로 넘어가게 두면, 한 번 켜 두고 잊은 토글이 원고를
14849
- // 없앨 수 있다.
14836
+ // 🔴 Closing a window cannot be undone and the user's draft may be inside, so
14837
+ // it is asked per call rather than covered by a forgotten session toggle.
14850
14838
  editor_reduce_windows: "destructive",
14851
- // 🔴 삽입은 덧붙이기라 덮어쓰기 확인을 거치지 않는다 — 그래서 등급이 유일한
14852
- // 방벽이고, 호출마다 묻는다. 넣을 그림의 출처를 사용자가 매번 보고 정한다.
14839
+ // 🔴 Insertion appends, so it never passes through the overwrite prompt; the
14840
+ // tier is the only barrier, and the user approves each image's source.
14853
14841
  editor_insert_image: "destructive",
14854
14842
  editor_set_title: "write",
14855
- // 🔴 `title_optimize` 는 편집기 도구가 아닌데 등급이 있다. 사용자가 제목 후보를 고르는
14856
- // 순간 그것이 곧 확인이 되어 **제목을 덮어쓰기** 때문이다 — 그래서 같은 일을 하는
14857
- // `editor_set_title` 과 같은 등급이다. 이름에 표면 접두가 없는 것은 이 도구가
14858
- // 사진/영상 편집기 표면에 속하지 않아서다(접두 규약은 그 둘의 이름 충돌 방지용).
14843
+ // 🔴 Not an editor tool, but tiered like one: picking a title candidate IS
14844
+ // the confirmation and overwrites the title, so it matches editor_set_title.
14859
14845
  title_optimize: "write",
14860
14846
  editor_undo: "write",
14861
14847
  editor_replace: "destructive"
14862
14848
  });
14863
14849
  var PAID_TOOL_COSTS = Object.freeze({
14864
- photo_ai_image_add: Object.freeze({
14865
- basis: "AI \uC774\uBBF8\uC9C0 1\uC7A5 \uC0DD\uC131",
14866
- editorAsksAgain: false
14867
- }),
14868
14850
  video_narration_generate: Object.freeze({
14869
14851
  basis: "\uBAA9\uC18C\uB9AC\uAC00 \uC5C6\uB294 \uC7A5\uBA74 \uC218\uB9CC\uD07C \uC74C\uC131 \uC0DD\uC131",
14870
14852
  editorAsksAgain: true
14871
- }),
14872
- video_image_realign: Object.freeze({
14873
- basis: "\uC0AC\uC9C4 \uC218\uB9CC\uD07C \uC774\uBBF8\uC9C0 \uBD84\uC11D",
14874
- editorAsksAgain: true
14875
14853
  })
14876
14854
  });
14877
14855
  var HINTS_BY_TIER = Object.freeze({
@@ -14901,9 +14879,9 @@ var LOOKUP_ANNOTATIONS = Object.freeze({
14901
14879
  idempotentHint: true
14902
14880
  });
14903
14881
  var BRIDGE_LIMITS = Object.freeze({
14904
- /** 한 번에 몰아 쓸 수 있는 호출 수. */
14882
+ /** Calls available in one burst. */
14905
14883
  capacity: 10,
14906
- /** 초당 다시 차는 칸 수. */
14884
+ /** Slots refilled per second. */
14907
14885
  refillPerSecond: 1
14908
14886
  });
14909
14887
  var CONNECTOR_RESULT_CAP_BYTES = 4 * 1024 * 1024;
@@ -14912,7 +14890,7 @@ var RESULT_BUDGET_BYTES = Math.floor(
14912
14890
  CONNECTOR_RESULT_CAP_BYTES / RESULT_HEADROOM
14913
14891
  );
14914
14892
  var REASON_POLICY = Object.freeze({
14915
- // --- 기다리면 풀린다 ---
14893
+ // --- Resolves on its own with time ---
14916
14894
  rate_limited: Object.freeze({
14917
14895
  retryable: true,
14918
14896
  retryAfterMs: Math.ceil(1e3 / BRIDGE_LIMITS.refillPerSecond)
@@ -14920,20 +14898,20 @@ var REASON_POLICY = Object.freeze({
14920
14898
  timeout: Object.freeze({ retryable: true }),
14921
14899
  connection_lost: Object.freeze({ retryable: true }),
14922
14900
  tool_error: Object.freeze({ retryable: true }),
14923
- // --- 요청을 고쳐야 한다 ---
14901
+ // --- The request itself must change ---
14924
14902
  unknown_tool: Object.freeze({ retryable: false }),
14925
14903
  invalid_args: Object.freeze({ retryable: false }),
14926
14904
  tool_closed: Object.freeze({ retryable: false }),
14927
14905
  missing_in_build: Object.freeze({ retryable: false }),
14928
- // --- 🔴 사람이 움직여야 한다. 다시 거는 것은 조르는 것이다 ---
14906
+ // --- 🔴 A human must act; re-calling only nags them ---
14929
14907
  needs_consent: Object.freeze({ retryable: false }),
14930
14908
  not_confirmed: Object.freeze({ retryable: false }),
14931
14909
  confirm_timeout: Object.freeze({ retryable: false }),
14932
14910
  confirm_too_late: Object.freeze({ retryable: false }),
14933
- // --- 사용자가 브라우저에서 무언가 해야 한다 ---
14911
+ // --- The user must do something in the browser ---
14934
14912
  multiple_editors: Object.freeze({ retryable: false }),
14935
14913
  editor_busy: Object.freeze({ retryable: false }),
14936
- // --- 연결 자체를 고쳐야 한다 ---
14914
+ // --- The connection itself must be repaired ---
14937
14915
  not_connected: Object.freeze({ retryable: false }),
14938
14916
  unauthorized: Object.freeze({ retryable: false })
14939
14917
  });
@@ -14962,12 +14940,9 @@ function projectTool(d) {
14962
14940
  name: d.name,
14963
14941
  description: d.description,
14964
14942
  inputSchema: d.inputSchema,
14965
- // 🔴 Re-assembly, not spread — and that is exactly why this line has to
14966
- // exist. Naming the fields keeps a malformed peer from smuggling extra
14967
- // keys into a host payload, but it also means a field added upstream is
14968
- // dropped here in silence: the extension believes it advertised the
14969
- // hints, the host never sees them, and nothing anywhere reports a
14970
- // failure. Any future descriptor field must be added HERE too.
14943
+ // 🔴 Re-assembly, not spread: naming each field keeps a malformed peer
14944
+ // from smuggling extra keys into a host payload, but it also drops any
14945
+ // new descriptor field in silence. Add future fields HERE too.
14971
14946
  ...d.annotations ? { annotations: d.annotations } : {}
14972
14947
  }
14973
14948
  };
@@ -14995,6 +14970,324 @@ function projectTools(descriptors) {
14995
14970
  }
14996
14971
  return { tools, rejected };
14997
14972
  }
14973
+ var BLOG_BRIEF = [
14974
+ "my_daily_brief",
14975
+ "my_blog_summary",
14976
+ "my_realtime",
14977
+ "my_traffic_series",
14978
+ "my_top_content",
14979
+ "my_soaring",
14980
+ "my_publish_calendar",
14981
+ "daily_trend",
14982
+ "my_moments"
14983
+ ];
14984
+ var BLOG_INSIGHTS = [
14985
+ "my_content_audience",
14986
+ "my_inflow",
14987
+ "my_audience",
14988
+ "my_inflow_domain",
14989
+ "my_country",
14990
+ "my_device",
14991
+ "my_dwell",
14992
+ "my_followers",
14993
+ "my_revisit",
14994
+ "my_revenue",
14995
+ "my_impression_click",
14996
+ "my_impression_ranks",
14997
+ "my_channels",
14998
+ "my_revenue_ranks",
14999
+ "my_revenue_efficiency",
15000
+ "my_content_info",
15001
+ "my_content_read",
15002
+ "my_content_detail",
15003
+ "my_content_inflow"
15004
+ ];
15005
+ var RESEARCH = [
15006
+ "local_regions",
15007
+ "local_job_categories",
15008
+ "local_region_trend",
15009
+ "local_area_category_rank",
15010
+ "content_volume",
15011
+ "autocomplete_keywords",
15012
+ "qra_keywords",
15013
+ "kin_question_demand",
15014
+ "web_read",
15015
+ "run_research",
15016
+ // Planning tools: they need a model but call none here, returning prompt
15017
+ // material instead. Grouped by job — all three design a post not yet written.
15018
+ "outline_suggest",
15019
+ "seo_aeo_geo_spec",
15020
+ "content_calendar",
15021
+ // The last step of that job: turn the design into an actual draft.
15022
+ "write_draft"
15023
+ ];
15024
+ var EDITOR_CONTROL = [
15025
+ // 🔴 The tool that opens a tab belongs in the group that requires one: a
15026
+ // model choosing this group was just told it needs a tab, and the way out
15027
+ // has to be visible at the wall, not in some other group.
15028
+ "editor_open_window",
15029
+ "editor_reduce_windows",
15030
+ "editor_read",
15031
+ "editor_read_structure",
15032
+ "editor_undo",
15033
+ "editor_replace",
15034
+ "editor_insert_draft",
15035
+ "editor_set_title",
15036
+ // Both act on the open draft: title_optimize overwrites the title the moment
15037
+ // a candidate is picked, and internal_links reads the current draft.
15038
+ "title_optimize",
15039
+ "internal_links",
15040
+ "editor_insert_image",
15041
+ "seo_scorecard"
15042
+ ];
15043
+ var BY_PREFIX = [
15044
+ { slug: "serp-watch", prefixes: ["search_", "serp_"] },
15045
+ { slug: "benchmark", prefixes: ["blog_", "cafe_", "influencer_"] },
15046
+ { slug: "place", prefixes: ["place_"] },
15047
+ { slug: "shopping", prefixes: ["shopping_"] },
15048
+ { slug: "ad-bidding", prefixes: ["ad_"] },
15049
+ { slug: "comment-stats", prefixes: ["comment_"] },
15050
+ { slug: "photo-editor", prefixes: ["photo_"] },
15051
+ { slug: "video-editor", prefixes: ["video_"] },
15052
+ { slug: "keyword-research", prefixes: ["keyword_"] }
15053
+ ];
15054
+ var GALLERY = ["gallery_image_add"];
15055
+ var EXPLICIT = [
15056
+ { slug: "gallery", tools: GALLERY },
15057
+ { slug: "blog-brief", tools: BLOG_BRIEF },
15058
+ { slug: "blog-insights", tools: BLOG_INSIGHTS },
15059
+ { slug: "keyword-research", tools: RESEARCH },
15060
+ { slug: "editor-control", tools: EDITOR_CONTROL },
15061
+ // Named like the group but caught by none of its prefixes, so it is listed
15062
+ // explicitly — a similar name is not a membership rule.
15063
+ { slug: "benchmark", tools: ["benchmark_gap"] }
15064
+ ];
15065
+ var META = [
15066
+ {
15067
+ slug: "blog-brief",
15068
+ label: "\uC624\uB298\uC758 \uBE14\uB85C\uADF8 \uBE0C\uB9AC\uD551",
15069
+ summary: "\uB0B4 \uBE14\uB85C\uADF8\uAC00 \uC9C0\uAE08 \uC5B4\uB5A4\uC9C0 \u2014 \uBC29\uBB38\xB7\uC870\uD68C\xB7\uC778\uAE30\uAE00\xB7\uC2E4\uC2DC\uAC04"
15070
+ },
15071
+ {
15072
+ slug: "blog-insights",
15073
+ label: "\uBE14\uB85C\uADF8 \uC2EC\uCE35 \uBD84\uC11D",
15074
+ summary: "\uC65C \uADF8\uB7F0\uC9C0 \u2014 \uC720\uC785 \uACBD\uB85C\xB7\uC131\uBCC4\uC5F0\uB839\xB7\uCCB4\uB958\xB7\uC218\uC775\xB7\uAE00\uBCC4 \uC0C1\uC138"
15075
+ },
15076
+ {
15077
+ slug: "keyword-research",
15078
+ label: "\uD0A4\uC6CC\uB4DC\xB7\uAE00\uAC10 \uB9AC\uC11C\uCE58",
15079
+ summary: "\uBB34\uC5C7\uC744 \uC4F8\uC9C0 \u2014 \uD0A4\uC6CC\uB4DC \uC218\uC694\xB7\uC5F0\uAD00\uC5B4\xB7\uC790\uB3D9\uC644\uC131\xB7\uBC1C\uD589\uB7C9\xB7\uC9C0\uC2DDiN \uC9C8\uBB38\xB7\uC9C0\uC5ED \uD2B8\uB80C\uB4DC"
15080
+ },
15081
+ {
15082
+ slug: "serp-watch",
15083
+ label: "\uB124\uC774\uBC84 \uAC80\uC0C9\uACB0\uACFC \uD655\uC778",
15084
+ summary: "\uC774 \uAC80\uC0C9\uC5B4\uC5D0 \uC9C0\uAE08 \uB124\uC774\uBC84\uAC00 \uBB34\uC5C7\uC744 \uB744\uC6B0\uB294\uC9C0"
15085
+ },
15086
+ {
15087
+ slug: "benchmark",
15088
+ label: "\uACBD\uC7C1\uC790 \uBCA4\uCE58\uB9C8\uD0B9",
15089
+ summary: "\uB2E4\uB978 \uBE14\uB85C\uADF8\xB7\uCE74\uD398\xB7\uC778\uD50C\uB8E8\uC5B8\uC11C\uAC00 \uBB34\uC5C7\uC744 \uD558\uB294\uC9C0"
15090
+ },
15091
+ {
15092
+ slug: "place",
15093
+ label: "\uB124\uC774\uBC84 \uD50C\uB808\uC774\uC2A4 \uAD00\uB9AC",
15094
+ summary: "\uB0B4 \uB9E4\uC7A5 \uC815\uBCF4\xB7\uB9AC\uBDF0\xB7\uC608\uC57D\xB7\uCFE0\uD3F0\xB7\uB300\uAE30"
15095
+ },
15096
+ {
15097
+ slug: "shopping",
15098
+ label: "\uC1FC\uD551 \uC778\uC0AC\uC774\uD2B8",
15099
+ summary: "\uC774 \uC0C1\uD488\uC774 \uC5B8\uC81C \uB204\uAD6C\uC5D0\uAC8C \uD314\uB9AC\uB294\uC9C0"
15100
+ },
15101
+ {
15102
+ slug: "ad-bidding",
15103
+ label: "\uAC80\uC0C9\uAD11\uACE0 \uC785\uCC30",
15104
+ // The calls themselves cost nothing, hence the read tier, but they return
15105
+ // data only when the user's search-ad account credentials are present.
15106
+ summary: "\uC785\uCC30\uAC00\uC640 \uC608\uC0C1 \uC131\uACFC \u2014 \uBB34\uB8CC, \uAC80\uC0C9\uAD11\uACE0 \uACC4\uC815 \uC790\uACA9\uC99D\uBA85 \uD544\uC694"
15107
+ },
15108
+ {
15109
+ slug: "comment-stats",
15110
+ label: "\uB313\uAE00 \uBC18\uC751 \uD1B5\uACC4",
15111
+ summary: "\uB274\uC2A4 \uB313\uAE00\uC774 \uC5B8\uC81C \uB204\uAD6C\uC5D0\uAC8C \uB2EC\uB9AC\uB294\uC9C0"
15112
+ },
15113
+ {
15114
+ slug: "editor-control",
15115
+ label: "\uAE00\uC4F0\uAE30 \uCC3D \uC81C\uC5B4",
15116
+ summary: "\uC5F4\uB824 \uC788\uB294 \uB124\uC774\uBC84 \uAE00\uC4F0\uAE30 \uCC3D\uC758 \uC6D0\uACE0\uB97C \uC77D\uACE0 \uACE0\uCE5C\uB2E4",
15117
+ needsTab: true,
15118
+ openerTool: "editor_open_window",
15119
+ reducerTool: "editor_reduce_windows"
15120
+ },
15121
+ {
15122
+ slug: "photo-editor",
15123
+ label: "\uC0AC\uC9C4 \uD3B8\uC9D1\uAE30 \uC870\uC791",
15124
+ summary: "\uC0AC\uC9C4 \uD3B8\uC9D1\uAE30 \uCE94\uBC84\uC2A4\uC5D0 \uD14D\uC2A4\uD2B8\xB7\uB3C4\uD615\xB7\uC774\uBBF8\uC9C0\uB97C \uB123\uB294\uB2E4",
15125
+ needsTab: true,
15126
+ reducerTool: "editor_reduce_windows"
15127
+ },
15128
+ {
15129
+ slug: "video-editor",
15130
+ label: "\uC601\uC0C1 \uD3B8\uC9D1\uAE30 \uC870\uC791",
15131
+ summary: "\uC601\uC0C1 \uD3B8\uC9D1\uAE30\uC758 \uC7A5\uBA74\xB7\uC790\uB9C9\xB7\uBAA9\uC18C\uB9AC\uB97C \uB9CC\uB4E0\uB2E4",
15132
+ needsTab: true,
15133
+ reducerTool: "editor_reduce_windows"
15134
+ },
15135
+ {
15136
+ slug: "gallery",
15137
+ label: "\uC774\uBBF8\uC9C0 \uAC24\uB7EC\uB9AC",
15138
+ summary: "\uB0B4\uAC00 \uB9CC\uB4E0 \uC0AC\uC9C4 \uD30C\uC77C\uC744 \uAC24\uB7EC\uB9AC\uC5D0 \uB123\uC5B4 \uB450 \uD3B8\uC9D1\uAE30\uC5D0\uC11C \uC4F4\uB2E4 \u2014 \uD3B8\uC9D1\uAE30 \uCC3D\uC774 \uC5C6\uC5B4\uB3C4 \uB41C\uB2E4"
15139
+ }
15140
+ ];
15141
+ var FALLBACK_SLUG = "other";
15142
+ function slugFor(name) {
15143
+ for (const e of EXPLICIT) {
15144
+ if (e.tools.includes(name)) return e.slug;
15145
+ }
15146
+ for (const p of BY_PREFIX) {
15147
+ if (p.prefixes.some((x) => name.startsWith(x))) return p.slug;
15148
+ }
15149
+ return FALLBACK_SLUG;
15150
+ }
15151
+ function buildToolsets(names) {
15152
+ const byslug = /* @__PURE__ */ new Map();
15153
+ for (const n of names) {
15154
+ const s = slugFor(n);
15155
+ (byslug.get(s) ?? byslug.set(s, []).get(s)).push(n);
15156
+ }
15157
+ const out = [];
15158
+ for (const m of META) {
15159
+ const tools = byslug.get(m.slug) ?? [];
15160
+ if (tools.length > 0) out.push({ ...m, tools });
15161
+ byslug.delete(m.slug);
15162
+ }
15163
+ const leftover = byslug.get(FALLBACK_SLUG) ?? [];
15164
+ if (leftover.length > 0) {
15165
+ out.push({
15166
+ slug: FALLBACK_SLUG,
15167
+ label: "\uADF8 \uBC16\uC758 \uB3C4\uAD6C",
15168
+ summary: "\uC544\uC9C1 \uBB36\uC74C\uC774 \uC815\uD574\uC9C0\uC9C0 \uC54A\uC740 \uB3C4\uAD6C",
15169
+ tools: leftover
15170
+ });
15171
+ }
15172
+ return out;
15173
+ }
15174
+ var CATALOG_TOOL = "datalab_catalog";
15175
+ var DESCRIBE_TOOL = "datalab_describe";
15176
+ var CALL_TOOL = "datalab_call";
15177
+ var CONFIRM_STATUS_TOOL = "datalab_confirm_status";
15178
+ var SESSION_STATE_TOOL = "datalab_session_state";
15179
+ function discoveryDescriptors(entries) {
15180
+ const sets = buildToolsets(entries.map((e) => e.name));
15181
+ const names = sets.map((s) => s.slug).join(", ");
15182
+ return [
15183
+ {
15184
+ name: CATALOG_TOOL,
15185
+ description: `\uB370\uC774\uD130\uB7A9\uD234\uC988\uAC00 \uD560 \uC218 \uC788\uB294 \uC77C\uC758 \uBAA9\uB85D\uC744 \uB3CC\uB824\uC900\uB2E4. \uB124\uC774\uBC84 \uBE14\uB85C\uADF8\xB7\uD0A4\uC6CC\uB4DC\xB7\uAC80\uC0C9\uACB0\uACFC\xB7\uD50C\uB808\uC774\uC2A4\xB7\uC1FC\uD551\xB7\uAD11\uACE0 \uD1B5\uACC4\uC640 \uAE00\uC4F0\uAE30/\uC0AC\uC9C4/\uC601\uC0C1 \uD3B8\uC9D1\uAE30 \uC81C\uC5B4\uAE4C\uC9C0 ${entries.length}\uAC1C \uB3C4\uAD6C\uAC00 \uC788\uACE0, \uC5EC\uAE30 \uC815\uC758\uB85C \uBCF4\uC774\uB294 \uAC83\uC740 \uADF8\uC911 \uC77C\uBD80\uB2E4. **\uC0AC\uC6A9\uC790\uAC00 \uBB34\uC5C7\uC744 \uBD80\uD0C1\uD558\uB4E0, \uC9C0\uAE08 \uBCF4\uC774\uB294 \uB3C4\uAD6C\uB85C \uC548 \uB418\uBA74 \uBA3C\uC800 \uC774\uAC83\uC744 \uBD88\uB7EC\uB77C** \u2014 \uC5C6\uB2E4\uACE0 \uB2F5\uD558\uAE30 \uC804\uC5D0 \uD655\uC778\uD55C\uB2E4. toolset \uC5C6\uC774 \uBD80\uB974\uBA74 \uBB36\uC74C \uBAA9\uB85D, \uC8FC\uBA74 \uADF8 \uBB36\uC74C\uC758 \uB3C4\uAD6C \uC774\uB984\uACFC \uC124\uBA85. \uBB36\uC74C: ${names}.`,
15186
+ annotations: LOOKUP_ANNOTATIONS,
15187
+ inputSchema: {
15188
+ type: "object",
15189
+ properties: {
15190
+ toolset: {
15191
+ type: "string",
15192
+ enum: sets.map((s) => s.slug),
15193
+ description: "\uC774 \uBB36\uC74C\uC758 \uB3C4\uAD6C \uBAA9\uB85D\uC744 \uBCF8\uB2E4. \uC0DD\uB7B5\uD558\uBA74 \uBB36\uC74C \uBAA9\uB85D\uB9CC"
15194
+ },
15195
+ query: {
15196
+ type: "string",
15197
+ description: "\uCC3E\uB294 \uB9D0(\uC608: '\uBC29\uBB38\uC790', '\uACBD\uC7C1 \uBE14\uB85C\uADF8'). \uC774\uB984\uACFC \uC124\uBA85\uC5D0\uC11C \uCC3E\uC544 \uBB36\uC74C\uC744 \uAC00\uB85C\uC9C8\uB7EC \uB3CC\uB824\uC900\uB2E4"
15198
+ },
15199
+ withSchemas: {
15200
+ type: "boolean",
15201
+ description: `\uC778\uC790 \uC2A4\uD0A4\uB9C8\uAE4C\uC9C0 \uD568\uAED8 \uBC1B\uB294\uB2E4 \u2014 \uADF8\uB7EC\uBA74 ${DESCRIBE_TOOL} \uC5C6\uC774 \uBC14\uB85C \uC2E4\uD589\uD560 \uC218 \uC788\uB2E4. toolset \uC774\uB098 query \uC640 \uAC19\uC774 \uC904 \uB54C\uB9CC \uC4F4\uB2E4`
15202
+ }
15203
+ },
15204
+ additionalProperties: false
15205
+ }
15206
+ },
15207
+ {
15208
+ name: DESCRIBE_TOOL,
15209
+ description: `\uB3C4\uAD6C\uC758 \uC778\uC790 \uC2A4\uD0A4\uB9C8\uB97C \uB3CC\uB824\uC900\uB2E4. **${CALL_TOOL} \uB85C \uBD80\uB974\uAE30 \uC804\uC5D0 \uADF8 \uB3C4\uAD6C\uC5D0 \uB300\uD574 \uCD5C\uC18C \uD55C \uBC88\uC740 \uC774\uAC83\uC744 \uD638\uCD9C\uD574\uB77C** \u2014 \uC778\uC790 \uC774\uB984\uACFC \uD544\uC218 \uC5EC\uBD80\uB97C \uC9C0\uC5B4\uB0B4\uBA74 \uAC70\uC808\uB418\uACE0, \uC655\uBCF5\uC774 \uD55C \uBC88 \uB354 \uB4E0\uB2E4. \uC774\uBBF8 \uC2A4\uD0A4\uB9C8\uB97C \uBC1B\uC544 \uB454 \uB3C4\uAD6C\uB294 \uB2E4\uC2DC \uBD80\uB97C \uD544\uC694 \uC5C6\uB2E4.`,
15210
+ annotations: LOOKUP_ANNOTATIONS,
15211
+ inputSchema: {
15212
+ type: "object",
15213
+ properties: {
15214
+ tools: {
15215
+ type: "array",
15216
+ items: { type: "string" },
15217
+ maxItems: 10,
15218
+ description: "\uC2A4\uD0A4\uB9C8\uB97C \uBCFC \uB3C4\uAD6C \uC774\uB984 (\uD55C \uBC88\uC5D0 10\uAC1C\uAE4C\uC9C0)"
15219
+ }
15220
+ },
15221
+ required: ["tools"],
15222
+ additionalProperties: false
15223
+ }
15224
+ },
15225
+ {
15226
+ name: CALL_TOOL,
15227
+ description: `${CATALOG_TOOL} \uB85C \uCC3E\uC740 \uB3C4\uAD6C\uB97C \uC2E4\uD589\uD55C\uB2E4. args \uB294 ${DESCRIBE_TOOL} \uAC00 \uC900 \uC2A4\uD0A4\uB9C8\uB97C \uADF8\uB300\uB85C \uB530\uB978\uB2E4 \u2014 \uB9DE\uC9C0 \uC54A\uC73C\uBA74 \uC2E4\uD589\uD558\uC9C0 \uC54A\uACE0 \uBB34\uC5C7\uC774 \uD2C0\uB838\uB294\uC9C0 \uB3CC\uB824\uC900\uB2E4. \uBB38\uC11C\uB97C \uACE0\uCE58\uAC70\uB098 \uC694\uAE08\uC774 \uB4DC\uB294 \uB3C4\uAD6C\uB294 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD655\uC778\uC744 \uBC1B\uC740 \uB4A4\uC5D0 \uC2E4\uD589\uB41C\uB2E4.`,
15228
+ // 🔴 Deliberately unannotated: this tool's effect is the delegated tool's
15229
+ // effect, so any fixed hint would be half false. Tier is judged at run time.
15230
+ inputSchema: {
15231
+ type: "object",
15232
+ properties: {
15233
+ tool: { type: "string", description: "\uC2E4\uD589\uD560 \uB3C4\uAD6C \uC774\uB984" },
15234
+ args: {
15235
+ type: "object",
15236
+ description: `\uADF8 \uB3C4\uAD6C\uC758 \uC778\uC790. ${DESCRIBE_TOOL} \uC758 \uC2A4\uD0A4\uB9C8\uB97C \uB530\uB978\uB2E4`
15237
+ }
15238
+ },
15239
+ required: ["tool"],
15240
+ additionalProperties: false
15241
+ }
15242
+ },
15243
+ {
15244
+ name: CONFIRM_STATUS_TOOL,
15245
+ description: `\uC0AC\uC6A9\uC790 \uD655\uC778\uC744 \uAE30\uB2E4\uB9AC\uB294 \uD638\uCD9C\uC758 \uACB0\uACFC\uB97C \uAC00\uC838\uC628\uB2E4. ${CALL_TOOL} \uC774 \`awaiting_confirm\` \uACFC ticket \uC744 \uB3CC\uB824\uC92C\uC744 \uB54C\uB9CC \uC4F4\uB2E4. \uC544\uC9C1 \uB2F5\uC774 \uC5C6\uC73C\uBA74 \`pending\` \uC774\uB2C8 \uBA87 \uCD08 \uB4A4 \uB2E4\uC2DC \uD655\uC778\uD574\uB77C \u2014 **\uC6D0\uB798 \uB3C4\uAD6C\uB97C \uB2E4\uC2DC \uBD80\uB974\uC9C0 \uB9C8\uB77C**, \uD655\uC778 \uCC3D\uC774 \uD558\uB098 \uB354 \uB728\uACE0 \uC694\uAE08\uC774 \uB4DC\uB294 \uC791\uC5C5\uC774\uBA74 \uB450 \uBC88 \uCCAD\uAD6C\uB41C\uB2E4.`,
15246
+ annotations: LOOKUP_ANNOTATIONS,
15247
+ inputSchema: {
15248
+ type: "object",
15249
+ properties: {
15250
+ ticket: {
15251
+ type: "string",
15252
+ description: `${CALL_TOOL} \uC774 \uB3CC\uB824\uC900 ticket \uAC12 \uADF8\uB300\uB85C`
15253
+ }
15254
+ },
15255
+ required: ["ticket"],
15256
+ additionalProperties: false
15257
+ }
15258
+ }
15259
+ ];
15260
+ }
15261
+ function sessionStateDescriptor() {
15262
+ return {
15263
+ name: SESSION_STATE_TOOL,
15264
+ description: "\uC9C0\uAE08 \uC774 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uD655\uC778 \uCC3D \uC5C6\uC774 \uC5BC\uB9C8\uB098 \uB354 \uC791\uC5C5\uD560 \uC218 \uC788\uB294\uC9C0 \uC54C\uB824\uC900\uB2E4. \uC5EC\uB7EC \uBC88\uC758 \uD3B8\uC9D1\uC774\uB098 \uBC30\uCE58\uB97C \uC2DC\uC791\uD558\uAE30 \uC804\uC5D0 \uBA3C\uC800 \uBD80\uB978\uB2E4 \u2014 \uB0A8\uC740 \uC2DC\uAC04\uC774 \uC9E7\uC73C\uBA74 \uB3C4\uC911\uC5D0 \uD655\uC778 \uCC3D\uC774 \uB728\uAE30 \uC2DC\uC791\uD558\uACE0, \uC0AC\uC6A9\uC790\uAC00 \uC790\uB9AC\uC5D0 \uC5C6\uC73C\uBA74 \uAC70\uAE30\uC11C \uBA48\uCD98\uB2E4.",
15265
+ inputSchema: {
15266
+ type: "object",
15267
+ properties: {},
15268
+ additionalProperties: false
15269
+ },
15270
+ annotations: LOOKUP_ANNOTATIONS
15271
+ };
15272
+ }
15273
+ function allowlistNames() {
15274
+ return [.../* @__PURE__ */ new Set([...READ_ONLY_TOOLS, ...Object.keys(TOOL_TIERS)])];
15275
+ }
15276
+ function staticDiscoveryCatalog() {
15277
+ const entries = allowlistNames().map((name) => ({
15278
+ name,
15279
+ description: "",
15280
+ inputSchema: {}
15281
+ }));
15282
+ return [...discoveryDescriptors(entries), sessionStateDescriptor()].map(
15283
+ (e) => ({
15284
+ name: e.name,
15285
+ description: e.description,
15286
+ inputSchema: e.inputSchema,
15287
+ ...e.annotations ? { annotations: { ...e.annotations } } : {}
15288
+ })
15289
+ );
15290
+ }
14998
15291
  var BRIDGE_USER_MESSAGES = Object.freeze({
14999
15292
  /** Tool call arrived while no panel is connected. */
15000
15293
  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.",
@@ -15003,12 +15296,9 @@ var BRIDGE_USER_MESSAGES = Object.freeze({
15003
15296
  /** The session hit the per-session rate limit. */
15004
15297
  rateLimited: "\uC694\uCCAD\uC774 \uB108\uBB34 \uC7A6\uC544\uC694. \uC7A0\uAE50 \uAE30\uB2E4\uB838\uB2E4\uAC00 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
15005
15298
  /**
15006
- * The local connector accepted nothing back inside its ceiling.
15007
- *
15008
- * Distinct from panelClosed on purpose: that one sends the user to Chrome,
15009
- * which does not help when the browser side is fine and the connector is the
15010
- * part that stopped. Pointing at the wrong thing costs them a round of
15011
- * checking before they can even start fixing it.
15299
+ * The local connector accepted nothing back inside its ceiling. Distinct
15300
+ * from panelClosed: that one sends the user to Chrome, which does not help
15301
+ * when the browser side is fine and the connector is what stopped.
15012
15302
  */
15013
15303
  connectorStuck: "\uC5F0\uACB0 \uD504\uB85C\uADF8\uB7A8\uC774 \uC751\uB2F5\uD558\uC9C0 \uC54A\uC544\uC694. \uC7A0\uC2DC \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uACE0, \uACC4\uC18D \uADF8\uB7EC\uBA74 \uD06C\uB86C \uD328\uB110\uC5D0\uC11C MCP \uC5F0\uACB0\uC744 \uAED0\uB2E4\uAC00 \uB2E4\uC2DC \uCF1C \uC8FC\uC138\uC694."
15014
15304
  });
@@ -15053,22 +15343,10 @@ var PendingRegistry = class {
15053
15343
  /**
15054
15344
  * The id a request is known by, unique for the life of this registry.
15055
15345
  *
15056
- * 🔴 The prefix is what makes it unique, and it is load-bearing. A counter
15057
- * alone starts again at one in every new process, and this process is
15058
- * replaced routinely — an update steps the old one aside, an idle one exits.
15059
- * The panel outlives both: it can be part-way through a slow tool when the
15060
- * server it was talking to goes away, reconnect to the replacement, and then
15061
- * post its answer for the request it was given. With a bare counter that id
15062
- * names a live request in the new server — someone else's — and the answer
15063
- * settles the wrong call. Nothing detects it, because from the inside it is
15064
- * an ordinary reply arriving for an id that exists.
15065
- *
15066
- * Making ids not collide is enough on its own: an answer for an id nobody is
15067
- * waiting on is already dropped in silence, which is exactly the right
15068
- * outcome for a reply that arrives after its server is gone.
15069
- *
15070
- * The value stays an opaque string that the panel echoes back untouched, so
15071
- * nothing else has to know this changed.
15346
+ * 🔴 The prefix is load-bearing. This process is replaced routinely while
15347
+ * the panel outlives it, so a panel can reconnect to a replacement and post
15348
+ * an answer there. A bare counter restarts at one, so that id would settle
15349
+ * someone else's live call, indistinguishable from an ordinary reply.
15072
15350
  */
15073
15351
  nextId() {
15074
15352
  this.#seq += 1;
@@ -15079,10 +15357,9 @@ var PendingRegistry = class {
15079
15357
  return this.#entries.size;
15080
15358
  }
15081
15359
  /**
15082
- * Register a request and get the promise the host will await.
15083
- *
15084
- * The promise is created before the caller sends anything, so a reply that
15085
- * races back before `send` returns still finds its entry.
15360
+ * Register a request and get the promise the host will await. Created before
15361
+ * the caller sends anything, so a reply that races back before the send
15362
+ * returns still finds its entry.
15086
15363
  */
15087
15364
  register(id) {
15088
15365
  return new Promise((resolve) => {
@@ -15093,13 +15370,9 @@ var PendingRegistry = class {
15093
15370
  });
15094
15371
  }
15095
15372
  /**
15096
- * Settle a request by id.
15097
- *
15098
- * A reply for an unknown id is dropped, not thrown: it means we already timed
15099
- * the request out, or the extension reconnected and replayed. Neither is worth
15100
- * crashing a server over, and both are expected under a flaky stream.
15101
- *
15102
- * @returns whether an entry was actually settled.
15373
+ * Settle a request by id. A reply for an unknown id is dropped rather than
15374
+ * thrown — the request already timed out, or the extension reconnected and
15375
+ * replayed, and both are expected under a flaky stream.
15103
15376
  */
15104
15377
  settle(id, outcome) {
15105
15378
  const entry = this.#entries.get(id);
@@ -15108,11 +15381,7 @@ var PendingRegistry = class {
15108
15381
  entry.settle(outcome);
15109
15382
  return true;
15110
15383
  }
15111
- /**
15112
- * Fail everything past its deadline. Call on a tick.
15113
- *
15114
- * @returns how many were expired.
15115
- */
15384
+ /** Fail everything past its deadline. Called on a tick by the daemon. */
15116
15385
  sweep() {
15117
15386
  const now = this.#now();
15118
15387
  let expired = 0;
@@ -15131,10 +15400,8 @@ var PendingRegistry = class {
15131
15400
  }
15132
15401
  /**
15133
15402
  * Fail everything now — the stream dropped, so nothing in flight can land.
15134
- *
15135
- * Called on disconnect. Without it, every request outstanding at the moment
15136
- * the panel closes would hang until its own deadline, and the host would sit
15137
- * there for 30s per call with no explanation.
15403
+ * Without this, every request outstanding when the panel closes would hang
15404
+ * until its own deadline with no explanation.
15138
15405
  */
15139
15406
  rejectAll(reason, message) {
15140
15407
  const n = this.#entries.size;
@@ -15149,9 +15416,9 @@ var DEFAULT_RATE_LIMIT = BRIDGE_LIMITS;
15149
15416
  var STALE_TOKEN_LIMIT = 5;
15150
15417
  var Bridge = class {
15151
15418
  #deps;
15152
- // 🔴 필드 초기화가 아니라 생성자에서 만든다. 필드 초기화는 생성자 본문보다 먼저
15153
- // 돌아 deps 가 아직 없고, 그래서 now / timeoutMs 가 조용히 기본값으로
15154
- // 떨어졌다 — 선언만 있고 배선되지 않은 죽은 옵션이었다.
15419
+ // 🔴 Built in the constructor, not at field initialisation: initialisers run
15420
+ // before the constructor body, so deps would not exist yet and the injected
15421
+ // clock and timeout would silently fall back to their defaults.
15155
15422
  #pending;
15156
15423
  #session = null;
15157
15424
  #sessionSeq = 0;
@@ -15159,12 +15426,9 @@ var Bridge = class {
15159
15426
  #badTokenStreak = 0;
15160
15427
  #bucketTokens = 0;
15161
15428
  #bucketRefilledAt = 0;
15162
- // Last catalog the panel reported, kept across disconnects. The hosts we
15163
- // target (Claude Desktop, Cursor) ignore tools/list_changed and cache the
15164
- // FIRST tools/list they get; serving [] while the panel is briefly down makes
15165
- // that cache empty forever. Serving the last-known list keeps the catalog
15166
- // stable across a panel flap — honest because a call while disconnected still
15167
- // fails through the isError channel (never executes without a live panel).
15429
+ // Last catalog the panel reported, kept across disconnects: many hosts cache
15430
+ // the first list they get, so serving an empty one during a brief outage
15431
+ // would empty that cache for good. A call while disconnected still fails.
15168
15432
  #cachedTools = [];
15169
15433
  constructor(deps) {
15170
15434
  this.#deps = deps;
@@ -15181,56 +15445,29 @@ var Bridge = class {
15181
15445
  return this.#session !== null;
15182
15446
  }
15183
15447
  /**
15184
- * The last catalog the panel reported, or `[]` if it has never connected in
15185
- * this daemon's life. Served from `tools/list` while the panel is
15186
- * disconnected so a host does not cache an empty list it never re-fetches.
15448
+ * The last catalog the panel reported, empty if it has never connected in
15449
+ * this daemon's life. Served while the panel is disconnected so a host does
15450
+ * not cache an empty list it never re-fetches.
15187
15451
  */
15188
15452
  get lastKnownTools() {
15189
15453
  return this.#cachedTools;
15190
15454
  }
15191
15455
  /**
15192
- * Does `presented` match the pairing token? Used to authorise the shutdown
15193
- * request an updating adapter sends to replace a stale daemon — a local DoS
15194
- * that a random process should not be able to trigger, so it costs the token.
15456
+ * Does the presented value match the pairing token? Authorises the shutdown
15457
+ * an updating adapter sends, which no other local process should be able to
15458
+ * trigger — hence the token.
15195
15459
  */
15196
15460
  verifyToken(presented) {
15197
15461
  return checkToken(presented ?? void 0, this.#deps.token).ok;
15198
15462
  }
15199
- /**
15200
- * Run the handshake.
15201
- *
15202
- * Order is deliberate: origin, then token, then version. Origin and token are
15203
- * the security gates and must both pass before we tell an unauthenticated public-copy-exception: 우리 게이트를 긍정 서술
15204
- * caller anything — including which protocol versions we speak, which is a
15205
- * small fingerprint we owe nobody.
15206
- */
15207
15463
  /**
15208
15464
  * The extension is right about the token, and we can be wrong about it.
15209
15465
  *
15210
- * 🔴 This server is spawned detached and outlives the app that started it, and
15211
- * a replacement only takes over when its VERSION is newer. So when the user
15212
- * issues a fresh pairing token, nothing here changes: the extension presents
15213
- * the new one, this server keeps checking against the old one, and the refusal
15214
- * repeats forever. Updating the host config and restarting the app does not
15215
- * help — the new adapter finds a same-version server on the port and leaves it
15216
- * alone. There is no way out from the outside either: stepping aside is gated
15217
- * on the very token that no longer matches.
15218
- *
15219
- * So the server has to notice on its own. A run of refusals from the right
15220
- * extension, with nobody connected, says one thing — the token here is stale.
15221
- * Exiting costs nothing: the next call starts a fresh one that reads the
15222
- * current token.
15223
- *
15224
- * Both conditions matter. The identity gate ahead of this drops anything not
15225
- * claiming to be our extension before the token is looked at, and "nobody
15226
- * connected" keeps stray refusals from ending a server that is working for
15227
- * someone right now.
15228
- *
15229
- * The identity gate trusts the Origin header when one is present — a browser
15230
- * sets it, a native process can forge it. A local program can therefore drive
15231
- * this to an exit, and gains nothing: running as the user, it could kill the
15232
- * process outright. What this filters is stray traffic, not an intruder who is
15233
- * already past the door.
15466
+ * 🔴 This server is detached and outlives the app that started it, and a
15467
+ * replacement only takes over when its version is newer. Once the user
15468
+ * issues a fresh token, stepping aside is gated on the very token that no
15469
+ * longer matches, so the server has to notice on its own. A run of refusals
15470
+ * with nobody connected says exactly that, and exiting costs nothing.
15234
15471
  */
15235
15472
  #noteStaleToken() {
15236
15473
  if (this.#session) return;
@@ -15242,6 +15479,13 @@ var Bridge = class {
15242
15479
  this.#badTokenStreak = 0;
15243
15480
  this.#deps.onStaleToken?.();
15244
15481
  }
15482
+ /**
15483
+ * Run the handshake.
15484
+ *
15485
+ * 🔴 Order is deliberate: identity, then token, then version. Both gates must
15486
+ * pass before an unauthorised caller learns anything — including which
15487
+ * protocol versions we speak, which is a fingerprint we owe nobody.
15488
+ */
15245
15489
  handshake(raw, origin) {
15246
15490
  const parsed = HelloSchema.safeParse(raw);
15247
15491
  if (!parsed.success) {
@@ -15289,9 +15533,9 @@ var Bridge = class {
15289
15533
  this.#pending.rejectAll("superseded", "The side panel reconnected.");
15290
15534
  }
15291
15535
  this.#session = {
15292
- // Counter, not a timestamp: two handshakes inside the same millisecond —
15293
- // exactly what a panel reload produces — would otherwise collide, and the
15294
- // new session would be indistinguishable from the one it replaced.
15536
+ // 🔴 A counter, not a timestamp: a panel reload produces two handshakes
15537
+ // inside one millisecond, and the new session must never be
15538
+ // indistinguishable from the one it replaced.
15295
15539
  sessionId: `s${(this.#sessionSeq += 1).toString(36)}`,
15296
15540
  protocolVersion: negotiated.version,
15297
15541
  extensionVersion: parsed.data.extensionVersion
@@ -15304,8 +15548,8 @@ var Bridge = class {
15304
15548
  protocolVersion: negotiated.version,
15305
15549
  serverVersion: this.#deps.serverVersion,
15306
15550
  sessionId: this.#session.sessionId,
15307
- // This build defines no optional behaviours yet. The field exists so
15308
- // additive evolution has somewhere to go that is NOT a protocol bump.
15551
+ // No optional behaviours in this build. The field exists so additive
15552
+ // evolution has somewhere to go that is not a protocol bump.
15309
15553
  capabilities: []
15310
15554
  };
15311
15555
  }
@@ -15356,14 +15600,9 @@ var Bridge = class {
15356
15600
  /**
15357
15601
  * Ask the panel for its tools, and project them.
15358
15602
  *
15359
- * Projection is protocol conformance only — name grammar, descriptor shape,
15360
- * catalog size. There is deliberately NO allowlist here. A copy of the
15361
- * allowlist used to live on this path, justified as holding "against a
15362
- * compromised panel"; that justification was false — anything able to
15363
- * corrupt the panel's catalog already holds the browser session itself and
15364
- * needs no tool names from us — and the copy's real effect was to silently
15365
- * hide every tool added after this binary shipped. Membership is enforced
15366
- * where the session and the execution actually live: the panel executor.
15603
+ * 🔴 Projection is protocol conformance only — name grammar, descriptor
15604
+ * shape, catalog size — with no membership check. Membership is enforced
15605
+ * where the session and the execution live, in the panel's own executor.
15367
15606
  */
15368
15607
  async listTools() {
15369
15608
  const out = await this.#request({
@@ -15391,10 +15630,9 @@ var Bridge = class {
15391
15630
  return out.result;
15392
15631
  }
15393
15632
  /**
15394
- * Refill-then-take on the session's token bucket.
15395
- *
15396
- * Time-based refill computed lazily on each take — no timer to leak, and
15397
- * injectable `now` keeps it testable without waiting real seconds.
15633
+ * Refill-then-take on the session's token bucket. The refill is computed
15634
+ * lazily on each take, so there is no timer to leak and an injected clock
15635
+ * keeps it testable without waiting real seconds.
15398
15636
  */
15399
15637
  #takeRateToken() {
15400
15638
  const limit = this.#deps.rateLimit ?? DEFAULT_RATE_LIMIT;
@@ -15496,9 +15734,9 @@ function frameMcpResponse(payload, accept) {
15496
15734
  body: json32
15497
15735
  };
15498
15736
  }
15499
- function coldCatalogError(id, accept) {
15737
+ function coldCatalog(id, accept) {
15500
15738
  return frameMcpResponse(
15501
- rpcError(id, CODE_INTERNAL, agentActionableMessage("not_connected")),
15739
+ rpcResult(id, { tools: staticDiscoveryCatalog() }),
15502
15740
  accept
15503
15741
  );
15504
15742
  }
@@ -15514,8 +15752,7 @@ async function handleMcpRequest(bridge, body, accept) {
15514
15752
  const method = req.method;
15515
15753
  if (method === "tools/list") {
15516
15754
  if (!bridge.connected) {
15517
- if (bridge.lastKnownTools.length === 0)
15518
- return coldCatalogError(id, accept);
15755
+ if (bridge.lastKnownTools.length === 0) return coldCatalog(id, accept);
15519
15756
  return frameMcpResponse(
15520
15757
  rpcResult(id, { tools: bridge.lastKnownTools }),
15521
15758
  accept
@@ -15526,8 +15763,7 @@ async function handleMcpRequest(bridge, body, accept) {
15526
15763
  return frameMcpResponse(rpcResult(id, { tools }), accept);
15527
15764
  } catch (err) {
15528
15765
  if (err instanceof BridgeError) {
15529
- if (bridge.lastKnownTools.length === 0)
15530
- return coldCatalogError(id, accept);
15766
+ if (bridge.lastKnownTools.length === 0) return coldCatalog(id, accept);
15531
15767
  return frameMcpResponse(
15532
15768
  rpcResult(id, { tools: bridge.lastKnownTools }),
15533
15769
  accept
@@ -15741,8 +15977,8 @@ function createHttpBridge(opts) {
15741
15977
  lifecycle?.bump?.();
15742
15978
  void readJson(req, MAX_MCP_BODY_BYTES2).then((body) => handleMcpRequest(opts.bridge, body, req.headers.accept)).catch(
15743
15979
  () => (
15744
- // Unparseable or oversized body still owes a JSON-RPC answer, not a
15745
- // dropped socket; route a null body through as method-not-found.
15980
+ // An unparseable or oversized body still owes a JSON-RPC answer, not
15981
+ // a dropped socket; a null body routes through as method-not-found.
15746
15982
  handleMcpRequest(opts.bridge, null, req.headers.accept)
15747
15983
  )
15748
15984
  ).then((out) => {
@@ -15786,7 +16022,8 @@ function createHttpBridge(opts) {
15786
16022
  "content-type": "text/event-stream",
15787
16023
  "cache-control": "no-cache",
15788
16024
  connection: "keep-alive",
15789
- // Nagle would hold small frames back; a bridge frame is small by nature.
16025
+ // Buffering would hold small frames back, and a bridge frame is small
16026
+ // by nature.
15790
16027
  "x-accel-buffering": "no"
15791
16028
  });
15792
16029
  sse = res;
@@ -15831,7 +16068,8 @@ function createHttpBridge(opts) {
15831
16068
  res.writeHead(200, {
15832
16069
  "content-type": out.contentType,
15833
16070
  "content-length": String(out.byteLength),
15834
- // 파일명은 확장이 확인 창에 보여 준다 — 사용자가 어느 파일인지 알아야 한다.
16071
+ // The extension shows this in its confirmation, so the user knows
16072
+ // which file is about to be used.
15835
16073
  "x-file-name": encodeURIComponent(out.fileName),
15836
16074
  "cache-control": "no-store"
15837
16075
  });
@@ -16088,10 +16326,8 @@ var Lifecycle = class {
16088
16326
  return this.#refs;
16089
16327
  }
16090
16328
  /**
16091
- * Begin idle accounting.
16092
- *
16093
- * Called once at startup so a daemon nobody ever uses still reclaims itself:
16094
- * refs are zero, so the window arms immediately.
16329
+ * Begin idle accounting. Called once at startup so a daemon nobody ever uses
16330
+ * still reclaims itself.
16095
16331
  */
16096
16332
  start() {
16097
16333
  if (this.#refs === 0) this.#arm();
@@ -16107,12 +16343,9 @@ var Lifecycle = class {
16107
16343
  if (this.#refs === 0) this.#arm();
16108
16344
  }
16109
16345
  /**
16110
- * Traffic crossed the daemon — reset the debounce.
16111
- *
16112
- * While refs are held there is no timer to reset, so this is a no-op then.
16113
- * With refs at zero (a stateless `/mcp` POST between adapter connections) it
16114
- * re-arms the full window, so a steady drip of requests keeps the daemon warm
16115
- * without ever holding a long-lived ref.
16346
+ * Traffic crossed the daemon — reset the debounce. A no-op while refs are
16347
+ * held; at zero refs it re-arms the window, so a steady drip of stateless
16348
+ * requests keeps the daemon warm without holding a long-lived ref.
16116
16349
  */
16117
16350
  bump() {
16118
16351
  if (this.#refs === 0) this.#arm();
@@ -16156,10 +16389,9 @@ function runDaemon(env, deps = {}) {
16156
16389
  });
16157
16390
  const bridge = new Bridge({
16158
16391
  send: (frame) => http.send(frame),
16159
- // 🔴 우리 토큰이 낡았다고 판단되면 물러난다. 이 프로세스는 detached 라 앱을
16160
- // 껐다 켜도 살아 있고, 교체는 **버전이 새것일 때만** 일어난다 — 그래서
16161
- // 사용자가 토큰을 새로 발급하면 밖에서는 아무것도 할 수 없다. 나가는 판단을
16162
- // 우리가 하지 않으면 그 설치는 영구히 막힌다.
16392
+ // 🔴 Step aside once our own token looks stale. This process is detached
16393
+ // and survives an app restart, and a replacement only takes over on a newer
16394
+ // version — so nothing outside can unblock the install if we do not.
16163
16395
  onStaleToken: () => {
16164
16396
  log("pairing token is stale; exiting so the next call can start fresh");
16165
16397
  void http.close().finally(() => exit(0));
@@ -30754,17 +30986,14 @@ var BridgeToolDescriptorSchema2 = external_exports2.object({
30754
30986
  /** JSON Schema for the arguments, passed to the host verbatim. */
30755
30987
  inputSchema: external_exports2.record(external_exports2.string(), external_exports2.unknown()),
30756
30988
  /**
30757
- * MCP tool annotations, derived from the tool's tier. OPTIONAL by ABI rule.
30989
+ * MCP tool annotations, derived from the tool's tier. Optional by ABI rule.
30758
30990
  *
30759
- * Typed loosely on purpose. This end only forwards it — the shape is owned
30760
- * by `annotations.ts` on the extension side, and pinning it here would make
30761
- * every future hint a wire-contract change on a schema that binaries we
30762
- * cannot reach must keep parsing.
30991
+ * Typed loosely because this end only forwards it; pinning the shape here
30992
+ * would make every future hint a wire-contract change.
30763
30993
  *
30764
- * 🔴 A server built before this field existed strips it (z.object() without
30765
- * .strict()), so annotations simply do not arrive there. That degradation is
30766
- * silent and acceptable; a version marker that must survive old servers goes
30767
- * INSIDE `inputSchema`, which is forwarded verbatim.
30994
+ * 🔴 A server predating this field strips it, so annotations never arrive
30995
+ * there. Anything that must survive such a server goes inside the input
30996
+ * schema, which is forwarded verbatim.
30768
30997
  */
30769
30998
  annotations: external_exports2.record(external_exports2.string(), external_exports2.unknown()).optional()
30770
30999
  });
@@ -30777,19 +31006,15 @@ var HelloSchema2 = external_exports2.object({
30777
31006
  /** Pairing token. Not auto-attached by the browser (unlike a cookie). */
30778
31007
  token: external_exports2.string(),
30779
31008
  /**
30780
- * The extension's own id (`chrome.runtime.id`). ADDITIVE: older servers
30781
- * ignore it and identify the caller from the `Origin` header — but an
30782
- * extension service worker's privileged loopback fetch sends no `Origin`, so
30783
- * newer servers verify identity from this field when the header is absent.
30784
- * Optional so every binary that predates it still parses (see the ABI note).
31009
+ * The extension's own id. Additive: an extension service worker's privileged
31010
+ * loopback fetch sends no Origin header, so servers verify identity from this
31011
+ * field when the header is absent.
30785
31012
  */
30786
31013
  extensionId: external_exports2.string().optional(),
30787
31014
  /**
30788
- * Feature flags this panel build understands, e.g. a future
30789
- * "catalog-push". Additive evolution happens HERE, not via a protocol
30790
- * bump: a capability an old peer never sent is simply absent, and code
30791
- * must treat absence as "do not use". Optional with a default so every
30792
- * binary that predates the field still parses.
31015
+ * Feature flags this panel build understands. Additive evolution happens
31016
+ * HERE, not via a protocol bump: a capability an old peer never sent is
31017
+ * simply absent, and code must treat absence as "do not use".
30793
31018
  */
30794
31019
  capabilities: external_exports2.array(external_exports2.string()).default([])
30795
31020
  });
@@ -30871,7 +31096,7 @@ var REQUEST_DEADLINE_MS2 = 9e4;
30871
31096
  var EXEC_RESERVE_MS2 = 3e4;
30872
31097
  var CONFIRM_TIMEOUT_MS2 = REQUEST_DEADLINE_MS2 - EXEC_RESERVE_MS2;
30873
31098
  var READ_ONLY_TOOLS2 = [
30874
- // --- Search-ad estimates. Uses your own advertiser API credentials. ---
31099
+ // --- Search-ad estimates. Uses the user's own advertiser API credentials. ---
30875
31100
  "ad_average_position_bid",
30876
31101
  "ad_competition_density",
30877
31102
  "ad_estimate_bid",
@@ -30926,7 +31151,7 @@ var READ_ONLY_TOOLS2 = [
30926
31151
  "local_job_categories",
30927
31152
  "local_region_trend",
30928
31153
  "local_regions",
30929
- // --- Your OWN channel analytics ---
31154
+ // --- The user's own channel analytics ---
30930
31155
  "daily_trend",
30931
31156
  "my_audience",
30932
31157
  "my_blog_summary",
@@ -30955,7 +31180,7 @@ var READ_ONLY_TOOLS2 = [
30955
31180
  "my_soaring",
30956
31181
  "my_top_content",
30957
31182
  "my_traffic_series",
30958
- // --- Public place data, and reads of a place you own ---
31183
+ // --- Public place data, and reads of a place the user owns ---
30959
31184
  "place_ai_briefing",
30960
31185
  "place_announcements",
30961
31186
  "place_autocomplete",
@@ -30987,8 +31212,8 @@ var READ_ONLY_TOOLS2 = [
30987
31212
  "search_influencer",
30988
31213
  "search_kin",
30989
31214
  "search_news",
30990
- // `search_total` is referenced by search_blog's description — leaving it out
30991
- // made that reference a broken link for MCP hosts (2026-07-17 audit).
31215
+ // Referenced by search_blog's description, so omitting it would leave a
31216
+ // dangling reference for hosts.
30992
31217
  "search_total",
30993
31218
  "search_video",
30994
31219
  "search_web",
@@ -31012,47 +31237,37 @@ var READ_ONLY_TOOLS2 = [
31012
31237
  // Its siblings that suggest titles, outlines, or specs all call a language
31013
31238
  // model, and are excluded for that reason.
31014
31239
  "seo_scorecard",
31015
- // --- 모델이 필요하지만 MCP 에서는 우리가 모델을 부르지 않는 도구들 ---
31016
- // 🔴 이 계열은 "이중과금" 을 이유로 통째로 제외돼 있었다. 호스트는 이 도구를 부르기로
31017
- // 결정하려고 **이미 자기 모델을 돌렸는데**, 우리가 사용자의 모델을 또 부르면 한 턴에
31018
- // 두 번 청구된다. 이제 `llmJson` 이 호출자를 보고 재료(프롬프트)만 돌려주므로 우리 쪽
31019
- // 모델 호출이 0 이다 — 제외의 근거가 사라졌다.
31020
- //
31021
- // 읽고 제안만 하므로 여기(읽기 전용)가 맞다. 되돌릴 것이 없다.
31240
+ // --- Planning tools that need a model, but never call one from here ---
31241
+ // 🔴 The calling host already ran its own model, so calling the user's too
31242
+ // would bill one turn twice. These return prompt material and make no model
31243
+ // call, which is what lets them read and suggest here, undoing nothing.
31022
31244
  "outline_suggest",
31023
31245
  "seo_aeo_geo_spec",
31024
- // 🔴 `write_draft` 는 2단(분석 → 초안)이라 한 번에 재료를 다 줄 수 없다. 먼저 오는
31025
- // 단계의 프롬프트를 주고, 호스트가 결과를 `structure` 로 되넘기면 다음 단계를 준다.
31026
- // 문체 프로필은 **객체가 아니라 프롬프트 안에 문장으로** 들어가고, 그 빌더는 문체 세
31027
- // 항목만 쓰므로 blogId·sourcePostIds·sourceTitles 는 나가지 않는다.
31246
+ // Two-stage (analyse, then draft), so it returns the first stage's prompt and
31247
+ // the second only once the host hands the result back. The
31248
+ // style profile travels as prose inside that prompt, not as an object.
31028
31249
  "write_draft",
31029
31250
  "benchmark_gap",
31030
31251
  "content_calendar",
31031
31252
  "internal_links",
31032
31253
  // --- Grouped with the writing tools, but a pure collector. ---
31033
- // `run_research` is categorised alongside tools that call a model, yet calls
31034
- // none itself — it only gathers public data. Its label is misleading; it was
31035
- // admitted after checking what it actually does. Classify by behaviour, not
31036
- // by category.
31254
+ // Calls no model itself — it only gathers public data. Admitted after
31255
+ // checking behaviour rather than trusting the category label.
31037
31256
  "run_research"
31038
31257
  ];
31039
31258
  var ALLOWED2 = Object.freeze(new Set(READ_ONLY_TOOLS2));
31040
31259
  var EXCLUDED_TOOLS2 = Object.freeze({
31041
- // 🔴 editor_* 는 더 이상 여기 없다 — 등급 표(TOOL_TIERS)로 옮겼다. 제외였던
31042
- // 이유는 "확인을 받을 방법이 없고 되돌릴 수단도 없다" 였는데, 둘 다 생겼다:
31043
- // 세션 동의 + 호출별 확인이 서고, 주입 전후로 임시저장을 부르며 그것이
31044
- // 거절될 때를 위해 문서 사본을 따로 뜬다.
31045
31260
  generate_images: "writes to your post, needs confirmation, and spends your AI credits",
31046
- // 🔴 editor_read / editor_read_structure 도 더 이상 여기 없다. 보류 사유는 "탭이
31047
- // 열려 있어야 하는데 외부 에이전트에겐 열 탭이 없다" 였지만, 그것은 제외할 이유가
31048
- // 아니라 **읽고 나서 알게 될 사실**이다 — 탭이 없으면 도구가 그렇게 답하면 된다.
31049
- // 지금은 TOOL_TIERS 에서 read 등급으로 노출된다.
31050
- // Spend your own AI credits on a turn the calling host already paid for.
31051
31261
  // Side effect outside the request/response contract.
31052
- export_report: "triggers a file download"
31262
+ export_report: "triggers a file download",
31263
+ // 🔴 A host that can generate its own image has a free way onto the canvas —
31264
+ // gallery_image_add. Spending credits for what the caller already has is the
31265
+ // charge nobody meant to make.
31266
+ photo_ai_image_add: "spends your AI credits for an image your own app can make and hand over",
31267
+ video_image_realign: "spends your AI credits per photo to reach what the free scene tools already reach"
31053
31268
  });
31054
31269
  var TOOL_TIERS2 = Object.freeze({
31055
- // ── 사진 편집기 (우리 표면) ──────────────────────────────────────
31270
+ // ── Photo editor (our own surface) ──────────────────────────────
31056
31271
  photo_canvas_get: "read",
31057
31272
  photo_gallery_list: "read",
31058
31273
  photo_text_add: "write",
@@ -31069,13 +31284,21 @@ var TOOL_TIERS2 = Object.freeze({
31069
31284
  photo_node_remove: "destructive",
31070
31285
  photo_page_remove: "destructive",
31071
31286
  photo_template_apply: "destructive",
31072
- photo_ai_image_add: "paid",
31073
- // ── 영상 편집기 (우리 표면) ──────────────────────────────────────
31287
+ // ── Gallery: the free way in ────────────────────────────────────
31288
+ //
31289
+ // 🔴 Destructive because it reads a path off the user's disk, the same
31290
+ // capability editor_insert_image carries. A session toggle would open the
31291
+ // whole filesystem once and stay open.
31292
+ gallery_image_add: "destructive",
31293
+ // ── Video editor (our own surface) ──────────────────────────────
31074
31294
  video_timeline_get: "read",
31075
31295
  video_sources_list: "read",
31076
31296
  video_scene_add: "write",
31077
31297
  video_subtitle_set: "write",
31078
31298
  video_scene_image_set: "write",
31299
+ // Brings a gallery picture into this video — an add, like video_image_add.
31300
+ // The disk read already happened at gallery_image_add, under its own confirm.
31301
+ video_image_add_from_gallery: "write",
31079
31302
  video_scene_update: "write",
31080
31303
  video_background_set: "write",
31081
31304
  video_canvas_resize: "write",
@@ -31087,52 +31310,39 @@ var TOOL_TIERS2 = Object.freeze({
31087
31310
  video_node_remove: "destructive",
31088
31311
  video_scene_remove: "destructive",
31089
31312
  video_narration_generate: "paid",
31090
- video_image_realign: "paid",
31091
- // ── 스마트에디터 읽기 ────────────────────────────────────────────
31313
+ // ── Post editor: reads ──────────────────────────────────────────
31092
31314
  editor_read: "read",
31093
31315
  editor_read_structure: "read",
31094
- // ── 스마트에디터 쓰기 ────────────────────────────────────────────
31316
+ // ── Post editor: writes ─────────────────────────────────────────
31095
31317
  //
31096
- // 🔴 안전망이 선 뒤에 열었다. 주입 전후로 벤더 임시저장을 부르고, 그것이
31097
- // 거절되는 세 경우(빈 문서 · 길이 초과 · 충돌)를 위해 문서 사본을 따로 뜬다.
31098
- // 그전에는 되돌릴 수단이 에디터 undo 한 스텝뿐이었다 — 그 상태로 외부
31099
- // 프로세스에 본문 쓰기를 열 수는 없었다.
31318
+ // 🔴 Every injection is bracketed by a vendor autosave, plus a separate copy
31319
+ // of the document for the cases the vendor refuses one. Without that
31320
+ // recovery point, editor undo would be the only way back.
31100
31321
  //
31101
- // 🔴 `editor_replace` 만 파괴적이다. 나머지는 넣거나 덧붙이는 것이라 임시저장
31102
- // 지점으로 돌아갈 수 있지만, 본문 전체 교체는 그 지점 자체를 갈아치운다.
31322
+ // 🔴 Only whole-body replacement is destructive: the others append, so the
31323
+ // autosave point still holds, while replacing the body overwrites it.
31103
31324
  editor_insert_draft: "write",
31104
- // 🔴 창을 여는 것은 문서를 고치는 것이 아니다. 그래도 `write` 로 둔다 — 사용자의
31105
- // 브라우저에 탭을 만드는 일이고, 문서 편집을 켜지 않은 세션에서 창만 열어 두는
31106
- // 것은 어차피 할 수 있는 일이 없다. 등급이 하나면 사용자가 켜고 끄는 것도 하나다.
31325
+ // Opening a window edits no document, but it does create a tab in the user's
31326
+ // browser, and a session that has not enabled editing can do nothing with the
31327
+ // window anyway — one tier keeps it one thing for the user to allow.
31107
31328
  editor_open_window: "write",
31108
- // 🔴 창을 닫는 것은 되돌릴 수 없다 — 사용자가 쓰던 원고가 그 안에 있을 수 있으므로
31109
- // 호출마다 묻는다. 세션 동의로 넘어가게 두면, 한 번 켜 두고 잊은 토글이 원고를
31110
- // 없앨 수 있다.
31329
+ // 🔴 Closing a window cannot be undone and the user's draft may be inside, so
31330
+ // it is asked per call rather than covered by a forgotten session toggle.
31111
31331
  editor_reduce_windows: "destructive",
31112
- // 🔴 삽입은 덧붙이기라 덮어쓰기 확인을 거치지 않는다 — 그래서 등급이 유일한
31113
- // 방벽이고, 호출마다 묻는다. 넣을 그림의 출처를 사용자가 매번 보고 정한다.
31332
+ // 🔴 Insertion appends, so it never passes through the overwrite prompt; the
31333
+ // tier is the only barrier, and the user approves each image's source.
31114
31334
  editor_insert_image: "destructive",
31115
31335
  editor_set_title: "write",
31116
- // 🔴 `title_optimize` 는 편집기 도구가 아닌데 등급이 있다. 사용자가 제목 후보를 고르는
31117
- // 순간 그것이 곧 확인이 되어 **제목을 덮어쓰기** 때문이다 — 그래서 같은 일을 하는
31118
- // `editor_set_title` 과 같은 등급이다. 이름에 표면 접두가 없는 것은 이 도구가
31119
- // 사진/영상 편집기 표면에 속하지 않아서다(접두 규약은 그 둘의 이름 충돌 방지용).
31336
+ // 🔴 Not an editor tool, but tiered like one: picking a title candidate IS
31337
+ // the confirmation and overwrites the title, so it matches editor_set_title.
31120
31338
  title_optimize: "write",
31121
31339
  editor_undo: "write",
31122
31340
  editor_replace: "destructive"
31123
31341
  });
31124
31342
  var PAID_TOOL_COSTS2 = Object.freeze({
31125
- photo_ai_image_add: Object.freeze({
31126
- basis: "AI \uC774\uBBF8\uC9C0 1\uC7A5 \uC0DD\uC131",
31127
- editorAsksAgain: false
31128
- }),
31129
31343
  video_narration_generate: Object.freeze({
31130
31344
  basis: "\uBAA9\uC18C\uB9AC\uAC00 \uC5C6\uB294 \uC7A5\uBA74 \uC218\uB9CC\uD07C \uC74C\uC131 \uC0DD\uC131",
31131
31345
  editorAsksAgain: true
31132
- }),
31133
- video_image_realign: Object.freeze({
31134
- basis: "\uC0AC\uC9C4 \uC218\uB9CC\uD07C \uC774\uBBF8\uC9C0 \uBD84\uC11D",
31135
- editorAsksAgain: true
31136
31346
  })
31137
31347
  });
31138
31348
  var HINTS_BY_TIER2 = Object.freeze({
@@ -31162,9 +31372,9 @@ var LOOKUP_ANNOTATIONS2 = Object.freeze({
31162
31372
  idempotentHint: true
31163
31373
  });
31164
31374
  var BRIDGE_LIMITS2 = Object.freeze({
31165
- /** 한 번에 몰아 쓸 수 있는 호출 수. */
31375
+ /** Calls available in one burst. */
31166
31376
  capacity: 10,
31167
- /** 초당 다시 차는 칸 수. */
31377
+ /** Slots refilled per second. */
31168
31378
  refillPerSecond: 1
31169
31379
  });
31170
31380
  var CONNECTOR_RESULT_CAP_BYTES2 = 4 * 1024 * 1024;
@@ -31173,7 +31383,7 @@ var RESULT_BUDGET_BYTES2 = Math.floor(
31173
31383
  CONNECTOR_RESULT_CAP_BYTES2 / RESULT_HEADROOM2
31174
31384
  );
31175
31385
  var REASON_POLICY2 = Object.freeze({
31176
- // --- 기다리면 풀린다 ---
31386
+ // --- Resolves on its own with time ---
31177
31387
  rate_limited: Object.freeze({
31178
31388
  retryable: true,
31179
31389
  retryAfterMs: Math.ceil(1e3 / BRIDGE_LIMITS2.refillPerSecond)
@@ -31181,20 +31391,20 @@ var REASON_POLICY2 = Object.freeze({
31181
31391
  timeout: Object.freeze({ retryable: true }),
31182
31392
  connection_lost: Object.freeze({ retryable: true }),
31183
31393
  tool_error: Object.freeze({ retryable: true }),
31184
- // --- 요청을 고쳐야 한다 ---
31394
+ // --- The request itself must change ---
31185
31395
  unknown_tool: Object.freeze({ retryable: false }),
31186
31396
  invalid_args: Object.freeze({ retryable: false }),
31187
31397
  tool_closed: Object.freeze({ retryable: false }),
31188
31398
  missing_in_build: Object.freeze({ retryable: false }),
31189
- // --- 🔴 사람이 움직여야 한다. 다시 거는 것은 조르는 것이다 ---
31399
+ // --- 🔴 A human must act; re-calling only nags them ---
31190
31400
  needs_consent: Object.freeze({ retryable: false }),
31191
31401
  not_confirmed: Object.freeze({ retryable: false }),
31192
31402
  confirm_timeout: Object.freeze({ retryable: false }),
31193
31403
  confirm_too_late: Object.freeze({ retryable: false }),
31194
- // --- 사용자가 브라우저에서 무언가 해야 한다 ---
31404
+ // --- The user must do something in the browser ---
31195
31405
  multiple_editors: Object.freeze({ retryable: false }),
31196
31406
  editor_busy: Object.freeze({ retryable: false }),
31197
- // --- 연결 자체를 고쳐야 한다 ---
31407
+ // --- The connection itself must be repaired ---
31198
31408
  not_connected: Object.freeze({ retryable: false }),
31199
31409
  unauthorized: Object.freeze({ retryable: false })
31200
31410
  });
@@ -31206,12 +31416,9 @@ var BRIDGE_USER_MESSAGES2 = Object.freeze({
31206
31416
  /** The session hit the per-session rate limit. */
31207
31417
  rateLimited: "\uC694\uCCAD\uC774 \uB108\uBB34 \uC7A6\uC544\uC694. \uC7A0\uAE50 \uAE30\uB2E4\uB838\uB2E4\uAC00 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
31208
31418
  /**
31209
- * The local connector accepted nothing back inside its ceiling.
31210
- *
31211
- * Distinct from panelClosed on purpose: that one sends the user to Chrome,
31212
- * which does not help when the browser side is fine and the connector is the
31213
- * part that stopped. Pointing at the wrong thing costs them a round of
31214
- * checking before they can even start fixing it.
31419
+ * The local connector accepted nothing back inside its ceiling. Distinct
31420
+ * from panelClosed: that one sends the user to Chrome, which does not help
31421
+ * when the browser side is fine and the connector is what stopped.
31215
31422
  */
31216
31423
  connectorStuck: "\uC5F0\uACB0 \uD504\uB85C\uADF8\uB7A8\uC774 \uC751\uB2F5\uD558\uC9C0 \uC54A\uC544\uC694. \uC7A0\uC2DC \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uACE0, \uACC4\uC18D \uADF8\uB7EC\uBA74 \uD06C\uB86C \uD328\uB110\uC5D0\uC11C MCP \uC5F0\uACB0\uC744 \uAED0\uB2E4\uAC00 \uB2E4\uC2DC \uCF1C \uC8FC\uC138\uC694."
31217
31424
  });
@@ -31383,9 +31590,9 @@ function createAdapterServer(deps = {}) {
31383
31590
  const mcpUrl = `http://${host}:${port}/mcp`;
31384
31591
  const server = new Server(
31385
31592
  { name: deps.name ?? NAME2, version: deps.version ?? "0.0.0" },
31386
- // `listChanged` tells the host we will send notifications/tools/list_changed
31387
- // — the daemon signals us when the panel connects or drops, and we forward
31388
- // it so the host re-fetches the catalog without a restart.
31593
+ // Declares that we send list-changed notifications: the daemon signals us
31594
+ // when the panel connects or drops, and forwarding it lets the host
31595
+ // re-fetch the catalog without a restart.
31389
31596
  { capabilities: { tools: { listChanged: true } } }
31390
31597
  );
31391
31598
  server.setRequestHandler(ListToolsRequestSchema, async () => {