Skip to content

feat: accept a full page URL so the sandbox opens that exact page - #6

Open
rkttu wants to merge 1 commit into
mainfrom
feat/target-url-channel
Open

feat: accept a full page URL so the sandbox opens that exact page#6
rkttu wants to merge 1 commit into
mainfrom
feat/target-url-channel

Conversation

@rkttu

@rkttu rkttu commented Aug 3, 2026

Copy link
Copy Markdown
Member

검색 MCP가 물어온 딥 URL(예: 국민은행 특정 상품 페이지)을 그대로 샌드박스에서 열 수 있게 TABLECLOTH_TARGET_URL 채널을 끝까지 연결합니다.

지금까지는 샌드박스로 들어가는 유일한 입구가 카탈로그 service id였습니다. 그래서 URL은 버려지고 사용자는 은행 첫 화면에 떨어졌습니다 — 정작 보려던 페이지는 사라진 채로요.

도구 변경

  • generate_wsb / launch_sandboxtargetUrl 추가 (serviceIds둘 중 최소 하나 필요)
  • 신규 check_url(url) — 이 URL이 보안프로그램이 필요한 사이트인지 판정만 하고 아무것도 실행하지 않습니다. 모델이 "샌드박스로 열기 / 그냥 브라우저로 열기"를 고르는 데 씁니다. 이 서버는 여전히 호스트 브라우저를 열지 않습니다.
check_url("https://www.wooribank.com/pot/Dream?withyou=CTCER0149&fromSite=pib")
  → sandboxSupported: true, serviceId: "WooriBank", requiredPackages: [...]

launch_sandbox(targetUrl: "…같은 URL…")
  → siteIds: ["WooriBank"]   ← serviceIds 없이 URL만으로 자동 판별

생성되는 .wsb:

$env:TABLECLOTH_SITE_IDS = ''WooriBank'';
$env:TABLECLOTH_TARGET_URL = ''https://www.wooribank.com/pot/Dream?withyou=CTCER0149&fromSite=pib'';

tablecloth: 스킴은 만들지 않습니다

그 핸들러는 Windows TableCloth 앱만 등록하므로 macOS/Linux에서 GA가 아닙니다. 이 서버의 산출물은 항상 .wsb이고, 그걸 실행하는 게스트는 Windows Sandbox든 macSandbox든 항상 Windows이므로 환경변수 채널 2개는 모든 호스트에서 동일하게 동작합니다.

부수 효과: 기존 드리프트 해소

shared/wsb-template.xml이 정본 대신 자체 __SITEIDS__ 구문 치환을 쓰고 있었습니다. 이제 상류 릴리스 자산 no-install-spork-deeplink.wsb의 LogonCommand와 바이트 일치합니다(__SPORK_SITE_IDS__ / __SPORK_TARGET_URL__ 값 자리 치환).

URL 처리 규칙 (SPEC.md §7.1 신설)

  • 검증(§3.3): http/https 절대 URL, 자격증명 없음, 2048자 이하, 공백·제어문자 없음
  • 이중 이스케이프: 따옴표 → %27/%22, 그 다음 & < > → XML 엔티티. 비ASCII도 퍼센트 인코딩 — 정본 .wsb가 ASCII only를 요구합니다(게스트 코드페이지가 호스트 언어팩마다 다르고, macSandbox는 명령을 .cmd 파일로 한 번 더 경유시킵니다)
  • 해석: 퍼블릭 서픽스를 인식한 등록 도메인 비교. 한국 2단계 서픽스를 처리하지 않으면 co.kr 서비스 95개가 한 덩어리가 됩니다
  • 동점이면 추측하지 않고 candidates 반환 — 게스트는 카탈로그 선순위를 택할 수밖에 없지만 호스트에는 되물을 수 있는 모델이 있고, 잘못 고르면 엉뚱한 은행 플러그인이 설치됩니다
  • 카탈로그 밖 URL은 거부 — 게스트는 조용히 URL을 버리고 카탈로그 UI를 띄우는데, 모델에게는 그게 성공으로 보여 "요청하신 페이지를 열었다"고 거짓말하게 됩니다

URL은 Base64 등으로 감추지 않고 평문으로 둡니다 — 실행 전에 어떤 주소가 열리는지 확인할 수 있어야 한다는 정본의 신뢰 모델 그대로입니다.

검증

  • .NET -warnaserror 경고 0 / Node typecheck 통과
  • conformance 전 항목 PASS. URL 케이스 7종(딥 URL·URL-only·동점·카탈로그 밖·도메인 불일치·형식 위반·비ASCII)의 응답이 두 구현에서 동일하고, 생성된 .wsb가 ASCII를 유지하는지 검증합니다
  • 하네스가 실제 드리프트를 잡았습니다: .NET이 오류 응답에 unknownIds: []를 내보내는데 Node는 생략 → "비어 있으면 생략"으로 통일

리뷰어가 봐주셨으면 하는 점

  • macSandbox 실기 검증 미완료. $env: 두 줄이 늘어난 LogonCommand가 .cmd 왕복을 견디는지 확인이 필요합니다 — WSB-SUPPORT.md도 "multi-step 명령은 스크립트 파일 권장"이라고 적고 있습니다
  • 한국 2단계 서픽스 12개가 코드에 하드코딩돼 있습니다. 카탈로그가 새 서픽스를 쓰면 갱신이 필요합니다
  • 동점 시 후보 반환이 게스트의 "첫 항목 선택"과 다른 정책인데, 항상 id를 함께 실어 보내므로 게스트와 판정이 충돌하지는 않습니다

🤖 Generated with Claude Code

A search MCP hands the model a deep URL (say a KB product page). Until now the
only way into a sandbox was a catalog service id, so that URL had to be thrown
away and the user landed on the bank's homepage — the page they actually wanted
was gone. This wires up the TABLECLOTH_TARGET_URL channel end to end.

`generate_wsb` / `launch_sandbox` take an optional `targetUrl` (either it or
`serviceIds` is required). New `check_url` answers "does this URL need the
sandbox?" without running anything, so the model can pick sandbox vs. plain
browser. This server still never opens a host browser.

No `tablecloth:` URI deep link is emitted — that handler is registered only by
the Windows TableCloth app. The output is always a .wsb, and whatever runs it
(Windows Sandbox or macSandbox) boots a Windows guest, so both env-var channels
behave identically on every host.

Template now matches the upstream no-install-spork-deeplink.wsb LogonCommand
byte for byte, which also closes a pre-existing drift: shared/wsb-template.xml
had its own `__SITEIDS__` statement-level splice instead of the canonical
`__SPORK_*` value placeholders.

URL handling:
- Validate per spec §3.3 (http/https, no credentials, <=2048, no control chars).
- Escape both layers: quotes -> %27/%22, then & < > -> XML entities. Also
  percent-encode non-ASCII: the canonical .wsb requires ASCII only (guest
  codepage varies by host language pack, and macSandbox routes the command
  through a .cmd file).
- Resolve the URL against the catalog host-side, public-suffix aware. Skipping
  the KR two-level suffixes would collapse all 95 co.kr services into one
  registered domain.
- On a tie, return candidates instead of guessing. The guest can only take the
  first catalog entry; the host has a model it can ask, and guessing wrong
  installs the wrong bank's plugins.
- Reject URLs outside catalog domains. The guest would silently drop them and
  show the catalog UI, which reads to the model as success.

Conformance covers 7 URL cases across both lanes plus the ASCII-only property;
it caught one real drift (.NET emitted `unknownIds: []` where Node omitted it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 3, 2026 15:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends TableClothMcp’s sandbox pipeline to accept and propagate a full deep-link page URL end-to-end (via TABLECLOTH_TARGET_URL), so the sandbox can open the exact requested page instead of only landing on a service’s default entry page. It also adds a read-only check_url tool to classify whether a given URL is sandbox-supported (catalog-backed) without launching anything.

Changes:

  • Add targetUrl support to generate_wsb / launch_sandbox, including URL validation, catalog-based URL→service resolution, and .wsb injection via TABLECLOTH_TARGET_URL.
  • Introduce check_url(url) tool that returns sandbox support status and (when resolvable) the catalog service + required packages.
  • Align .wsb template + serialization behavior (camelCase + omit nulls), and add conformance coverage for URL channel parity across .NET and Node.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
Tools/ToolModels.cs Extends tool DTOs to include targetUrl, URL-resolution metadata, and check_url response types.
Tools/TargetUrl.cs Adds .NET implementation for target URL validation, escaping, and catalog resolution logic.
Tools/SandboxTools.cs Implements planning logic for (serviceIds, targetUrl) and injects both channels into generated .wsb.
Tools/CatalogTools.cs Adds check_url tool implementation on the .NET side.
Tools/AppJsonContext.cs Registers CheckUrlResponse for source-gen JSON serialization.
SPEC.md Documents the new targetUrl channel contract and the check_url tool.
SharedResources.cs Loads new shared strings used by URL validation/resolution flows and check_url notes.
shared/wsb-template.xml Updates template to include TABLECLOTH_TARGET_URL injection point (__SPORK_TARGET_URL__).
shared/strings.json Adds check_url strings and new sandbox messages for targetUrl behavior/errors.
README.md Documents new tool signatures and provides an example deep-URL flow.
node/test/conformance.mjs Adds cross-implementation parity checks and escaping/ASCII assertions for targetUrl cases.
node/src/tools/target-url.ts Adds Node implementation for validation/escaping/URL→service resolution (parity with .NET).
node/src/tools/sandbox-tools.ts Adds targetUrl planning/injection to Node generate_wsb / launch_sandbox.
node/src/tools/catalog-tools.ts Adds Node implementation of check_url.
node/src/index.ts Registers check_url and updates schemas for generate_wsb / launch_sandbox to accept targetUrl.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Tools/TargetUrl.cs
Comment on lines +29 to +38
public static string ReasonText(RejectReason r) => r switch
{
RejectReason.Empty => "empty",
RejectReason.TooLong => "tooLong",
RejectReason.BadChars => "badChars",
RejectReason.NotHttp => "notHttp",
RejectReason.Credentials => "credentials",
RejectReason.NoHost => "noHost",
_ => "unknown",
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants