diff --git a/AGENTS.md b/AGENTS.md index 2a52658ac..80b486e54 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -61,12 +61,12 @@ ccteam = 多 harness agent 团队的桥接与治理层:常驻 daemon(IM gateway | **session 调度门 = daemon 校验 per-session principal `(sid,secret)`(best-effort,非硬边界)** | 3 个 `agent*` 工具由 daemon 按 principal 校验:spawn 时 mint per-session secret,受管会话走 HTTP bearer `ccteam-sid::`;`verify_session_principal` 常数时比对;caller 的 project 服务端覆写(只能操作自己 project),授权只认 principal;`agent`/`agent_stop` 是显式调度,stop 限后代 + depth/children/delegated/cycle/预算护栏。手起会话 = enrolled client:凭 vendor 全局配置里的 enrollment 凭据(`ccteam-enroll::`)在 `initialize` 领 `Mcp-Session-Id`,daemon 铸 ledger 节点 + principal 并 promote(同一条 Ambient 路);节点 `managed_by: external` 不进 gateway live map,`agent`/`agent_stop` 对它一律拒绝;凭据没钉 project 时 caller 必须自己点名 workspace(首次点名即终身绑定),cwd / peer / 最近项目等推断一律不做。ACP 面无 `--strict-mcp-config` 同类物 → `/mcp` 按进程血缘(adapter 握手前登记子 pid + `/proc` 上溯)绑回 principal,两条路都没到才发 `identity_degraded`(只广播)。诚实范围:单 OS-uid 全信任模型下 agent 间无硬边界,secret 只抬门槛;真隔离 = per-agent OS user/sandbox(deferred) | | **委派语义 = 路由,非新引擎;完成通知非注入** | `agent{task,sid}` 与 IM `@handle` 同路;完成通知 = gateway 生成的一条普通 user-role turn 投给 parent(live = vendor steer / dead = pending FIFO + resume),与人转告同构;通知单位 = 任务(vendor turn 边界),中途叙述只进账本;child turns 按 child sid 落 `turns.jsonl`,委派关系写 `progress.jsonl`,不伪造进对方 transcript。可靠性合同:幂等 `agent`(`idempotency_key`)· at-least-once 通知扛重启(`delegation.json` 落盘 + 启动 reconcile)· append→notify 顺序 · 原子写;单 daemon 无 HA,是协议语义可靠 | | **跨机 = host 是 project 属性,session 继承;网络方向 = 卫星拨入** | host 住 project catalog(`~/.ccteam/config.yaml`,条目含 `host` + `remote_slug`;slug 相同 ≠ 同一项目);spawn 面无 host 参数(传入即硬错 `HOST_SPAWN_PARAM_REMOVED`);记账一律 catalog slug;远程项目进入 = web 选主机新建或 `POST /projects/import` 接入卫星已上报项目(绝不自动接入;撞名累加、幂等);卫星零监听面:`ccteam start` 进程内嵌卫星客户端出站长连 daemon,卫星自己解析 binary + cwd,主侧永不下发路径;terminal 永不上多机;rebuild 一律 re-gate project 绑定(host 不符或卫星 offline → 可读错误,绝不本地重生);远程 verdict 钉 claude,其余 vendor 远程 = 显式 NotImplemented | -| **不改写已有项目 `CLAUDE.md`/`AGENTS.md`** | 项目知识层归 vendor 原生 + 项目自己;唯一放宽:对真空项目(两文件都不存在)scaffold `AGENTS.md` + `CLAUDE.md` = `@AGENTS.md`,绝不覆盖已有内容;`.ccteam/` 幂等加进项目 `.gitignore` | -| **vendor 配置足迹 = 只写自家 MCP 注册** | 对 vendor 全局配置(`~/.claude.json`、`~/.codex/config.toml`、grok/opencode/kimi 对称面)的唯一写入 = 幂等注册/修复自己的 MCP server 条目;项目侧只写 `.claude/settings.local.json` 自己的段(绝不碰用户 `.claude/settings.json`);pi 不写任何配置 | +| **不改写已有项目 `CLAUDE.md`/`AGENTS.md`** | 项目知识层归 vendor 原生 + 项目自己;唯一放宽:对真空项目(两文件都不存在)只 scaffold `AGENTS.md`,不写 `CLAUDE.md`,绝不覆盖已有内容;`.ccteam/` 幂等加进项目 `.gitignore` | +| **vendor 配置足迹 = 只写自家注册与显式设置** | vendor 全局配置只幂等注册/修复自己的 MCP server 条目,另在 `~/.claude.json` 标记项目 trust;创建项目不写 Claude 设置,仅清理旧 ccteam hook/Telegram 插件钉;显式插件/effort 设置只动 `.claude/settings.local.json` 自己的键,绝不碰用户 `.claude/settings.json`;pi 不写任何配置 | | **`ccteam-core` 零 team 名字面量** | core = primitives leaf,team 名不入 core | | **repo 零提示词类型内容(零例外)** | agent/persona/skill/workflow 的内容一律不进 ccteam repo;住 ccteam-hub 或用户空间,ccteam 只读 index、按 sha 取内容;用户项目里既有 `.claude/agents/*.md` 是用户文件,不删不改 | | **跨项目记忆走官方接口** | Claude `~/.claude/CLAUDE.md` + `~/.claude/rules/*.md`;Codex `~/.codex/AGENTS.md` —— ccteam 只读 | -| **init 布局** | 项目 `.ccteam/` 只种 `state.json` + `workflow.yaml`(`routing.md` 用户可选自建,`status` 原文透传);`~/.ccteam` 规范布局 = `ccteam_core::canonical_home_dirs()`(doctor 查 home-drift) | +| **init 布局** | CLI/Web/IM/卫星统一走 `create_project`:项目 `.ccteam/` 只种 `state.json`,不种 `workflow.yaml`(`workflow.yaml`/`routing.md` 用户可选自建,读侧照用);`~/.ccteam` 规范布局 = `ccteam_core::canonical_home_dirs()`(doctor 查 home-drift) | | **新建项目 slug = `slugify(目录名)` + 撞名数字累加** | `ccteam init` 可在任意现有目录就地初始化;`--slug` 显式覆盖 | **vendor 红线**:不 vendor Claude / Codex 二进制(`references/` 仅协议参考;spawn 走 `$PATH` + `CCTEAM_{CLAUDE,CODEX}_BIN` override);`AgentVendor` 无 default,spawn 必须显式。 @@ -98,7 +98,7 @@ ccteam = 多 harness agent 团队的桥接与治理层:常驻 daemon(IM gateway ## 五、易踩的坑(实战累积) - **不要给 ccteam 自己加 ccteam 风格的 hook/orchestrator** —— 循环引用排错地狱;本仓用 Claude Code 默认行为开发,只产出项目挂 ccteam hook。 -- **ccteam 的 hook 写 `.claude/settings.local.json`**(gitignored、Claude 照读、与用户 `settings.json` 合并);`.claude/settings*.json` 的 `bypassPermissions` 是开发态便利,产品形态走 `--dangerously-skip-permissions`。 +- **创建项目不装 hook、不写 Claude 设置**;重跑 init 仅清理 `.claude/settings.local.json` 中旧 ccteam hook/Telegram 插件钉;冻结的 terminal 协议仍按需装自己的 hook。`.claude/settings*.json` 的 `bypassPermissions` 是开发态便利,产品形态走 `--dangerously-skip-permissions`。 - **测试 `bootstrap_project` / `bootstrap_meta_project` 前必先 `disable_tool_surface_bootstrap_for_tests()`**,否则向真实 `~/.claude.json` 写垃圾,破坏 claude 登录。 - **env-mutating 测试**(`set_var/remove_var HOME/CCTEAM_HOME/CLAUDE_CONFIG_HOME` …)只放 `crates/*/tests/*.rs` integration(独立进程),不放 lib `#[cfg(test)]`(全 workspace 有禁 env 写守卫);新状态面优先 `_in(root)` 注入式 API 而非 home 派生全局函数。 - **测试绝不写真实生产状态**:只把 `HOME` 指到 tempdir 不够 —— `CCTEAM_HOME` 优先级更高(实锤:fixture bot 注册写进真实 registry → telegram 静默失联数小时);隔离必须同时 pin `HOME` + `CCTEAM_HOME`。 diff --git a/Cargo.lock b/Cargo.lock index 676608dcf..b9dca580e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -394,7 +394,7 @@ dependencies = [ [[package]] name = "ccteam-cli" -version = "0.13.0" +version = "0.13.1" dependencies = [ "anyhow", "async-trait", @@ -427,7 +427,7 @@ dependencies = [ [[package]] name = "ccteam-core" -version = "0.13.0" +version = "0.13.1" dependencies = [ "anyhow", "async-trait", @@ -455,7 +455,7 @@ dependencies = [ [[package]] name = "ccteam-cost" -version = "0.13.0" +version = "0.13.1" dependencies = [ "chrono", "serde", @@ -468,7 +468,7 @@ dependencies = [ [[package]] name = "ccteam-flow" -version = "0.13.0" +version = "0.13.1" dependencies = [ "async-trait", "ccteam-harness", @@ -485,7 +485,7 @@ dependencies = [ [[package]] name = "ccteam-harness" -version = "0.13.0" +version = "0.13.1" dependencies = [ "anyhow", "async-trait", @@ -523,7 +523,7 @@ dependencies = [ [[package]] name = "ccteam-hooks" -version = "0.13.0" +version = "0.13.1" dependencies = [ "anyhow", "ccteam-core", @@ -538,7 +538,7 @@ dependencies = [ [[package]] name = "ccteam-im" -version = "0.13.0" +version = "0.13.1" dependencies = [ "anyhow", "async-trait", @@ -569,7 +569,7 @@ dependencies = [ [[package]] name = "ccteam-web" -version = "0.13.0" +version = "0.13.1" dependencies = [ "anyhow", "askama", @@ -592,6 +592,7 @@ dependencies = [ "nix 0.29.0", "notify", "portable-pty", + "pulldown-cmark", "rand 0.8.6", "reqwest", "rust-embed", @@ -2253,6 +2254,24 @@ dependencies = [ "psl-types", ] +[[package]] +name = "pulldown-cmark" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db2be46835f013dd3f4fa7f5f7402c537787b54ebb6fe2b94bbdd7e2669bb7b" +dependencies = [ + "bitflags 2.11.1", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + [[package]] name = "quinn" version = "0.11.9" diff --git a/Cargo.toml b/Cargo.toml index 51bfb1b98..5208e7159 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ ] [workspace.package] -version = "0.13.0" +version = "0.13.1" edition = "2021" rust-version = "1.85" license = "MIT" diff --git a/Makefile b/Makefile index 568789ef4..8cc493ef8 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ WIPE_DIRS := state run cache DAEMON_LOG := $(CCTEAM_HOME)/daemon.log .DEFAULT_GOAL := help -.PHONY: help build release clean fmt fmt-check clippy check test test-web perf-gate \ +.PHONY: help build release clean fmt fmt-check clippy check test test-web test-files-browser perf-gate \ web web-deps web-check gate \ install install-binary next-steps uninstall reinstall require-cli require-node \ config init start stop status doctor \ @@ -182,6 +182,11 @@ test-baseline: require-node test-web: require-node cargo test -p ccteam-web +# #219 — real browser verification against the tempdir-backed file API. +test-files-browser: require-node + cargo test -p ccteam-web --test files_test preview_runs_relative_modules_and_css_inside_an_opaque_browser_sandbox -- --ignored + cd $(WEB_DIR) && npm test -- --grep 'file workspace|mobile Files' + # Explicit, release-profile performance contract. The integration target # returns immediately unless CCTEAM_PERF_GATE=1, so ordinary test targets never # generate the large fixture or start its in-process server. diff --git a/README.md b/README.md index 394b4d0e8..7610ccd40 100644 --- a/README.md +++ b/README.md @@ -62,11 +62,11 @@ The installer runs the daemon; `ccteam status` reprints your link (`http:// - six formation playbooks (commander & crews, driver & advisor, cross review, bake-off, research triangulation, cost pyramid) that prefill the launcher with a vendor lineup -- a Chat tab per session (plus a byte-faithful terminal where applicable), including a clock on the composer to queue delayed user turns above the input; a turn's folded activity row opens into its steps, and each step into its full thinking text or tool input and output -- a context sidebar per session: what its context window is made of — system prompt, tools, each memory file by path, skills, MCP tools, agents, the conversation split by tool and attachment, last-request cache use — taken from the harness's own accounting, refreshed when a turn ends (Claude sessions) +- a chat per session with a compact title/model header and unified Files / Context toolbar, including a clock on the composer to queue delayed user turns above the input; a turn's folded activity row opens into its steps, and each step into its full thinking text or tool input and output +- a context sidebar per session: session status, harness/model/host, delegation parent, cumulative cost and last activity, followed by the harness's own context accounting — detailed categories for Claude; window totals and last-request input/cache/output/reasoning usage for Codex — refreshed when a turn ends, with dated snapshots after release - a Team page: the live delegation topology — vendor, the model and reasoning effort each session is actually running, cost, every row a real link so a parent and its delegate open side by side — plus a division-of-labor charter (the per-project `routing.md` agents read via `status`) edited in place, and a Runs tab listing every ccteam Flow run with the sessions it hired -- a DSH page that opens DeepSeek Harness Web inside ccteam: the daemon authenticates the request, starts or attaches the right local DSH web instance, and gives each logged-in user a separate DSH home -- a cost pill with daily budget caps +- a DSH page that opens DeepSeek Harness Web inside ccteam through the same web origin, including behind an HTTPS reverse proxy: the daemon authenticates the request, starts or attaches the right local DSH web instance, and gives each logged-in user a separate DSH home +- a Status page with per-session cost and daily budget caps - a per-project ⋯ menu in the sidebar: start a session there, copy its path, or take the project out of ccteam (deregister + stop its live sessions — your directory and code are never touched) - marketplace and settings @@ -233,8 +233,8 @@ Every spawn surface takes `model` and `effort` for every vendor and forwards bot ccteam adds a team to your repo without taking it over: - **Roleless by default** — the brain reads *your* `CLAUDE.md` / `AGENTS.md` through the vendor's own mechanism; ccteam never rewrites project knowledge. -- **Small footprint** — exactly `.ccteam/` (state), `.claude/agents/` (personas you install), and in `.claude/settings.local.json` only the keys you ask for (a hub plugin you enable, an `/effort` you pin) — never your `settings.json`, and no hooks (unless you pick the frozen terminal protocol, which installs its own). -- **Durable sessions** — ids (`s1`, `s2`, …) survive daemon restarts and cold-resume from disk; state is plain files in your repo. A session you are not talking to releases its harness process after an idle window (default one hour, matching the prompt-cache TTL) and comes straight back on your next message, on the same id and the same conversation — so a fleet of thirty sessions costs thirty transcripts, not thirty resident processes. One session, one process: a restart never kills an agent mid-turn and never starts a second one beside it — the daemon lets the process finish, queues what you send it meanwhile, recovers the answer it gave from the vendor's own record, and resumes the session by id. +- **Small footprint** — every project creation entry writes `.ccteam/state.json`, adds `.ccteam/` to `.gitignore`, and seeds only an `AGENTS.md` placeholder when neither `AGENTS.md` nor `CLAUDE.md` exists. It creates no `CLAUDE.md`, `workflow.yaml`, roles, or Claude settings. Personas you install live in `.claude/agents/`; explicit plugin/effort settings use only their own keys in `.claude/settings.local.json`, never your `settings.json`. Only the frozen terminal protocol installs hooks. +- **Durable sessions** — ids (`s1`, `s2`, …) survive daemon restarts and cold-resume from disk; state is plain files in your repo. A session you are not talking to releases its harness process after an idle window (default 250 minutes) and comes straight back on your next message, on the same id and the same conversation — so a fleet of thirty sessions costs thirty transcripts, not thirty resident processes. One session, one process: a restart never kills an agent mid-turn and never starts a second one beside it — the daemon lets the process finish, queues what you send it meanwhile, recovers the answer it gave from the vendor's own record, and resumes the session by id. ## Extras diff --git a/crates/ccteam-cli/src/commands.rs b/crates/ccteam-cli/src/commands.rs index ce1791a91..3e9ba4f74 100644 --- a/crates/ccteam-cli/src/commands.rs +++ b/crates/ccteam-cli/src/commands.rs @@ -43,10 +43,11 @@ pub struct InitOptions { /// flag — set internally by `project new` / tests; the CLI always /// takes the `dev` default. pub team: Option, - /// Overwrite every ccteam-managed file (state.json, settings.json - /// marker section, workflow.yaml, helper templates). Without `force` - /// re-runs preserve the user-edited workflow. Never touches - /// `.claude/agents/*.md` — ccteam seeds no roles (roleless default). + /// Re-stamp the team on a refresh (a re-run otherwise keeps the recorded + /// one) and step past the install-target guards: the ccteam source repo, + /// `$HOME` / the filesystem root, and a slug already registered at another + /// path. Never touches `.claude/agents/*.md` — ccteam seeds no roles + /// (roleless default). pub force: bool, /// Set the project owner identity (`ProjectState.owner`, /// `"channel:chat_id"` — e.g. `user:` / `telegram:`). A @@ -56,23 +57,23 @@ pub struct InitOptions { pub owner: Option, } -/// Unified project install. Scaffolds `.ccteam/state.json` + -/// `.ccteam/workflow.yaml`, seeds NO roles (roleless default — the -/// default session is a bare vendor reading the project -/// `CLAUDE.md`/`AGENTS.md`), and registers the project in -/// `~/.ccteam/config.yaml`. +/// Unified project install: the install-target and slug guards, then +/// [`ccteam_core::create_project`] — the same creation path web `POST +/// /projects`, IM `/newproject` and the satellite binding use, so they all leave +/// the same files. That is `.ccteam/state.json`, a placeholder `AGENTS.md` for a +/// project with neither `CLAUDE.md` nor `AGENTS.md`, and `.ccteam/` in +/// `.gitignore`, plus the `~/.ccteam/config.yaml::projects[]` entry. It seeds NO +/// roles (roleless default — the default session is a bare vendor reading the +/// project's `AGENTS.md`/`CLAUDE.md`). /// /// Three scenarios, one command: /// -/// 1. **Fresh cwd / fresh dir**: writes `.ccteam/state.json` + -/// `.ccteam/workflow.yaml` and the ccteam-managed hook section in -/// `.claude/settings.local.json`; appends to -/// `~/.ccteam/config.yaml::projects[]`. +/// 1. **Fresh cwd / fresh dir**: creates the project. /// 2. **Existing repo cwd** (no `.ccteam/` yet): same as (1) — never /// touches existing user files. -/// 3. **Already-ccteam project cwd**: refreshes state.json + the -/// settings marker section; preserves `workflow.yaml` unless -/// `--force` (overwrite). `.claude/agents/*.md` is always left alone. +/// 3. **Already-ccteam project cwd**: refreshes `state.json` (the recorded +/// team and owner survive unless given); `.claude/agents/*.md` is always +/// left alone. /// /// MCP registration lives in `ccteam config` (not here). The global /// `~/.ccteam/` skeleton + hook dispatcher are ensured idempotently on @@ -91,10 +92,9 @@ pub fn run_init(paths: &CcteamPaths, opts: InitOptions) -> Result { // `ensure_ccteam_home` (ccteam-core) folds that canonical-dir loop // and the V0.6.1 F139 `hook.sh` dispatcher materialization into one // idempotent call shared by every create/start path (init here, the - // web/IM `bootstrap_project_at_dir`, the daemon at `ccteam start`). - // The hook.sh write must precede `install_project_at` so the - // freshly-rendered `.claude/settings.local.json` hook commands point - // at a file that actually exists. + // web/IM `create_project`, the daemon at `ccteam start`). + // Project creation ensures this home too; keep it here so the install + // checks and receipt can use the global paths before creating a project. ccteam_core::ensure_ccteam_home(paths).context("ensure ~/.ccteam/ home (dirs + hook.sh)")?; // -- 2. Resolve project install target --------------------------- @@ -164,23 +164,10 @@ pub fn run_init(paths: &CcteamPaths, opts: InitOptions) -> Result { } } - // -- 4. Project install pass ------------------------------------ + // -- 4. Create / refresh the project (scaffold + owner + config.yaml) --- let project_report = install_project_at(paths, &target, &target_slug, &target_team, &opts)?; - // -- 5. Upsert config.yaml::projects[] -------------------------- - let entry = ccteam_core::ProjectEntry { - slug: project_report.slug.clone(), - path: target.clone(), - host: ccteam_core::LOCAL_HOST.to_string(), - remote_slug: None, - remote_path: None, - team: project_report.team.clone(), - installed_at: chrono::Utc::now(), - }; - ccteam_core::upsert_project_in_config(&paths.root, entry) - .context("upsert project into ~/.ccteam/config.yaml")?; - - // -- 6. Health check + optional wizard -------------------------- + // -- 5. Health check + optional wizard -------------------------- let bin = current_ccteam_bin().ok(); let claude = Command::new("claude").arg("--version").output(); let tmux = ccteam_core::tmux_version(); @@ -201,16 +188,12 @@ pub fn run_init(paths: &CcteamPaths, opts: InitOptions) -> Result { target.join(".ccteam").join("state.json").display(), project_report.state_action, )); - out.push_str(&format!( - " workflow.yaml {} ({})\n", - target.join(".ccteam").join("workflow.yaml").display(), - project_report.workflow_action, - )); - out.push_str(&format!( - " agents dir {} ({})\n", - target.join(".claude").join("agents").display(), - project_report.agents_action, - )); + if project_report.agents_md_scaffolded { + out.push_str(&format!( + " AGENTS.md {} (placeholder scaffolded)\n", + target.join("AGENTS.md").display(), + )); + } if let Some(note) = &project_report.hooks_note { out.push_str(&format!( " claude settings {} ({note})\n", @@ -319,8 +302,8 @@ struct ProjectInstallReport { owner: Option, fresh: bool, state_action: &'static str, - workflow_action: &'static str, - agents_action: &'static str, + /// A placeholder `AGENTS.md` was written for an empty project. + agents_md_scaffolded: bool, /// What happened to old ccteam hook entries in /// `.claude/settings.local.json` (docs-local issue #212); `None` when /// there was nothing to report. @@ -337,7 +320,8 @@ impl ProjectInstallReport { } } -/// Lay down (or refresh) a ccteam project at `target`. +/// Lay down (or refresh) a ccteam project at `target` — a thin wrapper over +/// [`ccteam_core::create_project`], the single creation path (issue #222). fn install_project_at( paths: &CcteamPaths, target: &std::path::Path, @@ -345,15 +329,11 @@ fn install_project_at( team: &str, opts: &InitOptions, ) -> Result { - let ccteam_dir = target.join(".ccteam"); - let state_path = ccteam_dir.join("state.json"); - let fresh = !state_path.exists(); - // v0.8.20 F1: `--owner` stamps `ProjectState.owner` at init. Normalize the - // raw value (bare → `user:`; `:`-bearing → verbatim) once, then apply it in - // whichever branch we take. Light, non-blocking validation: an unknown - // `user:` warns on stderr but is still written (the tenant may be - // created later). Override-on-reinit needs no `--force`. + // raw value (bare → `user:`; `:`-bearing → verbatim) once. Light, + // non-blocking validation: an unknown `user:` warns on stderr but is + // still written (the tenant may be created later). Override-on-reinit needs + // no `--force`. let requested_owner = opts.owner.as_deref().and_then(normalize_owner); if let Some(owner) = &requested_owner { if let Some(tenant_id) = owner.strip_prefix("user:") { @@ -370,93 +350,35 @@ fn install_project_at( } } - let state_action: &'static str; - let workflow_action: &'static str; - let agents_action: &'static str; - let final_team: String; - let owner_final: Option; - - if fresh { - ccteam_core::bootstrap_project_at_dir( - paths, - target, + // A refresh keeps the recorded team unless `--force` or an explicit team. + let team = (opts.force || opts.team.is_some()).then_some(team); + let created = ccteam_core::create_project( + paths, + &ccteam_core::NewProject { slug, - "(installed via `ccteam init`)", + dir: target, team, - )?; - ccteam_core::scaffold_workflow_yaml(target, false)?; - state_action = "created"; - workflow_action = "scaffolded"; - // v0.9.0 W2 (F6.1) — engine neutralization: init seeds NO role. The - // default session is roleless (bare vendor reads project CLAUDE.md / - // AGENTS.md); `.claude/agents/` is left untouched. - agents_action = "none (roleless default)"; - final_team = team.to_string(); - // Fresh state.json was written with owner = None; stamp it when asked. - if let Some(owner) = &requested_owner { - let mut st = ccteam_core::ProjectState::load(&state_path) - .with_context(|| format!("load {} to set owner", state_path.display()))?; - st.owner = Some(owner.clone()); - st.save(&state_path)?; - owner_final = Some(owner.clone()); - } else { - owner_final = None; - } - } else { - let mut existing_state = ccteam_core::ProjectState::load(&state_path) - .with_context(|| format!("load existing {}", state_path.display()))?; - if opts.force || opts.team.is_some() { - existing_state.team = team.to_string(); - } - existing_state.slug = slug.to_string(); - existing_state.tmux_session = format!("ccteam-{slug}"); - // Override on re-init only when `--owner` is given; otherwise preserve. - if let Some(owner) = &requested_owner { - existing_state.owner = Some(owner.clone()); - } - existing_state.save(&state_path)?; - final_team = existing_state.team.clone(); - owner_final = existing_state.owner.clone(); - state_action = "refreshed"; - - workflow_action = if opts.force { - ccteam_core::scaffold_workflow_yaml(target, true)?; - "overwritten (--force)" - } else { - "preserved" - }; - - // v0.9.0 W2 (F6.1) — ccteam seeds no roles, so there is nothing to - // (re)scaffold or reset; `--force` no longer touches - // `.claude/agents/` (user files there survive untouched). - agents_action = if opts.force { - "none (roleless default)" - } else { - "preserved" - }; - } + owner: requested_owner.as_deref(), + }, + )?; // docs-local issue #212 — re-running `ccteam init` is how an existing - // project sheds the Claude Code hooks earlier ccteam versions wrote (a - // fresh install already ran the same scrub inside - // `bootstrap_project_at_dir`, so here it only reports). Best-effort: a - // user file that is not valid JSON is left alone and noted, never fatal. - let hooks_note = match ccteam_core::remove_ccteam_hooks( - &target.join(".claude").join("settings.local.json"), - false, - ) { - Ok(report) => scrub_note(&report), - Err(err) => Some(format!("left untouched: {err:#}")), - }; + // project sheds the Claude Code hooks earlier ccteam versions wrote; + // `create_project` ran the scrub, this only reports it. A user file that is + // not valid JSON is left alone and noted, never fatal. + let hooks_note = created.settings_scrub.as_ref().and_then(scrub_note); Ok(ProjectInstallReport { slug: slug.to_string(), - team: final_team, - owner: owner_final, - fresh, - state_action, - workflow_action, - agents_action, + team: created.team, + owner: created.owner, + fresh: created.fresh, + state_action: if created.fresh { + "created" + } else { + "refreshed" + }, + agents_md_scaffolded: created.agents_md_scaffolded, hooks_note, }) } @@ -1846,8 +1768,8 @@ pub(crate) fn unroster_trigger_path(slug: &str) -> std::path::PathBuf { /// Options for `ccteam remove `. #[derive(Debug, Clone, Default)] pub struct RemoveOptions { - /// Also `rm -rf /.ccteam/`, `/.claude/agents/`, - /// and `/workflow.yaml`. Business code + `.env` untouched. + /// Also remove `/.ccteam/` and scrub old ccteam hook entries. + /// User roles, project instructions, business code and `.env` stay intact. pub purge: bool, /// Print every step that *would* run, but don't touch filesystem /// / config / daemon. @@ -3703,13 +3625,13 @@ fn purge_imd_registry_for_slug( /// Helper for `run_remove --purge` — deletes exactly ccteam's own /// footprint inside `/` (or, under `--dry-run`, just records /// the planned step). A project's on-disk ccteam footprint is `.ccteam/` -/// (state.json + workflow.yaml) plus any ccteam hook entries an earlier +/// (state.json and the rest of ccteam's per-project state) plus any ccteam hook entries an earlier /// version (or a terminal-protocol session) left inside /// `.claude/settings.local.json`. (ccteam seeds NO role, so there is /// no ccteam-owned persona to purge.) /// /// **DELETE** (ccteam-managed only): -/// - `/.ccteam/` — state.json + workflow.yaml live here (W2). +/// - `/.ccteam/` — state.json and everything else ccteam keeps per project. /// - ccteam's own hook entries inside `/.claude/settings.local.json` /// (surgically, via [`ccteam_core::remove_ccteam_hooks`]). If stripping /// them leaves the file an empty object, the now-vestigial file is deleted. @@ -3729,8 +3651,7 @@ fn purge_project_managed_paths( dry_run: bool, report: &mut RemoveReport, ) -> Result<()> { - // 1. `.ccteam/` dir (state.json + workflow.yaml). W2 moved - // workflow.yaml under `.ccteam/`, so this one delete covers both. + // 1. `.ccteam/` dir — state.json and everything else ccteam keeps per project. let ccteam_dir = project_dir.join(".ccteam"); if ccteam_dir.is_dir() { if dry_run { @@ -3828,7 +3749,12 @@ mod tests { DISABLE_TOOL_SURFACE.get_or_init(disable_tool_surface_bootstrap_for_tests); } + /// Every test builds its paths here, so this is the one place that keeps a + /// project creation (`pre_trust_project`) off the developer's real + /// `~/.claude.json` — tests that forgot `ensure_isolation()` used to leak + /// their tempdirs into it. fn fresh_paths(tmp: &TempDir) -> CcteamPaths { + ensure_isolation(); CcteamPaths { root: tmp.path().join("home"), projects_root: tmp.path().join("projects"), @@ -4374,7 +4300,7 @@ mod tests { let body = run_show(&paths, &slug, OutputFormat::Json).unwrap(); let v: Value = serde_json::from_str(&body).unwrap(); assert_eq!(v["state"]["slug"], slug); - // v0.8.6: `.ccteam/` keeps only state.json + workflow.yaml, so + // v0.8.6: `.ccteam/` keeps only state.json at creation, so // there is no `spec` artifact to surface. assert!( v["artifacts"].as_object().unwrap().is_empty(), @@ -4549,21 +4475,17 @@ mod tests { } #[test] - fn run_init_is_idempotent_and_preserves_user_workflow_yaml() { - // V0.5.0 F101: HELPER_TEMPLATES is empty so the V0.4.x - // `~/.ccteam/templates/review-with-user-loop.md` idempotency - // probe is gone. workflow.yaml is the new canonical - // "preserve without --force, overwrite with --force" artifact — - // it's user-territory by design (see `force_overwrites_user_workflow_and_agents`). + fn run_init_is_idempotent_and_never_overwrites_project_files() { + // The only knowledge file init writes is the empty-project `AGENTS.md` + // placeholder; once the user has edited it, no re-run (not even + // `--force`) may touch it. let tmp = TempDir::new().unwrap(); let paths = fresh_paths(&tmp); run_init(&paths, init_opts_targeting_tmp(&tmp, "idem-demo")).unwrap(); - let path = tmp - .path() - .join("idem-demo") - .join(".ccteam") - .join("workflow.yaml"); + let path = tmp.path().join("idem-demo").join("AGENTS.md"); std::fs::write(&path, "USER EDIT").unwrap(); + let workflow = tmp.path().join("idem-demo/.ccteam/workflow.yaml"); + std::fs::write(&workflow, "budgets_v060: {}\n").unwrap(); run_init(&paths, init_opts_targeting_tmp(&tmp, "idem-demo")).unwrap(); assert_eq!(std::fs::read_to_string(&path).unwrap(), "USER EDIT"); run_init( @@ -4575,7 +4497,11 @@ mod tests { }, ) .unwrap(); - assert_ne!(std::fs::read_to_string(&path).unwrap(), "USER EDIT"); + assert_eq!(std::fs::read_to_string(&path).unwrap(), "USER EDIT"); + assert_eq!( + std::fs::read_to_string(&workflow).unwrap(), + "budgets_v060: {}\n" + ); } /// Fresh install scaffolds the project skeleton AND registers in @@ -4596,14 +4522,28 @@ mod tests { ) .unwrap(); assert!(target.join(".ccteam").join("state.json").is_file()); - assert!( - target.join(".ccteam").join("workflow.yaml").is_file(), - "V0.4.6 F83: workflow.yaml must land in .ccteam/, not project root", - ); - assert!( - !target.join("workflow.yaml").exists(), - "V0.4.6 F83: workflow.yaml must NOT be at the project root after fresh init", - ); + // Issue #222 — init writes exactly what every other create entry writes + // (they share `ccteam_core::create_project`): no workflow.yaml, no + // CLAUDE.md, just the state, the AGENTS.md placeholder and the ignore. + let mut files = Vec::new(); + let mut stack = vec![target.clone()]; + while let Some(dir) = stack.pop() { + for entry in std::fs::read_dir(&dir).unwrap() { + let path = entry.unwrap().path(); + if path.is_dir() { + stack.push(path); + } else { + files.push( + path.strip_prefix(&target) + .unwrap() + .to_string_lossy() + .into_owned(), + ); + } + } + } + files.sort(); + assert_eq!(files, [".ccteam/state.json", ".gitignore", "AGENTS.md"]); // v0.9.0 (engine neutralization): fresh init seeds NO role — the default // session is roleless (bare vendor reads the project CLAUDE.md/AGENTS.md). assert!( @@ -4616,7 +4556,7 @@ mod tests { ); // v0.8.6: init no longer writes the `.ccteam/agents` neutral copy // nor the `.ccteam/skills` placeholder layout — `.ccteam/` holds - // only state.json + workflow.yaml. + // only state.json. assert!( !target.join(".ccteam").join("agents").exists(), "v0.8.6: .ccteam/agents neutral copy must NOT be written", @@ -4684,11 +4624,9 @@ mod tests { !lead.exists(), "__lead.md must NOT be scaffolded in default mode" ); - let wf = target.join(".ccteam").join("workflow.yaml"); - let body = std::fs::read_to_string(&wf).unwrap(); assert!( - !body.contains("mode: agent-team"), - "default workflow.yaml must NOT declare agent-team mode", + !target.join(".ccteam").join("workflow.yaml").exists(), + "init must NOT scaffold a workflow.yaml (issue #222)", ); // docs-local issue #212 — init writes no Claude settings at all. assert!( @@ -4697,11 +4635,10 @@ mod tests { ); } - /// Re-running on an existing ccteam project preserves user-edited - /// workflow.yaml + agents/*.md. workflow.yaml lives in `.ccteam/`, - /// not the root. + /// Re-running on an existing ccteam project preserves the user's files + /// (AGENTS.md, agents/*.md) and the recorded team and owner. #[test] - fn run_init_refresh_preserves_user_workflow_and_agents() { + fn run_init_refresh_preserves_user_files_team_and_owner() { let tmp = TempDir::new().unwrap(); let paths = fresh_paths(&tmp); let target = tmp.path().join("f72-refresh"); @@ -4709,12 +4646,13 @@ mod tests { &paths, InitOptions { install_in: Some(target.clone()), + team: Some("ops".into()), + owner: Some("user:u1".into()), ..InitOptions::default() }, ) .unwrap(); - let wf_path = target.join(".ccteam").join("workflow.yaml"); - std::fs::write(&wf_path, "USER WORKFLOW\n").unwrap(); + std::fs::write(target.join("AGENTS.md"), "USER GUIDE\n").unwrap(); // v0.9.0: init no longer creates `.claude/agents/`; the user authors it. std::fs::create_dir_all(target.join(".claude").join("agents")).unwrap(); std::fs::write( @@ -4733,13 +4671,17 @@ mod tests { ) .unwrap(); assert_eq!( - std::fs::read_to_string(&wf_path).unwrap(), - "USER WORKFLOW\n" + std::fs::read_to_string(target.join("AGENTS.md")).unwrap(), + "USER GUIDE\n" ); assert_eq!( std::fs::read_to_string(target.join(".claude").join("agents").join("cto.md")).unwrap(), "USER AGENT\n" ); + let state = + ccteam_core::ProjectState::load(&target.join(".ccteam").join("state.json")).unwrap(); + assert_eq!(state.team, "ops", "a plain re-run keeps the recorded team"); + assert_eq!(state.owner.as_deref(), Some("user:u1")); } /// docs-local issue #212 — re-running `ccteam init` on a project an @@ -4804,7 +4746,7 @@ mod tests { ..InitOptions::default() }; run_init(&paths, opts()).unwrap_or_else(|e| panic!("{name}: fresh init: {e:#}")); - assert!(target.join(".ccteam").join("workflow.yaml").exists()); + assert!(target.join(".ccteam").join("state.json").exists()); run_init(&paths, opts()).unwrap_or_else(|e| panic!("{name}: re-init: {e:#}")); assert_eq!(std::fs::read_to_string(&local).unwrap(), body, "{name}"); } @@ -4834,10 +4776,9 @@ mod tests { assert!(parse.contains("not valid JSON"), "{parse}"); } - /// `--force` re-runs overwrite user files. workflow.yaml lives in - /// `.ccteam/`, not the root. + /// `--force` re-stamps the team on a refresh; it never overwrites a project file. #[test] - fn run_init_force_overwrites_user_workflow_and_agents() { + fn run_init_force_restamps_team_and_keeps_project_files() { let tmp = TempDir::new().unwrap(); let paths = fresh_paths(&tmp); let target = tmp.path().join("f72-force"); @@ -4845,12 +4786,12 @@ mod tests { &paths, InitOptions { install_in: Some(target.clone()), + team: Some("ops".into()), ..InitOptions::default() }, ) .unwrap(); - let wf_path = target.join(".ccteam").join("workflow.yaml"); - std::fs::write(&wf_path, "USER WORKFLOW\n").unwrap(); + std::fs::write(target.join("AGENTS.md"), "USER GUIDE\n").unwrap(); run_init( &paths, InitOptions { @@ -4860,9 +4801,12 @@ mod tests { }, ) .unwrap(); - assert_ne!( - std::fs::read_to_string(&wf_path).unwrap(), - "USER WORKFLOW\n" + let state = + ccteam_core::ProjectState::load(&target.join(".ccteam").join("state.json")).unwrap(); + assert_eq!(state.team, "dev", "--force re-stamps the default team"); + assert_eq!( + std::fs::read_to_string(target.join("AGENTS.md")).unwrap(), + "USER GUIDE\n" ); } @@ -5019,12 +4963,6 @@ mod tests { "state.json must exist after --force init; checked {}", state_path.display() ); - let workflow_path = fake_repo.join(".ccteam").join("workflow.yaml"); - assert!( - workflow_path.exists(), - "workflow.yaml must exist after --force init; checked {}", - workflow_path.display() - ); } #[test] diff --git a/crates/ccteam-cli/src/main.rs b/crates/ccteam-cli/src/main.rs index d7d5965e5..c8c69c17b 100644 --- a/crates/ccteam-cli/src/main.rs +++ b/crates/ccteam-cli/src/main.rs @@ -142,7 +142,7 @@ enum Command { /// Registered project name (default: install-dir basename); does not move the install. #[arg(long, value_name = "NAME")] slug: Option, - /// Overwrite ccteam-managed files (state.json, workflow.yaml); never touches `.claude/agents/`. + /// Re-stamp the team on a refresh and step past the install-target guards (ccteam repo, $HOME, a slug registered elsewhere). #[arg(long, default_value_t = false)] force: bool, /// Project owner identity (e.g. `user:alice`; a bare name maps to `user:`). diff --git a/crates/ccteam-cli/src/web_chat_bridge.rs b/crates/ccteam-cli/src/web_chat_bridge.rs index e1e151d62..18d9f2287 100644 --- a/crates/ccteam-cli/src/web_chat_bridge.rs +++ b/crates/ccteam-cli/src/web_chat_bridge.rs @@ -198,7 +198,7 @@ mod tests { } } - /// `/newproject` scaffolds through `bootstrap_project_at_dir`, whose + /// `/newproject` scaffolds through `create_project`, whose /// `pre_trust_project` step writes a trust entry into `~/.claude.json` — /// the ONE path in this stack that resolves off `$HOME` rather than off /// the injected root. Switch it off process-wide, once, exactly as @@ -855,6 +855,9 @@ mod tests { // `/newproject ` scaffolds at the path and registers it. let proj_dir = home.path().join("code").join("demo"); + ccteam_core::ProjectState::initial_for_team("old".into(), "ops".into()) + .save(&CcteamPaths::project_state_in(&proj_dir)) + .unwrap(); send_text( &mut socket, "newproject", @@ -864,8 +867,13 @@ mod tests { recv_reply_contains(&mut socket, "已创建并切换到 demo").await; assert!(proj_dir.join(".ccteam").join("state.json").exists()); + assert!(proj_dir.join("AGENTS.md").is_file()); + assert!(proj_dir.join(".gitignore").is_file()); + assert!(!proj_dir.join("CLAUDE.md").exists()); + assert!(!proj_dir.join(".ccteam/workflow.yaml").exists()); let state = ccteam_core::ProjectState::load(&CcteamPaths::project_state_in(&proj_dir)) .expect("chat-created project state"); + assert_eq!(state.team, "ops"); assert_eq!( state.owner.as_deref(), Some("user:web-api"), diff --git a/crates/ccteam-core/src/config.rs b/crates/ccteam-core/src/config.rs index 03583bbf1..ebeadc962 100644 --- a/crates/ccteam-core/src/config.rs +++ b/crates/ccteam-core/src/config.rs @@ -154,10 +154,9 @@ impl DaemonConfig { /// - [`Self::idle_release_secs`] is the PRIMARY policy: a session nobody has /// spoken to for that long has its vendor process released (the session /// itself is untouched — it resumes by sid on the next message). The default -/// tracks the longest prompt-cache TTL a vendor offers (Claude: 1h), so the -/// process is held exactly as long as keeping it alive can still save a -/// cache re-read, and no longer. A 10–30 session fleet at ~300 MB per -/// resident claude is the case this exists for. +/// is 250 minutes, balancing continuity across work breaks with resident +/// process memory. A 10–30 session fleet at ~300 MB per resident claude is +/// the case this exists for. /// - [`Self::max_live`] is the SECONDARY ceiling: a hard cap for the burst /// case (many sessions active inside one TTL window), enforced by evicting /// the least-recently-active eligible session at admission time. @@ -184,11 +183,9 @@ pub fn default_sessions_max_live() -> u32 { 50 } -/// One hour — Claude's prompt-cache TTL, the longest window in which holding -/// the process open can still save the model a cache re-read. Past it, a -/// resident process buys nothing but RAM. +/// Owner-selected 250-minute idle window (docs-local/issues/#226). pub fn default_sessions_idle_release_secs() -> u64 { - 3600 + 250 * 60 } impl Default for SessionsConfig { @@ -595,7 +592,7 @@ mod tests { let cfg = CcteamConfig::default(); assert_eq!(cfg.daemon.workers, 4); assert_eq!(cfg.sessions.max_live, 50); - assert_eq!(cfg.sessions.idle_release_secs, 3600); + assert_eq!(cfg.sessions.idle_release_secs, 15_000); assert!(cfg.sessions.idle_release_by_vendor.is_empty()); assert_eq!(cfg.delegation.max_depth, 2); assert_eq!(cfg.delegation.max_children, 10); diff --git a/crates/ccteam-core/src/lib.rs b/crates/ccteam-core/src/lib.rs index 4db21e65d..935b50d91 100644 --- a/crates/ccteam-core/src/lib.rs +++ b/crates/ccteam-core/src/lib.rs @@ -272,9 +272,10 @@ pub use progress::{ AgentSessionStatus, AgentSessionSummary, }; pub use projects::{ - bootstrap_project, bootstrap_project_at_dir, ensure_project_data_home, pick_unused_slug, + bootstrap_project, create_project, ensure_project_data_home, pick_unused_slug, pick_unused_slug_verbatim, pre_trust_project, read_current_branch, refuses_active_session, - scaffold_workflow_yaml, slugify, slugify_brief, validate_slug_format, ActiveSessionRefusal, + slugify, slugify_brief, validate_slug_format, ActiveSessionRefusal, NewProject, + NewProjectReport, }; // v0.8.6 W5b ResDisk — read-side role reader for the resource API. pub use queries::{ diff --git a/crates/ccteam-core/src/paths.rs b/crates/ccteam-core/src/paths.rs index a3fbbd249..0cb2dc12b 100644 --- a/crates/ccteam-core/src/paths.rs +++ b/crates/ccteam-core/src/paths.rs @@ -263,7 +263,7 @@ impl CcteamPaths { .join(crate::pending_inject::PENDING_INJECT_FILE) } - /// `~/.ccteam/pty/` — V0.3.2 F56 directory holding FIFO files used + /// `~/.ccteam/state/pty/` — V0.3.2 F56 directory holding FIFO files used /// by the web layer's `tmux pipe-pane` relay (one FIFO per active /// `` or `-` subscription). Files are created / /// unlinked at runtime by `ccteam_web::routes::pty_ws`. diff --git a/crates/ccteam-core/src/projects.rs b/crates/ccteam-core/src/projects.rs index 5f8e4f43c..0cf28eee7 100644 --- a/crates/ccteam-core/src/projects.rs +++ b/crates/ccteam-core/src/projects.rs @@ -1,6 +1,7 @@ -//! Project bootstrap helpers used by `ccteam project new` (and reusable by the -//! M3+ inbox triage path). Pure: no tmux side effects, just file -//! creation under `~/projects//`. +//! Project creation. [`create_project`] is the one path every entry (`ccteam +//! init`, web `POST /projects`, IM `/newproject`, the satellite project binding) +//! goes through; the rest are slug and path helpers around it. Pure: no tmux +//! side effects, just file creation in the project working tree. use std::path::{Path, PathBuf}; @@ -212,28 +213,89 @@ fn pick_unused_with_prefixed(paths: &CcteamPaths, prefixed: &str) -> Result/.ccteam/state.json` ← `ProjectState::initial_for_team(slug, team)` -/// - `/.claude/settings.local.json` ← managed hooks + base -/// - `/.claude/agents/cto.md` ← default IM/chat persona -/// -/// v0.8.6: ccteam no longer generates a project `CLAUDE.md` / -/// `AGENTS.md` (project knowledge is vendor-native, owned by the -/// project) nor a `.ccteam/spec.md`; `.ccteam/` keeps only `state.json` -/// (+ `workflow.yaml`, written elsewhere). `request` is retained for -/// API stability but no longer persisted. +/// What a project creation is made from. See [`create_project`]. +#[derive(Debug, Clone, Copy)] +pub struct NewProject<'a> { + pub slug: &'a str, + /// The working tree. Created when absent; files already in it are left alone. + pub dir: &'a Path, + /// `None` = `dev` for a fresh project, keep the recorded team on a refresh. + pub team: Option<&'a str>, + /// `ProjectState.owner` to stamp. `None` = leave unowned (fresh) / keep the + /// existing owner (refresh). + pub owner: Option<&'a str>, +} + +/// What [`create_project`] did, for the callers' receipts. +#[derive(Debug, Clone)] +pub struct NewProjectReport { + pub dir: PathBuf, + /// `false` = `.ccteam/state.json` already existed and was refreshed. + pub fresh: bool, + /// The team now recorded in `state.json` and the registry. + pub team: String, + /// The owner now recorded in `state.json`. + pub owner: Option, + /// A placeholder `AGENTS.md` was written (the project had neither + /// `CLAUDE.md` nor `AGENTS.md`). + pub agents_md_scaffolded: bool, + /// What happened to old ccteam hooks in `.claude/settings.local.json`; + /// `None` when that file could not be inspected at all. + pub settings_scrub: Option, +} + +/// The one way a project comes into being — `ccteam init`, web +/// `POST /projects`, IM `/newproject` and the satellite project binding all +/// call this, so every entry leaves the same files behind: /// -/// `team` lands in state.json so downstream routing can pick the -/// matching workflow. +/// - `/.ccteam/state.json` (created, or refreshed when present); +/// - `/AGENTS.md` — a placeholder, only when the project has neither +/// `CLAUDE.md` nor `AGENTS.md`; +/// - `.ccteam/` appended to `/.gitignore`; +/// - the global `~/.ccteam/` home, a trust entry in `~/.claude.json`, and the +/// `config.yaml` registry entry. /// -/// Returns the full project directory path. +/// It writes no role (`.claude/agents/` stays the project's), no Claude +/// settings (and strips the hooks an older ccteam wrote there), no +/// `workflow.yaml`, and never overwrites an existing project file. +pub fn create_project(paths: &CcteamPaths, new: &NewProject<'_>) -> Result { + let report = scaffold_project(paths, new)?; + crate::config::upsert_project( + &paths.root, + crate::config::ProjectEntry { + slug: new.slug.to_string(), + path: new.dir.to_path_buf(), + host: crate::host_registry::LOCAL_HOST.to_string(), + remote_slug: None, + remote_path: None, + team: report.team.clone(), + installed_at: chrono::Utc::now(), + }, + ) + .context("register project in ~/.ccteam/config.yaml")?; + Ok(report) +} + +/// Test-fixture shorthand: scaffold `//` WITHOUT touching +/// the `config.yaml` registry (the layout `CcteamPaths::project_dir` falls back +/// to). Production creates go through [`create_project`]. `request` is unused. pub fn bootstrap_project( paths: &CcteamPaths, slug: &str, - request: &str, + _request: &str, team: &str, ) -> Result { - bootstrap_project_at_dir(paths, &paths.project_dir(slug), slug, request, team) + let dir = paths.project_dir(slug); + scaffold_project( + paths, + &NewProject { + slug, + dir: &dir, + team: Some(team), + owner: None, + }, + )?; + Ok(dir) } /// Materialize the daemon-side state home for a remote project without @@ -260,22 +322,13 @@ pub fn ensure_project_data_home( Ok(data_home.to_path_buf()) } -/// V0.4.2 F72: `bootstrap_project` generalized to install at an -/// arbitrary `target_dir`. Used by `ccteam init` to bring an existing -/// repo under management without moving files. Side effects (trust-marking) -/// still use `paths.root` / `paths.projects_root`, since those are global -/// concerns. -/// -/// `target_dir` is created if it doesn't exist. Existing files inside -/// it are left alone — this is the **fresh-install** path. Idempotent -/// refresh of an already-installed project lives in `ccteam init`'s -/// refresh helpers; this function assumes the caller already decided -/// "this is a new install". /// v0.8.9 (owner decision) — scaffold body for an EMPTY project's `AGENTS.md`. /// It tells a bare / roleless agent the project is uninitialized so it asks the /// user to set it up instead of spinning on an under-specified task. Written /// ONLY when the project has neither `CLAUDE.md` nor `AGENTS.md` (see -/// [`bootstrap_project_at_dir`]); never overwrites existing content. +/// [`create_project`]); never overwrites existing content. It is the only +/// knowledge file ccteam writes: Claude Code reads `AGENTS.md` itself when a +/// project has no `CLAUDE.md`, and Codex and the ACP harnesses read it natively. const EMPTY_PROJECT_AGENTS_MD: &str = "\ # Project guide @@ -294,80 +347,57 @@ Until this file is filled in, keep replies short, ask clarifying questions, and do **not** guess at the task or start open-ended exploration. "; -const DEFAULT_WORKFLOW_YAML: &str = r#"# ccteam workflow.yaml. -# Edit this file to declare your project's agent topology. Each agent -# is a role (filename of .claude/agents/.md) with a trigger that -# decides when ccteam spawns a session for it. -# -# Trigger grammar: -# manual # explicit `ccteam spawn ` only -# schedule # periodic; needs `schedule:` 5-field cron -# gate # waits for `trigger_gate` MCP / CLI call -# watch:.ccteam/issues/ # spawn one session per new file under the path -name: default-workflow -description: | - Minimal starter workflow. Edit me — declare your own agents below. - (v0.9.0: ccteam seeds no default role; sessions are roleless unless - you author `.claude/agents/.md` or install one from the hub.) - -agents: {} -"#; - -/// Write the standard `.ccteam/workflow.yaml` project scaffold. Existing -/// content is preserved unless `force` is true. -pub fn scaffold_workflow_yaml(target: &Path, force: bool) -> Result<()> { - let ccteam_dir = target.join(".ccteam"); - std::fs::create_dir_all(&ccteam_dir) - .with_context(|| format!("create {}", ccteam_dir.display()))?; - let path = ccteam_dir.join("workflow.yaml"); - if path.exists() && !force { - return Ok(()); - } - std::fs::write(&path, DEFAULT_WORKFLOW_YAML) - .with_context(|| format!("write {}", path.display()))?; - Ok(()) -} - -pub fn bootstrap_project_at_dir( - // `paths` is the global `~/.ccteam/` root. It feeds the - // home-ensure below (`ensure_ccteam_home`). - paths: &CcteamPaths, - target_dir: &Path, - slug: &str, - // v0.8.6: `request` is no longer persisted (no `.ccteam/spec.md`, - // no generated `CLAUDE.md`); kept in the signature for API - // stability so existing callers don't churn. - _request: &str, - team: &str, -) -> Result { +/// The disk half of [`create_project`] (no registry write). Everything here is +/// idempotent: a re-run refreshes `state.json` and adds nothing twice. +fn scaffold_project(paths: &CcteamPaths, new: &NewProject<'_>) -> Result { + let slug = new.slug; // Materialize the global `~/.ccteam/` home (canonical dirs + the - // `hooks/hook.sh` dispatcher) so every create path — `ccteam init`, web - // `POST /projects`, IM/web chat create — leaves the same complete home. - // Idempotent (cheap no-op when the home is already complete). + // `hooks/hook.sh` dispatcher) so every create path leaves the same + // complete home. Idempotent (cheap no-op when the home is already complete). crate::ensure_ccteam_home(paths).context("ensure ~/.ccteam/ home")?; - let project_dir = target_dir.to_path_buf(); + let project_dir = new.dir.to_path_buf(); let ccteam_dir = project_dir.join(".ccteam"); std::fs::create_dir_all(&ccteam_dir) .with_context(|| format!("create {}", ccteam_dir.display()))?; - let state = ProjectState::initial_for_team(slug.to_string(), team.to_string()); - state.save(&CcteamPaths::project_state_in(&project_dir))?; + let state_path = CcteamPaths::project_state_in(&project_dir); + let fresh = !state_path.exists(); + let mut state = if fresh { + match new.team { + Some(team) => ProjectState::initial_for_team(slug.to_string(), team.to_string()), + None => ProjectState::initial(slug.to_string()), + } + } else { + // A re-create never resets what the project has accumulated. + let mut state = ProjectState::load(&state_path) + .with_context(|| format!("load existing {}", state_path.display()))?; + state.slug = slug.to_string(); + state.tmux_session = format!("ccteam-{slug}"); + if let Some(team) = new.team { + state.team = team.to_string(); + } + state + }; + if let Some(owner) = new.owner { + state.owner = Some(owner.to_string()); + } + state.save(&state_path)?; // docs-local issue #212 — ccteam writes NO Claude settings for a project. // The `.claude/settings.local.json` hooks block it used to lay down only // served the retired tmux/rmux terminal protocol (the default stream-json // session is hookless), and creating that file for ccteam alone is // exactly what the owner ruled out. A directory that already carries - // ccteam's old hooks (a re-bootstrap over an earlier project) gets them + // ccteam's old hooks (a re-create over an earlier project) gets them // stripped instead; every other key and any user hook survive. // Best-effort: a user file that is unreadable or not valid JSON (empty, // trailing comma) is left untouched and never fails project creation. let settings_local = project_dir.join(".claude").join("settings.local.json"); - match crate::tool_surface::remove_ccteam_hooks(&settings_local, false) { + let settings_scrub = match crate::tool_surface::remove_ccteam_hooks(&settings_local, false) { Ok(report) => { use crate::tool_surface::CcteamHookScrubAction as A; - match report.action { + match &report.action { A::Unreadable { error } => tracing::warn!( path = %settings_local.display(), %error, @@ -380,13 +410,17 @@ pub fn bootstrap_project_at_dir( ), _ => {} } + Some(report) } - Err(err) => tracing::warn!( - path = %settings_local.display(), - error = %err, - "could not strip old ccteam hooks from .claude/settings.local.json", - ), - } + Err(err) => { + tracing::warn!( + path = %settings_local.display(), + error = %err, + "could not strip old ccteam hooks from .claude/settings.local.json", + ); + None + } + }; // v0.8.6 (review-fix #3): no longer stamp `~/.ccteam/templates/`. // `HELPER_TEMPLATES` has been empty since V0.5.0 F101, so the writer // only ever created an empty `templates/` dir that is *not* in @@ -405,8 +439,8 @@ pub fn bootstrap_project_at_dir( // v0.9.0 W2 (F6.1) — engine neutralization: ccteam seeds NO role. A fresh // project's `.claude/agents/` is left untouched (not even created); the - // default session is roleless (bare vendor reads the project CLAUDE.md / - // AGENTS.md). Orchestration personas live 100% in user space / the hub. + // default session is roleless (bare vendor reads the project's + // AGENTS.md / CLAUDE.md). Orchestration personas live 100% in user space / the hub. // v0.8.9 (owner decision) — scaffold a minimal project brain for an EMPTY // project so a roleless / bare-claude session asks the user to initialize @@ -414,16 +448,14 @@ pub fn bootstrap_project_at_dir( // has NEITHER CLAUDE.md NOR AGENTS.md (either present ⇒ a real project — // never touch it). This deliberately relaxes the earlier "ccteam never // generates the project knowledge layer" red-line, scoped to the - // empty-project bootstrap and never overwriting existing content. - let claude_md = project_dir.join("CLAUDE.md"); + // empty-project bootstrap and never overwriting existing content. Only + // `AGENTS.md` is written (issue #222): a second `CLAUDE.md` that just + // `@import`s it added nothing. let agents_md = project_dir.join("AGENTS.md"); - if !claude_md.exists() && !agents_md.exists() { + let agents_md_scaffolded = !project_dir.join("CLAUDE.md").exists() && !agents_md.exists(); + if agents_md_scaffolded { std::fs::write(&agents_md, EMPTY_PROJECT_AGENTS_MD) .with_context(|| format!("write {}", agents_md.display()))?; - // CLAUDE.md reuses AGENTS.md as the single source of truth via Claude - // Code's `@import` (Codex reads AGENTS.md directly). - std::fs::write(&claude_md, "@AGENTS.md\n") - .with_context(|| format!("write {}", claude_md.display()))?; } // v0.8.9 (owner request) — keep `.ccteam/` out of git: add a `.ccteam/` @@ -452,7 +484,14 @@ pub fn bootstrap_project_at_dir( } } - Ok(project_dir) + Ok(NewProjectReport { + dir: project_dir, + fresh, + team: state.team, + owner: state.owner, + agents_md_scaffolded, + settings_scrub, + }) } /// Pre-mark `project_dir` as trusted in `~/.claude.json` so the first @@ -1168,6 +1207,34 @@ mod tests { ); } + /// `create_project` with the defaults the web / IM / satellite entries pass. + fn create_demo(paths: &CcteamPaths, dir: &Path) -> NewProjectReport { + create_project( + paths, + &NewProject { + slug: "demo", + dir, + team: Some("dev"), + owner: None, + }, + ) + .unwrap() + } + + /// Every regular file under `root`, recursively. + fn walk(root: &Path) -> Vec { + let mut out = Vec::new(); + for entry in std::fs::read_dir(root).unwrap() { + let path = entry.unwrap().path(); + if path.is_dir() { + out.extend(walk(&path)); + } else { + out.push(path); + } + } + out + } + #[test] fn bootstrap_project_does_not_create_templates_dir() { // v0.8.6 (review-fix #3): `HELPER_TEMPLATES` has been empty since @@ -1219,7 +1286,7 @@ mod tests { /// the hooks an earlier ccteam wrote strips exactly those: user keys, a /// user hook in the same event section and `settings.json` survive. #[test] - fn bootstrap_project_at_dir_strips_old_ccteam_hooks() { + fn create_project_strips_old_ccteam_hooks() { ensure_isolation(); let tmp = tempfile::TempDir::new().unwrap(); let paths = CcteamPaths { @@ -1257,7 +1324,7 @@ mod tests { ); std::fs::write(&committed, &committed_body).unwrap(); - bootstrap_project_at_dir(&paths, &project_dir, "demo", "(re-create)", "dev").unwrap(); + create_demo(&paths, &project_dir); let v: Value = serde_json::from_str(&std::fs::read_to_string(&local).unwrap()).unwrap(); assert_eq!(v["model"], "opus", "user key kept"); @@ -1281,7 +1348,7 @@ mod tests { /// docs-local issue #212 — an empty or invalid user settings.local.json /// never fails project creation and is left exactly as it was. #[test] - fn bootstrap_project_at_dir_tolerates_unparseable_settings_local() { + fn create_project_tolerates_unparseable_settings_local() { ensure_isolation(); for (name, body) in [("empty", ""), ("comma", "{\"model\": \"opus\",}")] { let tmp = tempfile::TempDir::new().unwrap(); @@ -1294,18 +1361,26 @@ mod tests { std::fs::create_dir_all(local.parent().unwrap()).unwrap(); std::fs::write(&local, body).unwrap(); - bootstrap_project_at_dir(&paths, &project_dir, "demo", "(x)", "dev") - .unwrap_or_else(|e| panic!("{name}: bootstrap must not fail: {e:#}")); + create_project( + &paths, + &NewProject { + slug: "demo", + dir: &project_dir, + team: Some("dev"), + owner: None, + }, + ) + .unwrap_or_else(|e| panic!("{name}: create must not fail: {e:#}")); assert!(CcteamPaths::project_state_in(&project_dir).exists()); assert_eq!(std::fs::read_to_string(&local).unwrap(), body, "{name}"); } } /// Every create path (web / IM / daemon all flow through - /// `bootstrap_project_at_dir`) leaves a complete ccteam home: + /// `create_project`) leaves a complete ccteam home: /// `ensure_ccteam_home` runs up front, so hook.sh lands + is exec. #[test] - fn bootstrap_project_at_dir_materializes_hook_sh() { + fn create_project_materializes_hook_sh() { ensure_isolation(); let tmp = tempfile::TempDir::new().unwrap(); let paths = CcteamPaths { @@ -1314,13 +1389,12 @@ mod tests { }; let project_dir = tmp.path().join("workspace").join("my-repo"); - bootstrap_project_at_dir(&paths, &project_dir, "demo", "(from web/IM chat)", "dev") - .unwrap(); + create_demo(&paths, &project_dir); let hook_sh = paths.hooks_script(); assert!( hook_sh.exists(), - "bootstrap_project_at_dir must materialize {}", + "create_project must materialize {}", hook_sh.display(), ); let body = std::fs::read_to_string(&hook_sh).unwrap(); @@ -1353,7 +1427,7 @@ mod tests { /// re-run must succeed and leave hook.sh in place (it underpins the /// daemon-start + every create path, all of which may run repeatedly). #[test] - fn bootstrap_empty_project_scaffolds_claude_and_agents_md() { + fn create_empty_project_scaffolds_only_agents_md() { ensure_isolation(); let tmp = tempfile::TempDir::new().unwrap(); let paths = CcteamPaths { @@ -1362,30 +1436,138 @@ mod tests { }; let project_dir = tmp.path().join("workspace").join("empty-repo"); - bootstrap_project_at_dir(&paths, &project_dir, "demo", "(from web/IM chat)", "dev") - .unwrap(); + let report = create_demo(&paths, &project_dir); - let agents_md = project_dir.join("AGENTS.md"); - let claude_md = project_dir.join("CLAUDE.md"); + assert!(report.agents_md_scaffolded); assert!( - agents_md.exists(), - "an empty project must get a scaffolded AGENTS.md" + std::fs::read_to_string(project_dir.join("AGENTS.md")) + .unwrap() + .contains("has not been initialized"), + "scaffolded AGENTS.md must prompt the user to initialize the project" ); assert!( - claude_md.exists(), - "an empty project must get a scaffolded CLAUDE.md" + !project_dir.join("CLAUDE.md").exists(), + "issue #222: no CLAUDE.md alongside the AGENTS.md placeholder" ); + assert!( + !project_dir.join(".ccteam").join("workflow.yaml").exists() + && !project_dir.join("workflow.yaml").exists(), + "issue #222: creation seeds no workflow.yaml" + ); + } + + /// Issue #222 — the whole footprint of a creation, pinned in one place: every + /// entry point goes through `create_project`, so this is what each of them + /// leaves behind. + #[test] + fn create_project_footprint_is_state_agents_md_gitignore_and_registry() { + ensure_isolation(); + let tmp = tempfile::TempDir::new().unwrap(); + let paths = CcteamPaths { + root: tmp.path().join("home"), + projects_root: tmp.path().join("projects"), + }; + let project_dir = tmp.path().join("workspace").join("repo"); + + let report = create_project( + &paths, + &NewProject { + slug: "demo", + dir: &project_dir, + team: None, + owner: Some("user:alice"), + }, + ) + .unwrap(); + + assert!(report.fresh); + assert_eq!(report.team, "dev", "no team given → the default"); + assert_eq!(report.owner.as_deref(), Some("user:alice")); + + let mut files: Vec = Vec::new(); + for entry in walk(&project_dir) { + files.push( + entry + .strip_prefix(&project_dir) + .unwrap() + .display() + .to_string(), + ); + } + files.sort(); assert_eq!( - std::fs::read_to_string(&claude_md).unwrap(), - "@AGENTS.md\n", - "CLAUDE.md must @-import AGENTS.md (single source of truth)" + files, + [".ccteam/state.json", ".gitignore", "AGENTS.md"], + "a creation writes exactly these files into the project" ); + + let state = ProjectState::load(&CcteamPaths::project_state_in(&project_dir)).unwrap(); + assert_eq!(state.slug, "demo"); + assert_eq!(state.owner.as_deref(), Some("user:alice")); + + let entry = crate::config::load(&paths.root) + .unwrap() + .projects + .into_iter() + .find(|p| p.slug == "demo") + .expect("create_project registers the project in config.yaml"); + assert_eq!(entry.path, project_dir); + assert_eq!(entry.team, "dev"); + } + + /// Re-creating over a project that already has a `state.json` refreshes it: + /// what the project accumulated (team, owner) survives unless a value is given. + #[test] + fn create_project_refreshes_existing_state_instead_of_resetting_it() { + ensure_isolation(); + let tmp = tempfile::TempDir::new().unwrap(); + let paths = CcteamPaths { + root: tmp.path().join("home"), + projects_root: tmp.path().join("projects"), + }; + let project_dir = tmp.path().join("workspace").join("repo"); + let first = create_project( + &paths, + &NewProject { + slug: "demo", + dir: &project_dir, + team: Some("ops"), + owner: Some("user:alice"), + }, + ) + .unwrap(); + assert!(first.fresh); + + let again = create_project( + &paths, + &NewProject { + slug: "demo", + dir: &project_dir, + team: None, + owner: None, + }, + ) + .unwrap(); + assert!(!again.fresh); + assert_eq!(again.team, "ops", "no team given → keep the recorded one"); + assert_eq!(again.owner.as_deref(), Some("user:alice")); assert!( - std::fs::read_to_string(&agents_md) - .unwrap() - .contains("has not been initialized"), - "scaffolded AGENTS.md must prompt the user to initialize the project" + !again.agents_md_scaffolded, + "the AGENTS.md from the first creation is already there" ); + + let taken = create_project( + &paths, + &NewProject { + slug: "demo", + dir: &project_dir, + team: Some("dev"), + owner: Some("user:bob"), + }, + ) + .unwrap(); + assert_eq!(taken.team, "dev"); + assert_eq!(taken.owner.as_deref(), Some("user:bob")); } #[test] @@ -1406,8 +1588,7 @@ mod tests { ) .unwrap(); - bootstrap_project_at_dir(&paths, &project_dir, "demo", "(from web/IM chat)", "dev") - .unwrap(); + create_demo(&paths, &project_dir); assert_eq!( std::fs::read_to_string(project_dir.join("CLAUDE.md")).unwrap(), @@ -1434,7 +1615,7 @@ mod tests { std::fs::create_dir_all(&project_dir).unwrap(); std::fs::write(project_dir.join(".gitignore"), "target\nnode_modules").unwrap(); - bootstrap_project_at_dir(&paths, &project_dir, "demo", "(x)", "dev").unwrap(); + create_demo(&paths, &project_dir); let body = std::fs::read_to_string(project_dir.join(".gitignore")).unwrap(); assert!( body.contains("target") && body.contains("node_modules"), @@ -1449,7 +1630,7 @@ mod tests { ); // Re-bootstrap (idempotent / re-create) must NOT add a duplicate line. - bootstrap_project_at_dir(&paths, &project_dir, "demo", "(x)", "dev").unwrap(); + create_demo(&paths, &project_dir); let body2 = std::fs::read_to_string(project_dir.join(".gitignore")).unwrap(); assert_eq!( body2 diff --git a/crates/ccteam-core/src/tool_surface.rs b/crates/ccteam-core/src/tool_surface.rs index a419b56be..7a285d640 100644 --- a/crates/ccteam-core/src/tool_surface.rs +++ b/crates/ccteam-core/src/tool_surface.rs @@ -536,7 +536,7 @@ pub struct CcteamHookScrubReport { /// stream-json session is hookless: its events come from stdout, HITL from /// `can_use_tool`). Projects created before that still carry the old /// `hooks` block, so `ccteam init` (fresh or re-run), the web/IM create path -/// (`bootstrap_project_at_dir`) and `project rm --purge` all run this to +/// (`create_project`) and `project rm --purge` all run this to /// clean them. /// /// **Scope (red line).** Removes ONLY entries whose `command` is a ccteam diff --git a/crates/ccteam-harness/src/adapter.rs b/crates/ccteam-harness/src/adapter.rs index 6aafde952..c834b73c7 100644 --- a/crates/ccteam-harness/src/adapter.rs +++ b/crates/ccteam-harness/src/adapter.rs @@ -1526,6 +1526,9 @@ pub struct ContextRequestUsage { pub output_tokens: u64, pub cache_creation_tokens: u64, pub cache_read_tokens: u64, + /// Codex reports this as a subset of output, not an additional total. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reasoning_output_tokens: Option, } /// Account-level usage / rate-limits (Claude `get_usage` control_request; Codex @@ -1888,12 +1891,11 @@ pub struct SpawnCtx { /// spawn with a message naming stream-json instead of ignoring the pick. pub effort: Option, /// Explicit vendor session-mode token for this thread. `None` = the - /// ccteam default for that vendor. Same verbatim-ride contract as + /// vendor default. Same verbatim-ride contract as /// `effort`: an adapter with no mode axis REFUSES a non-empty value /// (`spawn_pick_refused`) instead of quietly ignoring it. Today only DSH - /// consumes it — its agent presets `standard` | `ptc`/`code` | `minimal` - /// | `creator`/`cordis` pick the toolset; unset ccteam-hires default to - /// `standard`. + /// consumes it as a native agent-preset id; DSH owns the roster and default. + /// ccteam's `creator` product label selects the native `cordis` preset. pub mode: Option, /// v0.8.7 W2 (DB.1) — per-session permission posture. `Skip` (default) /// keeps today's `--dangerously-skip-permissions` spawn; `Hitl` drops diff --git a/crates/ccteam-harness/src/execution/claude_stream_json/context.rs b/crates/ccteam-harness/src/execution/claude_stream_json/context.rs index e46be4a0b..623f05054 100644 --- a/crates/ccteam-harness/src/execution/claude_stream_json/context.rs +++ b/crates/ccteam-harness/src/execution/claude_stream_json/context.rs @@ -189,6 +189,7 @@ fn parse_request_usage(v: &Value) -> Option { output_tokens: n("output_tokens"), cache_creation_tokens: n("cache_creation_input_tokens"), cache_read_tokens: n("cache_read_input_tokens"), + reasoning_output_tokens: None, }) } diff --git a/crates/ccteam-harness/src/execution/codex_app_server.rs b/crates/ccteam-harness/src/execution/codex_app_server.rs index 580613f34..c840babf2 100644 --- a/crates/ccteam-harness/src/execution/codex_app_server.rs +++ b/crates/ccteam-harness/src/execution/codex_app_server.rs @@ -64,7 +64,7 @@ use crate::execution::progress_bridge::{ CODEX_PLAN_UPDATED, CODEX_RATE_LIMIT, CODEX_THREAD_STATUS, CODEX_TOKEN_USAGE, }; use crate::execution::session_meta::read_session_meta; -use crate::execution::session_status::write_status_file; +use crate::execution::session_status::{write_context_file, write_status_file}; use crate::{ AgentSpecBrief, AgentVendor, ExecutionMode, HarnessAdapter, HarnessError, PermissionMode, SpawnCtx, ThreadErrorEvent, ThreadEvent, ThreadHandle, ThreadItem, ThreadItemDetails, TurnId, @@ -161,6 +161,9 @@ pub struct ThreadLive { /// Latest context usage (total tokens + model context window) from /// `thread/tokenUsage/updated`. pub usage: Option, + /// The last vendor usage observation. No category RPC exists in Codex: + /// retain totals/request accounting, never estimate tools or memory files. + context: Option, /// The in-flight turn id, if any. Drives steer-vs-start (D2.2) and /// `/interrupt`'s `expectedTurnId`. pub active_turn: Option, @@ -252,6 +255,12 @@ fn persist_status(pending: Option<(StatusPersist, ThreadStatus)>) { } } +fn persist_context(pending: Option<(StatusPersist, crate::ContextBreakdown)>) { + if let Some((persist, context)) = pending { + write_context_file(&persist.project_dir, &persist.sid, &context); + } +} + /// One thread's in-flight turn and what it has said so far (GitHub #197 G). /// /// Codex's shape is DELTA: `item/agentMessage/delta` ships fragments of one @@ -2662,6 +2671,18 @@ impl HarnessAdapter for CodexAppServerAdapter { Ok(live.status()) } + async fn context_breakdown(&self, h: &ThreadHandle) -> Option { + // Read the existing dispatcher-owned snapshot; no extra subscription, + // model turn or RPC, and its timestamp remains the observation time. + self.tracker + .lock() + .await + .threads + .get(&h.identity)? + .context + .clone() + } + /// What this thread's in-flight turn has said so far — `None` when no turn /// is open (GitHub #197 E/G). /// @@ -4237,22 +4258,32 @@ async fn apply_notification_to_tracker( tracker.lock().await.entry(&tid).active_turn = Some(turn_id); } "turn/completed" => { - let pending = { + let (pending, context) = { let mut tracker = tracker.lock().await; let entry = tracker.entry(&tid); entry.active_turn = None; - entry.pending_write() + ( + entry.pending_write(), + entry.persist.clone().zip(entry.context.clone()), + ) }; // Turn boundary: the same moment every other adapter's status tap // writes, so context usage lands with the model it ran under. persist_status(pending); + persist_context(context); } "error" => { // Terminal failures (willRetry=false) clear the active turn; // retryable ones do not (the turn lives on until completion). let will_retry = pluck_bool(¬if.params, "will_retry", "willRetry").unwrap_or(false); if !will_retry { - tracker.lock().await.entry(&tid).active_turn = None; + let context = { + let mut tracker = tracker.lock().await; + let entry = tracker.entry(&tid); + entry.active_turn = None; + entry.persist.clone().zip(entry.context.clone()) + }; + persist_context(context); } } "thread/tokenUsage/updated" => { @@ -4267,17 +4298,25 @@ async fn apply_notification_to_tracker( let used = usage .get("last") .and_then(|t| t.get("total_tokens").or_else(|| t.get("totalTokens"))) - .and_then(|v| v.as_i64()) - .unwrap_or(0) - .max(0) as u64; + .and_then(|v| v.as_u64()); let window = usage .get("modelContextWindow") .or_else(|| usage.get("model_context_window")) .and_then(|v| v.as_i64()) .unwrap_or(0) .max(0) as u64; - tracker.lock().await.entry(&tid).usage = - Some(ContextUsage::known(used, window, ContextSource::Reported)); + let mut tracker = tracker.lock().await; + let entry = tracker.entry(&tid); + entry.usage = Some(ContextUsage { + used_tokens: used, + window_tokens: window, + source: if used.is_some() { + ContextSource::Reported + } else { + ContextSource::Unknown + }, + }); + entry.context = codex_context_usage(&usage, entry.model.clone()); } _ => {} } @@ -4463,6 +4502,66 @@ fn pluck_usage(v: &Value) -> Option { serde_json::from_value(raw).ok() } +/// Codex exposes usage observations, not Claude's category-analysis RPC. +/// `last.totalTokens` is also what its TUI uses for tokens-in-context; `total` +/// counts re-sent history many times. Keep unknown windows out of this shape +/// rather than claiming zero capacity. Cache/reasoning are nested wire fields. +fn codex_context_usage(usage: &Value, model: Option) -> Option { + let last = usage.get("last")?; + let used_tokens = last.get("totalTokens")?.as_u64()?; + let window_tokens = usage + .get("modelContextWindow")? + .as_u64() + .filter(|w| *w > 0)?; + let last_request = serde_json::from_value::(last.clone()) + .ok() + .and_then(|bucket| { + let written = last + .get("cacheWriteInputTokens") + .and_then(Value::as_u64) + .unwrap_or(0); + let uncached = bucket + .input_tokens + .checked_sub(bucket.cached_input_tokens)? + .checked_sub(written)?; + (bucket.reasoning_output_tokens <= bucket.output_tokens).then_some( + crate::ContextRequestUsage { + input_tokens: uncached, + cache_read_tokens: bucket.cached_input_tokens, + cache_creation_tokens: written, + output_tokens: bucket.output_tokens, + reasoning_output_tokens: Some(bucket.reasoning_output_tokens), + }, + ) + }); + Some(crate::ContextBreakdown { + vendor: "codex".into(), + model, + captured_at: Utc::now(), + used_tokens, + window_tokens, + // Only two totals, with no invented prompt/tool/memory attribution. + categories: vec![ + crate::ContextCategory { + id: "context_total".into(), + name: "Used context".into(), + tokens: used_tokens, + kind: crate::ContextCategoryKind::Used, + items: Vec::new(), + }, + crate::ContextCategory { + id: "free_space".into(), + name: "Free space".into(), + tokens: window_tokens.saturating_sub(used_tokens), + kind: crate::ContextCategoryKind::Free, + items: Vec::new(), + }, + ], + last_request, + ..Default::default() + }) +} + /// One Codex usage bucket (`tokenUsage.last` / `tokenUsage.total`) exactly as /// the live app-server spells it: camelCase, every field present. There are /// deliberately no defaults and no snake_case aliases — codex emits ONE shape @@ -5727,6 +5826,103 @@ mod tests { assert_eq!(line["model_context_window"], 128000); } + #[tokio::test] + async fn context_panel_uses_latest_request_and_persists_at_turn_end() { + let adapter = CodexAppServerAdapter::new(); + let tmp = tempfile::tempdir().unwrap(); + { + let mut tracker = adapter.tracker.lock().await; + let entry = tracker.entry("t-context"); + entry.model = Some("gpt-test".into()); + entry.persist = Some(StatusPersist { + project_dir: tmp.path().to_path_buf(), + sid: "s12".into(), + generation: Some(1), + }); + } + let handle = ThreadHandle { + vendor: AgentVendor::Codex, + mode: ExecutionMode::Chat, + identity: "t-context".into(), + started_at: Utc::now(), + raw_extras: json!({}), + }; + assert!(adapter.context_breakdown(&handle).await.is_none()); + for used in [15_765, 8_000] { + apply_notification_to_tracker(&adapter.tracker, &Notification { + method: "thread/tokenUsage/updated".into(), + params: json!({"threadId":"t-context","turnId":"turn-1","tokenUsage":{ + "total":{"totalTokens":9_000_000},"modelContextWindow":258_400, + "last":{"inputTokens":used-302,"cachedInputTokens":1_000,"outputTokens":302,"reasoningOutputTokens":110,"totalTokens":used,"cacheWriteInputTokens":0} + }}), + }).await; + let b = adapter + .context_breakdown(&handle) + .await + .expect("Codex usage must reach the panel"); + assert_eq!(b.used_tokens, used); + assert_eq!(b.window_tokens, 258_400); + assert_eq!(b.model.as_deref(), Some("gpt-test")); + assert_eq!(b.categories[0].tokens, used); + assert_eq!(b.categories[1].tokens, 258_400 - used); + assert!(b.messages.is_none(), "no invented per-tool accounting"); + let last = b.last_request.unwrap(); + assert_eq!(last.input_tokens, used - 302 - 1_000); + assert_eq!(last.cache_read_tokens, 1_000); + assert_eq!( + last.output_tokens, 302, + "reasoning is a subset, not an extra bucket" + ); + } + let other = ThreadHandle { + identity: "other-thread".into(), + ..handle.clone() + }; + assert!(adapter.context_breakdown(&other).await.is_none()); + apply_notification_to_tracker( + &adapter.tracker, + &Notification { + method: "turn/completed".into(), + params: json!({"threadId":"t-context","turn":{"id":"turn-1"}}), + }, + ) + .await; + let saved = crate::execution::session_status::read_context_file(tmp.path(), "s12") + .expect("turn end persists context"); + assert_eq!(saved.used_tokens, 8_000); + assert_eq!( + adapter + .context_breakdown(&handle) + .await + .unwrap() + .captured_at, + saved.captured_at, + "reading the panel does not refresh the observation time" + ); + } + + #[test] + fn context_panel_never_turns_missing_or_invalid_usage_into_zero() { + for usage in [ + json!({}), + json!({"last":{"totalTokens":1},"modelContextWindow":null}), + json!({"last":{},"modelContextWindow":100}), + json!({"last":{"totalTokens":-1},"modelContextWindow":100}), + ] { + assert!(codex_context_usage(&usage, None).is_none()); + } + let partial = codex_context_usage( + &json!({"last":{"totalTokens":10},"modelContextWindow":100}), + None, + ) + .unwrap(); + assert_eq!(partial.used_tokens, 10); + assert!( + partial.last_request.is_none(), + "missing request buckets are not zero" + ); + } + #[test] fn thread_status_active_waiting_on_approval() { // Internally-tagged ThreadStatus: {"type":"active","activeFlags":[...]}. diff --git a/crates/ccteam-harness/src/execution/dsh_acp/assets/ccteam-ui.tgz b/crates/ccteam-harness/src/execution/dsh_acp/assets/ccteam-ui.tgz index 70d83063b..6ee171f16 100644 Binary files a/crates/ccteam-harness/src/execution/dsh_acp/assets/ccteam-ui.tgz and b/crates/ccteam-harness/src/execution/dsh_acp/assets/ccteam-ui.tgz differ diff --git a/crates/ccteam-harness/src/execution/dsh_acp/handshake.rs b/crates/ccteam-harness/src/execution/dsh_acp/handshake.rs index f5825a1e5..f8d88c1a9 100644 --- a/crates/ccteam-harness/src/execution/dsh_acp/handshake.rs +++ b/crates/ccteam-harness/src/execution/dsh_acp/handshake.rs @@ -29,8 +29,7 @@ pub struct CcteamSessionMeta { pub bearer: String, pub mcp_url: String, pub approval_mode: &'static str, - /// DSH agent-preset id for `session/new` ONLY (`standard` | `code` | - /// `minimal` | `cordis`). Never sent on `session/load`: the stored preset + /// DSH agent-preset id for `session/new` ONLY (a native preset id). Never sent on `session/load`: the stored preset /// in the vendor's own session data is authoritative on resume. pub agent_preset: Option, } @@ -74,6 +73,7 @@ impl CcteamSessionMeta { pub struct DshAgentOptions { pub provider: Option, pub model: Option, + pub reasoning_effort: Option, } impl DshAgentOptions { @@ -82,6 +82,7 @@ impl DshAgentOptions { return Self { provider: None, model: None, + reasoning_effort: None, }; }; if let Some((provider, model)) = model.split_once('/') { @@ -91,17 +92,27 @@ impl DshAgentOptions { return Self { provider: Some(provider.to_string()), model: Some(model.to_string()), + reasoning_effort: None, }; } } Self { provider: None, model: Some(model.to_string()), + reasoning_effort: None, } } + pub fn with_reasoning_effort(mut self, effort: Option<&str>) -> Self { + self.reasoning_effort = effort + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(str::to_string); + self + } + fn to_json(&self) -> Option { - if self.provider.is_none() && self.model.is_none() { + if self.provider.is_none() && self.model.is_none() && self.reasoning_effort.is_none() { return None; } let mut out = serde_json::Map::new(); @@ -111,6 +122,9 @@ impl DshAgentOptions { if let Some(model) = &self.model { out.insert("model".to_string(), json!(model)); } + if let Some(effort) = &self.reasoning_effort { + out.insert("reasoningEffort".to_string(), json!(effort)); + } Some(Value::Object(out)) } diff --git a/crates/ccteam-harness/src/execution/dsh_acp/materialize.rs b/crates/ccteam-harness/src/execution/dsh_acp/materialize.rs index eb4836a64..8f0da3df2 100644 --- a/crates/ccteam-harness/src/execution/dsh_acp/materialize.rs +++ b/crates/ccteam-harness/src/execution/dsh_acp/materialize.rs @@ -1134,7 +1134,13 @@ fn merged_profile_patch_yaml( // inserted the row, and an empty override would say nothing. let mut ours: Vec<(&PluginAsset, serde_yaml::Mapping)> = Vec::new(); if !spec.config.is_empty() { - ours.push((&UI_PLUGIN, flat_config(spec.config.entries()))); + let mut config = flat_config(spec.config.entries()); + // Engine ownership is explicit; credentials can also be entered by a human. + config.insert( + serde_yaml::Value::String("engineManaged".into()), + serde_yaml::Value::Bool(true), + ); + ours.push((&UI_PLUGIN, config)); } // Rows naming a former ccteam package ccteam itself wrote are pruned // whatever their id; every other row survives byte-for-byte. @@ -1185,10 +1191,22 @@ fn merged_profile_patch_yaml( serde_yaml::Value::String("name".to_string()), serde_yaml::Value::String(plugin.bundle.to_string()), ); - row.insert( - serde_yaml::Value::String("config".to_string()), - serde_yaml::Value::Mapping(config), - ); + // Native live settings share this row with our wiring. Preserve + // preferences and credentials this registration did not supply. + let key = serde_yaml::Value::String("config".to_string()); + let mut merged = match row.get(&key) { + None | Some(serde_yaml::Value::Null) => Mapping::new(), + Some(serde_yaml::Value::Mapping(existing)) => existing.clone(), + Some(_) => { + return Err(HarnessError::SpawnFailed(format!( + "DSH profile patch {} row `{}` config must be a mapping", + path.display(), + plugin.row_id + ))) + } + }; + merged.extend(config); + row.insert(key, serde_yaml::Value::Mapping(merged)); } None => { let mut row = serde_yaml::Mapping::new(); @@ -1659,6 +1677,54 @@ mod tests { ); } + #[test] + fn registration_preserves_native_live_settings_for_owned_and_operator_profiles() { + for policy in [ManifestPolicy::Owned, ManifestPolicy::MergeOnly] { + let root = tempfile::tempdir().unwrap(); + let home = tempfile::tempdir().unwrap(); + let mut spec = wired_web(); + spec.manifest = policy; + let out = materialize_profile_in(root.path(), home.path(), spec).unwrap(); + let path = out.profile_dir.join("cordis.patch.yml"); + let mut patch = read_patch(&out.profile_dir); + let config = patch.as_sequence_mut().unwrap()[0]["config"] + .as_mapping_mut() + .unwrap(); + for (key, value) in [ + ("autoStart", serde_yaml::Value::Bool(false)), + ( + "enginePath", + serde_yaml::Value::String("/opt/ccteam".into()), + ), + ( + "defaultProject", + serde_yaml::Value::String("my-project".into()), + ), + ( + "enrollment", + serde_yaml::Value::String("ccteam-enroll:saved:secret".into()), + ), + ] { + config.insert(serde_yaml::Value::String(key.into()), value); + } + fs::write(&path, serde_yaml::to_string(&patch).unwrap()).unwrap(); + spec.config.enrollment = None; + spec.config.daemon_url = Some("http://127.0.0.1:9999"); + materialize_profile_in(root.path(), home.path(), spec).unwrap(); + let patch = read_patch(&out.profile_dir); + let config = &ccteam_row(&patch)["config"]; + assert_eq!(config["autoStart"], serde_yaml::Value::Bool(false)); + assert_eq!(config["enginePath"].as_str(), Some("/opt/ccteam")); + assert_eq!(config["defaultProject"].as_str(), Some("my-project")); + assert_eq!( + config["enrollment"].as_str(), + Some("ccteam-enroll:saved:secret") + ); + assert_eq!(config["daemonUrl"].as_str(), Some("http://127.0.0.1:9999")); + assert_eq!(config["engineManaged"], serde_yaml::Value::Bool(true)); + } + } + #[test] fn web_profile_row_carries_flat_config_and_never_a_duplicate_insert() { let root = tempfile::tempdir().unwrap(); diff --git a/crates/ccteam-harness/src/execution/dsh_acp/mod.rs b/crates/ccteam-harness/src/execution/dsh_acp/mod.rs index c1c552ec7..d404c064a 100644 --- a/crates/ccteam-harness/src/execution/dsh_acp/mod.rs +++ b/crates/ccteam-harness/src/execution/dsh_acp/mod.rs @@ -75,27 +75,13 @@ struct LiveSession { _dispatcher: tokio::task::JoinHandle<()>, } -/// Resolve the ccteam `mode` axis to a DSH agent-preset id. The vendor's -/// shipped presets are `standard` | `code` (displayed "PTC") | `minimal` | -/// `cordis` (displayed "creator"); ccteam accepts both spellings and an unset -/// mode defaults a hire to `standard` (owner decree, matching DSH's own web -/// default). Presets pick the TOOLSET: a session created without one has no -/// bash/read/write at all in the web runtime. -pub fn mode_agent_preset(mode: Option<&str>) -> Result { - let token = mode - .map(str::trim) - .filter(|m| !m.is_empty()) - .unwrap_or("standard"); - match token.to_ascii_lowercase().as_str() { - "ptc" | "code" => Ok("code".to_string()), - "standard" => Ok("standard".to_string()), - "minimal" => Ok("minimal".to_string()), - "creator" | "cordis" => Ok("cordis".to_string()), - other => Err(HarnessError::SpawnFailed(format!( - "unknown DSH session mode `{other}`: accepts standard | ptc (code) | minimal | \ - creator (cordis); omit it for the ccteam default (standard)" - ))), - } +/// Pass native preset ids through so new DSH presets need no engine release. +/// An absent mode leaves the vendor's configured default authoritative. +/// `creator` is ccteam's product label for DSH's `cordis` preset. +pub fn mode_agent_preset(mode: Option<&str>) -> Option { + mode.map(str::trim) + .filter(|mode| !mode.is_empty()) + .map(|mode| if mode == "creator" { "cordis" } else { mode }.to_string()) } /// Per-process singleton holding live DSH ACP sessions keyed by DSH session id. @@ -438,29 +424,18 @@ impl HarnessAdapter for DshAcpAdapter { "DSH sessions are roleless-only; ccteam does not inject role prompts".into(), )); } - if let Some(effort) = ctx - .effort - .as_deref() - .map(str::trim) - .filter(|e| !e.is_empty()) - { - return Err(crate::execution::acp::spawn_pick_refused( - "effort", - effort, - "DSH ACP has no reasoning-effort axis", - )); - } - let mcp = Self::session_mcp_endpoint(ctx)?; let cwd = project_cwd(ctx)?; let inbound = Self::inbound_policy(ctx.permission_mode); - let agent_options = DshAgentOptions::new(ctx.model_id.as_deref()); + let agent_options = DshAgentOptions::new(ctx.model_id.as_deref()) + .with_reasoning_effort(ctx.effort.as_deref()); // `session/load` carries the identity WITHOUT a preset (the stored one // is authoritative); `session/new` — fresh or as the failed-load - // fallback — carries the resolved mode, ccteam-defaulting to PTC. - let agent_preset = mode_agent_preset(ctx.mode.as_deref())?; + // fallback — carries an explicitly requested native preset. + let agent_preset = mode_agent_preset(ctx.mode.as_deref()); let meta = CcteamSessionMeta::new(&ctx.sid, &mcp, ctx.permission_mode); - let new_meta = meta.clone().with_agent_preset(agent_preset); + let mut new_meta = meta.clone(); + new_meta.agent_preset = agent_preset; // The runtime is per identity, so the socket and the DSH home are too. // `CCTEAM_DSH_SOCKET` (test-only) names a socket a fake already serves @@ -743,18 +718,18 @@ mod tests { } #[test] - fn mode_tokens_resolve_to_vendor_preset_ids() { - assert_eq!(mode_agent_preset(None).unwrap(), "standard"); - assert_eq!(mode_agent_preset(Some("")).unwrap(), "standard"); - assert_eq!(mode_agent_preset(Some("ptc")).unwrap(), "code"); - assert_eq!(mode_agent_preset(Some("PTC")).unwrap(), "code"); - assert_eq!(mode_agent_preset(Some("code")).unwrap(), "code"); - assert_eq!(mode_agent_preset(Some("standard")).unwrap(), "standard"); - assert_eq!(mode_agent_preset(Some("minimal")).unwrap(), "minimal"); - assert_eq!(mode_agent_preset(Some("creator")).unwrap(), "cordis"); - assert_eq!(mode_agent_preset(Some("cordis")).unwrap(), "cordis"); - let err = mode_agent_preset(Some("turbo")).unwrap_err().to_string(); - assert!(err.contains("turbo") && err.contains("standard"), "{err}"); + fn mode_tokens_defer_defaults_and_native_preset_validation_to_dsh() { + assert_eq!(mode_agent_preset(None), None); + assert_eq!(mode_agent_preset(Some(" ")), None); + assert_eq!(mode_agent_preset(Some(" ptc ")).as_deref(), Some("ptc")); + assert_eq!( + mode_agent_preset(Some("creator")).as_deref(), + Some("cordis") + ); + assert_eq!( + mode_agent_preset(Some("custom-preset")).as_deref(), + Some("custom-preset") + ); } #[test] diff --git a/crates/ccteam-harness/src/tmux_backend/fifo_relay.rs b/crates/ccteam-harness/src/tmux_backend/fifo_relay.rs index d8e429527..95cf427ec 100644 --- a/crates/ccteam-harness/src/tmux_backend/fifo_relay.rs +++ b/crates/ccteam-harness/src/tmux_backend/fifo_relay.rs @@ -21,7 +21,7 @@ //! //! `ccteam-core` depends on `ccteam-harness` (tmux_ops moved here in W1), so //! mux MUST NOT depend on core — that would cycle. The FIFO directory -//! (`~/.ccteam/pty`, honoring `CCTEAM_HOME`) is therefore resolved by a +//! (`~/.ccteam/state/pty`, honoring `CCTEAM_HOME`) is therefore resolved by a //! small local helper that mirrors `CcteamPaths::pty_dir` rather than //! importing it. //! @@ -48,7 +48,7 @@ use crate::MuxSessionId; /// this surfaces as a `MuxEvent::OutputDropped` on the stream. pub const BROADCAST_CAPACITY: usize = 256; -/// Resolve `~/.ccteam/pty` honoring `CCTEAM_HOME`, mirroring +/// Resolve `~/.ccteam/state/pty` honoring `CCTEAM_HOME`, mirroring /// `ccteam_core::CcteamPaths::pty_dir` without taking the dependency /// (which would create a cargo cycle). Falls back to `$TMPDIR` / `/tmp` /// when neither `CCTEAM_HOME` nor `$HOME` is set (tests / sandboxes). @@ -62,7 +62,9 @@ fn pty_dir() -> PathBuf { } else { PathBuf::from("/tmp").join(".ccteam") }; - root.join("pty") + // Keep the leaf-crate resolver aligned with CcteamPaths::pty_dir. + // The canonical home stores runtime FIFOs under state/, not at root. + root.join("state").join("pty") } /// Registry of live `tmux pipe-pane` relays keyed by `MuxSessionId`. diff --git a/crates/ccteam-harness/tests/dsh_acp_test.rs b/crates/ccteam-harness/tests/dsh_acp_test.rs index b770d9862..963e1b374 100644 --- a/crates/ccteam-harness/tests/dsh_acp_test.rs +++ b/crates/ccteam-harness/tests/dsh_acp_test.rs @@ -1440,8 +1440,6 @@ async fn session_new_carries_the_per_session_ccteam_identity() { "bearer": "ccteam-sid:s-meta:seKret1234", "mcpUrl": "http://127.0.0.1:65535/mcp", "approvalMode": "skip", - // Unset mode = the ccteam default: `standard` (the vendor's own). - "agentPreset": "standard", })), "session/new must install this hire's ccteam identity: {params}" ); @@ -1527,22 +1525,37 @@ async fn mode_axis_picks_the_agent_preset_on_session_new() { ); adapter.close_thread(&handle).await.unwrap(); - let mut bad = spawn_ctx_with_model(&tmp, "s-mode-bad"); - bad.mode = Some("turbo".to_string()); - let err = adapter + let mut custom = spawn_ctx_with_model(&tmp, "s-mode-custom"); + custom.mode = Some("custom-preset".to_string()); + custom.effort = Some("high".to_string()); + let handle = adapter .start_thread( &AgentSpecBrief { role: String::new(), }, - &bad, + &custom, ) .await - .expect_err("unknown mode must refuse the spawn"); - let text = err.to_string(); - assert!( - text.contains("turbo") && text.contains("ptc"), - "the refusal names the token and the valid set: {text}" + .expect("DSH owns native preset validation"); + let records = read_dump(&dump); + let params = &records + .iter() + .rev() + .find(|record| record["method"] == "session/new") + .unwrap()["params"]; + assert_eq!( + params + .pointer("/_meta/ccteam/agentPreset") + .and_then(Value::as_str), + Some("custom-preset") + ); + assert_eq!( + params + .pointer("/agentOptions/reasoningEffort") + .and_then(Value::as_str), + Some("high") ); + adapter.close_thread(&handle).await.unwrap(); } /// hitl vs skip is a per-session posture on a runtime that serves both. diff --git a/crates/ccteam-im/src/gateway.rs b/crates/ccteam-im/src/gateway.rs index 5cbadf07a..39ac68e4c 100644 --- a/crates/ccteam-im/src/gateway.rs +++ b/crates/ccteam-im/src/gateway.rs @@ -12,8 +12,8 @@ use std::sync::Arc; use std::time::{Duration, Instant}; use anyhow::{anyhow, Context, Result}; -use ccteam_core::config::{upsert_project, CcteamConfig, ProjectEntry}; -use ccteam_core::projects::{bootstrap_project_at_dir, validate_slug_format}; +use ccteam_core::config::CcteamConfig; +use ccteam_core::projects::{create_project, validate_slug_format, NewProject}; use ccteam_core::{CcteamPaths, HotConfig, RoleDetail}; use ccteam_harness::execution::session_body::{self, BodyProbe, SessionBody}; use ccteam_harness::usage_catalog::VendorAccountUsage; @@ -7232,7 +7232,7 @@ impl Gateway { /// `config.yaml`, and make it addressable by `/cd ` in this /// running daemon. `raw_path` may be `~`-relative; it must resolve to /// an absolute directory (existing repos are adopted in place, empty - /// dirs are created — `bootstrap_project_at_dir` leaves user files + /// dirs are created — `create_project` leaves user files /// alone). Requires [`Gateway::enable_project_creation`]. fn create_project( &mut self, @@ -7253,39 +7253,19 @@ impl Gateway { return Err(anyhow!("project already exists: {slug}")); } let abs = expand_project_path(raw_path)?; - bootstrap_project_at_dir(&paths, &abs, &slug, "(created from web/IM chat)", "dev") - .with_context(|| format!("scaffold project {slug} at {}", abs.display()))?; - // v0.8.18 柱2 — record the creating chat as owner (explicit field; NOT - // path-derived). Use the known project dir: the config upsert happens - // below, so `paths.project_state(slug)` would still resolve the fallback - // projects-root path and miss an arbitrary-path project's real state. - if let Some(owner) = owner { - let state_path = ccteam_core::CcteamPaths::project_state_in(&abs); - match ccteam_core::ProjectState::load(&state_path) { - Ok(mut state) => { - state.owner = Some(owner.to_string()); - if let Err(err) = state.save(&state_path) { - tracing::warn!(%slug, error = %err, "set project owner failed"); - } - } - Err(err) => { - tracing::warn!(%slug, error = %err, "load state to set owner failed") - } - } - } - upsert_project( - &paths.root, - ProjectEntry { - slug: slug.clone(), - path: abs.clone(), - host: ccteam_core::LOCAL_HOST.to_string(), - remote_slug: None, - remote_path: None, - team: "dev".to_string(), - installed_at: chrono::Utc::now(), + // v0.8.18 柱2 — the creating chat is recorded as owner (explicit field; + // NOT path-derived). One shared creation path (issue #222): scaffold, + // owner stamp and config.yaml registration happen together. + create_project( + &paths, + &NewProject { + slug: &slug, + dir: &abs, + team: None, + owner, }, ) - .with_context(|| format!("register project {slug} in config.yaml"))?; + .with_context(|| format!("create project {slug} at {}", abs.display()))?; self.register_project(slug.clone(), abs.clone()); // Switch the creating chat INTO the new project (mirror `/cd`): point its // `current_project` at the fresh slug and clear the active session — a @@ -13813,6 +13793,12 @@ impl Gateway { Some(residency_of_meta(&meta)) } + /// Durable facts for one sid, using the same catalog as session listings. + /// Read-only detail surfaces need neither a fleet listing nor a disk scan. + pub fn session_meta_snapshot(&self, sid: &str) -> Option { + self.find_meta_for_sid(sid).ok().map(|(_, _, meta)| meta) + } + /// Every RELEASED session of the given projects — real, resumable, and /// simply not holding a process right now. `slugs = None` means every /// project the gateway has loaded. @@ -14864,8 +14850,8 @@ impl Gateway { // The policy is a two-level ladder, in this order of importance: // 1. IDLE RELEASE (primary) — a session nobody has spoken to for its // harness's `sessions.idle_release_secs` window is released. The - // default window is the vendor's prompt-cache TTL, so a process is - // held exactly as long as holding it can still save a cache re-read. + // default window is 250 minutes; explicit per-harness overrides can + // tune the balance between continuity and resident memory. // 2. CAPACITY (secondary) — `sessions.max_live` still caps the burst // case, evicting the least-recently-active eligible session at // admission time. diff --git a/crates/ccteam-im/tests/inbound_wiring_test.rs b/crates/ccteam-im/tests/inbound_wiring_test.rs index 5664a5db1..5eb452eb3 100644 --- a/crates/ccteam-im/tests/inbound_wiring_test.rs +++ b/crates/ccteam-im/tests/inbound_wiring_test.rs @@ -2311,7 +2311,16 @@ async fn real_ws_dual_harness_smoke() { ) .unwrap(); let paths = ccteam_core::CcteamPaths::from_env().unwrap(); - ccteam_core::bootstrap_project_at_dir(&paths, project.path(), &slug, "", "real-ws").unwrap(); + ccteam_core::create_project( + &paths, + &ccteam_core::NewProject { + slug: &slug, + dir: project.path(), + team: Some("real-ws"), + owner: None, + }, + ) + .unwrap(); ccteam_core::install_hooks(&paths).unwrap(); seed_role(project.path(), "api"); seed_role(project.path(), "reviewer"); diff --git a/crates/ccteam-web/Cargo.toml b/crates/ccteam-web/Cargo.toml index cc58429a4..20896029b 100644 --- a/crates/ccteam-web/Cargo.toml +++ b/crates/ccteam-web/Cargo.toml @@ -148,6 +148,7 @@ futures-util = "0.3" # `fs` is required for `nix::sys::stat::Mode` + `nix::unistd::mkfifo` # (F56 uses these to create the FIFO under `~/.ccteam/pty/`). nix = { version = "0.29", default-features = false, features = ["signal", "fs"] } +pulldown-cmark = { version = "0.13.6", default-features = false, features = ["html"] } [dev-dependencies] tempfile = "3" diff --git a/crates/ccteam-web/src/auth.rs b/crates/ccteam-web/src/auth.rs index 05e0b8bd6..0e5eea6d2 100644 --- a/crates/ccteam-web/src/auth.rs +++ b/crates/ccteam-web/src/auth.rs @@ -609,6 +609,19 @@ pub async fn auth_layer( next: Next, ) -> Response { let auth = app.auth.clone(); + if let Some(identity) = + crate::routes::files::preview_identity(&app, req.method(), req.uri().path()) + { + let Some(identity) = identity else { + return ( + StatusCode::UNAUTHORIZED, + "Preview expired; reopen it from Files", + ) + .into_response(); + }; + req.extensions_mut().insert(identity); + return next.run(req).await; + } if !auth.enabled { // Loopback / --no-auth: the local operator IS the owner → admin identity. req.extensions_mut().insert(Identity::admin()); diff --git a/crates/ccteam-web/src/dsh_web.rs b/crates/ccteam-web/src/dsh_web.rs index b0132f026..2b0f5b6f6 100644 --- a/crates/ccteam-web/src/dsh_web.rs +++ b/crates/ccteam-web/src/dsh_web.rs @@ -416,7 +416,6 @@ async fn proxy_http( ) -> Response { let (parts, body) = req.into_parts(); let port = target.port; - let plugin_bundle = is_plugin_bundle_path(parts.uri.path()); let upstream = format!( "http://127.0.0.1:{port}{}", parts @@ -430,7 +429,7 @@ async fn proxy_http( builder = builder.headers(headers); builder = builder.body(reqwest::Body::wrap_stream(body.into_data_stream())); match builder.send().await { - Ok(resp) => response_from_reqwest(resp, plugin_bundle).await, + Ok(resp) => response_from_reqwest(resp).await, Err(err) => ( StatusCode::BAD_GATEWAY, Json(serde_json::json!({ "error": err.to_string() })), @@ -457,48 +456,10 @@ async fn proxy_http( const CRYPTO_RANDOM_UUID_POLYFILL: &str = r#""#; /// Declares that the page owns its Host, through DSH's own transport hook. -/// -/// DSH keeps its **privileged surface** — the settings document (every -/// Settings page: General, Models, Plugin configuration), and the deliverables -/// file actions — reachable only from "the operator's own machine". The client -/// decides that from the page authority (`dsh-client-connection`: -/// `isLoopback = transport.ownsHost || page hostname is loopback`), and a -/// non-loopback page gets every settings scope in `memory` mode: the describe -/// mirror never asks the Host, so the Plugin configuration tab renders nothing -/// at all — not the cards, not even the "no settings" line — while the same -/// instance opened from `127.0.0.1` shows everything. Real-machine v0.10.4: a -/// LAN browser on ccteam web → DSH saw an empty tab and had nowhere to paste a -/// REST token. -/// -/// Through the companion port that stand-in is ccteam's authenticated -/// identity: the page reaches an instance that belongs to that identity alone -/// (the operator's own `~/.dsh`, or the tenant's isolated home), after ccteam -/// auth, and the server-side Host/Origin fence is already satisfied by the -/// loopback rewrite in [`proxy_request_headers`]. `__DSH_TRANSPORT__.ownsHost` -/// is the hook DSH documents for exactly this ("a shell that assembles its -/// own transport"); the companion port is that shell. -/// -/// Version-safe by construction, because the two published shapes of the hook -/// differ and a served page carries the global for both: -/// - DSH ≤ 0.1.1-rc.2 has no `ownsHost` and reads -/// `transport?.createApiClient() ?? new WebApiClient()` — a global WITHOUT -/// `createApiClient` aborts the whole boot ("transport?.createApiClient is -/// not a function", real-machine), so the declaration ships a -/// `createApiClient` that answers `undefined` and lets the default carrier -/// through. On those versions the flag is simply not read: settings stay -/// loopback-only there (a `127.0.0.1` tunnel is the way in), nothing else -/// changes. -/// - DSH after rc.2 reads `ownsHost`, ignores `createApiClient`, and takes -/// `fetch` / `openStream` / `loadBundle` from the global only when present — -/// none are, so HTTP + WebSocket + HTTP bundles stay the defaults. -/// -/// An existing global (a shell that brought its own transport) is left alone -/// except for the missing flag. Consequence to know once the flag is read: -/// deliverables actions then act on the daemon host — the workspace machine. -/// For DSH ≤ 0.1.1-rc.2, whose client never reads the flag, the companion -/// port back-ports that read into the served client-connection bundle -/// (`backport_owns_host`), so the declaration is effective on every version. -const DSH_TRANSPORT_OWNS_HOST: &str = r#""#; +/// The authenticated companion page owns its per-identity Host. Use DSH's +/// public transport hook before browser modules initialize; vendor bundles +/// remain opaque and stream through unchanged across DSH releases. +const DSH_TRANSPORT_OWNS_HOST: &str = r#""#; /// Splice the companion head scripts in as the first thing inside `` so /// they run before the boot script and every client plugin module. Returns @@ -518,33 +479,6 @@ fn inject_polyfill(html: &str) -> Option { Some(out) } -/// The one line DSH ≤ 0.1.1-rc.2 decides `isLoopback` on: page hostname -/// alone. Upstream added the `__DSH_TRANSPORT__.ownsHost` read after rc.2 -/// shipped, and the client-connection bundle is served verbatim -/// (`/plugins//client.js`), so the companion port back-ports exactly that -/// read: the rc.2 line gains the same branch newer DSH evaluates natively. -/// Anything else — another version, a minified build — passes through -/// byte-for-byte, which is also how this retires: the first DSH release that -/// reads the flag itself no longer carries the line. -const RC2_IS_LOOPBACK_LINE: &str = - "isLoopback: pageLocation === void 0 || isLoopbackHostname(pageLocation.hostname),"; -const RC2_IS_LOOPBACK_OWNS_HOST: &str = "isLoopback: pageLocation === void 0 || \ - globalThis.__DSH_TRANSPORT__?.ownsHost === true || \ - isLoopbackHostname(pageLocation.hostname),"; - -fn backport_owns_host(bundle: &str) -> Option { - bundle - .contains(RC2_IS_LOOPBACK_LINE) - .then(|| bundle.replacen(RC2_IS_LOOPBACK_LINE, RC2_IS_LOOPBACK_OWNS_HOST, 1)) -} - -/// `/plugins//client.js` — DSH client-modules' bundle route (the rev rides -/// the query string, which is not part of the path). -fn is_plugin_bundle_path(path: &str) -> bool { - path.strip_prefix("/plugins/") - .is_some_and(|rest| !rest.starts_with('/') && rest.ends_with("/client.js")) -} - fn content_type_starts_with(headers: &HeaderMap, prefixes: &[&str]) -> bool { headers .get(header::CONTENT_TYPE) @@ -559,53 +493,20 @@ fn is_html_response(headers: &HeaderMap) -> bool { content_type_starts_with(headers, &["text/html"]) } -fn is_javascript_response(headers: &HeaderMap) -> bool { - content_type_starts_with(headers, &["text/javascript", "application/javascript"]) -} - -/// What the companion port splices into a buffered upstream body. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Splice { - /// The document: head scripts (secure-context polyfill + `ownsHost`). - Html, - /// A plugin client bundle: the rc.2 `ownsHost` back-port. - PluginBundle, -} - -fn splice_for(headers: &HeaderMap, plugin_bundle: bool) -> Option { - if is_html_response(headers) { - Some(Splice::Html) - } else if plugin_bundle && is_javascript_response(headers) { - Some(Splice::PluginBundle) - } else { - None - } -} - -async fn response_from_reqwest(resp: reqwest::Response, plugin_bundle: bool) -> Response { +async fn response_from_reqwest(resp: reqwest::Response) -> Response { let status = resp.status(); let headers = resp.headers().clone(); - // HTML (small) and plugin client bundles (a few hundred KB, browser-cached - // per rev) are buffered so their splice can be applied; everything else — - // assets, RPC payloads, downloads — keeps streaming untouched. - // `accept-encoding` is stripped on the way out (see - // `should_strip_request_header`), so the body here is always identity and - // splicing can never corrupt a compressed stream. - if let Some(splice) = splice_for(&headers, plugin_bundle) { + // Only HTML needs the public transport declaration. Scripts, downloads, + // and RPC streams remain opaque, including their original headers. + if is_html_response(&headers) { match resp.bytes().await { Ok(bytes) => { let body = match std::str::from_utf8(&bytes) { - Ok(text) => { - let patched = match splice { - Splice::Html => inject_polyfill(text), - Splice::PluginBundle => backport_owns_host(text), - }; - match patched { - Some(patched) => Body::from(patched), - None => Body::from(bytes), - } - } + Ok(text) => match inject_polyfill(text) { + Some(patched) => Body::from(patched), + None => Body::from(bytes), + }, // Not UTF-8 → not something to splice; pass it through. Err(_) => Body::from(bytes), }; @@ -940,10 +841,6 @@ mod tests { assert!(!patched.contains("fetch:")); assert!(!patched.contains("openStream")); assert!(patched.contains("t.ownsHost===undefined")); - // DSH ≤ 0.1.1-rc.2 calls `transport?.createApiClient()` unconditionally - // and `??`-falls through to its default carrier on `undefined`; a - // global without it aborts the boot. - assert!(patched.contains("createApiClient:function(){return undefined}")); } #[test] @@ -952,80 +849,66 @@ mod tests { assert!(inject_polyfill("plain text").is_none()); } - #[test] - fn rc2_connection_bundle_gains_the_owns_host_branch() { - let bundle = format!( - "\t\t\tconst handle = {{\n\t\t\t\tapi,\n\t\t\t\t{RC2_IS_LOOPBACK_LINE}\n\t\t\t\thostDescription: {{}}\n" + async fn proxy_fixture(content_type: &'static str, body: &'static [u8]) -> Response { + let app = axum::Router::new().route( + "/", + axum::routing::get( + move || async move { ([(header::CONTENT_TYPE, content_type)], body) }, + ), ); - let patched = backport_owns_host(&bundle).expect("the rc.2 line is back-ported"); + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let address = listener.local_addr().unwrap(); + let server = tokio::spawn(async move { axum::serve(listener, app).await.unwrap() }); + let response = reqwest::get(format!("http://{address}/")).await.unwrap(); + let output = response_from_reqwest(response).await; + // The proxied stream must be consumed before its source server stops. + let (parts, body) = output.into_parts(); + let bytes = axum::body::to_bytes(body, usize::MAX).await.unwrap(); + server.abort(); + Response::from_parts(parts, Body::from(bytes)) + } + + #[tokio::test] + async fn vendor_javascript_is_forwarded_byte_for_byte() { + let bundle = + b"isLoopback: pageLocation === void 0 || isLoopbackHostname(pageLocation.hostname),"; + let response = proxy_fixture("text/javascript", bundle).await; assert_eq!( - patched - .matches("__DSH_TRANSPORT__?.ownsHost === true") - .count(), - 1, - "exactly one ownsHost read is added" - ); - assert!( - !patched.contains(RC2_IS_LOOPBACK_LINE), - "the hostname-only line is replaced, not duplicated" + response.headers()[header::CONTENT_LENGTH], + bundle.len().to_string() ); - assert!( - patched.contains("isLoopbackHostname(pageLocation.hostname),"), - "the hostname read stays as the fallback" + assert_eq!( + axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap() + .as_ref(), + bundle ); - let (before, after) = bundle.split_once(RC2_IS_LOOPBACK_LINE).unwrap(); - assert!(patched.starts_with(before) && patched.ends_with(after)); } - #[test] - fn other_bundles_pass_through_untouched() { - for js in [ - "", - "export const x = 1;", - // The post-rc.2 shape already reads the flag: nothing to back-port. - "isLoopback: transport?.ownsHost === true || isLoopbackHostname(pageLocation.hostname),", - ] { - assert!(backport_owns_host(js).is_none(), "{js:?}"); - } + #[tokio::test] + async fn html_proxy_declares_native_transport_without_stale_length() { + let response = proxy_fixture("text/html", b"").await; + assert!(!response.headers().contains_key(header::CONTENT_LENGTH)); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap(); + assert!(std::str::from_utf8(&body) + .unwrap() + .contains("ownsHost:true")); } - #[test] - fn only_plugin_bundle_paths_are_back_port_candidates() { - assert!(is_plugin_bundle_path( - "/plugins/@deepseek-ai/dsh-client-connection/client.js" - )); - assert!(is_plugin_bundle_path("/plugins/x/client.js")); - for path in [ - "/", - "/plugins", - "/plugins/", - "/plugins/client.js", - "/plugins//client.js", - "/assets/index-abc.js", - "/ccteam/api/tree", - ] { - assert!(!is_plugin_bundle_path(path), "{path}"); - } - let js = |ct: &str| { - let mut h = HeaderMap::new(); - h.insert(header::CONTENT_TYPE, ct.parse().unwrap()); - h - }; - assert_eq!( - splice_for(&js("text/javascript; charset=utf-8"), true), - Some(Splice::PluginBundle) - ); - assert_eq!( - splice_for(&js("application/javascript"), true), - Some(Splice::PluginBundle) - ); + #[tokio::test] + async fn non_utf8_assets_are_forwarded_without_text_decoding() { + let bytes = b"\x00\xff\xfe\x80"; + let response = proxy_fixture("application/octet-stream", bytes).await; assert_eq!( - splice_for(&js("text/javascript"), false), - None, - "a JS asset off the bundle route streams through" + axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap() + .as_ref(), + bytes ); - assert_eq!(splice_for(&js("text/html"), true), Some(Splice::Html)); - assert_eq!(splice_for(&js("application/json"), true), None); } #[test] diff --git a/crates/ccteam-web/src/lib.rs b/crates/ccteam-web/src/lib.rs index 6a7de6488..7fdf4bc71 100644 --- a/crates/ccteam-web/src/lib.rs +++ b/crates/ccteam-web/src/lib.rs @@ -174,9 +174,14 @@ pub fn router_with_state(state: AppState) -> Router { // spawn came out rootless (the v0.9.2 "delegation parent lost over HTTP" // fix). The web token is not a credential HERE at all: it names a browser // session and authenticates `/api/v1/**`, never the MCP data plane. + // Use the same authenticated proxy as the dedicated companion listener. + // A relative iframe URL survives TLS termination and external port mapping; + // the vendor's assets, RPC and WebSockets all stay under this mount. + let dsh = dsh_web::companion_router().with_state(state.clone()); let mcp = routes::mcp::router().with_state(state); Router::new() .merge(routes::stateless_router(health)) + .nest("/dsh/", dsh) .merge(mcp) .merge(stateful) .layer(CompressionLayer::new().gzip(true).br(true)) diff --git a/crates/ccteam-web/src/routes/files.rs b/crates/ccteam-web/src/routes/files.rs new file mode 100644 index 000000000..d77f5a10e --- /dev/null +++ b/crates/ccteam-web/src/routes/files.rs @@ -0,0 +1,787 @@ +//! #219: project-contained, read-only files and sandboxed HTML previews. +//! Preview capabilities carry the minting identity, never a web bearer. Even +//! cookieless requests from an opaque sandbox pass the ordinary project ACL. + +use std::{ + collections::{HashMap, HashSet}, + io::Read, + path::{Component, Path as FsPath, PathBuf}, + sync::Mutex, + time::{Duration, Instant}, +}; + +use axum::{ + extract::{Path, Query, State}, + http::{header, HeaderValue, Method, StatusCode}, + response::{IntoResponse, Response}, + Extension, Json, +}; +use rand::RngCore; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use crate::{auth::Identity, state::AppState}; + +const MAX_PATHS: usize = 20_000; +const MAX_TEXT: u64 = 2 * 1024 * 1024; +const MAX_ASSET: u64 = 16 * 1024 * 1024; +const PREVIEW_TTL: Duration = Duration::from_secs(3600); +const PREVIEW_CSP: &str = "sandbox allow-scripts; default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' http: https: data: blob:; style-src 'unsafe-inline' http: https: data:; img-src http: https: data: blob:; font-src http: https: data:; media-src http: https: data: blob:; connect-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'"; + +const MARKDOWN_CSP: &str = "sandbox; default-src 'none'; script-src 'none'; style-src 'unsafe-inline'; img-src http: https:; connect-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'"; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum PreviewKind { + Html, + Markdown, +} + +fn preview_kind(path: &FsPath) -> Option { + match path.extension()?.to_str()?.to_ascii_lowercase().as_str() { + "html" | "htm" => Some(PreviewKind::Html), + "md" | "markdown" => Some(PreviewKind::Markdown), + _ => None, + } +} + +struct PreviewGrant { + slug: String, + directory: PathBuf, + kind: PreviewKind, + identity: Identity, + expires: Instant, +} + +#[derive(Default)] +pub(crate) struct PreviewRegistry(Mutex>); + +impl PreviewRegistry { + fn mint( + &self, + slug: String, + identity: Identity, + directory: PathBuf, + kind: PreviewKind, + ) -> Result { + let mut grants = self.0.lock().unwrap_or_else(|e| e.into_inner()); + grants.retain(|_, grant| grant.expires > Instant::now()); + if grants.len() >= 1024 { + return Err(StatusCode::TOO_MANY_REQUESTS); + } + let mut random = [0u8; 24]; + rand::thread_rng().fill_bytes(&mut random); + let token: String = random.iter().map(|b| format!("{b:02x}")).collect(); + grants.insert( + token.clone(), + PreviewGrant { + slug, + directory, + kind, + identity, + expires: Instant::now() + PREVIEW_TTL, + }, + ); + Ok(token) + } + + fn principal(&self, slug: &str, token: &str) -> Option { + let grants = self.0.lock().unwrap_or_else(|e| e.into_inner()); + let grant = grants.get(token)?; + (grant.slug == slug && grant.expires > Instant::now()).then(|| grant.identity.clone()) + } + + fn scope(&self, slug: &str, token: &str) -> Option<(PathBuf, PreviewKind)> { + let grants = self.0.lock().unwrap_or_else(|e| e.into_inner()); + let grant = grants.get(token)?; + (grant.slug == slug && grant.expires > Instant::now()) + .then(|| (grant.directory.clone(), grant.kind)) + } +} + +/// Only preview-resource GET/HEADs can authenticate with this capability. +/// Normal cookies must not override it (including after a login switch). +pub(crate) fn preview_identity( + app: &AppState, + method: &Method, + path: &str, +) -> Option> { + let rest = path.strip_prefix("/api/v1/projects/")?; + let (slug, rest) = rest.split_once('/')?; + let rest = rest.strip_prefix("files/preview/")?; + let (ticket, _) = rest.split_once('/')?; + Some(if method == Method::GET || method == Method::HEAD { + app.file_previews.principal(slug, ticket) + } else { + None + }) +} + +#[derive(Debug, Serialize, ToSchema)] +pub struct FileTree { + pub root: String, + pub paths: Vec, + pub truncated: bool, + pub git_status: HashMap, +} + +#[derive(Debug, Deserialize)] +pub(crate) struct FileQuery { + path: String, +} + +#[derive(Debug, Serialize, ToSchema)] +pub struct FileContent { + pub path: String, + pub size: u64, + pub content: Option, + pub binary: bool, +} + +#[derive(Debug, Deserialize, ToSchema)] +pub struct PreviewRequest { + pub path: String, +} + +#[derive(Debug, Serialize, ToSchema)] +pub struct PreviewLink { + pub url: String, + pub expires_in: u64, +} + +fn error(status: StatusCode, message: impl Into) -> Response { + (status, Json(serde_json::json!({"error": message.into()}))).into_response() +} + +fn private_component(name: &str) -> bool { + let name = name.to_ascii_lowercase(); + matches!( + name.as_str(), + ".git" + | ".ccteam" + | ".ssh" + | ".aws" + | ".gnupg" + | "node_modules" + | "target" + | ".next" + | ".venv" + | "__pycache__" + ) || name == ".env" + || (name.starts_with(".env.") + && !name.ends_with(".example") + && !name.ends_with(".sample") + && !name.ends_with(".template")) + || name.ends_with(".pem") + || name.ends_with(".key") + || matches!(name.as_str(), "id_rsa" | "id_ed25519" | "credentials.json") +} + +fn safe_relative(path: &FsPath) -> bool { + !path.as_os_str().is_empty() + && path.components().all(|c| match c { + Component::Normal(name) => name + .to_str() + .is_some_and(|s| !private_component(s) && !s.contains('\\')), + _ => false, + }) +} + +fn project_root(app: &AppState, slug: &str) -> Result { + if !app.paths.project_state(slug).exists() { + return Err((StatusCode::NOT_FOUND, "Project not found")); + } + let config = ccteam_core::config::load(&app.paths.root).map_err(|_| { + ( + StatusCode::SERVICE_UNAVAILABLE, + "Project catalog is unreadable", + ) + })?; + if config + .projects + .iter() + .any(|p| p.slug == slug && p.host != "local") + { + return Err(( + StatusCode::NOT_IMPLEMENTED, + "File browsing is not yet supported for remote projects", + )); + } + app.paths + .project_dir(slug) + .canonicalize() + .map_err(|_| (StatusCode::NOT_FOUND, "Project directory not found")) +} + +fn resolve(root: &FsPath, relative: &str) -> Result { + if !safe_relative(FsPath::new(relative)) { + return Err(StatusCode::FORBIDDEN); + } + let path = root + .join(relative) + .canonicalize() + .map_err(|_| StatusCode::NOT_FOUND)?; + let inside = path.strip_prefix(root).map_err(|_| StatusCode::FORBIDDEN)?; + if !safe_relative(inside) { + return Err(StatusCode::FORBIDDEN); + } + Ok(path) +} + +fn read_bounded(path: &FsPath, max: u64) -> Result, StatusCode> { + // Check before opening: opening a FIFO can block before File::metadata. + if !std::fs::metadata(path).is_ok_and(|meta| meta.is_file()) { + return Err(StatusCode::NOT_FOUND); + } + let mut options = std::fs::OpenOptions::new(); + options.read(true); + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + options.custom_flags(libc::O_NONBLOCK); + } + let file = options.open(path).map_err(|_| StatusCode::NOT_FOUND)?; + let meta = file.metadata().map_err(|_| StatusCode::NOT_FOUND)?; + if !meta.is_file() { + return Err(StatusCode::NOT_FOUND); + } + if meta.len() > max { + return Err(StatusCode::PAYLOAD_TOO_LARGE); + } + let mut bytes = Vec::new(); + file.take(max + 1) + .read_to_end(&mut bytes) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + if bytes.len() as u64 > max { + return Err(StatusCode::PAYLOAD_TOO_LARGE); + } + Ok(bytes) +} + +fn walk(root: &FsPath) -> std::io::Result<(Vec, bool)> { + let mut paths = Vec::new(); + let mut pending = vec![root.to_path_buf()]; + let mut visited = 0; + while let Some(dir) = pending.pop() { + let entries = match std::fs::read_dir(dir) { + Ok(e) => e, + Err(_) => continue, + }; + for entry in entries { + visited += 1; + if visited > MAX_PATHS { + paths.sort(); + return Ok((paths, true)); + } + let entry = entry?; + let kind = entry.file_type()?; + // Never traverse symlinks: cycles and out-of-project targets must + // not make listing unbounded or reveal private path names. + if kind.is_symlink() || (!kind.is_dir() && !kind.is_file()) { + continue; + } + let path = entry.path(); + let Ok(relative) = path.strip_prefix(root) else { + continue; + }; + if !safe_relative(relative) { + continue; + } + let Some(relative) = relative.to_str() else { + continue; + }; + let mut name = relative.replace('\\', "/"); + if kind.is_dir() { + name.push('/'); + pending.push(path); + } + paths.push(name); + } + } + paths.sort(); + Ok((paths, false)) +} + +async fn git_status(root: &FsPath) -> HashMap { + use tokio::io::AsyncReadExt; + let mut command = tokio::process::Command::new("git"); + command + .args(["--no-optional-locks", "-c", "core.fsmonitor=false", "-C"]) + .arg(root) + .args(["status", "--porcelain=v1", "-z", "--untracked-files=normal"]) + .kill_on_drop(true) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::null()); + let read_status = async move { + let mut child = command.spawn().ok()?; + let stdout = child.stdout.take()?; + let mut output = Vec::new(); + stdout + .take(2 * 1024 * 1024 + 1) + .read_to_end(&mut output) + .await + .ok()?; + if output.len() > 2 * 1024 * 1024 { + return None; + } + child.wait().await.ok()?.success().then_some(output) + }; + let Ok(Some(output)) = tokio::time::timeout(Duration::from_secs(3), read_status).await else { + return HashMap::new(); + }; + let mut result = HashMap::new(); + let mut entries = output.split(|b| *b == 0); + while let Some(entry) = entries.next() { + let Ok(entry) = std::str::from_utf8(entry) else { + continue; + }; + if entry.len() < 4 { + continue; + } + let code = &entry[..2]; + let path = &entry[3..]; + if code.contains('R') || code.contains('C') { + entries.next(); + } + if !safe_relative(FsPath::new(path)) { + continue; + } + let status = if code == "??" { + "untracked" + } else if code.contains('U') { + "modified" + } else if code.contains('R') { + "renamed" + } else if code.contains('D') { + "deleted" + } else if code.contains('A') { + "added" + } else { + "modified" + }; + result.insert(path.to_string(), status.to_string()); + } + result +} + +#[utoipa::path(get, path = "/api/v1/projects/{slug}/files", tag = "projects", + params(("slug" = String, Path)), responses((status = 200, body = FileTree, description = "Bounded project file tree"), (status = 501, description = "Remote project")))] +pub(crate) async fn handle_files( + State(app): State, + Path(slug): Path, +) -> Response { + let root = match project_root(&app, &slug) { + Ok(p) => p, + Err((s, message)) => return error(s, message), + }; + let walk_root = root.clone(); + let paths = tokio::task::spawn_blocking(move || walk(&walk_root)); + let status = git_status(&root).await; + match paths.await { + Ok(Ok((paths, truncated))) => Json(FileTree { + root: root.to_string_lossy().into_owned(), + paths, + truncated, + git_status: status, + }) + .into_response(), + _ => error( + StatusCode::INTERNAL_SERVER_ERROR, + "Could not list project files", + ), + } +} + +#[utoipa::path(get, path = "/api/v1/projects/{slug}/file", tag = "projects", + params(("slug" = String, Path), ("path" = String, Query)), responses((status = 200, body = FileContent, description = "UTF-8 source or binary metadata"), (status = 413, description = "File exceeds 2 MiB")))] +pub(crate) async fn handle_file( + State(app): State, + Path(slug): Path, + Query(query): Query, +) -> Response { + let root = match project_root(&app, &slug) { + Ok(p) => p, + Err((s, message)) => return error(s, message), + }; + let path = match resolve(&root, &query.path) { + Ok(p) => p, + Err(s) => return error(s, "File is unavailable or outside the browsable project"), + }; + let result = tokio::task::spawn_blocking(move || read_bounded(&path, MAX_TEXT)).await; + match result { + Ok(Ok(bytes)) => { + let size = bytes.len() as u64; + let content = if bytes.contains(&0) { + None + } else { + String::from_utf8(bytes).ok() + }; + Json(FileContent { + path: query.path, + size, + binary: content.is_none(), + content, + }) + .into_response() + } + Ok(Err(s)) => error(s, "File is unavailable or exceeds the 2 MiB source limit"), + Err(_) => error(StatusCode::INTERNAL_SERVER_ERROR, "Could not read file"), + } +} + +#[utoipa::path(post, path = "/api/v1/projects/{slug}/files/preview", tag = "projects", + params(("slug" = String, Path)), request_body = PreviewRequest, + responses((status = 200, body = PreviewLink, description = "One-hour read-only preview capability")))] +pub(crate) async fn handle_mint_preview( + State(app): State, + Path(slug): Path, + Extension(identity): Extension, + Json(body): Json, +) -> Response { + let root = match project_root(&app, &slug) { + Ok(p) => p, + Err((s, message)) => return error(s, message), + }; + let path = match resolve(&root, &body.path) { + Ok(path) if path.is_file() && preview_kind(&path).is_some() => path, + _ => return error(StatusCode::NOT_FOUND, "Preview document not found"), + }; + let kind = preview_kind(&path).expect("preview kind checked above"); + let directory = path.parent().unwrap_or(&root).to_path_buf(); + let ticket = match app + .file_previews + .mint(slug.clone(), identity, directory, kind) + { + Ok(t) => t, + Err(s) => return error(s, "Too many open previews"), + }; + // URL path encoding is required for spaces, #, ?, Unicode, and filenames + // that might otherwise escape the capability's project resource route. + let encoded = body + .path + .split('/') + .map(|part| { + part.bytes() + .map(|b| { + if b.is_ascii_alphanumeric() || b"-._~".contains(&b) { + (b as char).to_string() + } else { + format!("%{b:02X}") + } + }) + .collect::() + }) + .collect::>() + .join("/"); + Json(PreviewLink { + url: format!("/api/v1/projects/{slug}/files/preview/{ticket}/{encoded}"), + expires_in: PREVIEW_TTL.as_secs(), + }) + .into_response() +} + +#[utoipa::path(get, path = "/api/v1/projects/{slug}/files/preview/{ticket}/{*path}", tag = "projects", + params(("slug" = String, Path), ("ticket" = String, Path), ("path" = String, Path)), + responses((status = 200, description = "Sandboxed preview or relative asset")))] +pub(crate) async fn handle_preview( + State(app): State, + Path((slug, ticket, relative)): Path<(String, String, String)>, +) -> Response { + let root = match project_root(&app, &slug) { + Ok(p) => p, + Err((s, message)) => return error(s, message), + }; + let path = match resolve(&root, &relative) { + Ok(p) => p, + Err(s) => return error(s, "Preview resource unavailable"), + }; + let Some((directory, grant_kind)) = app.file_previews.scope(&slug, &ticket) else { + return error(StatusCode::UNAUTHORIZED, "Preview expired"); + }; + // A page (and any third-party script it includes) can read its ticket. + // It grants only web assets below that page's directory, never arbitrary + // Rust, configuration or other project source. Markdown grants additionally + // allow rendered Markdown links; HTML grants retain their web-assets scope. + let extension = path + .extension() + .and_then(|e| e.to_str()) + .unwrap_or("") + .to_ascii_lowercase(); + let markdown = preview_kind(&path) == Some(PreviewKind::Markdown); + if !path.starts_with(directory) + || (markdown && grant_kind != PreviewKind::Markdown) + || !(markdown + || matches!( + extension.as_str(), + "html" + | "htm" + | "css" + | "js" + | "mjs" + | "json" + | "png" + | "jpg" + | "jpeg" + | "gif" + | "webp" + | "svg" + | "ico" + | "woff" + | "woff2" + | "ttf" + | "otf" + | "wasm" + | "mp4" + | "webm" + | "mp3" + | "ogg" + )) + { + return error( + StatusCode::FORBIDDEN, + "Resource is outside this document preview's asset scope", + ); + } + let mime = if markdown { + "text/html".to_string() + } else { + mime_guess::from_path(&path) + .first_or_octet_stream() + .to_string() + }; + let result = tokio::task::spawn_blocking(move || { + let bytes = read_bounded(&path, if markdown { MAX_TEXT } else { MAX_ASSET })?; + if !markdown { + return Ok(bytes); + } + let source = std::str::from_utf8(&bytes).map_err(|_| StatusCode::UNPROCESSABLE_ENTITY)?; + Ok(render_markdown_document( + source, + &path.file_name().unwrap_or_default().to_string_lossy(), + ) + .into_bytes()) + }) + .await; + let mut response = match result { + Ok(Ok(bytes)) => ( + [(header::CONTENT_TYPE, format!("{mime}; charset=utf-8"))], + bytes, + ) + .into_response(), + Ok(Err(s)) => error( + s, + if markdown { + "Markdown preview unavailable: expected UTF-8 text up to 2 MiB" + } else { + "Preview resource unavailable or exceeds 16 MiB" + }, + ), + Err(_) => error(StatusCode::INTERNAL_SERVER_ERROR, "Could not read preview"), + }; + let headers = response.headers_mut(); + headers.insert( + header::CONTENT_SECURITY_POLICY, + HeaderValue::from_static(if markdown { MARKDOWN_CSP } else { PREVIEW_CSP }), + ); + headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store")); + headers.insert( + header::REFERRER_POLICY, + HeaderValue::from_static("no-referrer"), + ); + headers.insert( + header::X_CONTENT_TYPE_OPTIONS, + HeaderValue::from_static("nosniff"), + ); + // Opaque sandbox module scripts use CORS without cookies. The random + // resource path is their ONLY credential; no global API CORS is opened. + headers.insert( + header::ACCESS_CONTROL_ALLOW_ORIGIN, + HeaderValue::from_static("*"), + ); + response +} + +fn markdown_url_is_safe(url: &str) -> bool { + let normalized: String = url + .chars() + .filter(|c| !c.is_whitespace() && !c.is_control()) + .collect(); + if normalized.contains('\\') { + return false; + } + let first_segment = normalized.split(['/', '#', '?']).next().unwrap_or_default(); + match first_segment.split_once(':') { + Some((scheme, _)) => matches!( + scheme.to_ascii_lowercase().as_str(), + "http" | "https" | "mailto" | "tel" + ), + None => true, + } +} + +fn escape_html(text: &str) -> String { + text.replace('&', "&") + .replace('<', "<") + .replace('>', ">") + .replace('"', """) + .replace('\'', "'") +} + +/// Filter Markdown parser events before rendering: raw HTML stays visible as +/// text, links retain a small protocol set, and no scripts are enabled. The +/// response-level sandbox also holds when the document is opened separately. +fn render_markdown_document(source: &str, title: &str) -> String { + use pulldown_cmark::{html, Event, Options, Parser, Tag, TagEnd}; + let options = Options::ENABLE_TABLES + | Options::ENABLE_TASKLISTS + | Options::ENABLE_STRIKETHROUGH + | Options::ENABLE_FOOTNOTES + | Options::ENABLE_GFM; + let mut events: Vec<_> = Parser::new_ext(source, options) + .map(|event| match event { + Event::Html(html) | Event::InlineHtml(html) => Event::Text(html), + Event::Start(mut tag) => { + if let Tag::Link { dest_url, .. } | Tag::Image { dest_url, .. } = &mut tag { + if !markdown_url_is_safe(dest_url) { + *dest_url = "#".into(); + } + } + Event::Start(tag) + } + other => other, + }) + .collect(); + let mut heading_counts = HashMap::::new(); + let mut used_heading_ids = HashSet::new(); + for index in 0..events.len() { + if !matches!(events[index], Event::Start(Tag::Heading { .. })) { + continue; + } + let text: String = events[index + 1..] + .iter() + .take_while(|e| !matches!(e, Event::End(TagEnd::Heading(_)))) + .filter_map(|e| match e { + Event::Text(s) | Event::Code(s) => Some(s.as_ref()), + _ => None, + }) + .collect(); + let slug: String = text + .chars() + .flat_map(char::to_lowercase) + .filter_map(|c| { + if c.is_alphanumeric() || matches!(c, '-' | '_') { + Some(c) + } else if c.is_whitespace() { + Some('-') + } else { + None + } + }) + .collect(); + let base = if slug.trim_matches('-').is_empty() { + "section" + } else { + slug.trim_matches('-') + }; + let count = heading_counts.entry(base.to_string()).or_default(); + let mut id = if *count == 0 { + base.to_string() + } else { + format!("{base}-{count}") + }; + while !used_heading_ids.insert(id.clone()) { + *count += 1; + id = format!("{base}-{count}"); + } + *count += 1; + if let Event::Start(Tag::Heading { id: heading_id, .. }) = &mut events[index] { + *heading_id = Some(id.into()); + } + } + let mut body = String::new(); + html::push_html(&mut body, events.into_iter()); + format!("{}
{body}
", escape_html(title)) +} + +const MARKDOWN_STYLE: &str = r#" +:root{color-scheme:light dark;--bg:#fff;--fg:#24292f;--muted:#57606a;--border:#d0d7de;--soft:#f6f8fa;--link:#0969da} +@media(prefers-color-scheme:dark){:root{--bg:#111318;--fg:#e6edf3;--muted:#9da7b3;--border:#30363d;--soft:#1c2129;--link:#79b8ff}} +*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--bg);color:var(--fg);font:15px/1.75 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;overflow-wrap:anywhere} +main{max-width:960px;margin:auto;padding:clamp(20px,4vw,48px)}h1,h2,h3,h4,h5,h6{line-height:1.3;margin:1.5em 0 .65em;scroll-margin-top:20px}h1{font-size:2em}h2{font-size:1.5em;border-bottom:1px solid var(--border);padding-bottom:.3em}main>:first-child{margin-top:0} +p,ul,ol,pre,blockquote,table{margin:0 0 1em}li>p{margin:.3em 0}a{color:var(--link);text-underline-offset:3px}a:hover{text-decoration-thickness:2px}img{max-width:100%;height:auto}blockquote{margin-left:0;border-left:3px solid var(--border);padding:0 1em;color:var(--muted)} +code{font: .88em/1.65 ui-monospace,SFMono-Regular,Consolas,monospace;background:var(--soft);padding:.15em .35em;border-radius:4px}pre{overflow:auto;padding:16px;border:1px solid var(--border);border-radius:8px;background:var(--soft)}pre code{padding:0;background:none;white-space:pre} +table{display:block;max-width:100%;overflow:auto;border-collapse:collapse}th,td{padding:7px 12px;border:1px solid var(--border)}th{background:var(--soft);text-align:left}input[type=checkbox]{margin-right:.5em;accent-color:var(--link)}hr{border:0;border-top:1px solid var(--border);margin:1.5em 0} +"#; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn markdown_escapes_html_titles_and_unsafe_urls_and_assigns_unique_anchors() { + let html = render_markdown_document("# Same\n\n# Same\n\n# Same-1\n\n[x](javascript:alert%281%29)\n\n\n", "<&>.md"); + assert!(html.contains("<&>.md")); + for id in ["same", "same-1", "same-1-1"] { + assert_eq!(html.matches(&format!("id=\"{id}\"")).count(), 1); + } + assert!(!html.contains(" OpenApiRouter { .routes(routes!(super::sessions_api::handle_session_status)) .routes(routes!(super::sessions_api::handle_session_context)) .routes(routes!(super::sessions_api::handle_session_turn)) + .routes(routes!(super::files::handle_files)) + .routes(routes!(super::files::handle_file)) + .routes(routes!(super::files::handle_mint_preview)) + .routes(routes!(super::files::handle_preview)) .routes(routes!(super::sessions_api::handle_session_step)) .routes(routes!( super::sessions_api::handle_list_scheduled, diff --git a/crates/ccteam-web/src/routes/projects.rs b/crates/ccteam-web/src/routes/projects.rs index 04dc53fd7..332e8bb9f 100644 --- a/crates/ccteam-web/src/routes/projects.rs +++ b/crates/ccteam-web/src/routes/projects.rs @@ -145,43 +145,27 @@ pub(crate) async fn handle_create_project( let paths = app.paths.clone(); let slug_for_blocking = slug.clone(); let abs_for_blocking = abs.clone(); - let team_for_blocking = team.clone(); + let team_for_blocking = form + .team + .as_deref() + .map(str::trim) + .filter(|team| !team.is_empty()) + .map(str::to_owned); // v0.8.18 档1 — bind the new project to its creating web user // (`user:`; admin → the shared `web-api` pool). Project is the unit of // ownership; its sessions inherit it. let owner_for_blocking = identity.owner_tag(); let scaffold = tokio::task::spawn_blocking(move || -> anyhow::Result<()> { - ccteam_core::bootstrap_project_at_dir( + // Scaffold + owner stamp (binds the project to its creator so the + // tenant can see its own project) + config.yaml registration, as one + // shared creation path (issue #222). + ccteam_core::create_project( &paths, - &abs_for_blocking, - &slug_for_blocking, - "(created from web resource API)", - &team_for_blocking, - )?; - // Owner stamp — bind the project to its creator so the tenant can see - // its own project. Use the KNOWN project path (`abs`), NOT - // `paths.project_state(slug)`: that resolves the dir through the config - // registry, which does not yet contain this project (the upsert is - // below) → it would fall back to the wrong path, the load would miss, - // and the owner would never persist → the creating tenant could not see - // its own project (404). `abs` IS the project working tree. - let state_path = ccteam_core::CcteamPaths::project_state_in(&abs_for_blocking); - if let Ok(mut state) = ccteam_core::ProjectState::load(&state_path) { - state.owner = Some(owner_for_blocking.clone()); - if let Err(err) = state.save(&state_path) { - tracing::warn!(slug = %slug_for_blocking, error = %err, "set project owner failed"); - } - } - ccteam_core::upsert_project_in_config( - &paths.root, - ProjectEntry { - slug: slug_for_blocking.clone(), - path: abs_for_blocking.clone(), - host: ccteam_core::LOCAL_HOST.to_string(), - remote_slug: None, - remote_path: None, - team: team_for_blocking.clone(), - installed_at: chrono::Utc::now(), + &ccteam_core::NewProject { + slug: &slug_for_blocking, + dir: &abs_for_blocking, + team: team_for_blocking.as_deref(), + owner: Some(&owner_for_blocking), }, )?; Ok(()) diff --git a/crates/ccteam-web/src/routes/sessions_api.rs b/crates/ccteam-web/src/routes/sessions_api.rs index 87396869a..a3656213d 100644 --- a/crates/ccteam-web/src/routes/sessions_api.rs +++ b/crates/ccteam-web/src/routes/sessions_api.rs @@ -357,10 +357,9 @@ pub struct CreateSessionForm { #[serde(default)] pub effort: Option, /// Explicit vendor session-mode token, forwarded verbatim and validated - /// by the vendor adapter. DSH only today: its agent preset — `standard` | - /// `ptc` (alias `code`) | `minimal` | `creator` (alias `cordis`); - /// omitted → DSH hires default to `standard`. Other vendors refuse a non-empty - /// mode. + /// by the vendor adapter. DSH accepts native agent-preset ids and resolves + /// its own default when omitted; `creator` selects `cordis`. Other vendors + /// refuse a non-empty mode. #[serde(default)] pub mode: Option, } @@ -842,7 +841,7 @@ pub(crate) async fn handle_session_status( tag = "sessions", params(("sid" = String, Path, description = "Gateway session id (`s{n}`)")), responses( - (status = 200, description = "`{sid, live, breakdown}`; `breakdown` = `{vendor, model, captured_at, used_tokens, window_tokens, auto_compact_threshold, categories:[{id, name, tokens, kind: used|deferred|buffer|free, items:[{name, source, detail, tokens, loaded}]}], messages:{tool_call_tokens, tool_result_tokens, attachment_tokens, assistant_tokens, user_tokens, other_tokens, tools:[{name, call_tokens, result_tokens}], attachments:[{name, tokens}]}, last_request:{input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens}}` or null", body = serde_json::Value), + (status = 200, description = "`{sid, live, session, breakdown}`; session contains current per-sid activity, residency, harness/model/host, parent/depth, cumulative cost/tokens and last activity, including released/stopped sessions. Breakdown is vendor-reported `{vendor, model, captured_at, used_tokens, window_tokens, auto_compact_threshold, categories, messages, last_request}` or null. Claude supplies detailed categories/messages; Codex supplies used/free totals and last-request input/cache/output/reasoning accounting (reasoning is a subset of output), without estimated tool/memory categories or compaction threshold.", body = serde_json::Value), (status = 404, description = "Unknown session"), (status = 503, description = "No live gateway (standalone web)"), ), @@ -868,24 +867,78 @@ pub(crate) async fn handle_session_context( let Some(resolved) = resolved else { return unknown_session(&sid); }; - let fresh = match resident { - Some((adapter, thread)) => adapter.context_breakdown(&thread).await, + let fresh = match resident.as_ref() { + Some((adapter, thread)) => adapter.context_breakdown(thread).await, + None => None, + }; + let reported_model = match resident.as_ref() { + Some((adapter, thread)) => adapter + .thread_status(thread) + .await + .ok() + .and_then(|s| s.model), None => None, }; let live = fresh.is_some(); - let breakdown = match fresh { - Some(b) => Some(b), + let (breakdown, persisted_model) = match fresh { + Some(b) => (Some(b), None), None => { let (dir, snapshot_sid) = (resolved.project_dir, sid.clone()); tokio::task::spawn_blocking(move || { - ccteam_harness::execution::session_status::read_context_file(&dir, &snapshot_sid) + ( + ccteam_harness::execution::session_status::read_context_file( + &dir, + &snapshot_sid, + ), + ccteam_harness::execution::session_status::read_status_file( + &dir, + &snapshot_sid, + ) + .and_then(|s| s.model), + ) }) .await - .ok() - .flatten() + .unwrap_or_default() } }; - Json(json!({ "sid": sid, "live": live, "breakdown": breakdown })).into_response() + // Read one sid after the usage query: ordinary turns update these durable + // counters even when the shell's rail has not reconciled yet. Reuse the + // shared activity/residency resolvers; never synthesize a historical + // session as local/root, and never read the whole team graph. + let (meta, residency, live_turn, pending) = { + let guard = gw.lock().await; + ( + guard.session_meta_snapshot(&sid), + guard.session_residency(&sid), + guard.live_turn_for(&sid), + guard.pending_handle(), + ) + }; + let waiting_approval = pending.lock().await.pending_for_sid(&sid).is_some(); + let session = meta.map(|m| { + let status = if residency == Some("detached") { + "detached".to_string() + } else { + ProjectActivity::new(&app.progress_projection, &m.slug) + .resolve(&sid, live_turn) + .status + .activity + .to_string() + }; + json!({ + "sid":m.sid,"project":m.slug,"role":m.role,"vendor":m.vendor, + "permission_mode":m.permission_mode,"protocol":m.protocol, + "host":if m.host.is_empty() {"local"} else {&m.host}, + "current":false,"status":status,"residency":residency, + "model":reported_model.or(persisted_model).or(m.observed_model).or(m.model),"parent_sid":m.parent_sid, + "delegation_depth":m.delegation_depth,"cost_usd":m.cost_usd, + "tokens_total":m.tokens_total,"last_active":m.last_active, + "created_at":m.created_at,"title":m.title,"turn_count":m.turn_count, + "waiting_approval":waiting_approval, + }) + }); + Json(json!({ "sid": sid, "live": live, "breakdown": breakdown, "session":session })) + .into_response() } /// POST body for a turn submission — `text` (required unless `attachments` diff --git a/crates/ccteam-web/src/satellite.rs b/crates/ccteam-web/src/satellite.rs index 72c8bf47d..09063f0df 100644 --- a/crates/ccteam-web/src/satellite.rs +++ b/crates/ccteam-web/src/satellite.rs @@ -371,23 +371,13 @@ fn initialize_satellite_project( } let requested_slug = ccteam_core::validate_slug_format(requested_slug)?; let slug = ccteam_core::pick_unused_project_slug(&paths.root, &requested_slug)?; - ccteam_core::bootstrap_project_at_dir( + ccteam_core::create_project( paths, - &path, - &slug, - "(created by daemon project binding)", - "dev", - )?; - ccteam_core::upsert_project_in_config( - &paths.root, - ccteam_core::ProjectEntry { - slug: slug.clone(), - path: path.clone(), - host: ccteam_core::LOCAL_HOST.to_string(), - remote_slug: None, - remote_path: None, - team: "dev".to_string(), - installed_at: chrono::Utc::now(), + &ccteam_core::NewProject { + slug: &slug, + dir: &path, + team: None, + owner: None, }, )?; Ok((slug, path)) @@ -487,4 +477,31 @@ mod tests { Some(HOST_CHANNEL_SUBPROTOCOL) ); } + + #[test] + fn project_binding_uses_the_shared_creation_footprint() { + ccteam_core::disable_tool_surface_bootstrap_for_tests(); + let tmp = tempfile::TempDir::new().unwrap(); + let paths = CcteamPaths { + root: tmp.path().join("home"), + projects_root: tmp.path().join("projects"), + }; + let target = tmp.path().join("workspace"); + ccteam_core::ProjectState::initial_for_team("old".into(), "ops".into()) + .save(&CcteamPaths::project_state_in(&target)) + .unwrap(); + let (slug, dir) = + initialize_satellite_project(&paths, target.to_str().unwrap(), "demo").unwrap(); + assert_eq!(dir, target); + assert!(target.join(".ccteam/state.json").is_file()); + assert!(target.join("AGENTS.md").is_file()); + assert!(target.join(".gitignore").is_file()); + assert!(!target.join("CLAUDE.md").exists()); + assert!(!target.join(".ccteam/workflow.yaml").exists()); + let registered = ccteam_core::lookup_project_in_config(&paths.root, &slug) + .unwrap() + .unwrap(); + assert_eq!(registered.path, target); + assert_eq!(registered.team, "ops"); + } } diff --git a/crates/ccteam-web/src/state.rs b/crates/ccteam-web/src/state.rs index a5f0fbb55..f2c3a608b 100644 --- a/crates/ccteam-web/src/state.rs +++ b/crates/ccteam-web/src/state.rs @@ -132,6 +132,7 @@ pub struct AppState { /// Coalesces concurrent daemon-wide status aggregations. The completed /// result is never retained after its in-flight computation finishes. pub(crate) status_singleflight: crate::routes::status::StatusSingleflight, + pub(crate) file_previews: Arc, } /// v0.8.8 F4 — state of an in-flight Telegram `chat_id` long-poll capture @@ -208,6 +209,7 @@ impl AppState { ), vendor_quotas: Arc::new(crate::routes::vendor_quota::VendorQuotaService::default()), status_singleflight: crate::routes::status::StatusSingleflight::default(), + file_previews: Arc::new(crate::routes::files::PreviewRegistry::default()), } } diff --git a/crates/ccteam-web/tests/dsh_web_test.rs b/crates/ccteam-web/tests/dsh_web_test.rs index 7f6e82390..ff50e1f34 100644 --- a/crates/ccteam-web/tests/dsh_web_test.rs +++ b/crates/ccteam-web/tests/dsh_web_test.rs @@ -145,7 +145,10 @@ async fn spawn_fake_dsh_origin() -> SocketAddr { }), ) .route("/api/events.mux", get(echo_websocket)) - .route("/api/events.host", get(echo_websocket)); + .route("/api/events.host", get(echo_websocket)) + .fallback(|uri: axum::http::Uri| async move { + (StatusCode::NOT_FOUND, format!("unexpected upstream URI: {uri}")) + }); spawn_app(app).await } @@ -210,6 +213,74 @@ async fn companion_compresses_assets_after_splicing_html() { assert!(decoded_html.contains("dsh web")); } +#[tokio::test] +async fn main_origin_dsh_mount_proxies_html_assets_and_websockets_with_auth() { + let origin = spawn_fake_dsh_origin().await; + let tmp = TempDir::new().unwrap(); + let paths = fake_paths(tmp.path()); + let supervisor = supervisor_for(&paths.root, Some(format!("http://{origin}"))); + let state = + AppState::with_auth(paths, AuthState::enabled(TOKEN_HEX.into())).with_dsh_web(supervisor); + let addr = spawn_app(router_with_state(state)).await; + let client = reqwest::Client::builder().no_proxy().build().unwrap(); + + for path in ["/dsh/", "/dsh/client.js", "/dsh/api/events.mux"] { + assert_eq!( + client + .get(format!("http://{addr}{path}")) + .send() + .await + .unwrap() + .status(), + 401 + ); + } + for (path, marker) in [ + ("/dsh/", "dsh web"), + ("/dsh/client.js", "window.__DSH_ASSET__"), + ] { + let response = client + .get(format!("http://{addr}{path}")) + .header(header::COOKIE, format!("ccteam_token={TOKEN_HEX}")) + .send() + .await + .unwrap(); + let status = response.status(); + let body = response.text().await.unwrap(); + assert_eq!(status, 200, "{path}: {body}"); + assert!(body.contains(marker)); + } + let request = Request::builder() + .uri(format!("ws://{addr}/dsh/api/events.mux")) + .header(header::HOST, addr.to_string()) + .header(header::COOKIE, format!("ccteam_token={TOKEN_HEX}")) + .header(header::CONNECTION, "Upgrade") + .header(header::UPGRADE, "websocket") + .header("Sec-WebSocket-Version", "13") + .header("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==") + .body(()) + .unwrap(); + let (mut socket, response) = tokio_tungstenite::connect_async(request).await.unwrap(); + assert_eq!(response.status(), StatusCode::SWITCHING_PROTOCOLS); + socket + .send(tokio_tungstenite::tungstenite::Message::Text( + "mounted".into(), + )) + .await + .unwrap(); + assert_eq!( + tokio::time::timeout(Duration::from_secs(3), socket.next()) + .await + .unwrap() + .unwrap() + .unwrap() + .into_text() + .unwrap(), + "mounted" + ); + socket.close(None).await.unwrap(); +} + #[tokio::test] async fn companion_websocket_downlinks_upgrade_and_carry_frames_with_compression_enabled() { let origin = spawn_fake_dsh_origin().await; diff --git a/crates/ccteam-web/tests/files_test.rs b/crates/ccteam-web/tests/files_test.rs new file mode 100644 index 000000000..3c020b7a2 --- /dev/null +++ b/crates/ccteam-web/tests/files_test.rs @@ -0,0 +1,400 @@ +use std::net::SocketAddr; + +use ccteam_core::{CcteamPaths, ProjectState}; +use ccteam_web::{router_with_state, AppState, AuthState}; +use serde_json::{json, Value}; +use tempfile::TempDir; + +const TOKEN: &str = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"; + +#[tokio::test] +async fn markdown_preview_renders_documents_and_links_without_executing_html() { + let (_tmp, paths, addr, client) = fixture().await; + std::fs::write(paths.project_dir("demo").join("web/README.md"), "# Markdown document\n\n| Name | Value |\n| --- | --- |\n| hello | world |\n\n- [x] finished\n\n```rust\nfn main() {}\n```\n\n![dot](assets/dot.svg)\n\n[Next](next.markdown)\n\n\n\n[bad](javascript:alert(1))").unwrap(); + std::fs::write( + paths.project_dir("demo").join("web/next.markdown"), + "# Next document", + ) + .unwrap(); + let response = client + .post(format!("http://{addr}/api/v1/projects/demo/files/preview")) + .bearer_auth(format!("ccteam:{TOKEN}")) + .json(&json!({"path":"web/README.md"})) + .send() + .await + .unwrap(); + assert_eq!( + response.status(), + 200, + "Markdown must share the preview flow" + ); + let link: Value = response.json().await.unwrap(); + let url = link["url"].as_str().unwrap(); + let response = client + .get(format!("http://{addr}{url}")) + .send() + .await + .unwrap(); + assert_eq!(response.status(), 200); + assert!(response.headers()["content-type"] + .to_str() + .unwrap() + .starts_with("text/html")); + assert!(!response.headers()["content-security-policy"] + .to_str() + .unwrap() + .contains("allow-scripts")); + let html = response.text().await.unwrap(); + assert!(html.contains("") && html.contains("type=\"checkbox\"")); + assert!(html.contains("language-rust") && html.contains("assets/dot.svg")); + assert!(!html.contains("").unwrap(); + std::fs::write(root.join("web/assets/style.css"), "h1 { color: red }").unwrap(); + std::fs::write( + root.join("web/assets/app.js"), + "import { loaded } from './helper.js'; document.body.dataset.loaded = loaded", + ) + .unwrap(); + std::fs::write( + root.join("web/assets/helper.js"), + "export const loaded = 'yes'", + ) + .unwrap(); + std::fs::write(root.join("web/assets/dot.svg"), "").unwrap(); + std::fs::write(root.join(".env"), "PRIVATE_TOKEN=secret").unwrap(); + std::fs::create_dir(root.join("node_modules")).unwrap(); + std::fs::write(root.join("node_modules/huge.js"), "ignored").unwrap(); + std::fs::write(root.join("binary.bin"), b"a\0b").unwrap(); + std::fs::write(root.join("web/private.rs"), "server-only source").unwrap(); + std::fs::write(root.join("web/README.md"), "# Markdown document\n\n[Section](#details)\n\n| Name | Value |\n| --- | --- |\n| hello | world |\n\n- [x] finished\n\n```rust\nfn main() {}\n```\n\n![dot](assets/dot.svg)\n\n[Next](next.markdown)\n\n## Details\n\n").unwrap(); + std::fs::write(root.join("web/next.markdown"), "# Next document").unwrap(); + std::fs::write(root.join("outside.html"), "outside preview directory").unwrap(); + std::fs::write(root.join("large.txt"), vec![b'a'; 2 * 1024 * 1024 + 1]).unwrap(); + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + let app = router_with_state(AppState::with_auth( + paths.clone(), + AuthState::enabled(TOKEN.into()), + )); + tokio::spawn(async move { + axum::serve(listener, app).await.unwrap(); + }); + ( + tmp, + paths, + addr, + reqwest::Client::builder().no_proxy().build().unwrap(), + ) +} + +#[tokio::test] +async fn bounded_read_only_files_hide_internal_paths_and_reject_escapes() { + let (_tmp, paths, addr, client) = fixture().await; + let base = format!("http://{addr}/api/v1/projects/demo"); + assert_eq!( + client + .get(format!("{base}/files")) + .send() + .await + .unwrap() + .status(), + 401 + ); + let response: Value = client + .get(format!("{base}/files")) + .bearer_auth(format!("ccteam:{TOKEN}")) + .send() + .await + .unwrap() + .json() + .await + .unwrap(); + let names = response["paths"].as_array().unwrap(); + assert!(names.contains(&json!("web/index.html"))); + assert!(!names.iter().any(|p| p.as_str().unwrap().contains(".env") + || p.as_str().unwrap().contains("node_modules") + || p.as_str().unwrap().contains(".ccteam"))); + for (path, status) in [ + ("web/index.html", 200), + ("../secret", 403), + (".env", 403), + ("node_modules/huge.js", 403), + ("large.txt", 413), + ] { + let response = client + .get(format!("{base}/file")) + .query(&[("path", path)]) + .bearer_auth(format!("ccteam:{TOKEN}")) + .send() + .await + .unwrap(); + assert_eq!(response.status(), status, "{path}"); + } + let binary: Value = client + .get(format!("{base}/file?path=binary.bin")) + .bearer_auth(format!("ccteam:{TOKEN}")) + .send() + .await + .unwrap() + .json() + .await + .unwrap(); + assert_eq!(binary["binary"], true); + assert!(binary["content"].is_null()); + #[cfg(unix)] + { + nix::unistd::mkfifo( + &paths.project_dir("demo").join("pipe.txt"), + nix::sys::stat::Mode::S_IRUSR, + ) + .unwrap(); + let response = tokio::time::timeout( + std::time::Duration::from_secs(2), + client + .get(format!("{base}/file?path=pipe.txt")) + .bearer_auth(format!("ccteam:{TOKEN}")) + .send(), + ) + .await + .expect("FIFO must not hang") + .unwrap(); + assert_eq!(response.status(), 404); + } + #[cfg(unix)] + { + std::os::unix::fs::symlink("/etc/passwd", paths.project_dir("demo").join("escape.txt")) + .unwrap(); + assert_eq!( + client + .get(format!("{base}/file?path=escape.txt")) + .bearer_auth(format!("ccteam:{TOKEN}")) + .send() + .await + .unwrap() + .status(), + 403 + ); + } +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +#[ignore = "Chromium integration: make test-files-browser"] +async fn preview_runs_relative_modules_and_css_inside_an_opaque_browser_sandbox() { + let (_tmp, _paths, addr, client) = fixture().await; + let link: Value = client + .post(format!("http://{addr}/api/v1/projects/demo/files/preview")) + .bearer_auth(format!("ccteam:{TOKEN}")) + .json(&json!({"path":"web/index.html"})) + .send() + .await + .unwrap() + .json() + .await + .unwrap(); + let script = + std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("web/tests/file-preview-browser.mjs"); + let markdown: Value = client + .post(format!("http://{addr}/api/v1/projects/demo/files/preview")) + .bearer_auth(format!("ccteam:{TOKEN}")) + .json(&json!({"path":"web/README.md"})) + .send() + .await + .unwrap() + .json() + .await + .unwrap(); + let status = tokio::process::Command::new("node") + .arg(script) + .arg(format!("http://{addr}")) + .arg(link["url"].as_str().unwrap()) + .arg(markdown["url"].as_str().unwrap()) + .status() + .await + .unwrap(); + assert!(status.success(), "Chromium preview verification failed"); +} + +#[tokio::test] +async fn preview_capability_loads_relative_assets_without_login_and_cannot_cross_projects() { + let (_tmp, _paths, addr, client) = fixture().await; + let base = format!("http://{addr}"); + let link: Value = client + .post(format!("{base}/api/v1/projects/demo/files/preview")) + .bearer_auth(format!("ccteam:{TOKEN}")) + .json(&json!({"path":"web/index.html"})) + .send() + .await + .unwrap() + .json() + .await + .unwrap(); + let url = link["url"].as_str().unwrap(); + let response = client.get(format!("{base}{url}")).send().await.unwrap(); + assert_eq!(response.status(), 200); + let csp = response.headers()["content-security-policy"] + .to_str() + .unwrap(); + assert!(csp.contains("sandbox allow-scripts")); + assert!(!csp.contains("allow-same-origin")); + assert_eq!(response.headers()["referrer-policy"], "no-referrer"); + assert!(response.headers().get("set-cookie").is_none()); + let asset = url.replace("index.html", "assets/app.js"); + let response = client + .get(format!("{base}{asset}")) + .header("Origin", "null") + .send() + .await + .unwrap(); + assert_eq!(response.status(), 200); + assert_eq!(response.headers()["access-control-allow-origin"], "*"); + assert!(response.text().await.unwrap().contains("dataset.loaded")); + let wrong_project = url.replace("/projects/demo/", "/projects/other/"); + assert_eq!( + client + .get(format!("{base}{wrong_project}")) + .bearer_auth(format!("ccteam:{TOKEN}")) + .send() + .await + .unwrap() + .status(), + 401 + ); + let private = url.replace("web/index.html", ".env"); + assert_eq!( + client + .get(format!("{base}{private}")) + .send() + .await + .unwrap() + .status(), + 403 + ); + for path in [ + url.replace("web/index.html", "outside.html"), + url.replace("index.html", "private.rs"), + url.replace("index.html", "README.md"), + ] { + assert_eq!( + client + .get(format!("{base}{path}")) + .send() + .await + .unwrap() + .status(), + 403 + ); + } + assert_eq!( + client + .get(format!( + "{base}/api/v1/projects/demo/files/preview/bad/web/index.html" + )) + .send() + .await + .unwrap() + .status(), + 401 + ); +} diff --git a/crates/ccteam-web/tests/openapi_test.rs b/crates/ccteam-web/tests/openapi_test.rs index 280d6141c..86884bb0f 100644 --- a/crates/ccteam-web/tests/openapi_test.rs +++ b/crates/ccteam-web/tests/openapi_test.rs @@ -136,6 +136,13 @@ fn expected_operations() -> BTreeSet<(&'static str, &'static str)> { ("PUT", "/api/v1/projects/{slug}/roles/{role}"), // v0.9.11 TEAM-2 — division-of-labor charter (routing.md) ("GET", "/api/v1/projects/{slug}/routing"), + ("GET", "/api/v1/projects/{slug}/files"), + ("GET", "/api/v1/projects/{slug}/file"), + ("POST", "/api/v1/projects/{slug}/files/preview"), + ( + "GET", + "/api/v1/projects/{slug}/files/preview/{ticket}/{*path}", + ), ("PUT", "/api/v1/projects/{slug}/routing"), // composer attachments — project uploads + installed-skill picker ("POST", "/api/v1/projects/{slug}/uploads"), diff --git a/crates/ccteam-web/tests/resource_api_test.rs b/crates/ccteam-web/tests/resource_api_test.rs index c6b21c7a2..abb16edaf 100644 --- a/crates/ccteam-web/tests/resource_api_test.rs +++ b/crates/ccteam-web/tests/resource_api_test.rs @@ -396,6 +396,10 @@ async fn post_project_creates_and_registers() { // Side effects: state.json on disk + registered in config.yaml. assert!(target.join(".ccteam/state.json").exists()); + assert!(target.join("AGENTS.md").is_file()); + assert!(target.join(".gitignore").is_file()); + assert!(!target.join("CLAUDE.md").exists()); + assert!(!target.join(".ccteam/workflow.yaml").exists()); let entry = ccteam_core::lookup_project_in_config(&root, "myapp") .unwrap() .expect("myapp registered"); @@ -418,6 +422,41 @@ async fn post_project_rejects_bad_slug() { assert_eq!(resp.status(), 400); } +#[tokio::test] +async fn post_project_refresh_preserves_team_unless_explicit() { + disable_tool_surface_bootstrap_for_tests(); + for (team, expected) in [(None, "ops"), (Some("design"), "design")] { + let tmp = TempDir::new().unwrap(); + let paths = fake_paths(tmp.path()); + let root = paths.root.clone(); + let target = tmp.path().join("existing-repo"); + let state_path = CcteamPaths::project_state_in(&target); + ccteam_core::ProjectState::initial_for_team("old".into(), "ops".into()) + .save(&state_path) + .unwrap(); + let addr = spawn(AppState::new(paths)).await; + let resp = reqwest::Client::new() + .post(format!("http://{addr}/api/v1/projects")) + .json(&serde_json::json!({ + "slug": "adopted", "path": target, "team": team, + })) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 201); + let state = ccteam_core::ProjectState::load(&state_path).unwrap(); + assert_eq!(state.team, expected); + assert_eq!(state.owner.as_deref(), Some("user:web-api")); + assert_eq!( + ccteam_core::lookup_project_in_config(&root, "adopted") + .unwrap() + .unwrap() + .team, + expected + ); + } +} + #[tokio::test] async fn post_project_rejects_relative_path() { let tmp = TempDir::new().unwrap(); diff --git a/crates/ccteam-web/tests/sessions_api_test.rs b/crates/ccteam-web/tests/sessions_api_test.rs index d24658de8..abcfe2b07 100644 --- a/crates/ccteam-web/tests/sessions_api_test.rs +++ b/crates/ccteam-web/tests/sessions_api_test.rs @@ -721,6 +721,8 @@ async fn session_context_is_live_then_falls_back_to_the_snapshot() { assert_eq!(live["live"], true); assert_eq!(live["breakdown"]["used_tokens"], 42); assert_eq!(live["breakdown"]["categories"][0]["kind"], "used"); + assert_eq!(live["session"]["sid"], sid.as_str()); + assert_eq!(live["session"]["residency"], "resident"); let stopped = client .post(format!("{base}/sessions/{sid}/stop")) @@ -731,6 +733,7 @@ async fn session_context_is_live_then_falls_back_to_the_snapshot() { let (code, gone) = context(sid.clone()).await; assert_eq!(code, 200, "a stopped session is still readable: {gone}"); assert_eq!(gone["live"], false); + assert_eq!(gone["session"]["residency"], "stopped"); assert!(gone["breakdown"].is_null(), "no snapshot yet: {gone}"); ccteam_harness::execution::session_status::write_context_file( @@ -742,6 +745,40 @@ async fn session_context_is_live_then_falls_back_to_the_snapshot() { assert_eq!(snap["live"], false); assert_eq!(snap["breakdown"]["used_tokens"], 7); + // A cold historical remote child must not inherit the shell's local/root + // defaults. This sid is never loaded into the gateway's live session map. + let mut remote = + ccteam_harness::execution::session_meta::read_session_meta(&project_dir, &sid).unwrap(); + remote.sid = "s987".into(); + remote.host = "satellite-1".into(); + remote.parent_sid = Some("s1497".into()); + remote.delegation_depth = 2; + remote.stopped_at = None; + remote.cost_usd = Some(716.425); + remote.tokens_total = Some(12345); + ccteam_harness::execution::session_meta::write_session_meta(&project_dir, &remote).unwrap(); + let (code, historical) = context("s987".into()).await; + assert_eq!(code, 200, "{historical}"); + assert_eq!(historical["session"]["host"], "satellite-1"); + assert_eq!(historical["session"]["parent_sid"], "s1497"); + assert_eq!(historical["session"]["delegation_depth"], 2); + assert_eq!(historical["session"]["residency"], "released"); + assert_eq!(historical["session"]["cost_usd"], 716.425); + assert_eq!(historical["session"]["tokens_total"], 12345); + ccteam_harness::execution::session_status::write_status_file( + &project_dir, + "s987", + &ccteam_harness::ThreadStatus { + model: Some("current-observed-model".into()), + ..Default::default() + }, + ); + let (_, refreshed) = context("s987".into()).await; + assert_eq!( + refreshed["session"]["model"], "current-observed-model", + "model changes survive release and Context refresh" + ); + let codex = create("codex").await; let (code, none) = context(codex).await; assert_eq!(code, 200); diff --git a/crates/ccteam-web/web/package-lock.json b/crates/ccteam-web/web/package-lock.json index cd11f2330..a313073ff 100644 --- a/crates/ccteam-web/web/package-lock.json +++ b/crates/ccteam-web/web/package-lock.json @@ -8,8 +8,14 @@ "name": "ccteam-web", "version": "0.3.2", "dependencies": { + "@codemirror/language-data": "6.5.2", + "@codemirror/search": "6.7.2", + "@codemirror/state": "6.7.6", + "@codemirror/view": "6.43.15", + "@pierre/trees": "1.0.0-beta.6", "@tailwindcss/vite": "^4.2.2", "@tanstack/react-table": "^8.21.3", + "@uiw/react-codemirror": "4.25.12", "@wterm/core": "^0.1.8", "@wterm/dom": "^0.1.8", "@wterm/react": "^0.1.8", @@ -22,6 +28,7 @@ "react": "^19.2.4", "react-diff-viewer-continued": "^4.2.2", "react-dom": "^19.2.4", + "react-resizable-panels": "4.16.1", "react-router-dom": "^7.14.2", "remark-gfm": "^4.0.1", "shiki": "^4.0.2", @@ -317,6 +324,426 @@ "specificity": "bin/cli.js" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.4", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.4.tgz", + "integrity": "sha512-yF8RhOf2SXlg00+qtupzLhrglcas61boqaONiNcbPIebHCpJocpqTBA6dyrtUrQO47hqyTdeob/3XnYilT7TaQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.1.tgz", + "integrity": "sha512-O/4hG3SC1YwcmQ0d2UVNDs+AsaNWd1iHVxbTeEBuqH+6bExAiPK3iS/BvpY6rZGURALv4ZD3sIgcCmRvw3ehBg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.7.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-angular": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-angular/-/lang-angular-0.1.4.tgz", + "integrity": "sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.3" + } + }, + "node_modules/@codemirror/lang-cpp": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz", + "integrity": "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/cpp": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-go": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz", + "integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/go": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.12", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.12.tgz", + "integrity": "sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-java": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.2.tgz", + "integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/java": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-jinja": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-jinja/-/lang-jinja-6.0.1.tgz", + "integrity": "sha512-P5kyHLObzjtbGj16h+hyvZTxJhSjBEeSx4wMjbnAf3b0uwTy2+F0zGjMZL4PQOm/mh2eGZ5xUDVZXgwP783Nsw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-less": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-less/-/lang-less-6.0.2.tgz", + "integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.2.0", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-liquid": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-liquid/-/lang-liquid-6.3.3.tgz", + "integrity": "sha512-U9EnyzWuAx1TX3MZSKDXydKH734n1Aogk/ijKdW+jdIwmbf9zSPQYOIKggC/b2PTRjQb7ddvMktmxnfTLuv/SQ==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.2.tgz", + "integrity": "sha512-AwBOdkWYuA//WcM0xO5PfHPUcmz/O2i5o0Nsg1U69SII/loCJlFI1Romd9xp2HYb1kYJRGZotyqRghuHH5n8Kw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-php": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.2.tgz", + "integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/php": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-python": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz", + "integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.3.2", + "@codemirror/language": "^6.8.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/python": "^1.1.4" + } + }, + "node_modules/@codemirror/lang-rust": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz", + "integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/rust": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-sass": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz", + "integrity": "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.2.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/sass": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-sql": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.10.0.tgz", + "integrity": "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-vue": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz", + "integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, + "node_modules/@codemirror/lang-wast": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz", + "integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-xml": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.3.tgz", + "integrity": "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.13.1.tgz", + "integrity": "sha512-zfvU2r3TyylQLMj/te+5CnQ0rSqZi8B1oTCBbQkagcLW4Xc6imab6qN+pEJhsuNOLaJqiRyBXp9UhWA1DhVvDw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/streamparser": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/language-data": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/language-data/-/language-data-6.5.2.tgz", + "integrity": "sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-angular": "^0.1.0", + "@codemirror/lang-cpp": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-go": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-java": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/lang-jinja": "^6.0.0", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-less": "^6.0.0", + "@codemirror/lang-liquid": "^6.0.0", + "@codemirror/lang-markdown": "^6.0.0", + "@codemirror/lang-php": "^6.0.0", + "@codemirror/lang-python": "^6.0.0", + "@codemirror/lang-rust": "^6.0.0", + "@codemirror/lang-sass": "^6.0.0", + "@codemirror/lang-sql": "^6.0.0", + "@codemirror/lang-vue": "^0.1.1", + "@codemirror/lang-wast": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/lang-yaml": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/legacy-modes": "^6.4.0" + } + }, + "node_modules/@codemirror/legacy-modes": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.5.tgz", + "integrity": "sha512-9ltT214oMb3Y1w5eAZMSigzrjzZnMI2Wa3Feq73rU/i4C56MkwrpfswfQYOr28C5pXr6dWwspNTMyeHXlzWMxg==", + "license": "MIT", + "dependencies": { + "@codemirror/streamparser": "^6.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", + "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.2.tgz", + "integrity": "sha512-gUYkYhT2+n/+VGZ+8EzE5WFkYZUZYm1VOKDudIsNqh42uRVQJ0a6Yss9sdKT3MeOYfuL1N6AZA57oza0Oyr0LA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.6.tgz", + "integrity": "sha512-kAz+AncRtKuIknedxT1bq4XwXv4UowhbkHU1myPrtVb/jZtImWuV5BXzv5vK6i3kYACsdiZiQKFQQ5Mq7elW8w==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/streamparser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/streamparser/-/streamparser-6.0.0.tgz", + "integrity": "sha512-JTFPF6xrfjt0mnfGPgzcLiMwYmvIyNaoizp8BYzv8FE8KrT6GBGDZ190rtt+O7Wke3gG/MFHyiY9YGNeimof4w==", + "license": "MIT" + }, + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.43.15", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.15.tgz", + "integrity": "sha512-HHsfpqFeHyXLs983pSRe38Xajs0vFWZQ2BuX0m7Ki8w3jZ5iD8cuR3lBz2/5/X53Dhf32SBHKh6rtSTB6tKeGw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@csstools/color-helpers": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", @@ -808,6 +1235,189 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lezer/common": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.5.tgz", + "integrity": "sha512-Pw7PVCndruF1pb3ebCxpSVpkMHSDjwRCLcTRw+RizUKco5QbWylwq2g4lnG/RYN7yTTQDXE9F77MwtkAyphrjg==", + "license": "MIT" + }, + "node_modules/@lezer/cpp": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.6.tgz", + "integrity": "sha512-vh9gWWJOXFVY8HBHK3Twzq8MgwG2iN4GSyzBP9sCGTe37P15x2R14VaBQk0VA0ezTRN1KHYBBsHhvpGZ2Xy/pA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/css": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.9.tgz", + "integrity": "sha512-ZmzgvTOBdJWGIsY/ItAfN7UW8DV802oZG+jeG6E508SwEzz5YhuAajptpWPICvIyqYAD2+VjMdOS40GgGkyEOQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/go": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.1.tgz", + "integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.5.tgz", + "integrity": "sha512-O1GMVKgtf5YspFaRzpmqkVgMtIk2HG9uMAbH6wdAIdNh3TVzCsch/+cRi+6l2//UGDKe6NB9BW3cvuR5ezMQ0w==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/java": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.5.tgz", + "integrity": "sha512-JppTd1iKHpeNlLYw/Zl8JX7zSY5HqAzK4zZ05lpn8l8Sk7Fo5KAKiDiMiezKoT5lfeC2xZf8HeKp1tSctj4G6Q==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.6.tgz", + "integrity": "sha512-sEvOqpl4vJQca0NugSQK02vQPbc5KrvoojnYGoqqFgFO0MtcYhwGiHKVEB+LuXYJnfyXo4QPA8ztbrEa4Fw5NA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.12.tgz", + "integrity": "sha512-On9HU4oMMiHPMkQDgoTnGahjgG5n/MkN38CgG77sMraKLL+3JBbeQNi1FcCXV9QRqwF9/BUXP3caHyJC1fBOTQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/markdown": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.8.1.tgz", + "integrity": "sha512-M5YmEcKWO4mJ45Y1hECwqyu8cLv25EiXOK554PVOvDOWOP0olwelHHQUWr9sjF+d1bENVhy0nfJ8E57XZUTiXg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@lezer/php": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.6.tgz", + "integrity": "sha512-QJ2xNXPmsm/Z3IpThq8fnJrEIVpxhsIoj6GZBW0JYEd/l9zxpJZW216X4fzqQY4vgpdGIumypvI4uQjd4tnYtw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.1.0" + } + }, + "node_modules/@lezer/python": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.19.tgz", + "integrity": "sha512-MhQIURHRytsNzP/YXnqpYKW6la6voAH3kyplTOOiCdjyFY6cWWGFVmYVdHIPrElqSDf4iCDktQCockB9FxuhzQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/rust": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.3.tgz", + "integrity": "sha512-XxErOjZzQ7yJt1agUT4fu9qQvESZ3acgoxpPaPTPOiUx+duCjaVAtZGFIgphkHxlN05djdVAIOy/wItShMEjqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/sass": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lezer/sass/-/sass-1.1.0.tgz", + "integrity": "sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz", + "integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", + "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==", + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", @@ -834,6 +1444,51 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@pierre/theming": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pierre/theming/-/theming-1.0.0.tgz", + "integrity": "sha512-WsdrnhKfjeyXGDikZmN9pkpeZ5S/cl6EE72feiSc0tlynT1tMYqXqouhuv/foK+PY9OEnebOAVRQn3+rAstR8g==", + "license": "apache-2.0", + "peerDependencies": { + "@pierre/theme": "^1.1.0", + "@shikijs/themes": "^3.0.0 || ^4.0.0", + "react": "^18.3.1 || ^19.0.0", + "react-dom": "^18.3.1 || ^19.0.0", + "shiki": "^3.0.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@pierre/theme": { + "optional": true + }, + "@shikijs/themes": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "shiki": { + "optional": true + } + } + }, + "node_modules/@pierre/trees": { + "version": "1.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@pierre/trees/-/trees-1.0.0-beta.6.tgz", + "integrity": "sha512-zxeuSFM9TveM7b5XofweJALCtm/tGYV9HZzdbf7Uf+kBxIlUyz24/EHaGRjB0dsmmfDQl2ETz7AWwJ15lhSnpw==", + "license": "apache-2.0", + "dependencies": { + "@pierre/theming": "1.0.0", + "preact": "11.0.0-beta.0", + "preact-render-to-string": "6.6.5" + }, + "peerDependencies": { + "react": "^18.3.1 || ^19.0.0", + "react-dom": "^18.3.1 || ^19.0.0" + } + }, "node_modules/@playwright/test": { "version": "1.60.0", "dev": true, @@ -2235,6 +2890,59 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@uiw/codemirror-extensions-basic-setup": { + "version": "4.25.12", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.12.tgz", + "integrity": "sha512-wR5lz7jbVcBGyiiLEObAmDikcc39KNqrrd4bdUhav85WZu48ttgnSf1YV+grHuMsj5l/WCw6u+r9ZuSfURJgiA==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/autocomplete": ">=6.0.0", + "@codemirror/commands": ">=6.0.0", + "@codemirror/language": ">=6.0.0", + "@codemirror/lint": ">=6.0.0", + "@codemirror/search": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, + "node_modules/@uiw/react-codemirror": { + "version": "4.25.12", + "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.25.12.tgz", + "integrity": "sha512-RE0Gj/QesD7/JWPnJREG8Kq8brGv2NAH17iUECcBrLkqXzzEAgsj/4Qqvz/OlK8XWKmzlsenLRkObT9H+gYQ0w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.6", + "@codemirror/commands": "^6.1.0", + "@codemirror/state": "^6.1.1", + "@codemirror/theme-one-dark": "^6.0.0", + "@uiw/codemirror-extensions-basic-setup": "4.25.12", + "codemirror": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.11.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/theme-one-dark": ">=6.0.0", + "@codemirror/view": ">=6.0.0", + "codemirror": ">=6.0.0", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.1", "license": "ISC" @@ -2658,6 +3366,21 @@ "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "dev": true, @@ -2717,6 +3440,12 @@ "node": ">=10" } }, + "node_modules/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "dev": true, @@ -4834,6 +5563,25 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "11.0.0-beta.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-11.0.0-beta.0.tgz", + "integrity": "sha512-IcODoASASYwJ9kxz7+MJeiJhvLriwSb4y4mHIyxdgaRZp6kPUud7xytrk/6GZw8U3y6EFJaRb5wi9SrEK+8+lg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.6.5.tgz", + "integrity": "sha512-O6MHzYNIKYaiSX3bOw0gGZfEbOmlIDtDfWwN1JJdc/T3ihzRT6tGGSEWE088dWrEDGa1u7101q+6fzQnO9XCPA==", + "license": "MIT", + "peerDependencies": { + "preact": ">=10 || >= 11.0.0-0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -4941,6 +5689,22 @@ } } }, + "node_modules/react-resizable-panels": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.16.1.tgz", + "integrity": "sha512-XI02YTQxqwVAzSJCvaQ6Ze7ubwyjVRCyihu835q7wx5a1NAJXt0rnvv3TdWxGklrQD1G+IrLJMMjlFUmbCLABg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.2.6 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "7.15.0", "license": "MIT", @@ -5254,6 +6018,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-mod": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.4.tgz", + "integrity": "sha512-XXWIQt633/EpAFx8aZDOTjBzrCaGmhvEQlQo6MVPfa2OzO2cWo+4hV9h+6UkHYlXGfy+ODXKUdP7Pthmcu5ATw==", + "license": "MIT" + }, "node_modules/stylis": { "version": "4.2.0", "license": "MIT" @@ -5838,6 +6608,12 @@ } } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/crates/ccteam-web/web/package.json b/crates/ccteam-web/web/package.json index e8d064ab1..8d6b44654 100644 --- a/crates/ccteam-web/web/package.json +++ b/crates/ccteam-web/web/package.json @@ -13,8 +13,14 @@ "test:unit": "vitest run" }, "dependencies": { + "@codemirror/language-data": "6.5.2", + "@codemirror/search": "6.7.2", + "@codemirror/state": "6.7.6", + "@codemirror/view": "6.43.15", + "@pierre/trees": "1.0.0-beta.6", "@tailwindcss/vite": "^4.2.2", "@tanstack/react-table": "^8.21.3", + "@uiw/react-codemirror": "4.25.12", "@wterm/core": "^0.1.8", "@wterm/dom": "^0.1.8", "@wterm/react": "^0.1.8", @@ -27,6 +33,7 @@ "react": "^19.2.4", "react-diff-viewer-continued": "^4.2.2", "react-dom": "^19.2.4", + "react-resizable-panels": "4.16.1", "react-router-dom": "^7.14.2", "remark-gfm": "^4.0.1", "shiki": "^4.0.2", diff --git a/crates/ccteam-web/web/playwright.config.ts b/crates/ccteam-web/web/playwright.config.ts index 2b27259f7..36f3b31dc 100644 --- a/crates/ccteam-web/web/playwright.config.ts +++ b/crates/ccteam-web/web/playwright.config.ts @@ -1,5 +1,8 @@ import { defineConfig } from "@playwright/test"; +// Parallel checkouts must never silently test another checkout's server. +const previewPort = Number(process.env.CCTEAM_WEB_TEST_PORT ?? 4173); + export default defineConfig({ testDir: "./tests", // V0.3.2 F59 keeps only ccteam-owned SPA smoke in the default @@ -11,7 +14,7 @@ export default defineConfig({ timeout: 30000, retries: process.env.CI ? 1 : 0, use: { - baseURL: "http://localhost:4173", + baseURL: `http://127.0.0.1:${previewPort}`, headless: true, screenshot: "only-on-failure", }, @@ -20,8 +23,8 @@ export default defineConfig({ // `npx` on PATH (only `node`/`npm`; observed in this wave's sandbox) since // `vite` is already a devDependency, so `node_modules/.bin/vite` always // resolves post-install without depending on the npx shim at all. - command: "node_modules/.bin/vite preview --port 4173", - port: 4173, + command: `node_modules/.bin/vite preview --host 127.0.0.1 --port ${previewPort} --strictPort`, + port: previewPort, reuseExistingServer: !process.env.CI, }, reporter: process.env.CI ? [["html", { open: "never" }], ["github"]] : "list", @@ -30,5 +33,10 @@ export default defineConfig({ name: "chromium", use: { browserName: "chromium" }, }, + { + name: "webkit-touch", + grep: /touch workspace/, + use: { browserName: "webkit" }, + }, ], }); diff --git a/crates/ccteam-web/web/src/components/ContextPanel.test.tsx b/crates/ccteam-web/web/src/components/ContextPanel.test.tsx index 5dbd8364a..68cd62b43 100644 --- a/crates/ccteam-web/web/src/components/ContextPanel.test.tsx +++ b/crates/ccteam-web/web/src/components/ContextPanel.test.tsx @@ -146,6 +146,32 @@ describe("ContextPanel", () => { expect($('[data-testid="ctx-loading"]')).toBeNull(); }); + it("shows session facts even without context data and distinguishes cost from tokens", async () => { + const session = {sid:"s12",project:"demo",role:"",vendor:"codex",model:"gpt-test",permission_mode:"skip",current:true,status:"idle",residency:"released",host:"local",parent_sid:"s1497",delegation_depth:2,cost_usd:716.425,tokens_total:12345,last_active:"2026-10-20T08:00:00Z"}; + globalThis.fetch = fetchAnswering({sid:"s12",live:false,breakdown:null,session}); + await mount(panel({model:"stale-transcript-model"})); + const facts=text('[data-testid="ctx-session-status"]'); + for(const value of ["s12","demo","codex","gpt-test","local","s1497","2","$716.4250","12,345","Released; resumes on next message","Last active"]) expect(facts).toContain(value); + expect(facts).not.toContain("Role"); + expect(facts).not.toContain("stale-transcript-model"); + expect($('[data-testid="ctx-none"]')).not.toBeNull(); + globalThis.fetch = fetchAnswering({sid:"s12",live:false,breakdown:null,session:{...session,cost_usd:null,waiting_approval:true}}); + await mount(panel({model:"gpt-test",refreshKey:1})); + expect(text('[data-testid="ctx-session-status"]')).toContain("Not priced yet"); + expect(text('[data-testid="ctx-session-status"]')).toContain("Waiting for approval"); + expect(text('[data-testid="ctx-session-status"]')).not.toContain("$0.0000"); + }); + + it("shows Codex totals and cache usage without fabricating category detail", async () => { + globalThis.fetch=fetchAnswering({sid:"s12",live:true,breakdown:{vendor:"codex",used_tokens:8000,window_tokens:100000,categories:[{id:"context_total",name:"Used context",tokens:8000,kind:"used"},{id:"free_space",name:"Free space",tokens:92000,kind:"free"}],last_request:{input_tokens:6000,cache_read_tokens:1000,cache_creation_tokens:0,output_tokens:1000}}}); + await mount(panel()); + expect(text('[data-testid="ctx-usage"]')).toContain("8k / 100k · 8%"); + expect(text('[data-testid="ctx-codex-scope"]')).toContain("without per-tool, skill or memory-file categories"); + expect(text('[data-testid="ctx-cache-hit"]')).toContain("14.3%"); + expect($('[data-testid="ctx-messages"]')).toBeNull(); + expect($('[data-testid="ctx-threshold"]')).toBeNull(); + }); + it("stacks used (desc) → buffer → free in the bar; deferred is NOT a segment", async () => { globalThis.fetch = fetchAnswering(BREAKDOWN); await mount(panel()); @@ -294,7 +320,7 @@ describe("ContextPanel", () => { globalThis.fetch = fetchAnswering({ sid: "s12", live: false, breakdown: null }); await mount(panel()); expect(text('[data-testid="ctx-none"]')).toContain("No context breakdown for this session yet."); - expect(text('[data-testid="ctx-none"]')).toContain("Only Claude stream-json sessions on this host"); + expect(text('[data-testid="ctx-none"]')).toContain("Codex app-server reports window totals and request usage"); expect($('[data-testid="ctx-meter"]')).toBeNull(); expect($('[data-testid="ctx-error"]')).toBeNull(); expect($('[data-testid="ctx-meta"]')).toBeNull(); diff --git a/crates/ccteam-web/web/src/components/ContextPanel.tsx b/crates/ccteam-web/web/src/components/ContextPanel.tsx index cf28cc2ad..657c70938 100644 --- a/crates/ccteam-web/web/src/components/ContextPanel.tsx +++ b/crates/ccteam-web/web/src/components/ContextPanel.tsx @@ -50,11 +50,13 @@ import { type MessageSplitKey, } from "../lib/contextBreakdown"; import { makeT, type Lang } from "../lib/i18n"; +import type { SessionView } from "../lib/sessionsApi"; type T = (key: string) => string; export interface ContextPanelProps { sid: string; + model?: string | null; lang: Lang; onClose: () => void; /** Identity of the newest turn-ending frame on this session; every change @@ -65,7 +67,7 @@ export interface ContextPanelProps { focusOnMount?: boolean; } -export function ContextPanel({ sid, lang, onClose, refreshKey, focusOnMount = false }: ContextPanelProps) { +export function ContextPanel({ sid, model, lang, onClose, refreshKey, focusOnMount = false }: ContextPanelProps) { const t = makeT(lang); const [data, setData] = useState(null); // Mount = a fetch is already in flight, so the first paint is the loading @@ -176,6 +178,7 @@ export function ContextPanel({ sid, lang, onClose, refreshKey, focusOnMount = fa
+ {data?.session ? : null} {error ? (
@@ -215,6 +218,41 @@ export function ContextPanel({ sid, lang, onClose, refreshKey, focusOnMount = fa ); } +/** The context response includes authoritative per-sid facts, even after + * release. The shell's capped history rows are not a session detail source. */ +export function SessionFacts({ session, model, lang }: { session: SessionView; model?: string | null; lang: Lang }) { + const t = makeT(lang); + const [now, setNow] = useState(() => Date.now()); + useEffect(() => { + const timer = window.setInterval(() => setNow(Date.now()), 60_000); + return () => window.clearInterval(timer); + }, []); + const activity: Record = { working: t("ctxWorking"), idle: t("ctxIdle"), stale: t("ctxStale"), stuck: t("ctxStuck"), detached: t("ctxDetached") }; + const residency: Record = { resident: t("ctxResident"), released: t("ctxReleased"), stopped: t("ctxStopped"), detached: t("ctxDetached"), external: t("ctxExternal") }; + const lastActive = session.last_active ? new Date(session.last_active) : null; + const validDate = lastActive && Number.isFinite(lastActive.getTime()); + return ( +
+

{t("ctxSessionStatus")}

+
+
{t("ctxSessionId")}
{session.sid}
+
{t("project")}
{session.project}
+
{t("ctxActivity")}
{session.waiting_approval ? t("ctxWaitingApproval") : activity[session.status] ?? session.status}
+ {session.residency ? <>
{t("ctxResidency")}
{residency[session.residency] ?? session.residency}
: null} +
Harness
{session.vendor}
+
{t("model")}
{session.model || model || "—"}
+
{t("host")}
{session.host || "local"}
+ {session.role ? <>
Role
{session.role}
: null} +
{t("parentSession")}
{session.parent_sid || t("noParent")}
+
{t("depth")}
{session.delegation_depth ?? "—"}
+
{t("ctxSessionCost")}
{session.cost_usd != null ? `$${session.cost_usd.toFixed(4)}` : t("ctxUnpriced")}
+ {session.tokens_total != null ? <>
{t("ctxTotalTokens")}
{formatTokensExact(session.tokens_total)}
: null} +
{t("ctxLastActive")}
{validDate ? : "—"}
+
+
+ ); +} + function Breakdown({ breakdown, live, @@ -238,6 +276,7 @@ function Breakdown({ <>
+ {breakdown.vendor === "codex" ?

{t("ctxCodexScope")}

: null} {rows.length > 0 ? (

{t("ctxCategories")}

@@ -270,7 +309,7 @@ function Breakdown({
) : null} {breakdown.messages ? : null} - {breakdown.last_request ? : null} + {breakdown.last_request ? : null} ); } @@ -293,7 +332,7 @@ function Header({ const captured = breakdown.captured_at ? new Date(breakdown.captured_at) : null; const capturedText = captured && !Number.isNaN(captured.getTime()) ? captured.toLocaleString(locale) : null; - const age = live ? null : relativeTime(breakdown.captured_at, fetchedAt, lang); + const age = live && breakdown.vendor !== "codex" ? null : relativeTime(breakdown.captured_at, fetchedAt, lang); return (
{breakdown.model ? ( @@ -310,8 +349,8 @@ function Header({ {pct !== null ? ` · ${formatPct(pct)}` : ""} {live ? ( - - {t("ctxLive")} + + {t(breakdown.vendor === "codex" ? "ctxReported" : "ctxLive")}{age ? ` · ${age}` : ""} ) : ( +

{t("ctxLastRequest")}

+
+
{t("ctxInputTotal")}
{formatTokensExact(input)}
+
{t("ctxCachedSubset")}
{formatTokensExact(last.cache_read_tokens)}
+ {(last.cache_creation_tokens ?? 0) > 0 ? <>
{t("ctxCacheWrite")}
{formatTokensExact(last.cache_creation_tokens)}
: null} +
{t("ctxOutput")}
{formatTokensExact(last.output_tokens)}
+ {last.reasoning_output_tokens != null ? <>
{t("ctxReasoningSubset")}
{formatTokensExact(last.reasoning_output_tokens)}
: null} + {ratio !== null ? <>
{t("ctxCacheHit")}
{formatPct(ratio * 100)}
: null} +
+ ; + } return (

{t("ctxLastRequest")}

diff --git a/crates/ccteam-web/web/src/components/FileWorkspace.tsx b/crates/ccteam-web/web/src/components/FileWorkspace.tsx new file mode 100644 index 000000000..8d65483dd --- /dev/null +++ b/crates/ccteam-web/web/src/components/FileWorkspace.tsx @@ -0,0 +1,650 @@ +import { + useCallback, + useEffect, + useMemo, + useRef, + useState, + type CSSProperties, +} from "react"; +import { FileTree, useFileTree } from "@pierre/trees/react"; +import { Group, Panel, Separator } from "react-resizable-panels"; +import CodeMirror, { type ReactCodeMirrorRef } from "@uiw/react-codemirror"; +import { languages } from "@codemirror/language-data"; +import type { Extension } from "@codemirror/state"; +import { EditorView } from "@codemirror/view"; +import { + ExternalLink, + Files, + FolderTree, + Maximize2, + Minimize2, + Pin, + RefreshCw, + X, +} from "lucide-react"; +import { + getProjectFile, + getProjectFiles, + mintFilePreview, + supportsFilePreview, + openFileTab, + projectRelativePath, + type FileTab, + type FileTarget, + type ProjectFile, + type ProjectFiles, +} from "../lib/filesApi"; +import { makeT, type Lang } from "../lib/i18n"; +const READER_THEME = EditorView.theme({ + "&": { fontSize: "12px" }, + ".cm-scroller": { + fontFamily: "var(--font-mono)", + lineHeight: "1.6", + overflow: "auto", + }, +}); + +export interface FileWorkspaceProps { + project: string; + lang: Lang; + onClose: () => void; + target?: FileTarget | null; + refreshKey?: string | number | null; + focusOnMount?: boolean; + visible?: boolean; +} + +function readExpanded(project: string): string[] { + try { + const stored: unknown = JSON.parse( + localStorage.getItem(`ccteam.files.expanded.${project}`) ?? "[]", + ); + return Array.isArray(stored) + ? stored + .filter((path): path is string => typeof path === "string") + .slice(0, 20_000) + : []; + } catch { + return []; + } +} + +function ProjectTree({ + data, + project, + active, + open, +}: { + data: ProjectFiles; + project: string; + active: string | null; + open: (path: string, pin?: boolean) => void; +}) { + const openRef = useRef(open); + useEffect(() => { + openRef.current = open; + }, [open]); + const { model } = useFileTree({ + paths: data.paths, + icons: "complete", + search: true, + density: "compact", + stickyFolders: true, + flattenEmptyDirectories: true, + initialExpansion: "closed", + initialExpandedPaths: readExpanded(project), + gitStatus: Object.entries(data.git_status).map(([path, status]) => ({ + path, + status, + })), + onSelectionChange: (selected) => { + const path = selected.at(-1); + if ( + path && + !path.endsWith("/") && + model.getItem(path)?.isDirectory() === false + ) + openRef.current(path); + }, + }); + useEffect(() => { + model.resetPaths(data.paths, { + initialExpandedPaths: readExpanded(project), + }); + model.setGitStatus( + Object.entries(data.git_status).map(([path, status]) => ({ + path, + status, + })), + ); + }, [data, model, project]); + useEffect(() => { + const directories = data.paths + .filter((p) => p.endsWith("/")) + .map((p) => p.slice(0, -1)); + let timer: ReturnType | undefined; + let previous = JSON.stringify(readExpanded(project).sort()); + const persist = () => { + const expanded = directories + .filter((path) => { + const item = model.getItem(path); + return item && "isExpanded" in item && item.isExpanded(); + }) + .sort(); + const next = JSON.stringify(expanded); + if (next === previous) return; + previous = next; + try { + localStorage.setItem(`ccteam.files.expanded.${project}`, next); + } catch { + /* storage optional */ + } + }; + const unsubscribe = model.subscribe(() => { + clearTimeout(timer); + timer = setTimeout(persist, 200); + }); + return () => { + clearTimeout(timer); + unsubscribe(); + persist(); + }; + }, [data.paths, model, project]); + useEffect(() => { + if (!active) return; + const parts = active.split("/"); + for (let count = 1; count < parts.length; count++) { + const parent = model.getItem(parts.slice(0, count).join("/")); + if (parent && "expand" in parent) parent.expand(); + } + if (!model.getSelectedPaths().includes(active)) + model.getItem(active)?.select(); + model.scrollToPath(active, { focus: false, offset: "nearest" }); + }, [active, data, model]); + const style = { + height: "100%", + width: "100%", + "--trees-bg-override": "var(--bg-side)", + "--trees-fg-override": "var(--text)", + "--trees-selected-bg-override": "var(--bg-soft)", + "--trees-border-color-override": "var(--border-soft)", + } as CSSProperties; + return ( + { + const path = model.getFocusedPath(); + if (path && model.getItem(path)?.isDirectory() === false) + openRef.current(path, true); + }} + /> + ); +} + +export default function FileWorkspace({ + project, + lang, + onClose, + target, + refreshKey, + focusOnMount, + visible = true, +}: FileWorkspaceProps) { + const t = makeT(lang); + const [tree, setTree] = useState(null); + const [treeError, setTreeError] = useState(null); + const [tabs, setTabs] = useState([]); + const [active, setActive] = useState(null); + const [file, setFile] = useState(null); + const [fileFailure, setFileFailure] = useState<{ + path: string; + message: string; + } | null>(null); + const [loading, setLoading] = useState(true); + const [revision, setRevision] = useState(0); + const [mode, setMode] = useState<"source" | "preview">("source"); + const [preview, setPreview] = useState<{ path: string; url: string } | null>( + null, + ); + const [previewFailure, setPreviewFailure] = useState<{ + path: string; + message: string; + } | null>(null); + const [treeVisible, setTreeVisible] = useState(true); + const [expanded, setExpanded] = useState(false); + const [extensions, setExtensions] = useState([]); + const [copied, setCopied] = useState(false); + const [appliedTarget, setAppliedTarget] = useState(null); + const panelRef = useRef(null); + const editorRef = useRef(null); + const scrollCleanup = useRef<(() => void) | null>(null); + const appliedNavigation = useRef(null); + const scrollPositions = useRef(new Map()); + const canPreview = supportsFilePreview(active ?? ""); + const readerExtensions = useMemo( + () => [...extensions, READER_THEME], + [extensions], + ); + + const open = useCallback( + (path: string, pin = false) => { + setTabs((old) => openFileTab(old, path, pin)); + setActive(path); + if (active !== path) + setMode(supportsFilePreview(path) ? "preview" : "source"); + }, + [active], + ); + + useEffect(() => { + const controller = new AbortController(); + getProjectFiles(project, controller.signal) + .then((data) => { + setTree(data); + setTreeError(null); + setLoading(false); + }) + .catch((e: Error) => { + if (!controller.signal.aborted) { + setTreeError(e.message); + setLoading(false); + } + }); + return () => controller.abort(); + }, [project, revision, refreshKey]); + + if (target && tree && target !== appliedTarget) { + const relative = projectRelativePath(tree.root, target.path); + setAppliedTarget(target); + if (relative) { + setTabs((old) => openFileTab(old, relative, true)); + setActive(relative); + setMode( + !target.line && supportsFilePreview(relative) ? "preview" : "source", + ); + } + } + + useEffect(() => { + if (focusOnMount && visible) panelRef.current?.focus(); + }, [focusOnMount, visible]); + + useEffect(() => { + if (!active) return; + const controller = new AbortController(); + getProjectFile(project, active, controller.signal) + .then((data) => { + setFile(data); + setFileFailure(null); + }) + .catch((e: Error) => { + if (!controller.signal.aborted) + setFileFailure({ path: active, message: e.message }); + }); + const language = languages.find((l) => + l.extensions.includes(active.split(".").at(-1) ?? ""), + ); + void Promise.resolve(language?.load()) + .then((support) => { + if (!controller.signal.aborted) setExtensions(support ? [support] : []); + }) + .catch(() => { + if (!controller.signal.aborted) setExtensions([]); + }); + return () => controller.abort(); + }, [project, active, revision, refreshKey]); + + useEffect(() => { + if ( + !visible || + !active || + !supportsFilePreview(active) || + mode !== "preview" + ) + return; + const controller = new AbortController(); + mintFilePreview(project, active, controller.signal) + .then((data) => { + setPreview({ path: active, url: data.url }); + setPreviewFailure(null); + }) + .catch((e: Error) => { + if (!controller.signal.aborted) + setPreviewFailure({ path: active, message: e.message }); + }); + return () => controller.abort(); + }, [project, active, mode, revision, refreshKey, visible]); + + // Only render responses for the selected path. A previous file's content + // must never flash while the next request is in flight. + const current = file?.path === active ? file : null; + const applyNavigation = useCallback( + (view: EditorView) => { + if ( + !appliedTarget?.line || + !tree || + !active || + appliedNavigation.current === appliedTarget || + projectRelativePath(tree.root, appliedTarget.path) !== active + ) + return false; + const pos = view.state.doc.line( + Math.min(appliedTarget.line, view.state.doc.lines), + ).from; + view.dispatch({ + selection: { anchor: pos }, + effects: EditorView.scrollIntoView(pos, { y: "center" }), + }); + appliedNavigation.current = appliedTarget; + return true; + }, + [active, appliedTarget, tree], + ); + useEffect(() => { + const view = editorRef.current?.view; + if (view && current) applyNavigation(view); + }, [applyNavigation, current]); + useEffect(() => () => scrollCleanup.current?.(), []); + useEffect(() => { + if (!active || mode !== "source") { + scrollCleanup.current?.(); + scrollCleanup.current = null; + } + }, [active, mode]); + const refresh = () => { + setLoading(true); + setRevision((v) => v + 1); + }; + const closeTab = (path: string) => { + const next = tabs.filter((tab) => tab.path !== path); + setTabs(next); + if (active === path) { + const last = next.at(-1); + setActive(last?.path ?? null); + setMode(supportsFilePreview(last?.path ?? "") ? "preview" : "source"); + } + }; + const freshPreview = preview?.path === active ? preview.url : null; + const previewError = + previewFailure?.path === active ? previewFailure.message : null; + const fileError = fileFailure?.path === active ? fileFailure.message : null; + + return ( + <> + + {t("filesTitle")} + + {project} + + + + +
+ {treeError ? ( +
+ {treeError} + +
+ ) : null} + {tree?.truncated ? ( +
{t("filesTruncated")}
+ ) : null} + + {treeVisible ? ( + <> + +
+ {tree ? ( + + ) : ( +
+ {loading ? t("filesLoading") : t("filesEmpty")} +
+ )} +
+
+ + + ) : null} + +
+
+ {tabs.map((tab) => ( +
+ + +
+ ))} +
+ {active ? ( + <> +
+ {active} + + +
+ {canPreview ? ( +
+ + + {freshPreview ? ( + + + {t("filesNewTab")} + + ) : null} +
+ ) : null} + {mode === "preview" && canPreview ? ( + previewError ? ( +
+ {previewError} +
+ ) : freshPreview && visible ? ( + `); + const frame = page.frameLocator("iframe"); + await frame.locator("body[data-loaded=yes]").waitFor(); + assert.equal(await frame.locator("img").evaluate((el) => el.complete && el.naturalWidth === 3), true); + assert.equal(await frame.locator("h1").evaluate((el) => getComputedStyle(el).color), "rgb(255, 0, 0)"); + assert.equal(await frame.locator("body").evaluate(() => { + try { return parent.document.body ? "leaked" : "bad"; } catch { return "isolated"; } + }), "isolated"); + assert.equal(await frame.locator("body").evaluate(() => { + try { return localStorage.getItem("preview-parent-secret"); } catch { return "isolated"; } + }), "isolated"); + const direct = await browser.newPage(); + await direct.goto(`${base}${path}`); + await direct.locator("body[data-loaded=yes]").waitFor(); + assert.equal(await direct.evaluate(() => { + try { document.cookie = "preview=unsafe"; return "leaked"; } catch { return "isolated"; } + }), "isolated"); + console.log("Chromium: relative module + stylesheet, iframe and standalone isolation passed"); + if (markdownPath) { + await page.setContent(``); + const markdown = page.frameLocator("iframe"); + await markdown.getByRole("heading", { name: "Markdown document", exact: true }).waitFor(); + assert.equal(await markdown.getByRole("cell", { name: "world", exact: true }).count(), 1); + assert.equal(await markdown.getByRole("checkbox").isChecked(), true); + assert.equal(await markdown.locator("pre code.language-rust").count(), 1); + await markdown.locator("img").evaluate((el) => el.decode()); + assert.equal(await markdown.locator("img").evaluate((el) => el.naturalWidth), 3); + assert.equal(await markdown.locator("script").count(), 0); + await markdown.getByRole("link", { name: "Section", exact: true }).click(); + await markdown.getByRole("link", { name: "Next", exact: true }).click(); + await markdown.getByRole("heading", { name: "Next document", exact: true }).waitFor(); + await direct.goto(`${base}${markdownPath}`); + await direct.getByRole("heading", { name: "Markdown document", exact: true }).waitFor(); + assert.equal(await direct.evaluate(() => { try { return localStorage.length; } catch { return "isolated"; } }), "isolated"); + console.log("Chromium: Markdown tables/tasks/code/images/relative links and standalone rendering passed"); + } +} finally { + await browser.close(); +} diff --git a/crates/ccteam-web/web/tests/v032-spa.spec.ts b/crates/ccteam-web/web/tests/v032-spa.spec.ts index 78c1e7787..9c3288a1e 100644 --- a/crates/ccteam-web/web/tests/v032-spa.spec.ts +++ b/crates/ccteam-web/web/tests/v032-spa.spec.ts @@ -49,6 +49,71 @@ const sessionHistory = { ], }; +for (const width of [1360, 390, 320]) { + test(`compact header, current context and Markdown reply copy at ${width}px`, async ({ page }, testInfo) => { + await mockCcteamApi(page); + await page.setViewportSize({ width, height: 1000 }); + await page.addInitScript(() => { + Object.defineProperty(navigator, "clipboard", { configurable: true, value: { + writeText: async (value: string) => { (window as typeof window & { copiedReply?: string }).copiedReply = value; }, + } }); + }); + const raw = "## Answer\n\n**Keep Markdown** and [the link](https://example.com).\n\n```ts\nconst answer = 42;\n```\n\n| A | B |\n| - | - |\n| 1 | 2 |"; + const facts = { ...sessionRows[0], role: "", vendor: "codex", title: "一个很长的会话标题用于验证窄屏省略与工具栏布局", model: "gpt-test", host: "satellite-1", residency: "released", status: "idle", parent_sid: "s1497", delegation_depth: 2, cost_usd: 716.425, tokens_total: 9000000, last_active: "2026-11-01T22:00:00Z" }; + // The shell is deliberately stale/wrong: Context must use its own sid + // response, including for the history-only path after a release. + await page.route("**/api/v1/projects/dev-team/sessions", route => route.fulfill({ json: [] })); + await page.route("**/api/v1/projects/dev-team/sessions/history", route => route.fulfill({ json: [{...facts,slug:"dev-team",host:"local",parent_sid:null,delegation_depth:0}] })); + await page.route("**/api/v1/sessions/s1", route => route.fulfill({ json: { sid:"s1",events:[ + {turn_id:"t1",ts:"2026-11-02T01:00:00Z",role:"",user:"first",assistant:raw}, + {turn_id:"t2",ts:"2026-11-02T02:00:00Z",role:"",user:"second",assistant:"Second **response**"}, + ] } })); + await page.route("**/api/v1/sessions/s1/status", route => route.fulfill({ json: {sid:"s1",model:"gpt-test",context:{used_tokens:8000,window_tokens:100000,pct:8},status_line:null} })); + let refreshes = 0; + await page.route("**/api/v1/sessions/s1/context", route => { + refreshes += 1; + return route.fulfill({ json: {sid:"s1",live:false,session:{...facts,cost_usd:716.425+refreshes-1},breakdown:{vendor:"codex",model:"gpt-test",captured_at:"2026-11-02T01:00:00Z",used_tokens:8000,window_tokens:100000,categories:[{id:"context_total",name:"Used context",tokens:8000,kind:"used"},{id:"free_space",name:"Free space",tokens:92000,kind:"free"}],last_request:{input_tokens:6000,cache_read_tokens:1000,cache_creation_tokens:0,output_tokens:1000,reasoning_output_tokens:200}}} }); + }); + await page.goto("/app/chat/s/s1"); + const head = page.locator(".conv-head"); + await expect(head.locator(".tabs")).toHaveCount(0); + await expect(head.locator(".cost-pill")).toHaveCount(0); + await expect(head.getByTestId("session-role")).toHaveCount(0); + await expect(head).not.toContainText("无 role"); + await expect(page.getByTestId("ctx-toggle")).toContainText("8%"); + await expect(page.getByTestId("files-toggle")).toBeVisible(); + await expect(page.getByTestId("ctx-toggle")).toBeVisible(); + expect(await head.evaluate(node => node.scrollWidth <= node.clientWidth)).toBe(true); + expect(await page.getByTestId("ctx-toggle").evaluate(node => node.getBoundingClientRect().height)).toBeLessThanOrEqual(44); + expect(await page.locator(".right-panel-tools").evaluate(node => node.getBoundingClientRect().width)).toBeGreaterThan(150); + const copies = page.getByTestId("reply-copy"); + await expect(copies).toHaveCount(2); + await copies.first().click(); + expect(await page.evaluate(() => (window as typeof window & { copiedReply?: string }).copiedReply)).toBe(raw); + await expect(page.locator(".reply-copy-feedback").first()).toHaveText(/已复制|Copied/); + await copies.last().click(); + expect(await page.evaluate(() => (window as typeof window & { copiedReply?: string }).copiedReply)).toBe("Second **response**"); + await page.evaluate(() => { + Object.defineProperty(navigator, "clipboard", {configurable:true,value:{writeText:async()=>{throw Error("denied");}}}); + document.execCommand = () => false; + }); + await copies.last().click(); + await expect(page.locator(".reply-copy-feedback").last()).toHaveText(/复制失败|Copy failed/); + await page.getByTestId("ctx-toggle").click(); + const status = page.getByTestId("ctx-session-status"); + await expect(status).toContainText("satellite-1"); + await expect(status).toContainText("s1497"); + await expect(status).toContainText("$716.4250"); + await expect(page.getByTestId("ctx-codex-scope")).toBeVisible(); + await expect(page.getByTestId("ctx-last-request")).toContainText("200"); + await page.getByTestId("ctx-refresh").click(); + await expect(status).toContainText("$717.4250"); + await page.screenshot({path:testInfo.outputPath(`context-${width}.png`),fullPage:true}); + await page.getByTestId("ctx-close").click(); + await expect(page.getByTestId("ctx-toggle")).toBeFocused(); + }); +} + const statusSnapshot = { daemon_healthy: true, sessions_live: 1, @@ -161,7 +226,10 @@ async function mockCcteamApi(page: Page): Promise { route.fulfill({ status: 200, json: { wire_token: null } }), ); await page.route("**/api/v1/me", (route) => - route.fulfill({ status: 200, json: { id: "admin", handle: "owner", is_admin: true } }), + route.fulfill({ + status: 200, + json: { id: "admin", handle: "owner", is_admin: true }, + }), ); await page.route("**/api/v1/status", (route) => route.fulfill({ status: 200, json: statusSnapshot }), @@ -171,7 +239,13 @@ async function mockCcteamApi(page: Page): Promise { status: 200, json: { hosts: [ - { host: "local", hostname: "dev01", is_local: true, agent_count: 5, agents_ready: 5 }, + { + host: "local", + hostname: "dev01", + is_local: true, + agent_count: 5, + agents_ready: 5, + }, ], }, }), @@ -181,7 +255,8 @@ async function mockCcteamApi(page: Page): Promise { status: 200, json: { token: "e2e-join-token", - command: "ccteam host join --daemon --token e2e-join-token", + command: + "ccteam host join --daemon --token e2e-join-token", }, }), ); @@ -285,12 +360,18 @@ async function mockCcteamApi(page: Page): Promise { ); await page.route("**/api/v1/projects", (route) => { if (route.request().method() === "POST") { - return route.fulfill({ status: 201, json: { slug: "new-proj", path: "/tmp/new-proj" } }); + return route.fulfill({ + status: 201, + json: { slug: "new-proj", path: "/tmp/new-proj" }, + }); } return route.fulfill({ status: 200, json: dashboardRows }); }); await page.route("**/api/v1/projects/dev-team/roles", (route) => - route.fulfill({ status: 200, json: [{ role: "cto", description: "默认管家", model: "" }] }), + route.fulfill({ + status: 200, + json: [{ role: "cto", description: "默认管家", model: "" }], + }), ); await page.route("**/api/v1/projects/dev-team/sessions", (route) => { if (route.request().method() === "POST") { @@ -315,12 +396,20 @@ async function mockCcteamApi(page: Page): Promise { await page.route("**/api/v1/sessions/*/status", (route) => route.fulfill({ status: 200, - json: { sid: "s1", model: null, effort: null, context: null, status_line: null }, + json: { + sid: "s1", + model: null, + effort: null, + context: null, + status_line: null, + }, }), ); async function captureJson( - route: Parameters[1] extends (route: infer R) => unknown ? R : never, + route: Parameters[1] extends (route: infer R) => unknown + ? R + : never, ) { const req = route.request(); captured.push({ @@ -363,21 +452,29 @@ test("desktop shell: sidebar groups sessions by project and opens a sid conversa await expect(page.getByText("hi from history")).toBeVisible(); }); -test("conversation posts a turn; sidebar hover-stop stops the session", async ({ page }) => { +test("conversation posts a turn; sidebar hover-stop stops the session", async ({ + page, +}) => { const captured = await mockCcteamApi(page); await page.goto("/app/chat/s/s1"); await page.getByTestId("composer-textarea").fill("ship gate note"); await page.getByTestId("composer-send").click(); await expect - .poll(() => captured.some((r) => r.url.endsWith("/api/v1/sessions/s1/turn"))) + .poll(() => + captured.some((r) => r.url.endsWith("/api/v1/sessions/s1/turn")), + ) .toBe(true); // The sidebar row's hover stop (prototype `.srow .stop`) → POST /stop. await page.getByText("评审登录页").first().hover(); - await page.getByRole("button", { name: "停止(状态保留,可 resume) s1", exact: true }).click(); + await page + .getByRole("button", { name: "停止(状态保留,可 resume) s1", exact: true }) + .click(); await expect - .poll(() => captured.some((r) => r.url.endsWith("/api/v1/sessions/s1/stop"))) + .poll(() => + captured.some((r) => r.url.endsWith("/api/v1/sessions/s1/stop")), + ) .toBe(true); expect(captured).toEqual( @@ -409,11 +506,15 @@ test("home lazy-create: first message POSTs session without a host override, the await expect .poll(() => captured.some( - (r) => r.url.endsWith("/api/v1/projects/dev-team/sessions") && r.method === "POST", + (r) => + r.url.endsWith("/api/v1/projects/dev-team/sessions") && + r.method === "POST", ), ) .toBe(true); - const create = captured.find((r) => r.url.endsWith("/projects/dev-team/sessions")); + const create = captured.find((r) => + r.url.endsWith("/projects/dev-team/sessions"), + ); expect(create?.body).toMatchObject({ vendor: "claude", protocol: "stream-json", @@ -423,7 +524,9 @@ test("home lazy-create: first message POSTs session without a host override, the // The first message rides as the new session's first turn → Conversation. await expect - .poll(() => captured.some((r) => r.url.endsWith("/api/v1/sessions/s2/turn"))) + .poll(() => + captured.some((r) => r.url.endsWith("/api/v1/sessions/s2/turn")), + ) .toBe(true); const turn = captured.find((r) => r.url.endsWith("/api/v1/sessions/s2/turn")); expect(turn?.body).toEqual({ text: "修复登录页布局" }); @@ -440,19 +543,30 @@ test("Pi picker keeps role, uses provider/model-id, and exposes strict-HITL trad await page.getByRole("button", { name: /cto/ }).click(); await page.getByTestId("model-btn").click(); - await page.getByRole("button", { name: /anthropic\/claude-opus-4-6/ }).click(); - await expect(page.getByTestId("model-btn")).toContainText("Pi · anthropic/claude-opus-4-6"); + await page + .getByRole("button", { name: /anthropic\/claude-opus-4-6/ }) + .click(); + await expect(page.getByTestId("model-btn")).toContainText( + "Pi · anthropic/claude-opus-4-6", + ); await page.getByTestId("model-btn").click(); await page.getByRole("button", { name: "xhigh", exact: true }).click(); await page.getByTestId("hitl-toggle").click(); await expect(page.getByTestId("pi-hitl-tradeoff")).toContainText("用户扩展"); - await expect(page.getByTestId("pi-hitl-tradeoff")).toContainText("技能、上下文文件和提示词模板不受影响"); + await expect(page.getByTestId("pi-hitl-tradeoff")).toContainText( + "技能、上下文文件和提示词模板不受影响", + ); await page.getByTestId("composer-textarea").fill("验证 Pi 配方"); await page.getByTestId("home-send").click(); await expect - .poll(() => captured.find((request) => request.url.endsWith("/projects/dev-team/sessions"))?.body) + .poll( + () => + captured.find((request) => + request.url.endsWith("/projects/dev-team/sessions"), + )?.body, + ) .toMatchObject({ role: "cto", vendor: "pi", @@ -479,13 +593,10 @@ test("工作流 and 设置 render as set-nav views; ops merges Status + Hosts re await expect(page.getByTestId("status-view")).toBeVisible(); await expect(page.getByTestId("hosts-view")).toBeVisible(); await expect(page.getByText(/1 .*live/).first()).toBeVisible(); - await expect - .poll(() => - page - .getByTestId("ops-view") - .evaluate((node) => getComputedStyle(node).gridTemplateColumns.split(" ").length), - ) - .toBe(2); + await expect(page.getByTestId("ops-view")).toHaveCSS( + "flex-direction", + "column", + ); // Both legacy settings and flat routes canonicalize to the merged tab. await page.goto("/app/settings/hosts"); @@ -496,13 +607,17 @@ test("工作流 and 设置 render as set-nav views; ops merges Status + Hosts re await expect(page).toHaveURL(/\/app\/settings\/ops$/); await page.setViewportSize({ width: 390, height: 844 }); + await expect(page.getByTestId("ops-view")).toHaveCSS( + "flex-direction", + "column", + ); await expect .poll(() => page .getByTestId("ops-view") - .evaluate((node) => getComputedStyle(node).gridTemplateColumns.split(" ").length), + .evaluate((node) => node.getBoundingClientRect().right), ) - .toBe(1); + .toBeLessThanOrEqual(391); }); test("mobile ≤820px: sidebar is a drawer behind the hamburger with a backdrop", async ({ @@ -523,11 +638,15 @@ test("mobile ≤820px: sidebar is a drawer behind the hamburger with a backdrop" await expect(page.getByTestId("side-backdrop")).toBeVisible(); // Backdrop click closes it again. - await page.getByTestId("side-backdrop").click({ position: { x: 350, y: 400 } }); + await page + .getByTestId("side-backdrop") + .click({ position: { x: 350, y: 400 } }); await expect(sidebar).not.toBeInViewport(); }); -test("401 after auth-required bootstrap shows the token entry flow", async ({ page }) => { +test("401 after auth-required bootstrap shows the token entry flow", async ({ + page, +}) => { await page.route("**/api/v1/auth/token", (route) => route.fulfill({ status: 200, json: { wire_token: "ccteam:deadbeef" } }), ); @@ -537,12 +656,14 @@ test("401 after auth-required bootstrap shows the token entry flow", async ({ pa await page.route("**/api/v1/me", (route) => route.fulfill({ status: 401, json: { error: "auth required" } }), ); - await page.route("**/sse/**", (route) => route.fulfill({ status: 200, body: "\n" })); + await page.route("**/sse/**", (route) => + route.fulfill({ status: 200, body: "\n" }), + ); await page.goto("/app/"); - await expect(page.getByLabel("Token or URL")).toBeVisible(); - await page.getByLabel("Token or URL").fill("abc123"); - await page.getByRole("button", { name: "Connect" }).click(); + await expect(page.getByLabel("访问令牌")).toBeVisible(); + await page.getByLabel("访问令牌").fill("abc123"); + await page.getByRole("button", { name: "登录", exact: true }).click(); await expect(page).toHaveURL(/\/\?token=ccteam%3Aabc123$/); }); @@ -557,8 +678,399 @@ test("fresh unauthenticated load surfaces the token entry page, not a wedged she await page.route("**/api/**", (route) => route.fulfill({ status: 401, body: "auth required" }), ); - await page.route("**/sse/**", (route) => route.fulfill({ status: 200, body: "\n" })); + await page.route("**/sse/**", (route) => + route.fulfill({ status: 200, body: "\n" }), + ); await page.goto("/app/"); - await expect(page.getByLabel("Token or URL")).toBeVisible(); + await expect(page.getByLabel("访问令牌")).toBeVisible(); +}); + +test("file workspace preserves Context, refreshes paths, and previews HTML", async ({ + page, +}) => { + await mockCcteamApi(page); + const paths = ["index.html", "README.md", "src/", "src/lib.rs"]; + await page.route("**/api/v1/projects/dev-team/files", (route) => + route.fulfill({ + json: { + root: "/repo", + paths, + git_status: { "README.md": "modified" }, + truncated: false, + }, + }), + ); + await page.route("**/api/v1/projects/dev-team/file?*", (route) => { + const path = new URL(route.request().url()).searchParams.get("path"); + return route.fulfill({ + json: { + path, + content: + path === "README.md" + ? "# Project\nRead me" + : "

Hello preview

", + size: 30, + binary: false, + }, + }); + }); + await page.route("**/api/v1/projects/dev-team/files/preview", (route) => + route.fulfill({ + json: { + url: "/api/v1/projects/dev-team/files/preview/test/index.html", + expires_in: 3600, + }, + }), + ); + await page.route("**/files/preview/test/index.html", (route) => + route.fulfill({ + contentType: "text/html", + headers: { "content-security-policy": "sandbox allow-scripts" }, + body: "

Hello preview

", + }), + ); + await page.route("**/api/v1/sessions/s1/context", (route) => + route.fulfill({ json: { sid: "s1", live: false, breakdown: null } }), + ); + await page.goto("/app/chat/s/s1"); + await page.getByTestId("files-toggle").click(); + await expect(page.getByTestId("ctx-toggle")).toBeVisible(); + await page + .getByTestId("project-file-tree") + .getByRole("treeitem", { name: "index.html", exact: true }) + .click(); + await expect( + page + .frameLocator("iframe.files-preview") + .getByRole("heading", { name: "Hello preview" }), + ).toBeVisible(); + await page + .frameLocator("iframe.files-preview") + .getByRole("button", { name: "Click me" }) + .click(); + await expect( + page + .frameLocator("iframe.files-preview") + .getByRole("button", { name: "Clicked" }), + ).toBeVisible(); + await page.getByTestId("ctx-toggle").click(); + await expect(page.getByTestId("context-panel")).toBeVisible(); + await expect(page.getByTestId("file-workspace")).not.toBeVisible(); + await page.getByTestId("files-toggle").click(); + await expect(page.getByRole("tab", { name: "index.html" })).toBeVisible(); + await page.getByRole("button", { name: /^(源码|Source)$/ }).click(); + await expect(page.locator(".cm-content")).toContainText("Hello preview"); + paths.push("new.txt"); + await page.getByRole("button", { name: /刷新文件|Refresh files/ }).click(); + await expect( + page + .getByTestId("project-file-tree") + .getByRole("treeitem", { name: "new.txt", exact: true }), + ).toBeVisible(); + await expect(page.locator("iframe.files-preview")).toHaveCount(0); + await expect(page.locator(".cm-content")).toContainText("Hello preview"); + await page + .getByTestId("project-file-tree") + .getByRole("treeitem", { name: "README.md", exact: true }) + .dblclick(); + await page.getByRole("button", { name: /^(源码|Source)$/ }).click(); + await expect(page.locator(".cm-content")).toContainText("Read me"); + await page.keyboard.press("Escape"); + await expect(page.getByTestId("file-workspace")).not.toBeVisible(); + await expect(page.getByTestId("files-toggle")).toBeFocused(); +}); + +test.describe("touch workspace", () => { + test.use({ hasTouch: true, isMobile: true }); + + async function openWorkspace(page: Page, width: number, height: number) { + await mockCcteamApi(page); + await page.setViewportSize({ width, height }); + await page.route("**/api/v1/projects/dev-team/files", route => route.fulfill({json:{root:"/repo",paths:["mobile.txt"],git_status:{},truncated:false}})); + await page.route("**/api/v1/projects/dev-team/file?*", route => route.fulfill({json:{path:"mobile.txt",content:"Keep this reading position",size:26,binary:false}})); + await page.route("**/api/v1/sessions/s1/context", route => route.fulfill({json:{sid:"s1",live:false,breakdown:null}})); + await page.goto("/app/chat/s/s1"); + await page.getByTestId("files-toggle").tap(); + await expect(page.getByTestId("file-workspace")).toBeVisible(); + } + + async function expectToolbarClickable(page: Page) { + for (const id of ["files-toggle", "ctx-toggle"]) { + await expect(page.getByTestId(id)).toBeInViewport(); + expect(await page.getByTestId(id).evaluate(element => { + const bounds = element.getBoundingClientRect(); + return document.elementFromPoint(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2)?.closest("button") === element; + })).toBe(true); + } + } + + for (const [width, height] of [[320,720],[390,844],[768,1024],[820,1180],[834,1194],[1024,768],[1180,820],[1440,900]]) { + test(`single taps open, close and switch panels at ${width}x${height}`, async ({ page }, testInfo) => { + await openWorkspace(page, width, height); + await expectToolbarClickable(page); + await page.getByTestId("files-toggle").tap(); + await expect(page.getByTestId("file-workspace")).not.toBeVisible(); + await page.getByTestId("files-toggle").tap(); + await expect(page.getByTestId("file-workspace")).toBeVisible(); + await page.getByTestId("project-file-tree").getByRole("treeitem", {name:"mobile.txt",exact:true}).tap(); + await expect(page.locator(".cm-content")).toContainText("Keep this reading position"); + const before = await page.getByTestId("file-workspace").boundingBox(); + await page.getByRole("button", {name:/放大工作区|Expand workspace/}).tap(); + const expanded = await page.getByTestId("file-workspace").boundingBox(); + const body = await page.locator(".conv-body").boundingBox(); + expect(expanded!.width).toBeGreaterThan(before!.width + 2); + expect(Math.abs(expanded!.width - body!.width)).toBeLessThan(2); + await expectToolbarClickable(page); + await page.screenshot({path:testInfo.outputPath(`files-expanded-${width}x${height}.png`)}); + await page.getByRole("button", {name:/恢复并排布局|Restore split view/}).tap(); + await page.getByTestId("ctx-toggle").tap(); + await expect(page.getByTestId("context-panel")).toBeVisible(); + await expect(page.getByTestId("file-workspace")).not.toBeVisible(); + await expectToolbarClickable(page); + await page.screenshot({path:testInfo.outputPath(`workspace-${width}x${height}.png`)}); + await page.getByTestId("ctx-toggle").tap(); + await expect(page.getByTestId("context-panel")).not.toBeVisible(); + await page.getByTestId("files-toggle").tap(); + await expect(page.locator(".cm-content")).toContainText("Keep this reading position"); + await page.getByRole("button", {name:/关闭文件面板|Close file panel/}).last().tap(); + await expect(page.getByTestId("file-workspace")).not.toBeVisible(); + await expect(page.getByTestId("files-toggle")).toBeFocused(); + }); + } + + test("expanded tablet workspace stays operable through rotation", async ({page}) => { + await openWorkspace(page, 1180, 820); + await page.getByRole("button", {name:/放大工作区|Expand workspace/}).tap(); + await page.setViewportSize({width:820,height:1180}); + await expectToolbarClickable(page); + await page.getByRole("button", {name:/恢复并排布局|Restore split view/}).tap(); + await page.getByTestId("ctx-toggle").tap(); + await expect(page.getByTestId("context-panel")).toBeVisible(); + await page.setViewportSize({width:1180,height:820}); + await expectToolbarClickable(page); + await page.getByTestId("files-toggle").tap(); + await expect(page.getByTestId("file-workspace")).toBeVisible(); + }); + + test("saved desktop layout survives narrow drawers, backdrop taps and rotation", async ({page}) => { + const layout = JSON.stringify({"session-chat":60,"session-right-workspace":40}); + await page.addInitScript(value => { + localStorage.setItem("ccteam.workspace.layout", value); + localStorage.setItem("ccteam.files.open", "1"); + }, layout); + // A remembered desktop panel must still open on the first phone tap. + await openWorkspace(page, 390, 844); + const savedLayout = () => page.evaluate(() => localStorage.getItem("ccteam.workspace.layout")); + expect(await savedLayout()).toBe(layout); + await page.locator(".files-backdrop").tap({position:{x:1,y:100}}); + await expect(page.getByTestId("file-workspace")).not.toBeVisible(); + await expect(page.getByTestId("files-toggle")).toBeFocused(); + await page.getByTestId("ctx-toggle").tap(); + await page.getByTestId("ctx-backdrop").tap({position:{x:1,y:100}}); + await expect(page.getByTestId("context-panel")).not.toBeVisible(); + await expect(page.getByTestId("ctx-toggle")).toBeFocused(); + await page.getByTestId("files-toggle").tap(); + await page.setViewportSize({width:834,height:1194}); + await expectToolbarClickable(page); + expect(await savedLayout()).toBe(layout); + await page.setViewportSize({width:390,height:844}); + await expectToolbarClickable(page); + await page.getByTestId("files-toggle").tap(); + await expect(page.getByTestId("file-workspace")).not.toBeVisible(); + expect(await savedLayout()).toBe(layout); + }); +}); + +test("mobile Files drawer accepts clicks above its backdrop", async ({ + page, +}) => { + await mockCcteamApi(page); + await page.setViewportSize({ width: 390, height: 844 }); + await page.route("**/api/v1/projects/dev-team/files", (route) => + route.fulfill({ + json: { + root: "/repo", + paths: ["mobile.txt"], + git_status: {}, + truncated: false, + }, + }), + ); + await page.route("**/api/v1/projects/dev-team/file?*", (route) => + route.fulfill({ + json: { + path: "mobile.txt", + content: "Mobile source", + size: 13, + binary: false, + }, + }), + ); + await page.goto("/app/chat/s/s1"); + await page.getByTestId("files-toggle").click(); + await page + .getByTestId("project-file-tree") + .getByRole("treeitem", { name: "mobile.txt", exact: true }) + .click(); + await expect(page.locator(".cm-content")).toContainText("Mobile source"); + await page + .getByRole("button", { name: /关闭文件面板|Close file panel/ }) + .last() + .click(); + await expect(page.getByTestId("file-workspace")).not.toBeVisible(); +}); + +test("file workspace follows repeated line links into the same file", async ({ + page, +}) => { + await mockCcteamApi(page); + await page.route("**/api/v1/sessions/s1", (route) => + route.fulfill({ + json: { + sid: "s1", + events: [ + { + turn_id: "links", + role: "reviewer", + user: "inspect", + assistant: + "[Lower](/repo/src/lib.rs:130) [Upper](/repo/src/lib.rs:10)", + }, + ], + }, + }), + ); + await page.route("**/api/v1/projects/dev-team/files", (route) => + route.fulfill({ + json: { + root: "/repo", + paths: ["other.txt", "src/", "src/lib.rs"], + git_status: {}, + truncated: false, + }, + }), + ); + await page.route("**/api/v1/projects/dev-team/file?*", (route) => { + const path = new URL(route.request().url()).searchParams.get("path"); + return route.fulfill({ + json: { + path, + content: + path === "other.txt" + ? "Other file" + : Array.from({ length: 160 }, (_, i) => `// line ${i + 1}`).join( + "\n", + ), + size: 2000, + binary: false, + }, + }); + }); + await page.goto("/app/chat/s/s1"); + await page.getByRole("link", { name: "Lower", exact: true }).click(); + await expect(page.locator(".cm-content")).toBeVisible(); + await expect( + page + .getByTestId("project-file-tree") + .getByRole("treeitem", { name: "lib.rs", exact: true }), + ).toBeVisible(); + await expect + .poll(() => page.locator(".cm-scroller").evaluate((el) => el.scrollTop)) + .toBeGreaterThan(1000); + await page.getByRole("link", { name: "Upper", exact: true }).click(); + await expect + .poll(() => page.locator(".cm-scroller").evaluate((el) => el.scrollTop)) + .toBeLessThan(500); + await expect( + page.getByRole("tab", { name: "lib.rs", exact: true }), + ).toHaveCount(1); + await page.locator(".cm-scroller").hover(); + await page.mouse.wheel(0, 64); + await expect + .poll(() => page.locator(".cm-scroller").evaluate((el) => el.scrollTop)) + .toBeGreaterThan(20); + const readingPosition = await page + .locator(".cm-scroller") + .evaluate((el) => el.scrollTop); + await page + .getByTestId("project-file-tree") + .getByRole("treeitem", { name: "other.txt", exact: true }) + .click(); + await expect(page.locator(".cm-content")).toContainText("Other file"); + await page.getByRole("tab", { name: "lib.rs", exact: true }).click(); + await expect + .poll(() => page.locator(".cm-scroller").evaluate((el) => el.scrollTop)) + .toBeGreaterThan(readingPosition - 5); + await expect + .poll(() => page.locator(".cm-scroller").evaluate((el) => el.scrollTop)) + .toBeLessThan(readingPosition + 5); +}); + +test("file workspace previews Markdown and opens the rendered document in a new page", async ({ + page, +}) => { + await mockCcteamApi(page); + await page.route("**/api/v1/projects/dev-team/files", (route) => + route.fulfill({ + json: { + root: "/repo", + paths: ["README.md"], + git_status: {}, + truncated: false, + }, + }), + ); + await page.route("**/api/v1/projects/dev-team/file?*", (route) => + route.fulfill({ + json: { + path: "README.md", + content: "# Markdown document\n\n| Name | Value |", + size: 44, + binary: false, + }, + }), + ); + await page.route("**/api/v1/projects/dev-team/files/preview", (route) => + route.fulfill({ + json: { + url: "/api/v1/projects/dev-team/files/preview/markdown/README.md", + expires_in: 3600, + }, + }), + ); + const markdownResponse = { + contentType: "text/html", + headers: { "content-security-policy": "sandbox" }, + body: "README.md

Markdown document

world
", + }; + await page.route("**/files/preview/markdown/README.md", (route) => route.fulfill(markdownResponse)); + // A popup's initial navigation belongs to the context, before its Page + // exists. Page-scoped mocks only cover the parent iframe request. + await page.context().route("**/files/preview/markdown/README.md", (route) => route.fulfill(markdownResponse)); + await page.goto("/app/chat/s/s1"); + await page.getByTestId("files-toggle").click(); + await page + .getByTestId("project-file-tree") + .getByRole("treeitem", { name: "README.md", exact: true }) + .click(); + await expect( + page + .frameLocator("iframe.files-preview") + .getByRole("heading", { name: "Markdown document" }), + ).toBeVisible(); + await page.getByRole("button", { name: /^(源码|Source)$/ }).click(); + await expect(page.locator(".cm-content")).toContainText( + "# Markdown document", + ); + await page.getByRole("button", { name: /^(预览|Preview)$/ }).click(); + const popup = page.waitForEvent("popup"); + await page + .getByRole("link", { name: /新标签页打开|Open in new tab/ }) + .click(); + const standalone = await popup; + await expect( + standalone.getByRole("heading", { name: "Markdown document" }), + ).toBeVisible(); + await expect(standalone).toHaveTitle("README.md"); }); diff --git a/crates/ccteam-web/web/tests/v090-agents.spec.ts b/crates/ccteam-web/web/tests/v090-agents.spec.ts index f4ce58515..709b74d91 100644 --- a/crates/ccteam-web/web/tests/v090-agents.spec.ts +++ b/crates/ccteam-web/web/tests/v090-agents.spec.ts @@ -43,38 +43,19 @@ const agentsGraph = { async function mockAgentsApi(page: Page): Promise { await page.addInitScript(() => { - class MockEventSource extends EventTarget { - static CONNECTING = 0; - static OPEN = 1; - static CLOSED = 2; - - readonly url: string; - readonly withCredentials = false; - readyState = MockEventSource.CONNECTING; - onopen: ((event: Event) => void) | null = null; - onmessage: ((event: MessageEvent) => void) | null = null; - onerror: ((event: Event) => void) | null = null; - - constructor(url: string | URL) { - super(); - this.url = String(url); - const bag = (window as unknown as { __agentsEventSources: MockEventSource[] }); - bag.__agentsEventSources = bag.__agentsEventSources || []; - bag.__agentsEventSources.push(this); - queueMicrotask(() => { - this.readyState = MockEventSource.OPEN; - const event = new Event("open"); - this.onopen?.(event); - this.dispatchEvent(event); - }); - } - - close() { - this.readyState = MockEventSource.CLOSED; - } - } - (window as unknown as { EventSource: typeof EventSource }).EventSource = - MockEventSource as unknown as typeof EventSource; + const originalFetch = window.fetch.bind(window); + window.fetch = (input, init) => { + const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; + if (!url.includes("/api/v1/agents/events")) return originalFetch(input, init); + const body = new ReadableStream({ + start(controller) { + const bag = window as unknown as { __pushAgentsEvent?: (payload: Record) => void }; + bag.__pushAgentsEvent = (payload) => controller.enqueue(new TextEncoder().encode(`event: delegation\ndata: ${JSON.stringify(payload)}\n\n`)); + init?.signal?.addEventListener("abort", () => controller.close(), { once: true }); + }, + }); + return Promise.resolve(new Response(body, { headers: { "content-type": "text/event-stream" } })); + }; }); await page.route("**/api/**", (route) => @@ -92,15 +73,11 @@ async function mockAgentsApi(page: Page): Promise { ); } -/** Dispatch a named SSE frame into the `/api/v1/agents/events` mock - * EventSource instance the page created (there is exactly one — AgentsView - * opens it once on mount). */ +/** Exercise the actual fetch-backed SSE reader, after its stream is ready. */ async function pushAgentsEvent(page: Page, payload: Record): Promise { + await expect.poll(() => page.evaluate(() => typeof (window as unknown as { __pushAgentsEvent?: unknown }).__pushAgentsEvent)).toBe("function"); await page.evaluate((p) => { - const bag = window as unknown as { __agentsEventSources?: EventTarget[] }; - const sources = bag.__agentsEventSources ?? []; - const es = sources.find((s) => (s as unknown as { url: string }).url.includes("/agents/events")); - es?.dispatchEvent(new MessageEvent("delegation", { data: JSON.stringify(p) })); + (window as unknown as { __pushAgentsEvent: (payload: Record) => void }).__pushAgentsEvent(p); }, payload); } diff --git a/docs/dsh-plugin-cn.md b/docs/dsh-plugin-cn.md index 442a90b95..d26b634a4 100644 --- a/docs/dsh-plugin-cn.md +++ b/docs/dsh-plugin-cn.md @@ -118,7 +118,7 @@ home、一个 daemon。规则如下: ## 4. 「引擎」段、首屏与横幅 -**设置卡。** DSH **设置 → 插件 → 插件配置 → ccteam-ui** 卡的最上面就是「引擎」段: +**设置卡。** DSH **设置 → ccteam-ui** 卡的最上面就是「引擎」段: - **状态** —— 一个状态点加下列之一:「正在读取引擎状态…」·「运行中」(本插件起的)· 「已挂靠」(别处起的;提示行「由 CLI/其他入口启动,插件已接管显示」)·「启动中」· @@ -148,8 +148,8 @@ home、一个 daemon。规则如下: 停止的进程。」 **工作台内。** 头部有一个「引擎:<状态>」状态点;点它在工作台内打开同一个引擎面板 -(DSH 没给插件跳到设置页的入口,所以面板末尾写着「引擎设置在 DSH 设置 → 插件 → -插件配置 → ccteam-ui」);**Esc** 关闭。首次使用时工作台闸在引擎上: +(DSH 没给插件跳到设置页的入口,所以面板末尾写着「引擎设置在 DSH 设置 → +ccteam-ui」);**Esc** 关闭。首次使用时工作台闸在引擎上: - **「ccteam 引擎未运行」** —— 附原因(「引擎已安装,但 daemon 没有运行。」或 「还没有安装引擎;启动时会从插件自带的平台包安装。」)和一个「启动引擎」按钮。 @@ -165,11 +165,9 @@ home、一个 daemon。规则如下: ## 5. 配置三个面 -连接设置在同一张卡上。DSH 只对它认为是「操作者本机」的浏览器显示这一页:手起 -的 `dsh web` 请从 `127.0.0.1` 打开,或经 ccteam 的 DSH 页访问(ccteam 会声明 -该页拥有其 Host,见 usage.md → DSH Web;对 0.1.1-rc.2 及更早版本,ccteam 把这 -处读取回填进下发的客户端 bundle);从局域网地址直接打开原生 `dsh web`,这一页 -会是空白。 +同一张卡配置所有连接。设置直接写入 DSH 原生 profile 配置并实时生效。 +凭据保持只写,界面仅读取 Host 提供的「是否已配置」元数据;重新注册插件 +会保留已保存的用户偏好和未被本次注册指定的凭据。 | 字段 | 是什么 | 什么时候填 | |---|---|---| @@ -195,7 +193,7 @@ home、一个 daemon。规则如下: harness 双字母格、标题、`harness · 模型 · 时间`、状态点和累计成本;委派出 的子会话缩进在父会话下面。状态点先说活动(daemon 的判定:动画 = 工作中、 绿 = 空闲、琥珀 = 停滞、红 = 卡住);空闲会话再显示常驻状态——空心圈表示 - 进程已在 harness 的 prompt-cache TTL 到期后被释放、下条消息按 sid 自动恢复, + 进程已在设定的空闲窗口(默认 250 分钟)后被释放、下条消息按 sid 自动恢复, 灰点(附「已停止」)表示你停掉了它;悬停行会解释当前状态。项目头可折叠并 显示该项目的总成本;悬停项目头会出现 **⋯**(新建会话、复制 slug、仅展开此 项目、折叠全部)和 **+**(打开新建会话页并预选该项目)——与 DSH 自己的 @@ -236,7 +234,7 @@ harness 的目录里选**模型**和 **effort**(留空即 harness 默认), **Esc** 先离开文本框,再关闭详情,整页时先停靠回去,最后关闭窗格。工作台关闭时,ccteam 按钮会显示自上次打开以来完成的回合数;打开后徽标清零。 -工作台需要 DSH 0.1.0-rc.7 起提供的原生侧栏底部座位与 overlay 座位。 +工作台通过 DSH 公共 slot 挂接侧栏入口、overlay 和独立设置页。 ## 7. 排查问题 @@ -251,13 +249,15 @@ harness 的目录里选**模型**和 **effort**(留空即 harness 默认), | **启动时报 `duplicate loader entry id`** | 同一个插件被插入了两次(常见于 registry 和 bundle patch 都有,或手改了 `cordis.patch.yml`)。只保留一条,删除重复项。ccteam 自己的注册绝不会在你装的那份旁边再加一条。 | | **`cordis.patch.yml` 被重新排版 / 注释没了** | ccteam 第一次向你手管的 profile 注册时会把 patch 文件重新序列化:格式被规范化,YAML 注释不保留(`serde_yaml` 没有保注释的往返)。之后只要内容不变 ccteam 就不再重写该文件——重复注册字节完全一致——且每次写入都是原子的(临时文件 + rename,权限 0600)。备注请放在 patch 文件之外。 | | **`ccteam doctor` 报 `plugin_version_mismatch`** | 你自己 `dsh plugin add` 的那份与这个 ccteam 内嵌的版本不一致。ccteam 不动它;执行 `dsh plugin --profile web update @ccteam/ccteam-ui`。 | -| **侧栏里没有 ccteam 按钮** | 插件需要 DSH 0.1.0-rc.7 或更新(原生侧栏底部座位与 overlay 座位)。升级 DSH 后,到 设置 → 插件 → 插件列表 确认 `ccteam-ui` 为 Enabled。 | +| **侧栏里没有 ccteam 按钮** | 插件需要 DSH 0.2.2-alpha.2 或更新(原生侧栏底部座位与 overlay 座位)。升级 DSH 后,到 设置 → 插件 → 插件列表 确认 `ccteam-ui` 为 Enabled。 | | **局域网明文 HTTP 异常** | 参阅 [usage.md](usage.md) 的“Access and security”安全上下文说明。 | | **DSH 里人手打的 turn 不在 ccteam 账本里** | 这是设计如此:DSH 自己页面里的输入属于 harness 原生对话;ccteam 只记录自己路由的 turn,完整对话仍保存在 DSH 中。 | ## 8. 版本与更新 -需要 **DSH 0.1.0-rc.7 或更高版本**。 +当前插件使用 **DSH 0.2.2-alpha.2** 提供的原生 Config/forms 与公共 slot。 +DSH 与 ccteam 无须版本锁步;会话 preset 和未指定时的默认值由 DSH 自己解析, +新增 preset 不需要 ccteam 更新。DSH 如果破坏插件 API,仍需要适配该 API。 **版本锁步。** 每个插件发布都钉死它一起发布的引擎版本(`package.json` 里的 `ccteam.engine`,与它依赖的 `@ccteam/engine-*` 版本相同)。没有 daemon 在跑 diff --git a/docs/dsh-plugin.md b/docs/dsh-plugin.md index 22ade3a52..4230d8b46 100644 --- a/docs/dsh-plugin.md +++ b/docs/dsh-plugin.md @@ -144,7 +144,7 @@ same OS user all share one home and one daemon. The rules: ## 4. The Engine section, the first screen and the banner -**Settings card.** DSH **Settings → Plugins → Plugin configuration → +**Settings card.** DSH **Settings → ccteam-ui** opens with the **Engine** section: - **State** — a dot and one of: *Reading engine state…* · **Running** @@ -187,7 +187,7 @@ engine is not on this machine; there is nothing here to start or stop.* **Inside the workbench.** The header carries an **Engine: ** dot; clicking it opens the same engine panel inside the workbench (DSH gives a plugin no way to jump to its settings page, so the panel ends with *Engine -settings: DSH Settings → Plugins → Plugin configuration → ccteam-ui*); **Esc** +settings: DSH Settings → ccteam-ui*); **Esc** closes it. On first use the workbench is gated on the engine: - **The ccteam engine is not running** — with the reason (*The engine is @@ -211,12 +211,10 @@ closes it. On first use the workbench is gated on the engine: ## 5. Configure the faces -The same card holds the connection settings. DSH shows this tab only to a -browser it considers the operator's own machine: open a hand-started -`dsh web` from `127.0.0.1`, or go through ccteam's DSH page, which declares -the page owns its Host (see usage.md → DSH Web; on 0.1.1-rc.2 and earlier -ccteam back-ports that read into the served client bundle); a native -`dsh web` opened over a LAN address leaves the tab empty. +The same card holds the connection settings. DSH persists live preferences +in the profile's native configuration. Credentials remain write-only; the +card reads only the Host's configured-state metadata. Registering the plugin +again preserves preferences and credentials already saved in that profile. | Field | What it is | When you fill it | |---|---|---| @@ -250,7 +248,7 @@ to it, and the details sheet slides over the conversation. parent. The dot is the daemon’s activity verdict first (animated = working, green = idle, amber = stale, red = stuck); a settled session then shows its residency — a hollow ring means the daemon released its process after the - harness’s prompt-cache TTL and it resumes by sid on the next message, a + configured idle window (250 minutes by default) and it resumes by sid on the next message, a dimmed disc (with a “stopped” caption) means you stopped it. Hovering a row explains the state. Project headers fold and show the project’s total cost; hovering one reveals **⋯** (new session, copy slug, expand only this @@ -308,7 +306,7 @@ completion count for turns finished since the last open; opening it clears the badge. The workbench needs the native sidebar-footer and overlay seats DSH ships -since 0.1.0-rc.7. +through its public plugin slots. ## 7. Troubleshooting @@ -323,13 +321,16 @@ since 0.1.0-rc.7. | **`duplicate loader entry id` at boot** | The plugin was inserted twice (for example, registry plus bundle patch, or a hand-edited `cordis.patch.yml`). Keep exactly one entry and remove the duplicate. ccteam's own registration never adds a second entry next to one you installed. | | **`cordis.patch.yml` came back reformatted or without its comments** | The first time ccteam registers into a profile you manage by hand, it re-serializes the patch file: formatting is normalized and YAML comments are not preserved (`serde_yaml` has no comment-preserving round-trip). After that ccteam never rewrites the file unless its content changes — a repeated registration is byte-identical — and every write is atomic (temp file + rename, mode 0600). Keep notes outside the patch file. | | **`ccteam doctor` warns `plugin_version_mismatch`** | Your own `dsh plugin add` copy differs from the version this ccteam embeds. ccteam leaves it alone; run `dsh plugin --profile web update @ccteam/ccteam-ui`. | -| **No ccteam button in the sidebar** | The plugin needs DSH 0.1.0-rc.7 or newer (the native sidebar footer and overlay seats). Update DSH; then check Settings → Plugins → Plugin list shows `ccteam-ui` as Enabled. | +| **No ccteam button in the sidebar** | The plugin needs DSH 0.2.2-alpha.2 or newer (the native sidebar footer and overlay seats). Update DSH; then check Settings → Plugins → Plugin list shows `ccteam-ui` as Enabled. | | **Plain-HTTP LAN problems** | See [usage.md](usage.md) → “Access and security” for the DSH Web security-context note. | | **Human DSH turns missing from ccteam** | Expected: turns typed in DSH’s own UI remain harness-native. ccteam’s ledger and transcript contain only turns ccteam routed; DSH keeps the complete conversation. | ## 8. Versions and updates -Use **DSH 0.1.0-rc.7 or newer**. +The plugin uses the native Config/forms and public slots provided by +**DSH 0.2.2-alpha.2**. DSH and ccteam versions are independent: DSH resolves +its own presets and default, so adding a preset needs no ccteam update. +Breaking changes to DSH's plugin APIs still require adapting those APIs. **Version lockstep.** Each plugin release pins the engine version it was published with (`ccteam.engine` in its `package.json`, equal to the diff --git a/docs/usage-cn.md b/docs/usage-cn.md index 11be7dcbd..3b202658f 100644 --- a/docs/usage-cn.md +++ b/docs/usage-cn.md @@ -19,7 +19,7 @@ - **session** = 一个独立的 agent 会话(像 Claude Code 原生会话一样自带上下文),属于某个项目。一个项目可同时开多个会话、互不串台,每个有持久句柄 `s`(扛重启、不复用)。 - **role** = 会话启动时**可选**绑定的角色(`.claude/agents/.md` 里的 persona + 工具)。默认 **roleless**:裸 vendor 自读项目的 `CLAUDE.md`/`AGENTS.md`。persona 从插件市场装或自建,ccteam 不内置任何角色。 -> **ccteam 只管自己的东西。** 它从不修改你的业务代码、`.git/`、`.env`,也不改写你的 `CLAUDE.md` / `AGENTS.md` —— 这些都归项目自己,Claude 和 Codex 原生读取。 +> **ccteam 只管自己的东西。** 它从不修改你的业务代码、`.git/`、`.env`,也不改写已有的 `CLAUDE.md` / `AGENTS.md`。两文件都不存在时,创建项目只种一个供你补充的 `AGENTS.md` 占位文件。项目指引归项目自己,由 vendor 原生读取。 --- @@ -145,7 +145,7 @@ web url: http://<你的局域网IP>:7331/?token=ccteam:<令牌> ### DSH Web -**DSH** 页把原生 DeepSeek Harness Web 嵌进 ccteam 控制台,中间走伴生端口反代。它复用 ccteam 登录 cookie;反代会剥掉 ccteam cookie / bearer,不会把它们交给 DSH 进程或日志。 +**DSH** 页把原生 DeepSeek Harness Web 嵌进 ccteam 控制台,中间走同源 `/dsh/` 反代。它复用 ccteam 登录 cookie;反代会剥掉 ccteam cookie / bearer,不会把它们交给 DSH 进程或日志。 一个身份只跑**一个** DSH 运行时,ccteam 是它的第二个 client:从 ccteam 雇出来的 DSH 会话(`/new dsh`、`agent {vendor:"dsh"}`)就建在这个运行时里,所以它们会实时出现在本页侧栏、挂在项目 workspace 下;agent 干到一半你可以点开围观或插话,agent 的下一次 dispatch 接着同一条对话继续。雇佣会话会加入 DSH 四种 agent preset 之一(`standard` / `ptc` / `minimal` / `creator`,决定工具集),ccteam 默认 `standard`,`/new dsh mode=`(或 spawn API 的 `mode`)可选其它;雇佣会话权限 preset 默认 `danger-full-access`(全文件访问、免审批),spawn 时选 `hitl` 则保留逐次审批。 @@ -154,11 +154,11 @@ web url: http://<你的局域网IP>:7331/?token=ccteam:<令牌> - **模型密钥**:在 DSH 原生 **Settings → Models** 里配置自己的 provider。同身份的所有 DSH 会话——你在这页打开的和在 ccteam 里雇出来的——都跑在同一个运行时、用同一份配置,改一次全体生效。ccteam 只逐字节复制和 hash 这些 DSH 配置文件,不解析 vendor YAML。 - **账本**:DSH Web 里原生跑的 turn 不是 ccteam session,不会在 ccteam 账本里伪装成 `$0` 或其它值;同一条规则在雇出来的会话里同样成立——你从 DSH 侧直接输入的 turn 是 vendor 原生 turn,ccteam 的 transcript 与账本只记 ccteam 路由的部分,完整对话以 DSH 家为准。从 DSH 通过 ccteam 插件委派出去的工作照常入账。 - **局域网明文 HTTP**:DSH Web 是按 loopback 源写的,浏览器只在安全上下文里给它 `crypto.randomUUID` 等 API;把 UI 搬离 loopback 的是 ccteam,所以伴生监听会在它下发的 HTML 里补回这一个 API(真 `crypto.getRandomValues` 生成的 UUID v4),浏览器自带时则不介入。走 HTTPS 或在 daemon 本机打开都不需要这些。 -- **局域网浏览器改设置**:DSH 把设置文档(所有 Settings 页,含「插件配置」)当作「操作者本机」才可触及的特权面,而且由客户端按页面地址判断——原生 `dsh web` 从局域网地址打开时,「插件配置」页一片空白、设置只读。经伴生监听访问时这个「本机」就是你的 ccteam 身份:实例只属于你,页面已经过 ccteam 鉴权,所以伴生监听用 DSH 自己的传输钩子(`__DSH_TRANSPORT__.ownsHost`)声明「本页拥有其 Host」,任何浏览器都能改设置。DSH 从 0.1.1-rc.2 之后的版本才原生读这个钩子(已在 DSH main 上);对 rc.2 及更早版本(客户端只按页面主机名判断),伴生监听把这一处读取回填进它下发的 client-connection bundle(精确到行的改写,其它版本上零操作),所以 rc.2 上同样任何浏览器都能改设置。一个连带后果:DSH 的产出文件动作会作用在 daemon 所在机器(即工作区机器)上。 +- **局域网浏览器改设置**:通过 ccteam 鉴权的伴生页面使用 DSH 官方 `__DSH_TRANSPORT__.ownsHost` 钩子声明拥有该身份的 Host。设置由 DSH 原生配置表单处理,厂商 JavaScript 原样转发;文件动作作用在 daemon 所在的工作区机器上。 - **信任边界**:租户 DSH Web 是同一 OS 用户下的软隔离。DSH agent 能跑 shell,用户自装 DSH 插件也是任意 npm 代码,信任级等同这个系统账号。同一个 OS 用户下的配置可见性只是便利边界,不是硬安全边界。 - **局域网明文访问**:DSH Web 是按 loopback 源写的 —— 浏览器只在安全上下文里给它 `crypto.randomUUID`,而它用这个 API 生成**每一个** RPC 请求 id;换成局域网地址 + 明文 HTTP,这个 API 就没了。把界面搬离 loopback 的是 ccteam,所以伴生监听会在下发的 HTML 里补回这一个 API(用 `crypto.getRandomValues` 实现的标准 v4 UUID,不降随机强度);浏览器本来就提供时它自动让位。用 HTTPS 访问控制台、或直接在 daemon 本机打开,都不需要这层补丁。 -如果 ccteam 前面有 HTTPS 反代,**伴生端口也必须一起反代**。DSH Web 没有 base-path 支持,所以用第二个 HTTPS 端口或子域;只反代 `:7331` 会让 iframe 继续加载明文 HTTP,浏览器按 mixed-content 拒绝。 +DSH 菜单通过当前 Web 入口的 `/dsh/` 同源代理加载 HTML、资源、RPC 和 WebSocket,自动沿用浏览器的协议与端口。直接访问主 Web 端口、通过 80/443 的 HTTP/HTTPS 反代均可用;反代只需覆盖主 Web 监听并支持 WebSocket upgrade,不用额外开放公网端口或子域。独立伴生入口(通常是 `:7332/`)仍可访问,它本身是明文 HTTP,需要 HTTPS 时由反代提供 TLS。 ### 创建项目 @@ -209,8 +209,21 @@ web url: http://<你的局域网IP>:7331/?token=ccteam:<令牌> ### Status / 成本 -- **Status** 页:daemon 健康、会话 live/idle 数、每条会话的成本、今日成本/预算(也是顶栏成本药丸的来源)。 -- **上下文侧栏**:在 web 控制台点会话头部的 `model · ctx` 标签(或右上角的侧栏按钮),右侧打开侧栏(切换会话时保持打开),显示上下文窗口由什么组成:system prompt、工具、memory 文件(每个 `CLAUDE.md` / 记忆文件按路径列出)、skills、MCP 工具与说明、自定义 agent,以及对话本身按工具(调用 vs 结果 token)和附件类型的拆分,外加上一次请求的缓存读 / 缓存写 / 未缓存输入。harness 延迟加载(未载入)的工具单独列出,因为它们不占窗口。数字是 harness 自己的账(Claude `/context` 的数据),不是让模型去估:在线会话每次打开和点「刷新」都现查,turn 结束时侧栏自动刷新(所以 `/compact` 后立刻看得到变化);已释放的会话显示最后一个 turn 结束时的快照并标出时间。目前只支持 Claude stream-json 会话,其他 harness 仍只显示 ctx 百分比。REST:`GET /api/v1/sessions/{sid}/context`。 +会话顶部显示状态、标题、非空 role 和模型文字;右上角统一排列「文件」和「上下文」,上下文按钮附带窗口使用百分比,点击切换右侧工作区。双击标题或点重命名按钮可改名。桌面可拖动聊天与工作区之间的分隔线调整宽度,手机以抽屉展示,Esc 关闭并返回入口。 + +平板和手机空间不足时,抽屉只覆盖聊天区,顶部「文件/上下文」始终可点:单击打开,再次单击关闭,点击另一个入口直接切换。文件可放大到整个聊天区并还原;也可点关闭按钮或露出的遮罩关闭。横竖屏切换保留已打开文件,抽屉宽度不会覆盖桌面的分栏偏好。 + +每条助手文本回复的 footer 都有复制按钮,复制该条回复的原始 Markdown(保留代码块、表格和链接,不含状态 footer 或工具过程),并显示成功或失败反馈。 + +「文件」支持目录搜索、键盘导航、文件图标和 Git 状态。单击临时预览,双击或点固定按钮保留标签;切换到上下文再回来仍保留已打开文件与阅读位置。聊天中的本地文件链接可直接打开源码并定位行号。点刷新可重新读取目录、源码与预览,turn 结束也会刷新。 + +Markdown (`.md` / `.markdown`) 默认显示排版预览,支持标题锚点、表格、任务列表、引用、代码块、相对图片和同目录文档链接;可切换「源码」或在新标签页独立阅读。文档按标准 Markdown 换行排版,原始 HTML 显示为文本,脚本不执行。使用与 HTML 相同的项目权限和一小时目录受限链接;Markdown 输入需为 UTF-8,最多 2 MiB。 + +HTML/HTM 默认打开交互预览,可切换「源码」或在新标签页打开。预览支持 HTML 所在目录及子目录中的相对 CSS、JS/ES module、图片和字体;资源链接一小时有效,过期后刷新。预览沙箱与控制台的 DOM、登录状态和存储隔离,不能调用控制台 API,也不会启动 React/Vite 构建或开发服务器。目前仅支持本地主机项目,远程项目明确显示未支持。内部状态、依赖目录和常见密钥文件不列出;目录最多 20,000 项,源码最多 2 MiB,单个预览资源最多 16 MiB。 + +- **Status** 页:daemon 健康、会话 live/idle 数、每条会话的成本、今日成本/预算。 +- **上下文侧栏**:顶部整理当前会话的活动与运行状态、ID/项目、harness/模型/主机、父会话和委派深度、本会话累计成本、累计 tokens、最后活动时间。尚未计价与 $0 区分显示,累计 tokens 不等于窗口占用。即使 harness 不提供上下文分析,也可查看会话状态。 +- **厂商上下文分析**:Claude stream-json 显示 system prompt、工具、memory 文件(按路径)、skills、MCP 工具与说明、自定义 agent,以及消息按工具调用/结果和附件类型的拆分;延迟加载工具单独列出,不计入窗口。Codex app-server 显示已用/剩余窗口、最近一次请求的输入、缓存命中、输出及其中推理 tokens;Codex 未提供分类明细和压缩阈值,面板不会补估。数字来自 harness 官方接口,不调用模型估算。每次打开、手动刷新和 turn 结束时更新;Codex 读取最近的用量事件,采集时间不会因打开面板而改变。释放后显示最后一次 turn 结束的快照及时间。REST:`GET /api/v1/sessions/{sid}/context`。 - 成本按 vendor 分别记账。Claude/Codex/Grok 有价表时用表计价;**OpenCode 只认自报 USD**(无上报或 0 显示「—」,绝不套用他家价表);**DSH 会上报原始 token,但暂时没有 USD 价表**。 ### 标准资源 API(给集成方) @@ -443,7 +456,7 @@ ccteam status # daemon 心跳 + 各项目及其会话 + web 链 ccteam doctor # 安装 / 依赖体检(--verify-mcp 校验 MCP 表面) ``` -`ccteam init` 只写 ccteam 自己的东西:项目 `.ccteam/`(状态)——**不种任何角色**,`.claude/agents/` 归你;也不写任何 Claude 设置(不建 `.claude/settings.local.json`、不装 hook),**不碰**你的 `.claude/settings.json`。重跑安全;对旧版 ccteam 初始化过的项目,重跑会把旧版写进 `.claude/settings.local.json` 的 Claude Code hook(及 Telegram 插件钉)剥掉,其余键和你自己的 hook 原样保留;文件不是合法 JSON 时原样不动、仅在回执里注明。偏好存 `~/.ccteam/preferences.toml`(目前一个键:`fallback.on_claude_quota` = `off`|`codex`,Claude 配额触顶是否回退 Codex)。 +`ccteam init`、Web 创建、IM `/newproject` 和卫星创建统一走一份代码:写 `.ccteam/state.json`,幂等把 `.ccteam/` 加进 `.gitignore`,仅在 `AGENTS.md` 和 `CLAUDE.md` 都不存在时种 `AGENTS.md` 占位文件,补全全局 `~/.ccteam/` 骨架,在 `~/.claude.json` 标记项目 trust,登记到 `~/.ccteam/config.yaml`。创建时不写 `CLAUDE.md`、`workflow.yaml`、角色或 Claude 设置;`.claude/agents/` 和 `.claude/settings.json` 归你。预算配置可自行创建 `.ccteam/workflow.yaml`。重跑刷新状态,未显式给出时保留已有 team 和 owner(`--force` 会重新盖默认 team),已有项目指引和 workflow 原样保留;同时清理 `.claude/settings.local.json` 中旧 ccteam hook/Telegram 插件钉,其余键和你的 hook 不动;非法 JSON 原样保留并在回执注明。偏好存 `~/.ccteam/preferences.toml`:`fallback.on_claude_quota` = `off`|`codex`,控制 Claude 配额触顶是否回退 Codex。 ### `project`(项目生命周期) @@ -586,7 +599,7 @@ ccteam host rm --daemon http://daemon-host:7331 --web-token ` that survives restarts and is never reused. - **role** = an optional persona bound at session start, loaded from `.claude/agents/.md`. The default is **roleless**: the bare vendor reads the project's own `CLAUDE.md`/`AGENTS.md`. Personas are installed from the marketplace or written by you; ccteam seeds none. -> **ccteam only manages its own footprint.** It never edits your product code, `.git/`, `.env`, `CLAUDE.md`, or `AGENTS.md`. Project instructions stay owned by the project and are read natively by Claude and Codex. +> **ccteam only manages its own footprint.** It never edits your product code, `.git/`, `.env`, or existing `CLAUDE.md` / `AGENTS.md`. When neither instruction file exists, creation seeds only an `AGENTS.md` placeholder for you to fill in. Project instructions stay owned by the project and are read natively by the vendor. --- @@ -145,7 +145,7 @@ Every daemon start (`ccteam start` / `ccteam daemon start`, including the DSH pl ### DSH Web -The **DSH** page embeds native DeepSeek Harness Web through the companion listener. It uses the same ccteam login cookie as the rest of the console; the DSH process never receives the ccteam cookie or bearer token. +The **DSH** page embeds native DeepSeek Harness Web through `/dsh/` on the same web origin. It uses the same ccteam login cookie as the rest of the console; the DSH process never receives the ccteam cookie or bearer token. The dedicated companion listener (normally `:7332/`) remains available. An identity runs **one** DSH runtime, and ccteam is its second client: sessions you hire from ccteam (`/new dsh`, `agent {vendor:"dsh"}`) are created inside that same runtime, so they show up live in this page's sidebar under the project's workspace, you can open one mid-task to watch or interject, and the agent's next dispatch continues the same conversation. A hire joins one of DSH's four agent presets — `standard`, `ptc`, `minimal`, or `creator` — which pick its toolset; ccteam defaults to `standard`, `mode=` on `/new dsh` (or `mode` on the spawn APIs) picks another, and a hire runs on the `danger-full-access` permission preset (full file access, no approval prompts) unless spawned with `hitl`. @@ -155,9 +155,9 @@ An identity runs **one** DSH runtime, and ccteam is its second client: sessions - **Ledger:** native DSH Web turns are not ccteam sessions, so they do not show up as `$0` or any other fake value in the ccteam ledger. The same rule holds inside a hired session: turns you type into it from the DSH side are vendor-native — ccteam's transcript and ledger record only the turns ccteam routed, and the DSH home keeps the complete conversation. Work delegated from DSH through the ccteam plugin is recorded normally. - **Trust boundary:** tenant DSH Web is same-OS-user isolation. DSH agents can run shell commands, and self-installed DSH plugins are arbitrary npm code with the same trust level as that OS account. Sharing one OS user means config visibility is a convenience boundary, not a hard security boundary. - **Plain-HTTP LAN access:** DSH Web is written for a loopback origin, where browsers grant it secure-context APIs — it mints every RPC id with `crypto.randomUUID`, which browsers withhold from plain HTTP on a LAN address. Because ccteam is what moves the UI off loopback, the companion listener restores that one API (a real `crypto.getRandomValues`-backed UUID v4) in the HTML it serves, and stays out of the way when the browser already provides it. Serving the console over HTTPS or opening it on the daemon host needs none of this. -- **Settings from a LAN browser:** DSH treats its settings document (every Settings page, including Plugin configuration) as a privileged surface for the operator's own machine, and its client decides that from the page address — a native `dsh web` opened over a LAN address shows an empty Plugin configuration tab and keeps settings read-only. Through the companion listener that stand-in is your ccteam identity: the instance is yours alone and ccteam already authenticated the page, so the listener declares the page owns its Host through DSH's own transport hook (`__DSH_TRANSPORT__.ownsHost`) and settings work from any browser. DSH reads that hook natively from the release after 0.1.1-rc.2 (it is on DSH main); for rc.2 and earlier, whose client decides from the page hostname alone, the listener back-ports that one read into the client-connection bundle it serves (an exact-line rewrite that is a no-op on any other version), so settings work from any browser on rc.2 as well. One consequence: DSH's deliverable file actions then act on the daemon host — the workspace machine. +- **Settings from a LAN browser:** the authenticated companion page declares ownership of its per-identity Host through DSH's public `__DSH_TRANSPORT__.ownsHost` hook. DSH handles settings through its native configuration forms; vendor JavaScript is forwarded unchanged. File actions act on the daemon host — the workspace machine. -When running ccteam behind HTTPS, proxy the DSH companion listener as well as the main web listener. DSH Web has no base-path support, so use a second HTTPS port or subdomain; proxying only `:7331` leaves the iframe on plain HTTP and browsers block it as mixed content. +When running ccteam behind an HTTP or HTTPS reverse proxy on port 80 or 443, proxy the main web listener, including WebSocket upgrades. The DSH page uses that same external origin for its HTML, assets, RPC and WebSockets; no additional public port or subdomain is needed. Direct access to the main web port also works. The dedicated companion listener is plain HTTP unless your reverse proxy adds TLS in front of it. For the opposite direction — installing ccteam's plugins into a `dsh web` you run yourself, and using the ccteam workbench inside DSH’s own console — see [dsh-plugin.md](dsh-plugin.md) ([中文](dsh-plugin-cn.md)). @@ -215,9 +215,22 @@ One daemon can serve multiple users on one machine. This is **soft isolation** u ### Status and Cost -- **Status** shows daemon health, live/idle session counts, per-session cost, and today's total cost / budget. The top-bar cost pill uses the same data. -- **Per-reply status line.** Every reply a session produces carries a compact status line built once by the daemon from the same data — `→ cct/s42 (reviewer) · codex · gpt-5.6-sol · turn 7 · ctx 19% · $0.42` (project/sid, role, vendor + observed model, context-window usage with `⚠` from 85%, completed turns, cumulative cost — or `12.3k tok` when the vendor has no price table; a field the vendor does not report is simply omitted, never shown as a placeholder). It is attached once per vendor turn, on the turn's final message only (a Codex turn that streams several messages gets exactly one), so it never repeats — and when a long turn's text was already delivered mid-turn, that one status line arrives on its own as the turn's closing receipt. IM chats get it as the last line of the reply; the web console shows it as a muted footer under the turn's last assistant message and keeps the header model/ctx chips in sync from it. Agent-facing surfaces get numbers, not prose: `agent_read` transcripts and roster rows, and inline `agent` results, carry `context_pct` (plus the existing `cost_usd` / `tokens_total` / `model`), and each child→parent completion notification opens with one header line — `s12 done · codex · turn 7 · ctx 19%` — so a parent can decide between reusing a child and spawning a fresh one without another call; interim notes carry nothing extra. -- **Context sidebar.** Click a session's `model · ctx` chip (or the sidebar button at the top right) in the web console to open a right-hand sidebar — it stays open as you switch sessions — that shows what the context window is made of: system prompt, tools, memory files (each `CLAUDE.md` / memory file by path), skills, MCP tools and instructions, custom agents, and the conversation itself split by tool (call vs result tokens) and by attachment type, plus the last request's cache read / cache write / uncached input. Tool schemas the harness has deferred (not loaded) are listed apart, because they do not occupy the window. The numbers are the harness's own accounting (Claude's `/context` data), not a model's guess: a live session is asked on every open and on **Refresh**, and the sidebar refreshes itself when a turn ends (so a `/compact` shows up at once); a released session shows the snapshot taken at the end of its last turn, with its time. Claude stream-json sessions only for now; other harnesses keep the ctx % alone. REST: `GET /api/v1/sessions/{sid}/context`. +The session header shows status, title, a nonempty role and plain model text. The top-right toolbar groups **Files** and **Context**, with window usage on Context. Double-click the title or use its rename button to rename. Drag the divider to resize the desktop workspace. On phones it opens as a drawer; Escape closes it and returns focus to its toolbar button. + +When a tablet or phone has limited conversation space, drawers cover only the chat body. Files / Context remain reachable: tap once to open, again to close, or tap the other button to switch. Files can expand across the chat body and restore; the close button or exposed backdrop also dismisses the drawer. Rotation preserves open files, and drawer widths do not overwrite desktop split preferences. + +Each assistant text reply has a footer copy button. It copies that reply's original Markdown, preserving code fences, tables and links without metadata or tool activity, and shows success or failure feedback. + +Files offers path search, keyboard navigation, file icons, and Git status. Click for a temporary preview; double-click or pin to keep a tab. Switching to Context and back preserves open files and reading positions. Local file links in chat open source at the referenced line. Refresh reloads the directory, source, and preview; turn completion also refreshes them. + +Markdown (`.md` / `.markdown`) opens as a formatted document with heading anchors, tables, task lists, quotes, code blocks, relative images, and same-directory document links. Switch to Source or open the rendered document in a new tab. Standard Markdown paragraph rules apply; raw HTML is shown as text and scripts do not run. Previews use the same project permissions and one-hour directory-scoped links as HTML. Markdown input must be UTF-8 and no larger than 2 MiB. + +HTML/HTM files open as interactive previews, with Source and Open in new tab controls. Relative CSS, JavaScript/ES modules, images, and fonts are served from the HTML file's directory and its descendants. Preview links expire after one hour; refresh to renew. Browser sandboxes isolate previews from the console DOM, login, and storage. They cannot call console APIs or start React/Vite builds or development servers. File browsing currently supports local projects; remote projects report that it is unsupported. Internal state, dependency directories, and common secret files are excluded. Limits: 20,000 tree entries, 2 MiB source files, and 16 MiB per preview asset. + +- **Status** shows daemon health, live/idle session counts, per-session cost, and today's total cost / budget. +- **Per-reply status line.** Every reply a session produces carries a compact status line built once by the daemon from the same data — `→ cct/s42 (reviewer) · codex · gpt-5.6-sol · turn 7 · ctx 19% · $0.42` (project/sid, role, vendor + observed model, context-window usage with `⚠` from 85%, completed turns, cumulative cost — or `12.3k tok` when the vendor has no price table; a field the vendor does not report is simply omitted, never shown as a placeholder). It is attached once per vendor turn, on the turn's final message only (a Codex turn that streams several messages gets exactly one), so it never repeats — and when a long turn's text was already delivered mid-turn, that one status line arrives on its own as the turn's closing receipt. IM chats get it as the last line of the reply; the web console shows it as a muted footer under the turn's last assistant message and keeps the header model text and Context percentage in sync from it. Agent-facing surfaces get numbers, not prose: `agent_read` transcripts and roster rows, and inline `agent` results, carry `context_pct` (plus the existing `cost_usd` / `tokens_total` / `model`), and each child→parent completion notification opens with one header line — `s12 done · codex · turn 7 · ctx 19%` — so a parent can decide between reusing a child and spawning a fresh one without another call; interim notes carry nothing extra. +- **Context sidebar.** Session status groups activity/execution, ID/project, harness/model/host, delegation parent/depth, cumulative session cost, total tokens and last activity. Unpriced usage is distinct from $0; total tokens are distinct from window occupancy. These facts remain available even when a harness provides no context analysis. +- **Harness context analysis.** Claude stream-json reports system prompt, tools, memory files by path, skills, MCP tools/instructions, custom agents, and the conversation split by tool calls/results and attachment type; deferred tools are listed separately and excluded from occupancy. Codex app-server reports used/free window totals and last-request input, cached input, output and its reasoning subset. Codex has no category or compaction-threshold interface, so the panel does not estimate them. All numbers come from vendor interfaces without asking a model. Opening, Refresh and turn completion update the panel; Codex reads its latest usage notification without changing its observation time. Released sessions show the dated snapshot from the last turn boundary. REST: `GET /api/v1/sessions/{sid}/context`. - Cost is tracked separately by vendor. Claude / Codex / Grok use embedded tables when the model is known; **OpenCode and Pi use only vendor-reported USD** (or "—" when missing/zero — never another vendor's price table); **DSH reports raw tokens but has no USD price table yet**; **Kimi always shows "—"** (its ACP wire carries no usage/cost). A turn that fails part-way is still billed by the vendor, so its tokens and cost land on the ledger like any other turn — a failure never reads as free. ### Standard Resource API @@ -462,7 +475,7 @@ ccteam status # Daemon heartbeat, projects, sessions, web link, ccteam doctor # Install/dependency checks; --verify-mcp checks MCP surface. ``` -`ccteam init` only writes ccteam-owned files: project `.ccteam/` (it seeds no roles — `.claude/agents/` stays yours, and it writes no Claude settings: no `.claude/settings.local.json`, no hooks). It does **not** touch your `.claude/settings.json`. Re-running is safe, and on a project an older ccteam initialized it strips the Claude Code hooks (and the Telegram plugin pin) that version wrote into `.claude/settings.local.json`, leaving every other key and your own hooks in place; a file that is not valid JSON is left untouched and noted. Preferences live in `~/.ccteam/preferences.toml`; currently `fallback.on_claude_quota = off|codex` controls whether Claude quota exhaustion falls back to Codex. +`ccteam init`, Web creation, IM `/newproject`, and satellite project creation share one creation path: write `.ccteam/state.json`, add `.ccteam/` to `.gitignore`, seed an `AGENTS.md` placeholder only when neither `AGENTS.md` nor `CLAUDE.md` exists, ensure the global `~/.ccteam/` home, mark the project trusted in `~/.claude.json`, and register it in `~/.ccteam/config.yaml`. Creation writes no `CLAUDE.md`, `workflow.yaml`, roles, or Claude settings; `.claude/agents/` and `.claude/settings.json` stay yours. You can create `.ccteam/workflow.yaml` yourself for budget settings. Re-running refreshes state while preserving the recorded team and owner unless explicitly supplied (`--force` re-stamps the default team), and preserves existing project instructions and workflows. It also strips old ccteam hooks and the Telegram plugin pin from `.claude/settings.local.json`, keeping every other key and your own hooks; invalid JSON is left untouched and noted. Preferences live in `~/.ccteam/preferences.toml`; `fallback.on_claude_quota = off|codex` controls whether Claude quota exhaustion falls back to Codex. ### `project` (Project Lifecycle) @@ -606,7 +619,7 @@ The satellite reports its agents and registered projects every ~25s over its con - **Create remotely:** web console → new project → pick the satellite in the host picker → absolute path on that machine. The daemon asks the satellite to bootstrap and register it in place. - **Import an existing checkout:** `ccteam init` in the repo on that machine, then hosts page → 接入/Import next to the reported project. Same-slug collisions get a distinct catalog slug (`demo` → `demo2`) — slug equality across machines is not project identity. -Remote execution currently supports Claude stream-json sessions; the connection self-heals with backoff, and a dropped exec link resumes context via vendor `--resume` on the next spawn. Pi and DSH are local-only by design — their sessions run on the daemon's own machine, and a project bound to a satellite refuses those spawns with a plain error rather than quietly running them somewhere else. Fleet capacity: ccteam releases a session's harness process once nobody has spoken to it for `sessions.idle_release_secs` (default 3600 — one hour, matching Claude's prompt-cache TTL, so the process is held exactly as long as holding it can still save a cache read; `0` never releases, and `sessions.idle_release_by_vendor: {claude: 7200, codex: 0, …}` overrides it per harness). A released session is NOT gone: it keeps its transcript, its place in `/sessions` (marked 💤), and its chat's focus — the next message resumes that same sid in about a second. Nothing is released mid-turn, while it waits on an approval, or while its harness reports a background task still running. `sessions.max_live` (default 50) remains as a hard ceiling for bursts: admitting one more past it gracefully releases the least-recently-active eligible session. Only `/stop` ends a session for good. +Remote execution currently supports Claude stream-json sessions; the connection self-heals with backoff, and a dropped exec link resumes context via vendor `--resume` on the next spawn. Pi and DSH are local-only by design — their sessions run on the daemon's own machine, and a project bound to a satellite refuses those spawns with a plain error rather than quietly running them somewhere else. Fleet capacity: ccteam releases a session's harness process once nobody has spoken to it for `sessions.idle_release_secs` (default 15000 seconds — 250 minutes; `0` never releases, and `sessions.idle_release_by_vendor: {claude: 7200, codex: 0, …}` overrides it per harness). A released session is NOT gone: it keeps its transcript, its place in `/sessions` (marked 💤), and its chat's focus — the next message resumes that same sid in about a second. Nothing is released mid-turn, while it waits on an approval, or while its harness reports a background task still running. `sessions.max_live` (default 50) remains as a hard ceiling for bursts: admitting one more past it gracefully releases the least-recently-active eligible session. Only `/stop` ends a session for good. --- diff --git a/dsh-plugins/ccteam-ui/README.md b/dsh-plugins/ccteam-ui/README.md index 317e93c9a..c04aa71dd 100644 --- a/dsh-plugins/ccteam-ui/README.md +++ b/dsh-plugins/ccteam-ui/README.md @@ -12,7 +12,7 @@ faces, and every DSH profile that installs it gets all three: Built the way a DSH client plugin is built: `dsh.client` in package.json names the packages it composes against, the client half contributes its seats through `ctx.slots.inject(...) → ctx.slots.register(...)` -(`sidebar.footer.action`, `shell.overlay`, `settings.plugin.item`), copy goes +(`sidebar.footer.action`, `shell.overlay`, `settings.section`), copy goes through `ctx.locale`, business state reaches components as framework-bound selector hooks, and stylesheets are lightningcss CSS Modules under the preset's `[hash]_[local]` pattern. @@ -30,8 +30,7 @@ the rest. ## Install (hand-started DSH) 1. `dsh plugin --profile web add @ccteam/ccteam-ui` -2. Restart `dsh web`, hard-refresh, then open DSH Settings → Plugins → - **ccteam-ui** and fill in one card: the daemon URL, your personal REST +2. Restart `dsh web`, hard-refresh, then open DSH Settings → **ccteam-ui** and fill in one card: the daemon URL, your personal REST token (ccteam web console → Settings → Account) for the workbench, and the enrollment credential (Settings → Access) for the agent's tools. 3. Look for the ccteam button at the bottom of the sidebar. diff --git a/dsh-plugins/ccteam-ui/build/client-externals.ts b/dsh-plugins/ccteam-ui/build/client-externals.ts index a97eacb75..4148d77ef 100644 --- a/dsh-plugins/ccteam-ui/build/client-externals.ts +++ b/dsh-plugins/ccteam-ui/build/client-externals.ts @@ -1,11 +1,9 @@ /** * The DSH client-bundle module table — build-time only, never shipped. * - * Verbatim from references/deepseek-harness/packages/client/web/src/platform.ts - * (`PLATFORM_MODULES` lines 8-12 + `PRELOADED_CLIENT_EXTERNALS` lines 15-17). - * These are the only specifiers the browser loader's injected `require` can - * answer; everything else must be bundled inline or it throws at - * materialization (system.ts:174-187). + * Only shared platform libraries this bundle actually imports are external. + * Cross-plugin services are accessed through Cordis and imported as types, + * so adding vendor plugins never requires another loader-table entry here. * * MATCHING IS EXACT STRING EQUALITY, never prefix. `react` being external does * not make `react/jsx-runtime` external — it is listed separately because it @@ -21,8 +19,6 @@ export const CLIENT_EXTERNALS: ReadonlySet = new Set([ '@deepseek-ai/cordis', '@deepseek-ai/dsh-client-ui-slots', '@deepseek-ai/dsh-client-ui-primitives', - // The `/client` subpath is part of the specifier; the loader strips it to key the row. - '@deepseek-ai/dsh-client-runtime/client', ]) /** Vendored libraries carry no shared identity, so inlining them is correct. */ diff --git a/dsh-plugins/ccteam-ui/package-lock.json b/dsh-plugins/ccteam-ui/package-lock.json index 20ef2e88e..bdcff72aa 100644 --- a/dsh-plugins/ccteam-ui/package-lock.json +++ b/dsh-plugins/ccteam-ui/package-lock.json @@ -1,34 +1,74 @@ { "name": "@ccteam/ccteam-ui", - "version": "0.13.0", + "version": "0.13.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ccteam/ccteam-ui", - "version": "0.13.0", + "version": "0.13.1", "bundleDependencies": [ "@deepseek-ai/schemastery" ], "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/schemastery": "3.18.6-alpha.1" }, "devDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-api-remotes": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-connection": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-locale": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-layout": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-primitives": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-sidebar": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-theme": "0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "0.1.1-rc.2", - "@deepseek-ai/dsh-settings": "0.1.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "0.1.1-rc.2", + "@deepseek-ai/cordis": "4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-agent-preset-registry": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-account-controller": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-gateway": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-job-controller": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-remote-access": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-remotes": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-session-controller": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-settings-controller": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-terminal-controller": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-workspace-controller": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-workspace-files": "0.2.2-alpha.2", + "@deepseek-ai/dsh-attachment": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-connection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-file-upload": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-locale": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-product-analytics": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-shortcuts": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-store": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-conversation": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-layout": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-primitives": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-renderer": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-settings": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-settings-plugins": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-sidebar": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-slots": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-theme": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-trajectory": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-workspace": "0.2.2-alpha.2", + "@deepseek-ai/dsh-command-feedback": "0.2.2-alpha.2", + "@deepseek-ai/dsh-commands": "0.2.2-alpha.2", + "@deepseek-ai/dsh-cordis-host-runner": "0.2.2-alpha.2", + "@deepseek-ai/dsh-file-reference": "0.2.2-alpha.2", + "@deepseek-ai/dsh-goal": "0.2.2-alpha.2", + "@deepseek-ai/dsh-host-plugin-inventory": "0.2.2-alpha.2", + "@deepseek-ai/dsh-host-webserver": "0.2.2-alpha.2", + "@deepseek-ai/dsh-message-feedback": "0.2.2-alpha.2", + "@deepseek-ai/dsh-office-to-pdf": "0.2.2-alpha.2", + "@deepseek-ai/dsh-permission-presets": "0.2.2-alpha.2", + "@deepseek-ai/dsh-plugin-manager": "0.2.2-alpha.2", + "@deepseek-ai/dsh-plugin-market": "0.2.2-alpha.2", + "@deepseek-ai/dsh-schedule": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-reference": "0.2.2-alpha.2", + "@deepseek-ai/dsh-settings": "0.2.2-alpha.2", + "@deepseek-ai/dsh-subagent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-tools": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/dsh-user-questions": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-code-language": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-file-drag": "0.2.2-alpha.2", "@types/node": "^22.13.0", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", @@ -41,44 +81,44 @@ "vitest": "^3.2.4" }, "optionalDependencies": { - "@ccteam/engine-darwin-arm64": "0.13.0", - "@ccteam/engine-darwin-x64": "0.13.0", - "@ccteam/engine-linux-arm64": "0.13.0", - "@ccteam/engine-linux-x64": "0.13.0" + "@ccteam/engine-darwin-arm64": "0.13.1", + "@ccteam/engine-darwin-x64": "0.13.1", + "@ccteam/engine-linux-arm64": "0.13.1", + "@ccteam/engine-linux-x64": "0.13.1" } }, "node_modules/@ccteam/engine-darwin-arm64": { "optional": true, - "version": "0.13.0" + "version": "0.13.1" }, "node_modules/@ccteam/engine-darwin-x64": { "optional": true, - "version": "0.13.0" + "version": "0.13.1" }, "node_modules/@ccteam/engine-linux-arm64": { "optional": true, - "version": "0.13.0" + "version": "0.13.1" }, "node_modules/@ccteam/engine-linux-x64": { "optional": true, - "version": "0.13.0" + "version": "0.13.1" }, "node_modules/@deepseek-ai/cordis": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@deepseek-ai/cordis/-/cordis-4.0.1.tgz", - "integrity": "sha512-YBdskTU2Po1kru3GgcUWUbkTsPMA9LkSQDAY8rBkFJeajdgcQad3QPJZE26JyK99Xb6HaASvoXg2DSUTeN/0Nw==", + "version": "4.0.6-alpha.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/cordis/-/cordis-4.0.6-alpha.1.tgz", + "integrity": "sha512-GgUFYPkAERAhjq/H7ASM9G2tWu07bY6XOD/q16jUXqsj/arawh0EIZKB+PjTXLjC7vgG3XptSvJ/8dZkkIWsfQ==", "dev": true, "license": "MIT", "dependencies": { - "@deepseek-ai/cosmokit": "^1.8.2", + "@deepseek-ai/cosmokit": "~1.8.7-alpha.1", "@standard-schema/spec": "^1.1.0" }, "bin": { "cordis": "bin.js" }, "peerDependencies": { - "@deepseek-ai/cordis-plugin-include": "^1.0.6", - "@deepseek-ai/cordis-plugin-loader": "^1.0.2" + "@deepseek-ai/cordis-plugin-include": "~1.0.11-alpha.1", + "@deepseek-ai/cordis-plugin-loader": "~1.0.7-alpha.1" }, "peerDependenciesMeta": { "@deepseek-ai/cordis-plugin-include": { @@ -89,35 +129,47 @@ } } }, + "node_modules/@deepseek-ai/cordis-plugin-group": { + "version": "1.0.6-alpha.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/cordis-plugin-group/-/cordis-plugin-group-1.0.6-alpha.1.tgz", + "integrity": "sha512-7Pi3rbKv2dTRwI+JI2EJQPJ3bhX5rfBww0tZJ1NT+KZHDef8SL23aISVQ80WSub83ltu4O5sLM3wJf9uf1CWhw==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/cordis-plugin-loader": "~1.0.7-alpha.1" + } + }, "node_modules/@deepseek-ai/cordis-plugin-include": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@deepseek-ai/cordis-plugin-include/-/cordis-plugin-include-1.0.6.tgz", - "integrity": "sha512-i1VXrZCbv6tk/iUgedCNjrxxArbWT3IvRZGB5sdqJ3ectnihivXXQbRZ8JJ73DSmAPvlMGmrbtjFAfm10yvXRg==", + "version": "1.0.11-alpha.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/cordis-plugin-include/-/cordis-plugin-include-1.0.11-alpha.1.tgz", + "integrity": "sha512-sw7+Wn84GxxeH58+nSNp9UplMxqY3uhjNN7Bs3Q87twLAaLgmYMN+Nk7ryE6riX1N85K+Ao0H8Z5OOH4euCAMg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@deepseek-ai/cosmokit": "^1.8.2", + "@deepseek-ai/cosmokit": "~1.8.7-alpha.1", "js-yaml": "^4.1.0" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/cordis-plugin-loader": "^1.0.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/cordis-plugin-loader": "~1.0.7-alpha.1" } }, "node_modules/@deepseek-ai/cordis-plugin-loader": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/cordis-plugin-loader/-/cordis-plugin-loader-1.0.2.tgz", - "integrity": "sha512-RIW9hoVyhYDWdCI9BsvtZccPde1ECLC4OAxupwowGTak78vwVTVdb3HezTSOK1Y1/Ax3Ru0LA1pYOB04CnTxIQ==", + "version": "1.0.7-alpha.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/cordis-plugin-loader/-/cordis-plugin-loader-1.0.7-alpha.1.tgz", + "integrity": "sha512-Xbx+LkoUjShfFZTwzG2CY5/Uw1lkuf7sXcj1Y39G1sRjye/FAVn/JoZ5E7pPW2oU70Zt/n/4beV+OROKkMvuiw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@deepseek-ai/cosmokit": "^1.8.2" + "@deepseek-ai/cosmokit": "~1.8.7-alpha.1", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "node-addon-require-builtin": "^0.1.4" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "node-addon-require-builtin": "^0.1.9" }, "peerDependenciesMeta": { "node-addon-require-builtin": { @@ -126,1835 +178,2075 @@ } }, "node_modules/@deepseek-ai/cosmokit": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/cosmokit/-/cosmokit-1.8.2.tgz", - "integrity": "sha512-muBOKtSrUKU5m/xpq8ZXWL6hQ/jgd4PhU2PqH97bcxIiLEJfNwZOGQEx4t/aS/GgxRAR+ra9pMHPMtTHU4sqqA==", + "version": "1.8.7-alpha.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/cosmokit/-/cosmokit-1.8.7-alpha.1.tgz", + "integrity": "sha512-+R+9sx+SJbkyeidV3s+fvgsqCMZAfx2Z00YCQNhc5RsTen1SnvIYWM8neQah/1SxjlsXRo9jxX9U58Wk68JjvA==", "inBundle": true, "license": "MIT" }, "node_modules/@deepseek-ai/dsh-agent": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-agent/-/dsh-agent-0.1.1-rc.2.tgz", - "integrity": "sha512-cC7lnJe7JgPFcreNXxcxLMxQd78LnpVO9ZXROjZsGRQN1zGH6i/DduI892F1am85IfzzO+XTxMwwUHmfwamb0g==", + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-agent/-/dsh-agent-0.2.2-alpha.2.tgz", + "integrity": "sha512-e1seyUG26WYUAFX7tJJzhtg1aBwqIR7sFijt8S9DujS9alXwEdOZAYpJG531qqRGZSlM+XNT6z5AeH5KSjqdsA==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "zod": "4.4.3" + }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-scope": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "@deepseek-ai/dsh-workspace": "0.2.2-alpha.2" + }, + "peerDependenciesMeta": { + "@deepseek-ai/dsh-workspace": { + "optional": true + } } }, "node_modules/@deepseek-ai/dsh-agent-default-model": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-agent-default-model/-/dsh-agent-default-model-0.1.1-rc.2.tgz", - "integrity": "sha512-Pv+4p20Eol7Ds/n0OS0vjtChCWfFTJAMThxugXcEcLKEJ9WAtpI4mzWfLgjmeVXnwD2yvp6HlLKDrrQV9PIkww==", + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-agent-default-model/-/dsh-agent-default-model-0.2.2-alpha.2.tgz", + "integrity": "sha512-Rq697rRmylANtB50EIro/ga09UfKNnidL0blZUfHtLHQTaO7MZ7Lfx015l+Yr7L3p7LkuMnIdwUqhj4c//CE4Q==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/dsh-config-editor": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-settings": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-agent-presets": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-agent-presets/-/dsh-agent-presets-0.1.1-rc.2.tgz", - "integrity": "sha512-88r3jkrbdwTgcP3MZLIUX458Ecu8JcLmZa7PtPszwf33yFoWlZvZmgjyn5ETHV55plNQ8gKMRm9a0RwzGGmzCw==", + "node_modules/@deepseek-ai/dsh-agent-preset-registry": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-agent-preset-registry/-/dsh-agent-preset-registry-0.2.2-alpha.2.tgz", + "integrity": "sha512-rH2xDF1BsrCn8QgVqylZuqwnTPbxzUMzlLUT/oIfjatc/2q2n1cv2IJlnEjNMGltHCKoeuIe9n1L7J6vV3++/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1", - "js-yaml": "^4.1.0" + "@deepseek-ai/dsh-app-boot": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "js-yaml": "^4.3.2", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/cordis-plugin-include": "^1.0.6", - "@deepseek-ai/cordis-plugin-loader": "^1.0.2", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-atomic-write": "^0.1.1-rc.2", - "@deepseek-ai/dsh-home-paths": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-scope": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-settings": "^0.1.1-rc.2", - "@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/cordis-plugin-include": "~1.0.11-alpha.1", + "@deepseek-ai/cordis-plugin-loader": "~1.0.7-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-settings": "0.2.2-alpha.2", + "@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.2", + "@deepseek-ai/dsh-tools": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-api-gateway": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-api-gateway/-/dsh-api-gateway-0.1.1-rc.2.tgz", - "integrity": "sha512-i/e/Ecg1QCjMePUFHBfjRQU3NbDV0IdORwQbQD6RpiirzyvAIm4vvZgmW/FfgkO2XYJHHIVjo3i1i0YaHPcQag==", + "node_modules/@deepseek-ai/dsh-anonymous-user-id": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-anonymous-user-id/-/dsh-anonymous-user-id-0.2.2-alpha.2.tgz", + "integrity": "sha512-ZB9mucpGuTseJyPj9O5T6rtDEYGkPaEdk8UuYu1KlrvojA+2FynWZPAkjwSbmp8dn+OzbOECzbO7ptEpZZThQA==", "dev": true, "license": "MIT", "peer": true, - "dependencies": { - "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2" - }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-typert-registry": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-home-paths": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-api-remotes": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-api-remotes/-/dsh-api-remotes-0.1.1-rc.2.tgz", - "integrity": "sha512-mtmMxA0TZwTjy46E2DF0kmsPCXVq6RBhrwEHWXBaRzc0DLjnYqmJODrZ0X+XwXOkwFZtDxWSv/uheiIsXf8now==", + "node_modules/@deepseek-ai/dsh-api-account-controller": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-api-account-controller/-/dsh-api-account-controller-0.2.2-alpha.2.tgz", + "integrity": "sha512-aXMpmdMfuTZOgr5EfQc/hhY77M1jYKevLHohSdxO1UAEV4UJSH6zq/X0HcAtZXe60W0Ee+vbEG6mgh+gRt83NQ==", "dev": true, "license": "MIT", "dependencies": { - "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2" + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-agent-presets": "^0.1.1-rc.2", - "@deepseek-ai/dsh-api-gateway": "^0.1.1-rc.2", - "@deepseek-ai/dsh-commands": "^0.1.1-rc.2", - "@deepseek-ai/dsh-cordis-host-runner": "^0.1.1-rc.2", - "@deepseek-ai/dsh-credentials": "^0.1.1-rc.2", - "@deepseek-ai/dsh-file-reference": "^0.1.1-rc.2", - "@deepseek-ai/dsh-goal": "^0.1.1-rc.2", - "@deepseek-ai/dsh-host-plugin-inventory": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-message-feedback": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-reference": "^0.1.1-rc.2", - "@deepseek-ai/dsh-settings": "^0.1.1-rc.2", - "@deepseek-ai/dsh-typert-registry": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-deepseek-account": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-atomic-write": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-atomic-write/-/dsh-atomic-write-0.1.1-rc.2.tgz", - "integrity": "sha512-QqNSF0+Ddn6qWY480dlilwEy6FLv3JKEWx1UQgoNJrxD4y54SDRzqBQB9yDXWKOoOGyC+05TN6/Px10GNIzMWA==", + "node_modules/@deepseek-ai/dsh-api-gateway": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-api-gateway/-/dsh-api-gateway-0.2.2-alpha.2.tgz", + "integrity": "sha512-5Wlz+8ejElX5t8zSc7cHeGk9eCDGAQApHaeJXwzycfkbAngglVoUVCdrp6xqmKqBRS45IUZHLh6IShiU/o1Xqw==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "@deepseek-ai/dsh-deque": "0.2.2-alpha.2", + "@deepseek-ai/dsh-timeout": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "ws": "^8.21.3" + }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-attachment": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-attachment/-/dsh-attachment-0.1.1-rc.2.tgz", - "integrity": "sha512-rCYAt8QsawP1yfDCU7XxNwYT/XWvyFsxYrkwhLLkdfW83QVD0CQHizSkTQE7RFX74nKUD1z3sTLfnLr7xneArw==", + "node_modules/@deepseek-ai/dsh-api-job-controller": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-api-job-controller/-/dsh-api-job-controller-0.2.2-alpha.2.tgz", + "integrity": "sha512-S3JISeL5bRMh78RwsxMBsap1K866QFaHSBViQ5P1BpOeDY5g0WOW0d/qv8jvonoTsBaE4jFXwhDxH2kB3MO5MQ==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" + }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-brand": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-brand/-/dsh-brand-0.1.1-rc.2.tgz", - "integrity": "sha512-8vXsAXoUdzKAgvd/E9DgyT6HKmR6ZM4rtJ3fs/XoJ6n2kBk4tWil8Lv+jxCMWJeMOnTJF55gu2+NWQ3ECFPtJw==", + "node_modules/@deepseek-ai/dsh-api-remote-access": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-api-remote-access/-/dsh-api-remote-access-0.2.2-alpha.2.tgz", + "integrity": "sha512-lbGVtqzr/tHdJCoEDv/uEi9NvfEQlzbtcdGqevh1XI7PMNyrIiGsxtMRwApou4o5XFdeFSDpvcNyy3k6AReu/Q==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "zod": "4.4.3" + }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-client-connection": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-connection/-/dsh-client-connection-0.1.1-rc.2.tgz", - "integrity": "sha512-YX2WLA/aZdDQsien4Zo7IHTEfYVJ+4QhRXbgA6BrRUM23NSP/+V3K00dQYyQVsF3ZwocE5uyvlZvbYeg1Iz4ug==", + "node_modules/@deepseek-ai/dsh-api-remotes": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-api-remotes/-/dsh-api-remotes-0.2.2-alpha.2.tgz", + "integrity": "sha512-RbSs2jnIf9KMN3jZLqQlSQbZlJhKfxPtULy91ANlzKCC8fQxMGLRhrgHIJVGA6o32jmF6GL5hzGKDkHQCjzvdA==", "dev": true, "license": "MIT", "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1", - "ws": "^8.21.0" + "@deepseek-ai/dsh-deque": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-attachment": "^0.1.1-rc.2", - "@deepseek-ai/dsh-commands": "^0.1.1-rc.2", - "@deepseek-ai/dsh-host-apiproxy": "^0.1.1-rc.2", - "@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-tools": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-client-locale": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-locale/-/dsh-client-locale-0.1.1-rc.2.tgz", - "integrity": "sha512-AMoR4FwMPLmt6WlYd4m0PCoxXj4qqMhpElBQZSc3r3NyGCZIuleU8tCG1xphGtr/w9mA1Y0kUX7Ftdmf7yV4FQ==", + "node_modules/@deepseek-ai/dsh-api-session-controller": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-api-session-controller/-/dsh-api-session-controller-0.2.2-alpha.2.tgz", + "integrity": "sha512-6mQ+oaQNnB/DrM+EtS7/sZVF8lA4o0ej8aaXKzBXHpOHj1BFVdeuasfNg+0Cu/bHiiu1yGJjptJXSvBFh+bOIg==", "dev": true, "license": "MIT", "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-deque": "0.2.2-alpha.2", + "@deepseek-ai/dsh-timeout": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "mime-types": "^3.0.2", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-api-remotes": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-settings": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-agent-default-model": "0.2.2-alpha.2", + "@deepseek-ai/dsh-agent-preset-registry": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-gateway": "0.2.2-alpha.2", + "@deepseek-ai/dsh-attachment": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-connection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-file-upload": "0.2.2-alpha.2", + "@deepseek-ai/dsh-commands": "0.2.2-alpha.2", + "@deepseek-ai/dsh-credentials": "0.2.2-alpha.2", + "@deepseek-ai/dsh-file-reference": "0.2.2-alpha.2", + "@deepseek-ai/dsh-fs": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-native-command": "0.2.2-alpha.2", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-persistence": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection-cache": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-query": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-title": "0.2.2-alpha.2", + "@deepseek-ai/dsh-settings": "0.2.2-alpha.2", + "@deepseek-ai/dsh-skill": "0.2.2-alpha.2", + "@deepseek-ai/dsh-subagent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-registry": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-time": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-workspace-path": "0.2.2-alpha.2", + "@deepseek-ai/dsh-working-directory": "0.2.2-alpha.2", + "@deepseek-ai/dsh-workspace": "0.2.2-alpha.2" + }, + "peerDependenciesMeta": { + "@deepseek-ai/dsh-session-persistence": { + "optional": true + }, + "@deepseek-ai/dsh-session-projection-cache": { + "optional": true + }, + "@deepseek-ai/dsh-settings": { + "optional": true + } } }, - "node_modules/@deepseek-ai/dsh-client-runtime": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-runtime/-/dsh-client-runtime-0.1.1-rc.2.tgz", - "integrity": "sha512-o1FH7Rlns0Xaxh4SBOWZ1wpa0ViGw6DXWNm5NFpsBTGYD94RGdIrud3QxgrfzmQKLzu33gvS8JL/IjqbzWyYsg==", + "node_modules/@deepseek-ai/dsh-api-settings-controller": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-api-settings-controller/-/dsh-api-settings-controller-0.2.2-alpha.2.tgz", + "integrity": "sha512-lnVsenDv2M3nF6wPXop33enjzw+FF1lqKJhMZjQkjaW3Z7CQdzh+NB5DjkCnP8QUpz6YdrbDmezu1/VXM093rQ==", "dev": true, "license": "MIT", "dependencies": { - "immer": "^10.1.1", - "zustand": "~4.4.7" + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-api-remotes": "^0.1.1-rc.2", - "@deepseek-ai/dsh-attachment": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2", - "@deepseek-ai/dsh-commands": "^0.1.1-rc.2", - "@deepseek-ai/dsh-host-apiproxy": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm-retry": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-projection": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-title": "^0.1.1-rc.2", - "@deepseek-ai/dsh-tools": "^0.1.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2", - "@deepseek-ai/dsh-typert-registry": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent-preset-registry": "0.2.2-alpha.2", + "@deepseek-ai/dsh-credentials": "0.2.2-alpha.2", + "@deepseek-ai/dsh-native-command": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-settings": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-client-ui-layout": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-layout/-/dsh-client-ui-layout-0.1.1-rc.2.tgz", - "integrity": "sha512-y7xSQyQYGuahLyJcSXpB+JbH1F5lGEc3L9K8cjLy5vd/L9N6gLFLWyeGdlGxxM4jxRt1+rAHDDZ/zl7b8GC5zQ==", + "node_modules/@deepseek-ai/dsh-api-terminal-controller": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-api-terminal-controller/-/dsh-api-terminal-controller-0.2.2-alpha.2.tgz", + "integrity": "sha512-aZp9Ts8NqWoA4qYvBVRyon5wEllDOpaJV7bAZwIsqzM/K7KPpuE33EJ1IghCTU+MXPMaldL7/ovufDJesYCTyA==", "dev": true, "license": "MIT", - "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-theme": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" - } - }, - "node_modules/@deepseek-ai/dsh-client-ui-primitives": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-primitives/-/dsh-client-ui-primitives-0.1.1-rc.2.tgz", - "integrity": "sha512-vCWEha1yhY//26j/LppHC/xZrU6MOuw6lcI4bJr5L7ppbY8h7GAR24ozBr+4ESMtQ5OmsvqGnsa/Kj5ZjSLfEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/langs": "^4.3.1", - "@types/mdast": "^4.0.4", - "anser": "^2.3.5", - "clsx": "^2.0.0", - "katex": "^0.16.47", - "mdast-util-from-markdown": "^2.0.3", - "mdast-util-gfm": "^3.1.0", - "mdast-util-math": "^3.0.0", - "micromark-core-commonmark": "^2.0.3", - "micromark-extension-gfm": "^3.0.0", - "micromark-extension-math": "^3.1.0", - "micromark-factory-space": "^2.0.1", - "micromark-util-character": "^2.1.1", - "micromark-util-classify-character": "^2.0.1", - "micromark-util-sanitize-uri": "^2.0.1", - "micromark-util-symbol": "^2.0.1", - "micromark-util-types": "^2.0.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "shiki": "^4.3.1" + "dependencies": { + "@deepseek-ai/dsh-deque": "0.2.2-alpha.2", + "@deepseek-ai/dsh-lazy-require": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "@xterm/addon-serialize": "^0.14.0", + "@xterm/headless": "^6.0.0", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-subprocess": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-client-ui-settings": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-settings/-/dsh-client-ui-settings-0.1.1-rc.2.tgz", - "integrity": "sha512-WqEQkyjW467leTJoA31BgqM+nALI3JnrvN1IXDhJuKd8E9nXhTLJcRgDrovEUkh2cjbHF8g8gQJ5Qafg9PzJiQ==", + "node_modules/@deepseek-ai/dsh-api-workspace-controller": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-api-workspace-controller/-/dsh-api-workspace-controller-0.2.2-alpha.2.tgz", + "integrity": "sha512-rEjrd/O7MLlvnWVnwCPuX8GLXFlpVjVpVfG6k36Oy88aK7m6m3Tl6xwsO8JpBip37JI1w6XBcyLTVdMrExHVZg==", "dev": true, "license": "MIT", "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/dsh-deque": "0.2.2-alpha.2", + "@deepseek-ai/dsh-native-command": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-api-remotes": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-settings": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-api-gateway": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-connection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-host-directory-picker": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-storage-domain": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/dsh-workspace": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-client-ui-settings-plugins": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-settings-plugins/-/dsh-client-ui-settings-plugins-0.1.1-rc.2.tgz", - "integrity": "sha512-NspsecIb4YdvE54jmaGBW/MjFyuu/MwUjQeM/I9iAgMOpLO+jOsoCOT4M8Su1Nfy1bxokgks9XcBJOcNGjN4Vg==", + "node_modules/@deepseek-ai/dsh-api-workspace-files": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-api-workspace-files/-/dsh-api-workspace-files-0.2.2-alpha.2.tgz", + "integrity": "sha512-yyEyOAI+1H+IROx0f+1VsUxBBuUV+jOn2vMntxH5asEDdxp80sjJwgOmbVcOE4LLT5IifgqjqKhNQfeFAyWl3w==", "dev": true, "license": "MIT", "dependencies": { - "clsx": "^2.0.0" + "@deepseek-ai/dsh-deque": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-api-remotes": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-client-ui-sidebar": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-sidebar/-/dsh-client-ui-sidebar-0.1.1-rc.2.tgz", - "integrity": "sha512-UCilyaNPwMO54JYtpCW5leLL+ecwtoRd4sHNVNG94aPQAqZOrqH+hqrBBsuolDJ9d0cKG9K5Mtew0fPz/hByZA==", + "node_modules/@deepseek-ai/dsh-app-boot": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-app-boot/-/dsh-app-boot-0.2.2-alpha.2.tgz", + "integrity": "sha512-GyQFybO4zW5M7n4PRFduRkqSX/T4RTPf5g6a73CKjxw/sl1w+VTSVwPvTMHWjnRgnc4wjQZ4TaS/1XPJ9gD6NQ==", "dev": true, "license": "MIT", "dependencies": { - "clsx": "^2.0.0" + "@deepseek-ai/dsh-atomic-write": "0.2.2-alpha.2", + "@deepseek-ai/dsh-official-bundles": "0.2.2-alpha.2", + "@deepseek-ai/dsh-package-manifest": "0.2.2-alpha.2", + "@eslint-community/regexpp": "^4.12.2", + "ajv": "^8.20.0", + "js-yaml": "^4.3.2", + "node-addon-require-builtin": "^0.1.9", + "resolve.exports": "^2.0.3", + "semver": "^7.8.5" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-layout": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/cordis-plugin-group": "~1.0.6-alpha.1", + "@deepseek-ai/cordis-plugin-include": "~1.0.11-alpha.1", + "@deepseek-ai/cordis-plugin-loader": "~1.0.7-alpha.1", + "@deepseek-ai/dsh-home-paths": "0.2.2-alpha.2", + "@deepseek-ai/dsh-launch-environment": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-client-ui-slots": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-slots/-/dsh-client-ui-slots-0.1.1-rc.2.tgz", - "integrity": "sha512-6uBi+Wq+dH1Q8RaJpbK66TjzPlkcSNDWvNZgQXw45RRQCi1gnm/gxFOyJcSQvgqk4pvm2/h/aIvaM/Pa5PRFCA==", + "node_modules/@deepseek-ai/dsh-atomic-write": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-atomic-write/-/dsh-atomic-write-0.2.2-alpha.2.tgz", + "integrity": "sha512-HbWIFGHYO1FMa60iXJiaLC0Lg9yr4pm3g+9kfaJ1kmZk2fOZyVRyn09xvC3j9zVczQXHx4zmPGgbRoRxyZV1Ug==", "dev": true, "license": "MIT", "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-client-ui-theme": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-theme/-/dsh-client-ui-theme-0.1.1-rc.2.tgz", - "integrity": "sha512-gRKI92D+EZtOy7UBkRfNvyL4e/3HikZ7exQSfgGpLEJ278/aXMvStNbL5ZgWVC41+MSZsBUk3dZAfbXNjelz2g==", + "node_modules/@deepseek-ai/dsh-attachment": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-attachment/-/dsh-attachment-0.2.2-alpha.2.tgz", + "integrity": "sha512-fl0hnNBtHHYpxN+X0WnR/+aGFq0tOWDQMImyDhJX4CA60OIlgReSPAn+VPrHtKkqK3h5t/4g2yMcsukX/0kehg==", "dev": true, "license": "MIT", - "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1", - "clsx": "^2.0.0" - }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-api-remotes": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2", - "@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-settings": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-code-runtime": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-code-runtime/-/dsh-code-runtime-0.1.1-rc.2.tgz", - "integrity": "sha512-SgFresqH5UABzRQZ7tOfqzOLMHF7089VeH+mfcwNQH5peOavgEKrAGOYz/9RnISH0XmMrj/x177t8gfO8Uvo/w==", + "node_modules/@deepseek-ai/dsh-brand": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-brand/-/dsh-brand-0.2.2-alpha.2.tgz", + "integrity": "sha512-VFHjxHZbfQRjaQHC9fO6oSeCZcHQeA54nThij/WB2Q6Qv8VBdJkitbVzYNLr54gmVDxQXfdK/yYJzHAQade6lg==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-commands": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-commands/-/dsh-commands-0.1.1-rc.2.tgz", - "integrity": "sha512-BOIe4Sht9rmMv1a6b3GWjWBbeWr7PtHlAy41vgpaymvUUuzOapOIA648ZMGCI/crRIt72Umev2FHtSwCNSbYZg==", + "node_modules/@deepseek-ai/dsh-chunked-list": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-chunked-list/-/dsh-chunked-list-0.2.2-alpha.2.tgz", + "integrity": "sha512-b1mtmylbH9V1VrbUMFRQFoiSZIlB4DETouFVGntc5PJ68lTr1fzmC3ACziUR+eBs5lX10oiq6FluOZJY7Ww8mQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "zod": "^4.4.3" + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-attachment": "^0.1.1-rc.2", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-scope": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-compaction": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-compaction/-/dsh-compaction-0.1.1-rc.2.tgz", - "integrity": "sha512-LV5GAIx7GO8DCRivnN2bmLmuucsYDG+ifG18BaXBqsVKdrzmaIu5o+CBxQAI2bX1N6mfBenLxmvCnROkyumLTg==", + "node_modules/@deepseek-ai/dsh-client-connection": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-connection/-/dsh-client-connection-0.2.2-alpha.2.tgz", + "integrity": "sha512-FRazdhNDkp2j3jiykqMouOJlv26wg+BEMNOTw/Ex3QO0BHeTv0GKeo8uwVXZQZoA/kIsmUP8zRf75ZfbEeGaiA==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "@deepseek-ai/dsh-credentials": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" + }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-commands": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-cordis-host-runner": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-cordis-host-runner/-/dsh-cordis-host-runner-0.1.1-rc.2.tgz", - "integrity": "sha512-+AQGegPy+gdtcjTTPxDDYwAFUn6BB1J/MFFbmpPr+WIqJoDyVsuCDe8Rr8mCJmxs12CmTZXXSOE03LW203W8fA==", + "node_modules/@deepseek-ai/dsh-client-file-upload": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-file-upload/-/dsh-client-file-upload-0.2.2-alpha.2.tgz", + "integrity": "sha512-eGfzr6IOsQwz9qTlPos6U33+J18J4+sruchW+Ff4Y/YQcIYHVkS+wYLrpp9N4boda2QpOix8bqGCuxluQUszuQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1", - "zod": "^4.4.3" + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-scope": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-tools": "^0.1.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-credentials": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-credentials/-/dsh-credentials-0.1.1-rc.2.tgz", - "integrity": "sha512-aeVBaH07rox7NuSNbSqbz8g0eNb2IIhNbrZngj/VxUsr/TR9TXOq7lm1CL6SBUDlstGw3vNWeXyhim/DmA5iSQ==", + "node_modules/@deepseek-ai/dsh-client-locale": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-locale/-/dsh-client-locale-0.2.2-alpha.2.tgz", + "integrity": "sha512-BsRQyLBnNbq6npmxyKWx3PyyaaddJygdDegJyQGGzgOpl6RjklWM2GcP80QMdOURjLbyxmrMuK2iukgSD+3Pog==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "@deepseek-ai/schemastery": "~3.18.6-alpha.1" + }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-file-reference": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-file-reference/-/dsh-file-reference-0.1.1-rc.2.tgz", - "integrity": "sha512-8Pd4SNHhV6OlbwfV5K4qmFb709I0Z68qgaUQJ7zM4BTjClNe1/dktQnETtWPfqbPTx/2sNKXM1rz4WXuROrIfQ==", + "node_modules/@deepseek-ai/dsh-client-product-analytics": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-product-analytics/-/dsh-client-product-analytics-0.2.2-alpha.2.tgz", + "integrity": "sha512-epjzLORSEXnSejcRnMu7V1eHZ/OZBnbakrKw+GyshvL9zIew9YXme5i6qf4+SxBgQVrLco7lwdWoxZm5E35mVg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "zod": "^4.4.3" + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-goal": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-goal/-/dsh-goal-0.1.1-rc.2.tgz", - "integrity": "sha512-lSHTh4vfS6eRb9to/y+bjRf2+0QkNpY3tHJ29HMTewR9fJYZsEVVu4Hc+GPhPEjF7RpiD35/sKx+akijtDasyg==", + "node_modules/@deepseek-ai/dsh-client-shortcuts": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-shortcuts/-/dsh-client-shortcuts-0.2.2-alpha.2.tgz", + "integrity": "sha512-Uq3gPJIlryEoKinbnzXTc3Q5tD4Yk+cZpoDU8zHpgLvJ3G3mb78GaWzcP6b6e4Wb/BNu2ZVJ7+ULe0/z6AJt0A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1", - "zod": "^4.4.3" + "@deepseek-ai/dsh-util-crypto": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-scope": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-projection": "^0.1.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-home-paths": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-home-paths/-/dsh-home-paths-0.1.1-rc.2.tgz", - "integrity": "sha512-lGsP7sbnu20AiRAb+gxYiKx9oa9r1D/8Fr6BwWHXpaPx6ZE4Qg0+ybh5SZVfGQ+XhLcQisu3nwWemEjtTJGiig==", + "node_modules/@deepseek-ai/dsh-client-store": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-store/-/dsh-client-store-0.2.2-alpha.2.tgz", + "integrity": "sha512-vGeI3ewFYtO3tUUmT0nd8SmcuDuMSJkj3G5MS1ovZRLgowMzqtUqii0GsYmOKLe6R/edIYdhvdVU6sahCZ2JXA==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-host-apiproxy": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-host-apiproxy/-/dsh-host-apiproxy-0.1.1-rc.2.tgz", - "integrity": "sha512-dplRnGGXXsQYFQ1KMHymAM0iaxuE9Z153JHYcGEgOwXNkS3HA20gSi3yMt6fz+zi/cMHYXvY1JQhS54BTc761A==", + "node_modules/@deepseek-ai/dsh-client-ui-conversation": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-conversation/-/dsh-client-ui-conversation-0.2.2-alpha.2.tgz", + "integrity": "sha512-EbQDWf1ASJ3cBlU1s+kb+Uk7vv5J+9U6EjA5/t87pEUbd/sZZmGeo5UjGa8vO52a9R1GQxLQrsuG+VsrqnWgfw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-agent-default-model": "^0.1.1-rc.2", - "@deepseek-ai/dsh-api-remotes": "^0.1.1-rc.2", - "@deepseek-ai/dsh-attachment": "^0.1.1-rc.2", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-commands": "^0.1.1-rc.2", - "@deepseek-ai/dsh-credentials": "^0.1.1-rc.2", - "@deepseek-ai/dsh-goal": "^0.1.1-rc.2", - "@deepseek-ai/dsh-host-directory-picker": "^0.1.1-rc.2", - "@deepseek-ai/dsh-jobs": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-native-command": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-projection": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-projection-cache": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-query": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-title": "^0.1.1-rc.2", - "@deepseek-ai/dsh-settings": "^0.1.1-rc.2", - "@deepseek-ai/dsh-skill": "^0.1.1-rc.2", - "@deepseek-ai/dsh-subagent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-tools": "^0.1.1-rc.2", - "@deepseek-ai/dsh-user-approval": "^0.1.1-rc.2", - "@deepseek-ai/dsh-user-questions": "^0.1.1-rc.2", - "@deepseek-ai/dsh-workspace": "^0.1.1-rc.2", - "@deepseek-ai/schemastery": "^3.18.1", - "fflate": "^0.8.2", - "zod": "^4.4.3" + "@deepseek-ai/schemastery": "~3.18.6-alpha.1" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent-presets": "^0.1.1-rc.2", - "@deepseek-ai/dsh-cordis-host-runner": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-host-directory-picker": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-host-directory-picker/-/dsh-host-directory-picker-0.1.1-rc.2.tgz", - "integrity": "sha512-m3puwS+bvJQ1eASdpEEwlQqKa9znhBSgtoSSI0GZN5VMynZVl7E31HMltENz3QC2Nd+C3bh7vNjrJFtumUDsLQ==", + "node_modules/@deepseek-ai/dsh-client-ui-layout": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-layout/-/dsh-client-ui-layout-0.2.2-alpha.2.tgz", + "integrity": "sha512-Dpi64wlOe1Dpz2Mr0QplpFkT0A58XWARyEOwD38FQ4AdsVJFNxnP6QQQN9itOfTwtQjQNoRD4UFNxfwhcEIEVA==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-host-plugin-inventory": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-host-plugin-inventory/-/dsh-host-plugin-inventory-0.1.1-rc.2.tgz", - "integrity": "sha512-Hud9ezW0bexWfhX7C+c5rdUDX1xzbEGDzj1lGQyj/QxdrxHYHjGrJq3tLRyvN6K4FSmEdG2IBKdQGCOLVrIthA==", + "node_modules/@deepseek-ai/dsh-client-ui-plugin-manager": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-plugin-manager/-/dsh-client-ui-plugin-manager-0.2.2-alpha.2.tgz", + "integrity": "sha512-Znf2d50YRywaGc/3DfG406tHnut4L2nDpE5faNfvx9u/APrvlD9djycDG1jGLxTMmZkuUeV+2I8tKPWBJhhLyQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "zod": "^4.4.3" + "@deepseek-ai/dsh-timeout": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/cordis-plugin-loader": "^1.0.2", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-host-webserver": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-host-webserver/-/dsh-host-webserver-0.1.1-rc.2.tgz", - "integrity": "sha512-t9MrjC65QHiiWhG9V8UZxgfE/aWYhJHHrIM0kbTvtXxg4tLGIKo/upHp7iiag65F3HTkVLrH/DUyPMi4v2ZA7g==", + "node_modules/@deepseek-ai/dsh-client-ui-primitives": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-primitives/-/dsh-client-ui-primitives-0.2.2-alpha.2.tgz", + "integrity": "sha512-hjf2/KLaAUxDrAhdnr2QQj6TZKFNi/O2UUqrZtRCBxwFfcRzRPBiXq9ayK4qFxllDDL0ZnjvDoqTUMd86ipCKQ==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" - }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-invariants": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-invariants/-/dsh-invariants-0.1.1-rc.2.tgz", - "integrity": "sha512-l+1Om/EDFyMjhgSuEx2WDLLA2fia/+ga9mBTCoT/MMslsnWaK5G0/lWwbwlTBSaJ6OfmYc3DuBgox8DbgIGHRQ==", + "node_modules/@deepseek-ai/dsh-client-ui-renderer": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-renderer/-/dsh-client-ui-renderer-0.2.2-alpha.2.tgz", + "integrity": "sha512-aEqbZxnzudASF7C2mK6a6uNkDoSpfHYwJPNnYfOBqYCjHxVf/o7kHbt2F5AWkeHiqlcFc5W0JCs3Qg4m2oIiJg==", "dev": true, "license": "MIT", - "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" - }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-jobs": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-jobs/-/dsh-jobs-0.1.1-rc.2.tgz", - "integrity": "sha512-SXvDJMvcUrGrlzIyE7j8/lI4Pj1nDe/UOR8C05Zagp+/0R8p46n6KylySvZdPAFENV5t8WX3Fw3eOaS4No0+wQ==", + "node_modules/@deepseek-ai/dsh-client-ui-session": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-session/-/dsh-client-ui-session-0.2.2-alpha.2.tgz", + "integrity": "sha512-IRz9khJrVIy5VwZz7yZDioJ1f59rp+w0FQuGXbHNMUuI8Hz1B0T0TFyLxSRXHQ5makFfuVshkmel4Rqrq7pgAw==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-llm": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-llm/-/dsh-llm-0.1.1-rc.2.tgz", - "integrity": "sha512-ASJfjIdZbIXvLwi3rGo+eZb/GxMVV/WO5/XVD3B96mT8EIzrlw3+nMR6/CvmJVzcycKQ2XN0wj7jD6TasPRySA==", + "node_modules/@deepseek-ai/dsh-client-ui-settings": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-settings/-/dsh-client-ui-settings-0.2.2-alpha.2.tgz", + "integrity": "sha512-tJ3OKD2gwVzvgztrC9INFa12uXZOAJ84yDnnVYPH0oxrs38r//kAPLGL2LfCObuYu85CdU+al/ohJ+h530YkMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/schemastery": "~3.18.6-alpha.1" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-attachment": "^0.1.1-rc.2", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-timeout": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-llm-retry": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-llm-retry/-/dsh-llm-retry-0.1.1-rc.2.tgz", - "integrity": "sha512-a13Fpmn8HFy8LCjl6gljaNPxcVjO/R+/uMPDl0QLjwQcHWVyGW38ZA8jP5iRuFgdyNNXpyxLWuZXi/0VOH00tA==", + "node_modules/@deepseek-ai/dsh-client-ui-settings-plugins": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-settings-plugins/-/dsh-client-ui-settings-plugins-0.2.2-alpha.2.tgz", + "integrity": "sha512-IP3pbtkxAgqAR/PPiDGMpimt9iLjyGjwqcMb37HlHJKVk4vNVrRBpCT07+5ghrXC+kDxa/BM+v8CqBhZcejuUg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" + } + }, + "node_modules/@deepseek-ai/dsh-client-ui-sidebar": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-sidebar/-/dsh-client-ui-sidebar-0.2.2-alpha.2.tgz", + "integrity": "sha512-gtb8rkbMZijfCfvYZRSOunxjxhBf+QrGJUifq7hQREbfpLAhYG/d/LPyMKWY8vjmpTkVomg86fnBvqS39TEw2A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" + } + }, + "node_modules/@deepseek-ai/dsh-client-ui-slots": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-slots/-/dsh-client-ui-slots-0.2.2-alpha.2.tgz", + "integrity": "sha512-OOl2LZXuoR2hXr4WHdl8dFnp2eONepBaphhnQLYSd+EAvtOKoh2xA7Uj1NvESgyYAzRzkYgJEzbNl6an/K47hQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" + } + }, + "node_modules/@deepseek-ai/dsh-client-ui-theme": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-theme/-/dsh-client-ui-theme-0.2.2-alpha.2.tgz", + "integrity": "sha512-YC4gr3nDvR9zLJ251RZOvX4zFAvDkcbbtIuVrnxEyqdMOuT4j0NRIl0ey7TGxoVJB0jFV2JP3TqUZDdF9RewBw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/schemastery": "~3.18.6-alpha.1" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-timeout": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-message-feedback": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-message-feedback/-/dsh-message-feedback-0.1.1-rc.2.tgz", - "integrity": "sha512-GzxDiNvyUVs/bbbm+Hr3MfuU3wM8ErRF5z9XadnSGPYZvipZDAMrvrS5v6JgOUaZt6ApBpEMJ4xVgIel1VnGYQ==", + "node_modules/@deepseek-ai/dsh-client-ui-trajectory": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-trajectory/-/dsh-client-ui-trajectory-0.2.2-alpha.2.tgz", + "integrity": "sha512-4TzJdQ9yYGcKZaKjzEeT4gJvjk83vIxxQj+9Ul5kvWficgiYQRsVxSWC0/ZhdETaN+iCSCLddxLOQZ4gIJO9oQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1", - "zod": "^4.4.3" + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2", - "@deepseek-ai/dsh-storage-domain": "^0.1.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-native-command": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-native-command/-/dsh-native-command-0.1.1-rc.2.tgz", - "integrity": "sha512-GIknPrwU7vZOUQ2o/ES7Z0uUhUrZGp/yigKP+RXEu41VI9tcybiXQAA8CEdDFmNQ11R2wfYQhr3WO5vy35akWg==", + "node_modules/@deepseek-ai/dsh-client-ui-workspace": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-workspace/-/dsh-client-ui-workspace-0.2.2-alpha.2.tgz", + "integrity": "sha512-pxgHISg0wp+ZnLvtRj/SGyKxZxpoGUpQqqpp6qof3y/yUFQBqEU9KLNn9xEwG4SMcobQPW6tZO9gvI0yyHgbGw==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-output-retention": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-output-retention/-/dsh-output-retention-0.1.1-rc.2.tgz", - "integrity": "sha512-tCni+bTEp/FWokfz3fqn4p6SzHn6pkY6H3HkS9UY5PHrztUE1ESUQUp41kIVtwUhRmJU7yissqjxe6kLDB6t2Q==", + "node_modules/@deepseek-ai/dsh-command-feedback": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-command-feedback/-/dsh-command-feedback-0.2.2-alpha.2.tgz", + "integrity": "sha512-4NAp6oQGawuxMk1qQ/xZqiV1OG/WQFlwHPIVAhNCoLtARVwJ6jOMNWh9UKbVqRdFM5sfa5tp/qz46wbFiP1bfw==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "zod": "4.4.3" + }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-anonymous-user-id": "0.2.2-alpha.2", + "@deepseek-ai/dsh-commands": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-scope": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-scope/-/dsh-scope-0.1.1-rc.2.tgz", - "integrity": "sha512-Xy3ejL6dwVSluZL7XOWy76ya4pCw1uHwxodDK4O9XiQUiUV4FBXnt0aNJUtMeAFN0c1YujxxCmRniMvuuNn1Nw==", + "node_modules/@deepseek-ai/dsh-commands": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-commands/-/dsh-commands-0.2.2-alpha.2.tgz", + "integrity": "sha512-342a/wwaS5QqZqRsl3QGM/WCPIWeYfNBY0lTwrMZs0B+TQOKhPmo34665YeK29CRy7BPnYoG/PQWN2XdT2iLxw==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "@deepseek-ai/dsh-util-crypto": "0.2.2-alpha.2", + "zod": "4.4.3" + }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-attachment": "0.2.2-alpha.2", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-session": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session/-/dsh-session-0.1.1-rc.2.tgz", - "integrity": "sha512-4/cv6X9HPhm47eyRhCu/WZwzrtJKegk5J+0xaxcZ9i8S0smdxP57tqy8a0jkSshLQn7BzMFxneQrlYExrLrDhQ==", + "node_modules/@deepseek-ai/dsh-compaction": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-compaction/-/dsh-compaction-0.2.2-alpha.2.tgz", + "integrity": "sha512-g1dl/75jkyp/ob1RYwwupNeIacZ17d/jVRqovDLjAQbKxHykeeOSkVGhEQzzEgwEFpvGiSHbZ0hmKwGqHA7RzQ==", "dev": true, "license": "MIT", "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-scope": "^0.1.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-commands": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-session-persistence": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-persistence/-/dsh-session-persistence-0.1.1-rc.2.tgz", - "integrity": "sha512-dxdYxRfmK5jWtiFFabqRNb/jGGjkXyF2djI7O8IIKmDVjhQiv170zpvhbAhRUuqClEdseCtbQpLBrRm2blzt3g==", + "node_modules/@deepseek-ai/dsh-compaction-image-offload": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-compaction-image-offload/-/dsh-compaction-image-offload-0.2.2-alpha.2.tgz", + "integrity": "sha512-z3A0fZsjRSE33YuNNdqK3VW7/CeTG4q/NMDstX7BxXkqq++V84HGIBWZTnAhTfsv8tDwA+B3IilntwddO1NTPA==", "dev": true, "license": "MIT", "peer": true, + "dependencies": { + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2" + }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-timeout": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-compaction": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-session-projection": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-projection/-/dsh-session-projection-0.1.1-rc.2.tgz", - "integrity": "sha512-SNaOrjRS4RMXocna6uL33TeS/uhcQ7jDJtJZ1HyDPL06mXUdAgje2MVt8OZCh6dH95xIiqpQQm+KjzeiQnhYSQ==", + "node_modules/@deepseek-ai/dsh-config-editor": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-config-editor/-/dsh-config-editor-0.2.2-alpha.2.tgz", + "integrity": "sha512-15xhR6IlrQ4BT1N+oT639zPj9QCepsPUOMA+KHmYlOoSs84qYEeI3Uj/UcNFQ6P5L9DJTzfy0BtN7cXeUoo7lg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "zod": "^4.4.3" + "@deepseek-ai/dsh-atomic-write": "0.2.2-alpha.2", + "js-yaml": "^4.3.2", + "yaml": "^2.8.1" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/cordis-plugin-include": "~1.0.11-alpha.1", + "@deepseek-ai/cordis-plugin-loader": "~1.0.7-alpha.1", + "@deepseek-ai/dsh-app-boot": "0.2.2-alpha.2", + "@deepseek-ai/dsh-hmr": "0.2.2-alpha.2" + }, + "peerDependenciesMeta": { + "@deepseek-ai/dsh-hmr": { + "optional": true + } } }, - "node_modules/@deepseek-ai/dsh-session-projection-cache": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-projection-cache/-/dsh-session-projection-cache-0.1.1-rc.2.tgz", - "integrity": "sha512-UvCRpIb+LoQI/nCLof17xc2P2KuZoucU3VuzfAukfsF3dYZAy5OP7jrCRdVZIvjRfvFUd7G+awdS9sWbnjcolg==", + "node_modules/@deepseek-ai/dsh-cordis-host-runner": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-cordis-host-runner/-/dsh-cordis-host-runner-0.2.2-alpha.2.tgz", + "integrity": "sha512-hQ+M8uPj3e/F1wiKaf79PekYBx5KhOULvOo5As/J5h+lcdNeXfxzlRPFnBVURXYQURqB8TGdE+P2NNQMV2+Z1w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1", - "zod": "^4.4.3" + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-projection": "^0.1.1-rc.2", - "@deepseek-ai/dsh-storage-domain": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-tools": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-session-query": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-query/-/dsh-session-query-0.1.1-rc.2.tgz", - "integrity": "sha512-QxQFRg/KnrZnYiO7fNtTJVTakuGs9ZFRkGGgYuNPzSz1pRqiohGNE/JZzBtq+HMv38RRTRZrM4aIOUV8OgqoXQ==", + "node_modules/@deepseek-ai/dsh-credentials": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-credentials/-/dsh-credentials-0.2.2-alpha.2.tgz", + "integrity": "sha512-2CBJncX9FvDNfXQOkaA4p/FoliK/6clNzsfQ9oB73f4s9d5NWYC2EARY8iB4BBn8KMtz6XDRC5d9vnDjIhtY5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" + } + }, + "node_modules/@deepseek-ai/dsh-deepseek-account": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-deepseek-account/-/dsh-deepseek-account-0.2.2-alpha.2.tgz", + "integrity": "sha512-BrGqyT93u8xABjSkaHx68MwFcs/NHa/WVOQ2XzG/HzRSBcD72/eUd44epF2xXHMdRUObG0CEGS641rdNOG78bg==", "dev": true, "license": "MIT", "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-title": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2" }, "peerDependenciesMeta": { - "@deepseek-ai/dsh-session-persistence": { + "@deepseek-ai/dsh-agent": { "optional": true } } }, - "node_modules/@deepseek-ai/dsh-session-reference": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-reference/-/dsh-session-reference-0.1.1-rc.2.tgz", - "integrity": "sha512-c9mz19ndxjVxSnXEhmJwGDjyKG4oNmu4Sfneix8OUJ+mAr3jIgb/QZZ8rYEc8Bi+Dd1z7Wr90IeWOBF0nolU3A==", + "node_modules/@deepseek-ai/dsh-deque": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-deque/-/dsh-deque-0.2.2-alpha.2.tgz", + "integrity": "sha512-NAEPdkznqy+i3s9tWaWB2krOTf/ac8LsNkUc6vISk00JkM4PL7onHut0VfiT8wNpqnVp8jVsFIC5AVFzpK2zVA==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1", - "zod": "^4.4.3" - }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-compaction": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-output-retention": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-query": "^0.1.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-session-title": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-title/-/dsh-session-title-0.1.1-rc.2.tgz", - "integrity": "sha512-qHv+9nE6J/piHsWwckmbJBS1sJjunCWsv00arhsgjW1XMLCHxG6QOB+U48P4EBZjve798Tz8AQIlWZy+9T8XmA==", + "node_modules/@deepseek-ai/dsh-file-reference": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-file-reference/-/dsh-file-reference-0.2.2-alpha.2.tgz", + "integrity": "sha512-PaOshyp4OA4beOWSTFSb+85P/nFPG4xvwALucl98TRZjycNLsihotJi7rt6/LfrIwtiGRrqd/tS2FiSEyHtkuQ==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1", - "zod": "^4.4.3" - }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-projection": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-settings": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-settings/-/dsh-settings-0.1.1-rc.2.tgz", - "integrity": "sha512-iGdKEt91Im3gE7xA9CzRfTJsPcFcxDeDOCLhAjzbpjEv7TAjx/EoYP7lPtiy+QmOjKSKy206SEtAKol9PXWbOw==", + "node_modules/@deepseek-ai/dsh-fs": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-fs/-/dsh-fs-0.2.2-alpha.2.tgz", + "integrity": "sha512-tKUWRUxlpBrFJT0eGRVUPCJDALizVyHwhT+a+otBnTOYZXjeqYI2uLzp3mqZn2n5uRg3f5ctA9mDOr+klRGS4A==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-sandbox": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-skill": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-skill/-/dsh-skill-0.1.1-rc.2.tgz", - "integrity": "sha512-FACjlOqdsWX+0RtSs3RWrdY0QQEpPJrBfvxUbWSh7E8UyCM8dKdIbsQnuXIjsAgC7GgYp7lyFDSCMovQ3ARp8g==", + "node_modules/@deepseek-ai/dsh-goal": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-goal/-/dsh-goal-0.2.2-alpha.2.tgz", + "integrity": "sha512-Z94O0czxrmXqckUTUOPuN9j4WbO+9T1umkKEi9u1dErG3D5EeIU4xkc/XcjPGFoUSUZ90Bl1wIxb40WnLoupDQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-scope": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-storage": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-storage/-/dsh-storage-0.1.1-rc.2.tgz", - "integrity": "sha512-ptJ1ss8spF+Y2VXjsMV37Qh1+hviYWZylDvXtfEBXjLbPi/BP3dktT4B6OMIAD1rRN+0A4HdTqJBgLk7Gp1rig==", + "node_modules/@deepseek-ai/dsh-home-paths": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-home-paths/-/dsh-home-paths-0.2.2-alpha.2.tgz", + "integrity": "sha512-Csxrpp2CFmdPWG3UYGsrGmE11GYpo/iuQuPHg/8ydPfykvhjC4F3BM6WSmYlrBrSE9SKEXVNyY32E2Ec3dHklg==", "dev": true, "license": "MIT", "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-storage-domain": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-storage-domain/-/dsh-storage-domain-0.1.1-rc.2.tgz", - "integrity": "sha512-9/3OuaZpxf9NZWhrARpgZ7UBa03pPaTIiDBw+SfESfwVk42NNLVCQgvnEbrWoNwdtzmv0aYCEV23sKwwlQ0LBA==", + "node_modules/@deepseek-ai/dsh-host-directory-picker": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-host-directory-picker/-/dsh-host-directory-picker-0.2.2-alpha.2.tgz", + "integrity": "sha512-mfBRpjoPHntENP/sIzNL6GiOzV2eYAYi9l2a5UKPJkyVN+bX8RypLg23lDgDHgpUV7BQBPsMdYnbPKx14dnCmg==", "dev": true, "license": "MIT", "peer": true, - "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1", - "zod": "^4.4.3" - }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-storage": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-subagent": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-subagent/-/dsh-subagent-0.1.1-rc.2.tgz", - "integrity": "sha512-CNa0WuFCR69TMnBKYQInz49/olDSaVHiYkDTyTuDh7YW7Y80/f/HjQe5G0fQaqZMLla3IUSXCIPl5EssWmjcQw==", + "node_modules/@deepseek-ai/dsh-host-plugin-inventory": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-host-plugin-inventory/-/dsh-host-plugin-inventory-0.2.2-alpha.2.tgz", + "integrity": "sha512-W5EXgxL3GoEQvJTiQdBluQqdMB/FmXvR6v1SlY6Ecf67b+PRYscnW7d4G8updtdb8fsuVmtxpEySisk6bIVCxg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "zod": "^4.4.3" + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-agent-presets": "^0.1.1-rc.2", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-jobs": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-sandbox": "^0.1.1-rc.2", - "@deepseek-ai/dsh-sandbox-policy": "^0.1.1-rc.2", - "@deepseek-ai/dsh-scope": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-projection": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-projection-cache": "^0.1.1-rc.2", - "@deepseek-ai/dsh-tools": "^0.1.1-rc.2", - "@deepseek-ai/dsh-user-approval": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/cordis-plugin-loader": "~1.0.7-alpha.1", + "@deepseek-ai/dsh-agent-preset-registry": "0.2.2-alpha.2", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2" }, "peerDependenciesMeta": { - "@deepseek-ai/dsh-agent-presets": { - "optional": true - }, - "@deepseek-ai/dsh-jobs": { - "optional": true - }, - "@deepseek-ai/dsh-sandbox": { - "optional": true - }, - "@deepseek-ai/dsh-sandbox-policy": { - "optional": true - }, - "@deepseek-ai/dsh-session-persistence": { - "optional": true - }, - "@deepseek-ai/dsh-session-projection": { - "optional": true - }, - "@deepseek-ai/dsh-session-projection-cache": { - "optional": true - }, - "@deepseek-ai/dsh-user-approval": { + "@deepseek-ai/dsh-agent-preset-registry": { "optional": true } } }, - "node_modules/@deepseek-ai/dsh-system-prompt": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-system-prompt/-/dsh-system-prompt-0.1.1-rc.2.tgz", - "integrity": "sha512-on4hjAlYI5uX9q7Sf95YkMMBVe6heywtA/H50ksrIMUub8U2B98hO9iQpHhjwIO1F1vu+5pLcPvRr6yUGGmtXQ==", + "node_modules/@deepseek-ai/dsh-host-webserver": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-host-webserver/-/dsh-host-webserver-0.2.2-alpha.2.tgz", + "integrity": "sha512-RYRAkfXjxgjDreg1WN1sHUI8Nvkl/3NAUfFueUW7LpOVFcBLjs1pLEAGOXmC31+oGJtl69MiM99qD+3IvK4LZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "compression": "^1.8.1", + "ipaddr.js": "^2.5.0", + "negotiator": "^1.1.0" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-scope": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-timeout": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-timeout/-/dsh-timeout-0.1.1-rc.2.tgz", - "integrity": "sha512-RrouVgU3G5gXr9zHhpThkMG6YKdcRJzXXdPm1dq3ioBxbvxlfMSfNY4tN8lWMJxLyGtvWkPra0HQX+YWxvdOOA==", + "node_modules/@deepseek-ai/dsh-http-proxy": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-http-proxy/-/dsh-http-proxy-0.2.2-alpha.2.tgz", + "integrity": "sha512-u+tO+aEtoKPWzdjmCxWiTRvw7C/HO3Fwfc+oJDUvNpEjgBLJ7TBKV16upxAo05A03zYQRhpqJhpgaAav/Rhg3w==", "dev": true, "license": "MIT", "peer": true, + "dependencies": { + "undici": "8.11.2" + }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-tools": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-tools/-/dsh-tools-0.1.1-rc.2.tgz", - "integrity": "sha512-0GGL4D55MwYDepzZMOI3L0ycu5b2qr96GL0Y7snwhAnpK2Di61rbX3fJE+PB3ZrovGX0csIRdt9n3iJZDVtDrw==", + "node_modules/@deepseek-ai/dsh-launch-environment": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-launch-environment/-/dsh-launch-environment-0.2.2-alpha.2.tgz", + "integrity": "sha512-T6LWWiZyirehZ4UrNTJPYEdtXcQWF5EMhx6hwFhI7/RTuVIY3mgKhq42fyIQrB5x5s5J83MdVfCeoGqQgcPVYA==", "dev": true, "license": "MIT", "peer": true, - "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" - }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-code-runtime": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-scope": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.2", - "@deepseek-ai/dsh-user-approval": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-typert-protocol": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-typert-protocol/-/dsh-typert-protocol-0.1.1-rc.2.tgz", - "integrity": "sha512-lxBssDc5Pz1qBE5kuIyaArA7AvIPq9rpaVclylodiSzVJe95e2xruBg73tflyjtd8y00toet+DLgQ6tSSsq6Kw==", + "node_modules/@deepseek-ai/dsh-lazy-require": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-lazy-require/-/dsh-lazy-require-0.2.2-alpha.2.tgz", + "integrity": "sha512-x7ubwhKH5L4WuyiuFT+ZzQnfH0EXzOvewwW5oRjgz4MjAU08jjHI68vHCVZZCtYNQCL9QbK8e6zVVUzHgXYvqw==", "dev": true, "license": "MIT", "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-typert-registry": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-typert-registry/-/dsh-typert-registry-0.1.1-rc.2.tgz", - "integrity": "sha512-ATZu3i7UId+ktsWW3pUFm5Hi1fVsRXFmByATVPp2RlxH1zjxXnauVW6k70ZwU/4FOsphRzo65SXOlAXA+natYg==", + "node_modules/@deepseek-ai/dsh-llm": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-llm/-/dsh-llm-0.2.2-alpha.2.tgz", + "integrity": "sha512-AwlZx8DW4e0xg/xB/YzvORSHKKQWMyhJgFXT4J9+5QV3x3BI2M35kHxRD4LoBQ2wRO1tImgaCgA4tH1haMw6IQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2", - "zod": "^4.4.3" + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-timeout": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-crypto": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-user-approval": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-user-approval/-/dsh-user-approval-0.1.1-rc.2.tgz", - "integrity": "sha512-SdsO4Rs+NeJFoertkVilXBACREOLfkKPJJznYKqDhJxeRo38RJ56dtj0Xd0/6rERmsQiMck4Bwdrzg1ubUqPNA==", + "node_modules/@deepseek-ai/dsh-message-feedback": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-message-feedback/-/dsh-message-feedback-0.2.2-alpha.2.tgz", + "integrity": "sha512-uEL1pPAcvjOAeBjYhb6ohAJM3eOaPxMuzB5PrX03RSSBt8/+DoBKKPBsElcRdFh/Fj7KTsA6QfYO60tMlRMM0g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2", - "@deepseek-ai/dsh-scope": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-command-feedback": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-persistence": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2" } }, - "node_modules/@deepseek-ai/dsh-user-questions": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-user-questions/-/dsh-user-questions-0.1.1-rc.2.tgz", - "integrity": "sha512-9lYoB7qCFE+Vvgwjny3MnRfS7QUefOspj4KpE3b30DAAJfxwrU4hRtLCHOKxyurF01qrkoEBBpXn4H5ilXYTKg==", + "node_modules/@deepseek-ai/dsh-native-command": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-native-command/-/dsh-native-command-0.2.2-alpha.2.tgz", + "integrity": "sha512-7jHBQEZtHZ/IpePxlA2KQo9pQyi861H7XbSywJRO6JQ8IOqMZmOaDSHt+8cdCXk/GKDxACz7k+IHQAgS0iklrw==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-llm": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/dsh-workspace": { - "version": "0.1.1-rc.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-workspace/-/dsh-workspace-0.1.1-rc.2.tgz", - "integrity": "sha512-jBUob4H5TZAiExq9YNVCglKAFmAKMtd1UbyqFfnZZ1Owm+3c3NbAXY947MHiD6NwCwFEW1y7FjrFj66UQvG90A==", + "node_modules/@deepseek-ai/dsh-office-to-pdf": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-office-to-pdf/-/dsh-office-to-pdf-0.2.2-alpha.2.tgz", + "integrity": "sha512-ljvzZMwpX8TRQnc0U/7Q2w/5U9t73tF8TpNw9SVcDL0sJ06LdyVo1pFVn4JNN+iBYhJ2/VMRsgr3PQnhMYEdtg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "zod": "^4.4.3" + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/libreoffice-kit": "^0.1.5", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-brand": "^0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session": "^0.1.1-rc.2", - "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2", - "@deepseek-ai/dsh-storage": "^0.1.1-rc.2", - "@deepseek-ai/dsh-storage-domain": "^0.1.1-rc.2" + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@deepseek-ai/schemastery": { - "version": "3.18.1", - "resolved": "https://registry.npmjs.org/@deepseek-ai/schemastery/-/schemastery-3.18.1.tgz", - "integrity": "sha512-Qn0FCSwCQnpnj6SB31I6i2sIKgKWnkbJM8O0EU91Gv2UsYVvtZTl6IA0sCwk2e2MZf5S8w5hpq9QkeVvK9qwxg==", - "inBundle": true, + "node_modules/@deepseek-ai/dsh-official-bundles": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-official-bundles/-/dsh-official-bundles-0.2.2-alpha.2.tgz", + "integrity": "sha512-1ZzPosfWZqq3gjySzmHCV4msx8znD1JmZx1Xc9nHFCqkimRFfLsiA2VdvZXgXenlfmamBg2y+HXPCRmhoEtmRg==", + "dev": true, "license": "MIT", "dependencies": { - "@deepseek-ai/cosmokit": "^1.8.2", - "@standard-schema/spec": "^1.1.0" + "@deepseek-ai/dsh-package-manifest": "0.2.2-alpha.2" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", - "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@deepseek-ai/dsh-output-retention": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-output-retention/-/dsh-output-retention-0.2.2-alpha.2.tgz", + "integrity": "sha512-KVg52DOp2ajDdMGZDaEUooLONfSHc8jUGyG/H2kYj1/vl3lD5BXHocNTXO3RAROfitXR9IYnS2rD7q7TRUiygw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" + "peer": true, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", - "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", - "cpu": [ - "arm" - ], + "node_modules/@deepseek-ai/dsh-package-manifest": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-package-manifest/-/dsh-package-manifest-0.2.2-alpha.2.tgz", + "integrity": "sha512-3+FJWR/YptEzUAYPjNJb9P90xX6+MPMT3KmHtQScNuJNi+6gWLw2b/0/xjHGfG11hZyIB9AqSgY1dUIb25x6mA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", - "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", - "cpu": [ - "arm64" - ], + "node_modules/@deepseek-ai/dsh-permission-presets": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-permission-presets/-/dsh-permission-presets-0.2.2-alpha.2.tgz", + "integrity": "sha512-WNoCifSMSBoLMP4PKChGUM3zmelLPURLMbzLuT2kp+X8q6ZRdSFbvD9oZ5BEeFMZbudVehgP00J378uScT336Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-commands": "0.2.2-alpha.2", + "@deepseek-ai/dsh-sandbox": "0.2.2-alpha.2", + "@deepseek-ai/dsh-sandbox-policy": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-shell": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/dsh-user-approval": "0.2.2-alpha.2" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", - "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", - "cpu": [ - "x64" - ], + "node_modules/@deepseek-ai/dsh-plugin-detail": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-plugin-detail/-/dsh-plugin-detail-0.2.2-alpha.2.tgz", + "integrity": "sha512-ueiDl/jwznp4Tk7qS8izSO9H14bGAHPMZ5ctK9NLcxdkpy+8s1352VvMF8zhbhL4PMTYOeKiJl6wv+P1yMDIIg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", - "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", - "cpu": [ - "arm64" - ], + "node_modules/@deepseek-ai/dsh-plugin-manager": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-plugin-manager/-/dsh-plugin-manager-0.2.2-alpha.2.tgz", + "integrity": "sha512-ImHMhwoCmfr7DMoEHx96qNJ7+QnAZSCIp3x9J8asRQH5d6+FmBo9bS7WyA36VPP6gNvDT4gNi7n7ZrP/RuCgUg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@deepseek-ai/dsh-app-boot": "0.2.2-alpha.2", + "@deepseek-ai/dsh-atomic-write": "0.2.2-alpha.2", + "@deepseek-ai/dsh-official-bundles": "0.2.2-alpha.2", + "@deepseek-ai/dsh-plugin-detail": "0.2.2-alpha.2", + "@deepseek-ai/dsh-timeout": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "execa": "^10.0.1", + "hosted-git-info": "^9.0.3", + "semver": "^7.8.5", + "yaml": "^2.9.0", + "zod": "4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/cordis-plugin-loader": "~1.0.7-alpha.1", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-hmr": "0.2.2-alpha.2", + "@deepseek-ai/dsh-host-plugin-inventory": "0.2.2-alpha.2", + "@deepseek-ai/dsh-sandbox": "0.2.2-alpha.2", + "@deepseek-ai/dsh-sandbox-policy": "0.2.2-alpha.2", + "@deepseek-ai/dsh-subprocess": "0.2.2-alpha.2", + "@deepseek-ai/dsh-tools": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/dsh-user-approval": "0.2.2-alpha.2" + }, + "peerDependenciesMeta": { + "@deepseek-ai/dsh-hmr": { + "optional": true + }, + "@deepseek-ai/dsh-user-approval": { + "optional": true + } } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", - "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", - "cpu": [ - "x64" - ], + "node_modules/@deepseek-ai/dsh-plugin-market": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-plugin-market/-/dsh-plugin-market-0.2.2-alpha.2.tgz", + "integrity": "sha512-PnXMMYWzAK3vwf+GVDFwM0/F+fF4/Sc0Bis72ZG7q/eYos/PMfnrV7j/YF2l9/tE0lPQeE3WB35JOLPGB8u+wg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@deepseek-ai/dsh-app-boot": "0.2.2-alpha.2", + "@deepseek-ai/dsh-atomic-write": "0.2.2-alpha.2", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-package-manifest": "0.2.2-alpha.2", + "@deepseek-ai/dsh-plugin-detail": "0.2.2-alpha.2", + "@deepseek-ai/dsh-plugin-market-index": "0.2.2-alpha.2", + "@deepseek-ai/dsh-timeout": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "execa": "^10.0.1", + "hosted-git-info": "^9.0.3", + "minisearch": "^7.2.0", + "semver": "^7.8.5", + "zod": "^4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/cordis-plugin-loader": "~1.0.7-alpha.1", + "@deepseek-ai/dsh-plugin-manager": "0.2.2-alpha.2", + "@deepseek-ai/dsh-settings": "0.2.2-alpha.2", + "@deepseek-ai/dsh-subprocess": "0.2.2-alpha.2", + "@deepseek-ai/dsh-tools": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2" + }, + "peerDependenciesMeta": { + "@deepseek-ai/dsh-settings": { + "optional": true + } } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", - "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", - "cpu": [ - "arm64" - ], + "node_modules/@deepseek-ai/dsh-plugin-market-index": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-plugin-market-index/-/dsh-plugin-market-index-0.2.2-alpha.2.tgz", + "integrity": "sha512-tHV3qjDDzi7kG2P2YevqI43B3MUNfkTrNfHwd+l8Ei5YIcb+bSRH5kdAXJsEj/0QZOpewPfYQTAyLvwoApkYAA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@deepseek-ai/dsh-package-manifest": "0.2.2-alpha.2", + "semver": "^7.8.5" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", - "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", - "cpu": [ - "x64" - ], + "node_modules/@deepseek-ai/dsh-sandbox": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-sandbox/-/dsh-sandbox-0.2.2-alpha.2.tgz", + "integrity": "sha512-GXgvCmabIQlnk55kn2Hs4eHGf9o2Nh62VumASxJ3AeYtHq6dE/cr15BvJXG5HqAQy0ZH++gby4fnkTSRiKEfTg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", - "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", - "cpu": [ - "arm" - ], + "node_modules/@deepseek-ai/dsh-sandbox-policy": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-sandbox-policy/-/dsh-sandbox-policy-0.2.2-alpha.2.tgz", + "integrity": "sha512-R7cwz/S6n1cWJYJvEgo+u3xRGBFeoLn0YopF/1uytftjKBuPslila3UtesPzussPT3kYWiWMGGvzrdJeTtPuWw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-sandbox": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.2" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", - "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", - "cpu": [ - "arm64" - ], + "node_modules/@deepseek-ai/dsh-schedule": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-schedule/-/dsh-schedule-0.2.2-alpha.2.tgz", + "integrity": "sha512-Ai44565k7u48YIdGYj4veTm3XCHBw4Zk6K2lDI7RHdgRv6yjJF812lXaXEJ93slkB24BpAD3e7nKoPRWqOGZ+Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "@js-temporal/polyfill": "0.5.1", + "zod": "4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-session-controller": "0.2.2-alpha.2", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-persistence": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-storage-domain": "0.2.2-alpha.2", + "@deepseek-ai/dsh-subagent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-tools": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-time": "0.2.2-alpha.2", + "@deepseek-ai/dsh-workspace": "0.2.2-alpha.2" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", - "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", - "cpu": [ - "ia32" - ], + "node_modules/@deepseek-ai/dsh-scope": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-scope/-/dsh-scope-0.2.2-alpha.2.tgz", + "integrity": "sha512-BiniiKjbLL7bRzzqCcq2ldVqi6ZnFKOTWhCvooyl/r2CDhstZfV6x2Z1hxSe4z/Gi5Vk7/sRjpPrO30bGBEYfw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "peer": true, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", - "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", - "cpu": [ - "loong64" - ], + "node_modules/@deepseek-ai/dsh-session": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session/-/dsh-session-0.2.2-alpha.2.tgz", + "integrity": "sha512-EvZBQIcxQCdz1gdUCgbUJMqTXSGQlnQpCYnogkNHyiuya7JWL8ye8ieqLBHRtqWarjSWxJWFtfHuun8EL81+jA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", - "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", - "cpu": [ - "mips64el" - ], + "node_modules/@deepseek-ai/dsh-session-format": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-format/-/dsh-session-format-0.2.2-alpha.2.tgz", + "integrity": "sha512-FrWdmAzGOxBvhv4udF2EyBkwduOBhdQuz0DmMXBVo6HcTttYs7pmQ1Y2vWW05XDArO4XQvOUy7o3kb8kw89jZQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", - "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@deepseek-ai/dsh-session-format-catalog": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-format-catalog/-/dsh-session-format-catalog-0.2.2-alpha.2.tgz", + "integrity": "sha512-iyK9iV6I83WP6TFYJ/9CqfCmDRQcI9GwGX81kFsNNlDr62NicGAvRW3ydH5FePncXZ0Z2fyWJZNiER0JCuOVcw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@deepseek-ai/dsh-compaction-image-offload": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-format": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-format-v0-to-v1": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-format-v1-to-v2": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-format-v2-to-v3": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-format-v3-to-v4": "0.2.2-alpha.2" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", - "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", - "cpu": [ - "riscv64" - ], + "node_modules/@deepseek-ai/dsh-session-format-v0-to-v1": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-format-v0-to-v1/-/dsh-session-format-v0-to-v1-0.2.2-alpha.2.tgz", + "integrity": "sha512-EEH0QxS/YNzGjYpXt3rsM76i0fd6FzKhB6gg5pSsSPeNEWAgAOtExfYjJb8SmV7gIYL1t0YvuMCVV4dnmSdDGA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@deepseek-ai/dsh-session-format": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-time": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", - "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", - "cpu": [ - "s390x" - ], + "node_modules/@deepseek-ai/dsh-session-format-v1-to-v2": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-format-v1-to-v2/-/dsh-session-format-v1-to-v2-0.2.2-alpha.2.tgz", + "integrity": "sha512-6yVd/C6Wgkdu5qLGO8Uzm8J8D1dBnelPizCZ+NDUFHPwBnCZEGfx+WblbAViqGdbydaNZm877Jw83LAPP5rKEA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", - "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-format": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-format-v0-to-v1": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", - "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", - "cpu": [ - "arm64" - ], + "node_modules/@deepseek-ai/dsh-session-format-v2-to-v3": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-format-v2-to-v3/-/dsh-session-format-v2-to-v3-0.2.2-alpha.2.tgz", + "integrity": "sha512-syohrFOOw0yKuXunrJV2jwtjQhUw+9PbvQs4Kss4CgWBgovEOCjlFVOQKKxWVN6dzKjHEBEjYPNA6gbLjoRNTg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@deepseek-ai/dsh-session-format": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-format-v0-to-v1": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-format-v1-to-v2": "0.2.2-alpha.2" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", - "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", - "cpu": [ - "x64" - ], + "node_modules/@deepseek-ai/dsh-session-format-v3-to-v4": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-format-v3-to-v4/-/dsh-session-format-v3-to-v4-0.2.2-alpha.2.tgz", + "integrity": "sha512-T7ilntgBIj7w5xsSDEyQexsq4kYJNk4CMxjQFCulzhvozYSA2oekhphHjmxv7tCtApVTGuG7DZ0hg+IFzs4UWw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@deepseek-ai/dsh-session-format": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-format-v2-to-v3": "0.2.2-alpha.2" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", - "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", - "cpu": [ - "arm64" - ], + "node_modules/@deepseek-ai/dsh-session-persistence": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-persistence/-/dsh-session-persistence-0.2.2-alpha.2.tgz", + "integrity": "sha512-+Cwfcx9O/7c6mVgfDdUbSATypG4Po2DsPAqUatJnwS1aBe9+srNZBTviTzq+0GQ1Jb2eq0eD1C9q9gzIZ2KNLQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-timeout": "0.2.2-alpha.2" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", - "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", - "cpu": [ - "x64" - ], + "node_modules/@deepseek-ai/dsh-session-projection": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-projection/-/dsh-session-projection-0.2.2-alpha.2.tgz", + "integrity": "sha512-RdVHzNU+UtS+pLc/PIF99/Eu34Epr2kKv1GhO2ir1Btqlk0FV2/8ZCeVhZIpiEug2e4AoaZwX1Cnuq4Liy86YQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "zod": "4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", - "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", - "cpu": [ - "arm64" - ], + "node_modules/@deepseek-ai/dsh-session-query": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-query/-/dsh-session-query-0.2.2-alpha.2.tgz", + "integrity": "sha512-TgwkXBuKQ05N2nr+k09wjMdptwLQqKg0pM9DTQbXjC1qmt5OICsvQCHXkxjd6yCb1IFgD4+Z3tUqt6rj4AKjJA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@deepseek-ai/dsh-session-format-catalog": "0.2.2-alpha.2" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-persistence": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection-cache": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-title": "0.2.2-alpha.2", + "@deepseek-ai/dsh-tool-todo": "0.2.2-alpha.2" + }, + "peerDependenciesMeta": { + "@deepseek-ai/dsh-session-persistence": { + "optional": true + }, + "@deepseek-ai/dsh-session-projection": { + "optional": true + }, + "@deepseek-ai/dsh-session-projection-cache": { + "optional": true + } } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", - "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", - "cpu": [ - "x64" - ], + "node_modules/@deepseek-ai/dsh-session-reference": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-reference/-/dsh-session-reference-0.2.2-alpha.2.tgz", + "integrity": "sha512-Hae3MQgU5u31ow9pGSaA+qH8gV2PMYqPrr4USVSf22pnAqVvijD7WsGuW8hChcnSJUDN0RPF7q5XNpecsyN8kg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-compaction": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-output-retention": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection-cache": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-query": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-title": "0.2.2-alpha.2", + "@deepseek-ai/dsh-spill": "0.2.2-alpha.2", + "@deepseek-ai/dsh-subagent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2" + }, + "peerDependenciesMeta": { + "@deepseek-ai/dsh-session-projection-cache": { + "optional": true + }, + "@deepseek-ai/dsh-spill": { + "optional": true + } } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", - "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", - "cpu": [ - "arm64" - ], + "node_modules/@deepseek-ai/dsh-session-title": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-title/-/dsh-session-title-0.2.2-alpha.2.tgz", + "integrity": "sha512-RNZ4rh6v30eIB00XbmKi5ZNxLKEowj01ox+rX36gB90lNNQc5v9kZV+Gsr8fvVR0bBZ5fOkJpUBJx3aIp3lrww==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", - "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", - "cpu": [ - "ia32" - ], + "node_modules/@deepseek-ai/dsh-settings": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-settings/-/dsh-settings-0.2.2-alpha.2.tgz", + "integrity": "sha512-t1ZxV83EJa2xDO7Wlr+ZNEb3O7HnYh859dDxARHlaQX7yoVM2YGm89SYpa6qzh2X9+0/ZUl2q5DqsZRCNxbnIg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@deepseek-ai/cordis-plugin-loader": "~1.0.7-alpha.1", + "@deepseek-ai/cosmokit": "~1.8.7-alpha.1", + "@deepseek-ai/dsh-config-editor": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "yaml": "^2.8.1" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", - "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", - "cpu": [ - "x64" - ], + "node_modules/@deepseek-ai/dsh-shell": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-shell/-/dsh-shell-0.2.2-alpha.2.tgz", + "integrity": "sha512-71p5Zd+T6lSLfz96BFZobMKz9UMd03zkLo/0lZXIn7gVNmZoN55uHO4ubX2t+H9fh5e7xVOqEz/7uvchW08f+A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "peer": true, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-sandbox": "0.2.2-alpha.2", + "@deepseek-ai/dsh-subprocess": "0.2.2-alpha.2" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@napi-rs/lzma-linux-x64-gnu": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", - "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", - "cpu": [ - "x64" - ], + "node_modules/@deepseek-ai/dsh-skill": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-skill/-/dsh-skill-0.2.2-alpha.2.tgz", + "integrity": "sha512-UPbD0hXdNJw4dI+b95C2YFAhoZNDCcODveM9VlrEpeKx0wTZN4UHy6LmsdbVTXoKGWIxk5MXdWOCYB/Nce36Hw==", "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^22.20 || ^24.12 || >=25" + "peer": true, + "dependencies": { + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2" } }, - "node_modules/@oxc-project/types": { - "version": "0.147.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.147.0.tgz", - "integrity": "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==", + "node_modules/@deepseek-ai/dsh-storage": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-storage/-/dsh-storage-0.2.2-alpha.2.tgz", + "integrity": "sha512-B78oLpjgKQfKRFQp5NZSq4rk4iZ26xlrnFzSRJ3DBMP9hezUMqemxG0JCyp1l2TmuWm3UyIt4tkOqfW1djq1sw==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" + "peer": true, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" } }, - "node_modules/@quansync/fs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz", - "integrity": "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==", + "node_modules/@deepseek-ai/dsh-storage-domain": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-storage-domain/-/dsh-storage-domain-0.2.2-alpha.2.tgz", + "integrity": "sha512-/K07UMUrqShfZfCAkjoQHf4KDBSxRGcgTYSuZ+nE7mA0peeuyI1UGSQafqZV46gbXaK5xgHN58j7BasyuXMtuw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "quansync": "^1.0.0" + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.6.tgz", - "integrity": "sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-storage": "0.2.2-alpha.2" } }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.6.tgz", - "integrity": "sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==", - "cpu": [ - "arm64" - ], + "node_modules/@deepseek-ai/dsh-subagent": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-subagent/-/dsh-subagent-0.2.2-alpha.2.tgz", + "integrity": "sha512-9owXbZytsbuNUy4ktKeIjTY2MnLSCg0XY6/l/+wn/96K5bIqnVt/ef1ExHDuz/nQAYtdQYucvz2oF6lG24WXoA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "dependencies": { + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-chunked-list": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-agent-preset-registry": "0.2.2-alpha.2", + "@deepseek-ai/dsh-attachment": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-permission-presets": "0.2.2-alpha.2", + "@deepseek-ai/dsh-sandbox": "0.2.2-alpha.2", + "@deepseek-ai/dsh-sandbox-policy": "0.2.2-alpha.2", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-persistence": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-query": "0.2.2-alpha.2", + "@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.2", + "@deepseek-ai/dsh-tools": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/dsh-user-approval": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-time": "0.2.2-alpha.2", + "@deepseek-ai/dsh-working-directory": "0.2.2-alpha.2", + "@deepseek-ai/dsh-workspace": "0.2.2-alpha.2" + }, + "peerDependenciesMeta": { + "@deepseek-ai/dsh-agent-preset-registry": { + "optional": true + }, + "@deepseek-ai/dsh-permission-presets": { + "optional": true + }, + "@deepseek-ai/dsh-sandbox": { + "optional": true + }, + "@deepseek-ai/dsh-sandbox-policy": { + "optional": true + }, + "@deepseek-ai/dsh-session-persistence": { + "optional": true + }, + "@deepseek-ai/dsh-session-query": { + "optional": true + }, + "@deepseek-ai/dsh-user-approval": { + "optional": true + }, + "@deepseek-ai/dsh-workspace": { + "optional": true + } + } }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.6.tgz", - "integrity": "sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==", + "node_modules/@deepseek-ai/dsh-subprocess": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-subprocess/-/dsh-subprocess-0.2.2-alpha.2.tgz", + "integrity": "sha512-rZHXZaeZWwKz6mFPVeuBL3vDgNrWrVppyBHdFR+CeOSmPvLi0tbjXZctuhWPCSLfn2kLZGFGPnRFimMdsjVWBQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-http-proxy": "0.2.2-alpha.2" + } + }, + "node_modules/@deepseek-ai/dsh-system-prompt": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-system-prompt/-/dsh-system-prompt-0.2.2-alpha.2.tgz", + "integrity": "sha512-mB6uW6yam2zETmCBwivB+WuziY/UJtARPxi7S7VC+kJGk3VuoJkyoiEH7XwrpVzncFVHqy6QdIRPD3Baey2qOA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@deepseek-ai/schemastery": "~3.18.6-alpha.1" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2" + } + }, + "node_modules/@deepseek-ai/dsh-timeout": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-timeout/-/dsh-timeout-0.2.2-alpha.2.tgz", + "integrity": "sha512-Rn5+DsqeUpytiSIOiN8Rdw6vAvMBG8CZTG8W2l0zXstHt0w0UiFQxv6UI21uFHjWL2fUVPvP6KLamv+nE72nmw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" + } + }, + "node_modules/@deepseek-ai/dsh-tool-todo": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-tool-todo/-/dsh-tool-todo-0.2.2-alpha.2.tgz", + "integrity": "sha512-iy3bxvhtKhiEfsH7Mflah/tgGi7R8i1oEFbz6HSC1sAD6yh99hSVcyEuTLmdNXB58sSG0zRwK6EozIDzenUiWg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-tools": "0.2.2-alpha.2" + } + }, + "node_modules/@deepseek-ai/dsh-tools": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-tools/-/dsh-tools-0.2.2-alpha.2.tgz", + "integrity": "sha512-2KXvznBB1R8rUpfdOmmmufeo65t3hqXDBjq7qSLCfM4RHibQz8WL692WBcTWznGnoMVWifIPXDfUas3SjJdNSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2", + "@deepseek-ai/schemastery": "~3.18.6-alpha.1" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.2", + "@deepseek-ai/dsh-user-approval": "0.2.2-alpha.2" + } + }, + "node_modules/@deepseek-ai/dsh-typert-protocol": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-typert-protocol/-/dsh-typert-protocol-0.2.2-alpha.2.tgz", + "integrity": "sha512-jFA+ub/v6jBwhmrdZtcVHKV6taJ731O2fzC9PXdIVOnOQ9bYds1kIdYsbhgBL7YFd40xWwV/LlJnE5L52v0jFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "zod": "^4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" + } + }, + "node_modules/@deepseek-ai/dsh-typert-registry": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-typert-registry/-/dsh-typert-registry-0.2.2-alpha.2.tgz", + "integrity": "sha512-Hfxy8A6Hlg/GZ0fZBPNwoeO/byREszZtKWKdYst3kjYDyFBRpb5ZxErellcz70ny5JMlCmAcD6O+9GNmswn/jg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "zod": "4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" + } + }, + "node_modules/@deepseek-ai/dsh-user-approval": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-user-approval/-/dsh-user-approval-0.2.2-alpha.2.tgz", + "integrity": "sha512-Jvvd67q5CHkPUZsIdspvMHKNRi4wsxNjVH5DJVTBgEXKl1hq8YwspzoPquTlunN5dpp26/+QPHHouWr7IHB0/Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.2" + } + }, + "node_modules/@deepseek-ai/dsh-user-questions": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-user-questions/-/dsh-user-questions-0.2.2-alpha.2.tgz", + "integrity": "sha512-yuzHmo2+rj+LfyfO/OMoQyYMj62wrpQxhyuQ2CpB4t7lvDBsv2u2pgsb/pU5IfBAkyMvTHImqUfR7QeK+pHsDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-scope": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2" + } + }, + "node_modules/@deepseek-ai/dsh-util-code-language": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-util-code-language/-/dsh-util-code-language-0.2.2-alpha.2.tgz", + "integrity": "sha512-bBEWlXMrD6mnd6vDiTzr0SfPLKEy43gtqUO3r2ryezsjyRWL+ldxkoEJkV6R/6Yb7fBtUTm9H4s9t/Vc/R9I4g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" + } + }, + "node_modules/@deepseek-ai/dsh-util-crypto": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-util-crypto/-/dsh-util-crypto-0.2.2-alpha.2.tgz", + "integrity": "sha512-Hmx7FvW7ok8iSEuaw4kQK/RWsHZnL6MFeyiOin46hfV9imA+pfZFT48/wrkUWwBzuuvpw0O1MwiLD3shqtb6lA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" + } + }, + "node_modules/@deepseek-ai/dsh-util-file-drag": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-util-file-drag/-/dsh-util-file-drag-0.2.2-alpha.2.tgz", + "integrity": "sha512-UsGdKPpVD7uDzBhsrDVl7kwsVHWIRK1JFsxU3WpsD4QG9/jUOn0JW7tzO4I8mk8yq40AiREP1G4h/nEuymSTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime": "^4.1.1" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" + } + }, + "node_modules/@deepseek-ai/dsh-util-time": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-util-time/-/dsh-util-time-0.2.2-alpha.2.tgz", + "integrity": "sha512-0BOeUcq781L6zU+F2V/g3WYXThfpYxD6cLiOQm1K8a/EjvM32AjfOMzHq6sUrbWpdSuOknbOzevNu9caaVIrkw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@js-temporal/polyfill": "0.5.1" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" + } + }, + "node_modules/@deepseek-ai/dsh-util-values": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-util-values/-/dsh-util-values-0.2.2-alpha.2.tgz", + "integrity": "sha512-g5Qrz9cEENcnNEzXjiR+saN0aV4G/8w3FP57wlew+SwZPm3bW5K2cj3iA4Rjg5BS3FGZvnqS95RMOPrxNdulig==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" + } + }, + "node_modules/@deepseek-ai/dsh-util-workspace-path": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-util-workspace-path/-/dsh-util-workspace-path-0.2.2-alpha.2.tgz", + "integrity": "sha512-KYEFURE/FoZj3RexcCNvZLNdG1f61aCIRZq1CCpZ+Ye58C7lyOmxkxEWG4JxnfIvh+uGgN9R1ny1SzYRp6CZCQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1" + } + }, + "node_modules/@deepseek-ai/dsh-working-directory": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-working-directory/-/dsh-working-directory-0.2.2-alpha.2.tgz", + "integrity": "sha512-rfBjLmOfn6dLErN/3Bh8iwFkQRwzPc+Jqwbjyc7kxshu9Hnkuk9rsSPf4HznZehhZN4sU13qx3/S6EZbnrwsJQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@deepseek-ai/schemastery": "~3.18.6-alpha.1", + "zod": "^4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-fs": "0.2.2-alpha.2", + "@deepseek-ai/dsh-llm": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.2" + } + }, + "node_modules/@deepseek-ai/dsh-workspace": { + "version": "0.2.2-alpha.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-workspace/-/dsh-workspace-0.2.2-alpha.2.tgz", + "integrity": "sha512-LRonx7FMJO9fVVaErTs4upKNxwb/SW0c8jG9m+/Jr5pu4NVHqu+KrnXvZiNqyM385sLqGfI6W56B+9e5jSCJsA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@deepseek-ai/dsh-brand": "0.2.2-alpha.2", + "zod": "4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "~4.0.6-alpha.1", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-persistence": "0.2.2-alpha.2", + "@deepseek-ai/dsh-storage": "0.2.2-alpha.2", + "@deepseek-ai/dsh-storage-domain": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2" + } + }, + "node_modules/@deepseek-ai/libreoffice-kit": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@deepseek-ai/libreoffice-kit/-/libreoffice-kit-0.1.5.tgz", + "integrity": "sha512-H12xtUsAyl70zFeQ5pk5GGRc3N1+6zn+DVAcqwlguI6EvqopBGP3xkUmePU7g5f6ydYlDZLWedHT9lTSL7zLxA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "fflate": "0.8.2", + "fontkit": "2.0.4", + "koffi": "3.1.1", + "saxes": "6.0.0" + }, + "bin": { + "dsoffice": "lib/cli.js" + }, + "engines": { + "node": ">=22.19.0" + }, + "optionalDependencies": { + "@deepseek-ai/libreoffice-kit-darwin-arm64": "0.1.5", + "@deepseek-ai/libreoffice-kit-darwin-x64": "0.1.5", + "@deepseek-ai/libreoffice-kit-wasm": "0.1.5", + "@deepseek-ai/libreoffice-kit-win32-arm64": "0.1.5", + "@deepseek-ai/libreoffice-kit-win32-x64": "0.1.5" + } + }, + "node_modules/@deepseek-ai/libreoffice-kit-darwin-arm64": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@deepseek-ai/libreoffice-kit-darwin-arm64/-/libreoffice-kit-darwin-arm64-0.1.5.tgz", + "integrity": "sha512-SjeXmyaTevEq2TxK7reb1rhCrQZ4QOXmXt/qEwfQD5Fx3odU0ILx2uiXN+nphSCQ95LFCzrElHGgv2c7a824yg==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=22.19.0" } }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.6.tgz", - "integrity": "sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==", + "node_modules/@deepseek-ai/libreoffice-kit-darwin-x64": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@deepseek-ai/libreoffice-kit-darwin-x64/-/libreoffice-kit-darwin-x64-0.1.5.tgz", + "integrity": "sha512-vuz2tTpH1Edk75+DISwg/TXdQ3BNSNSHoVnWVqxqbGMgrrK9trLOw2+0X7mjT1naGO00Zc1qkyQ4if3kx5XjqQ==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=22.19.0" } }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.6.tgz", - "integrity": "sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==", - "cpu": [ - "x64" - ], + "node_modules/@deepseek-ai/libreoffice-kit-wasm": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@deepseek-ai/libreoffice-kit-wasm/-/libreoffice-kit-wasm-0.1.5.tgz", + "integrity": "sha512-V+pu5xJsKhVD4AU1xbFlMWY91svK9ZIC2JsNOph9kWp/arF/KQcuytt76bcbozJqPbE8o0n04lyh0m3QW5RYSA==", "dev": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ - "freebsd" + "linux" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=22.19.0" } }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.6.tgz", - "integrity": "sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==", + "node_modules/@deepseek-ai/libreoffice-kit-win32-arm64": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@deepseek-ai/libreoffice-kit-win32-arm64/-/libreoffice-kit-win32-arm64-0.1.5.tgz", + "integrity": "sha512-Av3aFfNLaVxfffu0JXP5mqfmsCU3gQtlUH9UD+zdXl8ON06ukVAmu7LqNyuKAz/68WoOMTQ+YBlVQukgDRKd/A==", "cpu": [ - "arm" + "arm64" ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=22.19.0" } }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.6.tgz", - "integrity": "sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==", + "node_modules/@deepseek-ai/libreoffice-kit-win32-x64": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@deepseek-ai/libreoffice-kit-win32-x64/-/libreoffice-kit-win32-x64-0.1.5.tgz", + "integrity": "sha512-uKuDGZdofxuW4iRmUh8b3+XWU28JeOCYMTAONzo7tEHIq00DV2cARcgUYiTP7gUH49tH4A/RyPMOVEBzK7qjjw==", "cpu": [ - "arm64" + "x64" ], "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=22.19.0" } }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.6.tgz", - "integrity": "sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==", + "node_modules/@deepseek-ai/schemastery": { + "version": "3.18.6-alpha.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/schemastery/-/schemastery-3.18.6-alpha.1.tgz", + "integrity": "sha512-ID5DKpWa0BnrcNdZ3ofWkhOoRLQ3MsFr1FdRVHBiALeE9egaCm4QatuCvQK0Cd/haRBcEvefacz2JEYYMFAEnw==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@deepseek-ai/cosmokit": "~1.8.7-alpha.1", + "@standard-schema/spec": "^1.1.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ - "arm64" + "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ - "linux" + "aix" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.6.tgz", - "integrity": "sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==", + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ - "ppc64" + "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.6.tgz", - "integrity": "sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==", + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "cpu": [ - "s390x" + "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.6.tgz", - "integrity": "sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==", + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.6.tgz", - "integrity": "sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ - "x64" + "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ - "linux" + "darwin" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.6.tgz", - "integrity": "sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openharmony" + "darwin" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.6.tgz", - "integrity": "sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], @@ -1962,16 +2254,16 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "freebsd" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.6.tgz", - "integrity": "sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ "x64" ], @@ -1979,23 +2271,16 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "freebsd" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.0.tgz", - "integrity": "sha512-70TeIFezKKy65LgAVyQh+w94/gjWhvPWaLaGGeMEgVrPkQhuj/M5bAYYZzIFUj9Y69oHyTm5Um/R6gcLh4A8JA==", + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ "arm" ], @@ -2003,13 +2288,16 @@ "license": "MIT", "optional": true, "os": [ - "android" - ] + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.0.tgz", - "integrity": "sha512-YC86tYIHK6M1IV+wbzO+Bxk8RCBr6ZyWYgWxUCzaZD8mc8rrFoIJDNzDrkHBYRc/wKdrsIXmm6/F7NzrAO+OrA==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], @@ -2017,290 +2305,335 @@ "license": "MIT", "optional": true, "os": [ - "android" - ] + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.0.tgz", - "integrity": "sha512-oI+ECtUcli0y0fi4xpW82GdPIXdTkI8G8DSjG2LRuw09fPAGykaWYH/hXxiKuTxiAjiPSTIIuYUqof5Z2hShWw==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "cpu": [ - "arm64" + "ia32" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" - ] + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.0.tgz", - "integrity": "sha512-NwV+1s7TiKrMe4owHyKB/dTLD7ZJD0YEBEhIz+hvav1Cu1GReJjF+rsdNwjzENQeIAbE/CoNiaAc5Vz2h5DPAA==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ - "x64" + "loong64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" - ] + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.0.tgz", - "integrity": "sha512-tWtHBTu5gOPK4u4Urtk4qAHW3zZ9rQAmbssO8gp7ELvGTGI3aCiq6NqyTQ0PCIg7KbHJF2UkGDDs77YZGxfjCA==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ - "arm64" + "mips64el" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "freebsd" - ] + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.0.tgz", - "integrity": "sha512-2qPoJiwTvtHQ27NnYvTnsgk8laXWYuVmNESG8WFZBcEPKLfZ3I27qBJarjVRQtwGeYyRfq5ZowHXih9lm2BItw==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ - "x64" + "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "freebsd" - ] + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.0.tgz", - "integrity": "sha512-FQwsTRvLNuHoTdICABJQfbPUSEueISGmnpT06tXTMpfprf5NiKLSXKA0A+w45wJnCmZAnzgqBwbt6ARFuyOi5w==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ - "arm" + "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.0.tgz", - "integrity": "sha512-BBVTXziw8mY1a4ZbWME9tZyfzqXCDPqaC7Z3heQ29p5dkvXzwL0NwelO8zLa8c3RBKvl3YTuSnBgsBhYBtwjIw==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ - "arm" + "s390x" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.0.tgz", - "integrity": "sha512-w2Iyy9+RqKwx3d9qWMKsJg0FfRBsY0/pXNv0mCQ3ueRvJI6+QAScfD4nrMlzFLs2HNVW6Ew+mtZfDl9b7Ew5/Q==", + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ - "arm64" + "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.0.tgz", - "integrity": "sha512-YK++KtrFRHYE0P6/RtYEAy9t8F37znP+K03RrIuLPYOL6SVlObRumf/0OE4V/h63xL9DwkWbNssZfmA9hawuDA==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ - "linux" - ] + "netbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.0.tgz", - "integrity": "sha512-aBfOG6fP7YkkPmTqPwufRJeFyz7WPpECv9XNbnsk9+vg7rxdih0lbtEel7jcRng4LZrrmU3FfitCFyEj4BWDWg==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", "cpu": [ - "loong64" + "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ - "linux" - ] + "netbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.0.tgz", - "integrity": "sha512-LGaHEOeHNAag9VuS1Crs5DFg4RrU9MPi2nVnNJk9DTePx/B6RRYKVmrIXt2h7YOJlwjaFJ6lwtFDliZxScTLrQ==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "cpu": [ - "loong64" + "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ - "linux" - ] + "openbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.0.tgz", - "integrity": "sha512-jClvk+J0FC3b7Udvegiw5/4hErbHtmsNsQgENnKXDWtNCJXsJYZH5WURvu7imDOO38xYml24eeh5x3A04ppwCw==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ - "ppc64" + "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ - "linux" - ] + "openbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.0.tgz", - "integrity": "sha512-0OJlaGK+8+B777Ql5okIpD7ua5Ro9+VB9Ve0OKa28OQJZ1RbuUBVNHK/e3pr4BROqsyPl1JrPO1ZxJseCNffcA==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", "cpu": [ - "ppc64" + "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ - "linux" - ] + "openharmony" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.0.tgz", - "integrity": "sha512-Ygsx+HoNH7afwi1bTIXbnTvVnsO+zurPLSYxybV1hHFVU72OWOCl6v05ql/z0hkpAPx+DK7Kn9Bi7MayCcjLTA==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ - "riscv64" + "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ - "linux" - ] + "sunos" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.0.tgz", - "integrity": "sha512-pDQxtMGb+OvG3fLwR2OkZlSd47hW+kWg4BYMG/++sR6RqorQccwPTDsxda5hPwiIeIErAnCF9ma3SAU06bdQtQ==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "cpu": [ - "riscv64" + "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ - "linux" - ] + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.0.tgz", - "integrity": "sha512-0BnUG9mS8I4SSHr3XsxVhuCMEiu+rX61xxZF5vujso4LaiAGFZFxvDjg6Xn6tLPNTUAfuCvQYas4LMQMVsKRSQ==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "cpu": [ - "s390x" + "ia32" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ - "linux" - ] + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.0.tgz", - "integrity": "sha512-Adu/VttB1dpPNW+FEacrZ+xVm9tFty84+RrFzsqlFaPxoJB+9XXyDGtp5dCOoBwGBIEVH0To7lExFXEx0BIF4A==", + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ - "linux" - ] + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.0.tgz", - "integrity": "sha512-NQ3bDvjUbFKmP23671xUlXtKmqVsUBd6M4PQCvbmNtOy06hnQIdKHy8oG/6S3R/S6He1JgPk6A5VT+prAJMYEw==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@js-temporal/polyfill": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@js-temporal/polyfill/-/polyfill-0.5.1.tgz", + "integrity": "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "jsbi": "^4.3.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@koromix/koffi-darwin-arm64": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-darwin-arm64/-/koffi-darwin-arm64-3.1.1.tgz", + "integrity": "sha512-+Dl0zQDh1Wb55AWOn9hp7K30qgkODvrvN+ZNkFOh81Q0oFX/rpJQtocgjAuYk2zFAcajSeVDumkcHMPwnKSXzA==", "cpu": [ - "x64" + "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ - "linux" - ] + "darwin" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.0.tgz", - "integrity": "sha512-u2eDAl4+0aFvA13GxlGBtTI3SS3sdgwgtV0HyjZ0QaQVCgNE+jqNGey+GtxWiq+wxr/UycAx/OnfJzApCFamvA==", + "node_modules/@koromix/koffi-darwin-x64": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-darwin-x64/-/koffi-darwin-x64-3.1.1.tgz", + "integrity": "sha512-cDFAKn1qdZBFLrp7dAc9QUDw3l4xAhTJbOdPWWb0LxssVicUdHcRCLZGrDsmPW2tpH6LGNNeLgqRpAoD2Mo8iA==", "cpu": [ "x64" ], @@ -2308,13 +2641,16 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" - ] + "darwin" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.0.tgz", - "integrity": "sha512-XvRb5vfW3wAZQ+ZUG21AnHHDKtNcw99eigzEhjr//NZ3u7SoBaPP0seSc7FgP7p1epAEdAoZckMW9WY/+4w70w==", + "node_modules/@koromix/koffi-freebsd-arm64": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-arm64/-/koffi-freebsd-arm64-3.1.1.tgz", + "integrity": "sha512-zaP7FJISI/scQW9Wa5QicY3a09WmtKBWSbmC+5nfCqPzwWe7Hx2so74Er7mPsDfCiMMR0Ya+evKbJQDkfyXicg==", "cpu": [ "arm64" ], @@ -2322,420 +2658,280 @@ "license": "MIT", "optional": true, "os": [ - "openharmony" - ] + "freebsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.0.tgz", - "integrity": "sha512-iZPmniy4kNBf5yo2RezbkYNNK5HPbXE9+g+twnbqSng7dtLEJy1SKoxiE/ni4FDacjyuZpEeb9U054N4EoKHYw==", + "node_modules/@koromix/koffi-freebsd-ia32": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-ia32/-/koffi-freebsd-ia32-3.1.1.tgz", + "integrity": "sha512-7GejVb688TLM8rbjfc0oezJrATxZc0dn801xWEDJekN2DgmRXu7HquGqWQ6z3NeSq7ZxEggz4T3xtlbCysQapA==", "cpu": [ - "arm64" + "ia32" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] + "freebsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.0.tgz", - "integrity": "sha512-mFBBd+LF37fnE8JnYUOH+imj0aPFPK30vpar4ehJkgnLj9sZn8ZxiRENmLtgIwxK7TC8klF6N57fxdNBwQoqOA==", + "node_modules/@koromix/koffi-freebsd-x64": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-x64/-/koffi-freebsd-x64-3.1.1.tgz", + "integrity": "sha512-XLiCFP9OFCyOoGTjAimtDKLhzhfo34WcP1ShVWxRzNCWDGjfz8BYjwd69cp/cDSUXZbxamqs4+/6vmkePq9wxA==", "cpu": [ - "ia32" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] + "freebsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.0.tgz", - "integrity": "sha512-ujeqEY3B+zbGn3Z4Q03cUBG/LGWnBJncVT36WER31LcOsQk9+1dmINKKtvmmfChUvRbK1G0R8OhMWFgHgaZtAw==", + "node_modules/@koromix/koffi-linux-arm64": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-arm64/-/koffi-linux-arm64-3.1.1.tgz", + "integrity": "sha512-HA9xINK7G4dRAkpfnBWD9VfuyIBgW1SuK+KPHjksUwRMOnhgqP8J/JqgrAzdzcDiefGBkqEacIP776OUwz7knQ==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.0.tgz", - "integrity": "sha512-hncn90N4sOky0L2LKE5oESKLbxCPeVo4eLA2LSMoDzM+879ml4WSr+Rr4DWknNIVVvS1Hirkc9hx02W6YxS8rQ==", + "node_modules/@koromix/koffi-linux-ia32": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-ia32/-/koffi-linux-ia32-3.1.1.tgz", + "integrity": "sha512-jG7IFytmP8K5Qtbx0ro0ZeuX3JjSsLxmYhq+nmXDdrtOAlxIsWGynuiDLS6Jk3vOchVii2m6Y2f/L3GLG2fG5A==", "cpu": [ - "x64" + "ia32" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } }, - "node_modules/@shikijs/core": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.4.3.tgz", - "integrity": "sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==", + "node_modules/@koromix/koffi-linux-loong64": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-loong64/-/koffi-linux-loong64-3.1.1.tgz", + "integrity": "sha512-CIsT1cNnih8FuU52Me/IVlJBpH28SQfoDeYPctJswgJzaARktusF7m4MUbtR1PBDjuquCVM4/vFyNdOzfPonvA==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@shikijs/primitive": "4.4.3", - "@shikijs/types": "4.4.3", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.5", - "hast-util-to-html": "^9.0.5" - }, - "engines": { - "node": ">=20" + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/@shikijs/engine-javascript": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.4.3.tgz", - "integrity": "sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==", + "node_modules/@koromix/koffi-linux-riscv64": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-riscv64/-/koffi-linux-riscv64-3.1.1.tgz", + "integrity": "sha512-9D6RmqeKsSvs3U6jILJU9PcAjMwKKyn7yLxNBb5k6z9PCoUoGJ3/BrhXAX0qjrLLwEiIpP/hS/40RuXvH8Lc3Q==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@shikijs/types": "4.4.3", - "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^4.3.6" - }, - "engines": { - "node": ">=20" + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.4.3.tgz", - "integrity": "sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==", + "node_modules/@koromix/koffi-linux-x64": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-x64/-/koffi-linux-x64-3.1.1.tgz", + "integrity": "sha512-pyTcX5fePeYbt7TZAwRby69wdlRx3PT+g15ra5IYdat/Pgh3qAKEYeZ+uu7WpPGOy43p/oSRqqZoa2kORzozlA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@shikijs/types": "4.4.3", - "@shikijs/vscode-textmate": "^10.0.2" - }, - "engines": { - "node": ">=20" + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/@shikijs/langs": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.4.3.tgz", - "integrity": "sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==", + "node_modules/@koromix/koffi-openbsd-ia32": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-openbsd-ia32/-/koffi-openbsd-ia32-3.1.1.tgz", + "integrity": "sha512-iPnPzvG2HOfdzaiG1drdkt86sAqmTPDv9mAf+5gL7mRzkeeQC88EVGboRy7eXwdXn7R+v0ntA3iQxdHrBn6yXw==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@shikijs/types": "4.4.3" - }, - "engines": { - "node": ">=20" + "optional": true, + "os": [ + "openbsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/@shikijs/primitive": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.4.3.tgz", - "integrity": "sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==", + "node_modules/@koromix/koffi-openbsd-x64": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-openbsd-x64/-/koffi-openbsd-x64-3.1.1.tgz", + "integrity": "sha512-/Xqc3R0SVoMCYjMPZnJ9bULtRo364+dKmnQhfDrI83tSpxUHRw7HRNf12vBeL+hPgKxSBjtMpWfQ/ZIyVyLFag==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@shikijs/types": "4.4.3", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.5" - }, - "engines": { - "node": ">=20" + "optional": true, + "os": [ + "openbsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/@shikijs/themes": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.4.3.tgz", - "integrity": "sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==", + "node_modules/@koromix/koffi-win32-arm64": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-arm64/-/koffi-win32-arm64-3.1.1.tgz", + "integrity": "sha512-JhqHauEwQvdcWUERxrV5HH/DT9W7hY1A1eU6/o8tB+yck+D3kt5elpRDBt9KjpW6h+vHPy3V0sjDvO0CXyabTA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@shikijs/types": "4.4.3" - }, - "engines": { - "node": ">=20" + "optional": true, + "os": [ + "win32" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/@shikijs/types": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.4.3.tgz", - "integrity": "sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==", + "node_modules/@koromix/koffi-win32-ia32": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-ia32/-/koffi-win32-ia32-3.1.1.tgz", + "integrity": "sha512-ZRuyYmlGS/rCc966qqs0qREXDW4FRdul7rDF1VgSWHbVmdc196PUgUT+blq/GjZgTwqzeEXtMRgM+cU8krHjvA==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.5" - }, - "engines": { - "node": ">=20" + "optional": true, + "os": [ + "win32" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "node_modules/@koromix/koffi-win32-x64": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-x64/-/koffi-win32-x64-3.1.1.tgz", + "integrity": "sha512-KqHPmvj6QILhNyI/To8QSihHsijeVGIYYPBOUnXEpcnH2LuLbargY4Hd6dDeTN3Z90uUUxN+1FWz1UnhVzFOiA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "inBundle": true, - "license": "MIT" - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/debug": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/hast": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", - "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/katex": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", - "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.20.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", - "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.31", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", - "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^18.0.0" - } - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", - "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@vitest/expect": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", - "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.7", - "@vitest/utils": "3.2.7", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", - "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.2.7", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", - "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", - "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@vitest/utils": "3.2.7", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" } }, - "node_modules/@vitest/snapshot": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", - "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", + "node_modules/@oxc-project/types": { + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz", + "integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==", "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.7", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/oxc-project" } }, - "node_modules/@vitest/spy": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", - "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", + "node_modules/@quansync/fs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.1.0.tgz", + "integrity": "sha512-qAPG/t3HqML1TlN7sY/pTbEjzFVAKsMjNNMGheyDosro+kT4iw2KCUoHcVdmliwWjorm4elZbgNQyU2eD97eDg==", "dev": true, "license": "MIT", "dependencies": { - "tinyspy": "^4.0.3" + "quansync": "^1.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/@vitest/utils": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", - "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz", + "integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.7", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@yuku-codegen/binding-android-arm64": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-android-arm64/-/binding-android-arm64-0.8.7.tgz", - "integrity": "sha512-C/0zV5IhgVdYhGJTwrY0v8dknxlhiKwtVJkMUaexu9/QvRmzlV4vfU3hZlUSgqc2BxQHntL1mCVbDq8j0FRFDw==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz", + "integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==", "cpu": [ "arm64" ], @@ -2744,12 +2940,15 @@ "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@yuku-codegen/binding-darwin-arm64": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-darwin-arm64/-/binding-darwin-arm64-0.8.7.tgz", - "integrity": "sha512-/u+REDMI4a0/lsJXTM4c53/w31OGZLOReZIyg62uhgLs0kc8NHsj/nOcxTdlQjq5gi0zhdkccD9LaLTXcdzPvw==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz", + "integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==", "cpu": [ "arm64" ], @@ -2758,12 +2957,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@yuku-codegen/binding-darwin-x64": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-darwin-x64/-/binding-darwin-x64-0.8.7.tgz", - "integrity": "sha512-sMMzFOwCo4WXR+/6zIBThOocSC50iIIZZdfiIDbaLvj0Ax/rWt/iavyfEAqajyvzydLyCqR/ZItdLWSRlu1umw==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz", + "integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==", "cpu": [ "x64" ], @@ -2772,12 +2974,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@yuku-codegen/binding-freebsd-x64": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-freebsd-x64/-/binding-freebsd-x64-0.8.7.tgz", - "integrity": "sha512-MpdpKXix9P+Y1rKgjvcNeNtGjXeL1CmttNhYINrWls8kRpm4xM/oBGTmn6w7to8lAlwj5jm8q03dQPl5mRv4Qw==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz", + "integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==", "cpu": [ "x64" ], @@ -2786,16 +2991,36 @@ "optional": true, "os": [ "freebsd" - ] - }, - "node_modules/@yuku-codegen/binding-linux-arm-gnu": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-arm-gnu/-/binding-linux-arm-gnu-0.8.7.tgz", - "integrity": "sha512-rr1srFLlPAmC1vtxfc9C1YLDe3iH09YjfSeeIidBqKhzx1MATjOAq4mjlRUOnhr/L27MotWIYOFKwVsd5JZFOg==", + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz", + "integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz", + "integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==", + "cpu": [ + "arm64" + ], + "dev": true, "libc": [ "glibc" ], @@ -2803,14 +3028,17 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@yuku-codegen/binding-linux-arm-musl": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-arm-musl/-/binding-linux-arm-musl-0.8.7.tgz", - "integrity": "sha512-eAufXh8qBRpiSO6ueaMDL+yyoXIGLhpUce72YbcACtZU2qhExwBIJyEtQf5kH2Ki0X2aqkSjSfog4OPtKXan3Q==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz", + "integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==", "cpu": [ - "arm" + "arm64" ], "dev": true, "libc": [ @@ -2820,14 +3048,17 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@yuku-codegen/binding-linux-arm64-gnu": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.8.7.tgz", - "integrity": "sha512-gw4w6wPoHObBrdIC4duVWLmJOvpdE25j5D7yrM5mACNlK4klRz/lv8hK+ssQk9EJHBgZjSaqZIJVFgqNYbfv7A==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz", + "integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==", "cpu": [ - "arm64" + "ppc64" ], "dev": true, "libc": [ @@ -2837,29 +3068,35 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@yuku-codegen/binding-linux-arm64-musl": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.8.7.tgz", - "integrity": "sha512-L68N6Y4XkqcIaKo3Ra88JEvBEH4AHff44A4INcrxeVWZ8CZtu2tCpfVxe3hR8qQQMcvBSQlDn24KpkCKEhVvfA==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz", + "integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==", "cpu": [ - "arm64" + "s390x" ], "dev": true, "libc": [ - "musl" + "glibc" ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@yuku-codegen/binding-linux-x64-gnu": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.8.7.tgz", - "integrity": "sha512-dzyAbltJmf3Cqlb8HcFuYIf5Yn0fl1vTr3XJ9HiVNNvOlhqPSArOqtw9vI1p6/VTXTjrMLXlU+s+/kNHiIy/Cw==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz", + "integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==", "cpu": [ "x64" ], @@ -2871,12 +3108,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@yuku-codegen/binding-linux-x64-musl": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-x64-musl/-/binding-linux-x64-musl-0.8.7.tgz", - "integrity": "sha512-Otw4MH3404q0Bbvl+YTdW9aoUV5vXmUw8260bWvt1XlaoIX/ceSgI4ygheRDMfBPoHt6FDayQaO9OLVUZAgkFA==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz", + "integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==", "cpu": [ "x64" ], @@ -2888,12 +3128,32 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@yuku-codegen/binding-win32-arm64": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-win32-arm64/-/binding-win32-arm64-0.8.7.tgz", - "integrity": "sha512-qo/jyrzryiBuKEsFiuWaBCBe3tRMynQ0qFWFgOEjcCMQeZfBm+wKiVEUEFXXLc7bh8YezguAWp0Mtnhq4ARNyA==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz", + "integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz", + "integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==", "cpu": [ "arm64" ], @@ -2902,12 +3162,15 @@ "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@yuku-codegen/binding-win32-x64": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-win32-x64/-/binding-win32-x64-0.8.7.tgz", - "integrity": "sha512-D5lDsVDx6m00E6bWySlWdH72Ca4TPSaphDqB6QjU6MpuNLIJqoGoatYyq2rOmBE8Zv/kunot/o58KGL03P3eiA==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz", + "integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==", "cpu": [ "x64" ], @@ -2916,12 +3179,36 @@ "optional": true, "os": [ "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.67.4.tgz", + "integrity": "sha512-/v/pUAPOcwp+r1Hjjdg0cBV1TE89CzGKrKh2FgkIQw2QBcIMmVPOCSfuOZStCn4Xh/98HXfo/ht6ztcssctPNg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" ] }, - "node_modules/@yuku-parser/binding-android-arm64": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-parser/binding-android-arm64/-/binding-android-arm64-0.8.7.tgz", - "integrity": "sha512-eGKYiUDX7Y0V7tDTmg+JTVnXnjMqfXXsorZ+EDf5kxwchQ3Or1HS14MzI2fw+jFhHR85fCWt+mtX33Yao73hIQ==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.67.4.tgz", + "integrity": "sha512-6tmmB/P52Vt167FXXEw5auiEUgTtZz7aqUgWHqYXToIUE0HFDY6fTdAyeekjjsNk6O/8dtpSKtClxSj1mMneKQ==", "cpu": [ "arm64" ], @@ -2932,10 +3219,24 @@ "android" ] }, - "node_modules/@yuku-parser/binding-darwin-arm64": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.8.7.tgz", - "integrity": "sha512-Re0RHelKLnjEURulY2/KxW+Ngb8zuNA4BRZuMwgGQNzVumT6u4U2N2hc01oeYVNVof0i7GrXE4UCNBgbpRRnjQ==", + "node_modules/@rollup/rollup-android-x64": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-x64/-/rollup-android-x64-4.67.4.tgz", + "integrity": "sha512-8Z9Q4RmotVmuL4llOvqX814JNiOK1oqoBv+glG5KbpRfUiypqzz2/fxIzK4Pq129l4dT5a8Gszmpc0/XPYoFuQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.67.4.tgz", + "integrity": "sha512-pnq6hssxTtwuRAl6Kdi/+IDY+b1IhkUetjhUkdLAAJCssjfqtL2Oqy/FXJVJFtAWOu1oRlArIvf0Yk1O92x2HQ==", "cpu": [ "arm64" ], @@ -2946,10 +3247,10 @@ "darwin" ] }, - "node_modules/@yuku-parser/binding-darwin-x64": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-parser/binding-darwin-x64/-/binding-darwin-x64-0.8.7.tgz", - "integrity": "sha512-Hn8DROtQkjlA1ACbPgj4a7eP9IuVOI504oiTwpkWPbpaDWD9KdmnVYCqW+1LfenNK/g7O9NhWGpXEdaCNX7lIA==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.67.4.tgz", + "integrity": "sha512-BeIrGZvHMVdJg/N+ohZfFywG/gF41684YhDj2wBvouj7m9ziP1tZDUNwzXRiDY+3WIKngaC4hiUBqVtBPGP5og==", "cpu": [ "x64" ], @@ -2960,10 +3261,24 @@ "darwin" ] }, - "node_modules/@yuku-parser/binding-freebsd-x64": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.8.7.tgz", - "integrity": "sha512-bAP2OV8wRuzplX/jYxv9+vvqQT8JxyNphI8fLfXGL054Xs+4/J5u33cIm3y4rxY8rdoLmmdiJs2Tq7r7lrDRfA==", + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.67.4.tgz", + "integrity": "sha512-fRFX/oqNg/g780DKoS8RIfYcZD59Ru5O/bSeK1N7gQljBFrTxJY/q5EX4eMs5SrJf32VgVBVVYCdfPpzAwPPnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.67.4.tgz", + "integrity": "sha512-bSrBRopG1q+0RuOsW0NtYTtIODlvNA3T3ebx5WnQVdSxWIoXFmj+rZdHUHdSLgFdZ5M6VGmekYE7r4RMGQXjfg==", "cpu": [ "x64" ], @@ -2974,10 +3289,10 @@ "freebsd" ] }, - "node_modules/@yuku-parser/binding-linux-arm-gnu": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-arm-gnu/-/binding-linux-arm-gnu-0.8.7.tgz", - "integrity": "sha512-kTYwJQQgmZeAWdDIWabiReIZMpmfLueIj1tCmjStUtFGhR1Z0qwxonKVfUC4N7h/VhGGzLZ//7O1kgt1QKqgCg==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.67.4.tgz", + "integrity": "sha512-L8IvdrJVtl65g1ozwDSyjGPG+FmibhCdFUwmqTItJLw0S3+Oj4QzFs7rfgTUiK7rsPjkCLdG1/GApFxoynXFJQ==", "cpu": [ "arm" ], @@ -2991,10 +3306,10 @@ "linux" ] }, - "node_modules/@yuku-parser/binding-linux-arm-musl": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-arm-musl/-/binding-linux-arm-musl-0.8.7.tgz", - "integrity": "sha512-uL4jE8HPT2BLlxAXyD10LqgPuXa9eDa0BKpCdSANmzIJghq/2eZo3/gQNtaxPZMupWoxjYzSad9IXrwu7aYPXQ==", + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.67.4.tgz", + "integrity": "sha512-h7PcoQOzC31jshNpQ48J0akB9tsghjkSjSl5AZ6XsVGTxVsln7t7C4SQyw62tNVSaz2SeHgk4VT2ELbL17BSHQ==", "cpu": [ "arm" ], @@ -3008,10 +3323,10 @@ "linux" ] }, - "node_modules/@yuku-parser/binding-linux-arm64-gnu": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.8.7.tgz", - "integrity": "sha512-3gVN4pWSKZmXiNX7cU164dR9MPvesCHnlH6nPfpK+yQsCuYphjKKplcb4SnZBrhiqmXbgb2HR0c2TS0IZUPhgA==", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.67.4.tgz", + "integrity": "sha512-KSPuOBNHoOl7AsVjeY1KZci8+0up0U4TFAHU5IzBYN8P+Z6cB9DZq28+JQycZW7f6WLLa/tSzhV7Rdy95bVi+g==", "cpu": [ "arm64" ], @@ -3025,10 +3340,10 @@ "linux" ] }, - "node_modules/@yuku-parser/binding-linux-arm64-musl": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.8.7.tgz", - "integrity": "sha512-S0mwfEjoLpxzXeZw802Wa4RaELsQiPtWqG6INcy8j4GtvNFtl4LCX3eGO1XLn9pyLAISLzTRyU3zUCBUPin8lg==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.67.4.tgz", + "integrity": "sha512-0E/mKmVk1PlQeQYygIwQm0jlwjANTzGSiNBMPrFx+rdFGFj/2Axh4p6XwcrystQIqXZVudTaGsdbDpzYgJ4wSA==", "cpu": [ "arm64" ], @@ -3042,12 +3357,12 @@ "linux" ] }, - "node_modules/@yuku-parser/binding-linux-x64-gnu": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.8.7.tgz", - "integrity": "sha512-lnbWdPmerE5D1uH1G4IEZKnPzCrWCStRGrtgpSIe1RibAo5bZIjDbbbPYXmMHCEh4F+x/JaJpElh26a3r+BPbg==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.67.4.tgz", + "integrity": "sha512-Fvw7dOSc2Sa9UOipRZVI0zOgdLCzsjVyD/6JuHXzUHTXFu7mMgmeX/0/bCVBIut1dz+nWz3pV4WpS/Qg0XVnxw==", "cpu": [ - "x64" + "loong64" ], "dev": true, "libc": [ @@ -3059,12 +3374,12 @@ "linux" ] }, - "node_modules/@yuku-parser/binding-linux-x64-musl": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.8.7.tgz", - "integrity": "sha512-769uwndMvMzUvATWbAcEvyLHKA+DzhHSCl/obBUrRdYfRo26yxui6S8y3z7uJ+Naup7UKrDxrpK7OnQkxkl9KQ==", + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.67.4.tgz", + "integrity": "sha512-jzxbVHaE7V3F+P0wEK7UydQtvv7Twfdtx58jI5/3VRx1jJxyrn+qFwfXmqjVFxPhBl+zjaRs+ZJMFJw7vdHaQA==", "cpu": [ - "x64" + "loong64" ], "dev": true, "libc": [ @@ -3076,1784 +3391,2184 @@ "linux" ] }, - "node_modules/@yuku-parser/binding-win32-arm64": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-parser/binding-win32-arm64/-/binding-win32-arm64-0.8.7.tgz", - "integrity": "sha512-mEB/9PlaAkisJ6KWGz0zvywXoU6+80dTlR2LwS7s/jcXXoU6fm2+sitBZXtqu3+Q4DcDgPxM45uWMCzPs0TSRw==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.67.4.tgz", + "integrity": "sha512-fhtrzp9p4GjEJVaigaap9g6X9nP6xgJtKiLLXSDgpE18HXufA5aqfltGn+KAqMtVZReJZfOrXm//y5YSA9BWbA==", "cpu": [ - "arm64" + "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ] }, - "node_modules/@yuku-parser/binding-win32-x64": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-parser/binding-win32-x64/-/binding-win32-x64-0.8.7.tgz", - "integrity": "sha512-8vNB2DP0ou61nGb8tc/qfi41gfyDXz1MHr2zqL3nR+cJ6CEbiuWV/l/a/vv151gCgiZLLAyGkQGENpozdg716w==", + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.67.4.tgz", + "integrity": "sha512-uynaF7IUHwAckV0S+yC1f5esy9gB+aqCBsPH5BOck32TYdCuAn2lVG5tvUcFnEsrviGEmRZxE1NHSOieUPYJEg==", "cpu": [ - "x64" + "ppc64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ] }, - "node_modules/@yuku-toolchain/types": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@yuku-toolchain/types/-/types-0.8.7.tgz", - "integrity": "sha512-2Z53dNxAJL6UvFoIrDZvYf3zlO8s4VJK4O2hhaB4mXVwwpX/7ajtss3cmfqKvamlNLWyt9FSWs4eoYdlbxpnHA==", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.67.4.tgz", + "integrity": "sha512-uaqRFdfb8eIa2bVHN/FE3ndCvR0zV2xv0AwjCfxrqSu6l6fp/54VhjfhslKlf2RIik9L1jwlt4E56PeVX6tQtQ==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "MIT" + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/anser": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/anser/-/anser-2.3.5.tgz", - "integrity": "sha512-vcZjxvvVoxTeR5XBNJB38oTu/7eDCZlwdz32N1eNgpyPF7j/Z7Idf+CUwQOkKKpJ7RJyjxgLHCM7vdIK0iCNMQ==", + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.67.4.tgz", + "integrity": "sha512-JZRow7kmxUDEASnxHuVG5itTm+nTXi9BQUABcVUnUEngC+OPwMrpx1uJ9hIA/v+IcAEBL52ktCSPXk5OJn9Jqg==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "MIT" - }, - "node_modules/ansis": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", - "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0", - "peer": true + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.67.4.tgz", + "integrity": "sha512-rdrh4e+Lq9fCNKoDPaCqeHXbQCe7yl77Ukl45tlSClwYdZ1gpFGZz1XLJdYtY++FRlfL5aHvb6/0n3GDW/Nxsw==", + "cpu": [ + "s390x" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "engines": { - "node": ">=12" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/cac": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", - "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.67.4.tgz", + "integrity": "sha512-wL7MS4r0fYcxluF3ne2plofHZiB/LsqNCnyaUlyiKpAJLb6agPSFz7DEFCfkonWAZXMrLwCmC08b2ZEnEdJkog==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "engines": { - "node": ">=20.19.0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.67.4.tgz", + "integrity": "sha512-4H0XHfd2VufGjX7NjUyeue78sD0bRIoKSGjGqO+F5nSvL6jN48NAU7esdmtFEYxZXwObLtt3MJ/kHS0jASMW3g==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.67.4.tgz", + "integrity": "sha512-osRHQbE55pZpCLetpMsFmhkapyZnAUH73qRKMh+npi3boUom6u6hdaTTPSxlInXxwJHsn9r2uDhdz0V7Yoxx2A==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=18" - } + "optional": true, + "os": [ + "openbsd" + ] }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.67.4.tgz", + "integrity": "sha512-95+exYEyOwrZePsE0czi4q7X5lxKPa21+lmyAZlwbYENITQ0Unk4HAQAFp/p+KhXBHBx9mUABXRYjOC5A1wY3Q==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.67.4.tgz", + "integrity": "sha512-FOXOcKhNQpO4Po+Ynan6IbtM67SrXZbgpRJWVYwz53CT5veI7sVzDwVBmWS65N5Imv+wK3SVhIPAl+C5DXXNCg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.67.4.tgz", + "integrity": "sha512-cklKh0gJOkAZQcWziQa3hrjxZKN+PCHZ7SPTyGJDAiBVr05ZXlEjGFCG7zFJMaVibxsmzAe1FimN4u/HBns9+Q==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.67.4.tgz", + "integrity": "sha512-AegwxJPc9bvUp8fQtlsUHkFMOMkGylWU7glyL+YD4vwG+QaYoGXYLSsQoeTD7kvoLZrVlMWNVhTg1dedY5C1VQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">= 16" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.67.4.tgz", + "integrity": "sha512-RJZnrwTWj7Iq2ooBwoGbDdC65tDwYawneGcAfFMJqVUrRZMcskka6nTkBpXR/47G7kzn3EW2qupExnaBy11vwg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">=6" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "license": "MIT" }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 12" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "inBundle": true, "license": "MIT" }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "tslib": "^2.8.0" } }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } + "license": "MIT" }, - "node_modules/defu": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", - "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "node_modules/@types/estree": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.10.tgz", + "integrity": "sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==", "dev": true, "license": "MIT" }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "node_modules/@types/node": { + "version": "22.20.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "undici-types": "~6.21.0" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", "dev": true, "license": "MIT", "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "@types/prop-types": "*", + "csstype": "^3.2.2" } }, - "node_modules/dts-resolver": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/dts-resolver/-/dts-resolver-3.0.0.tgz", - "integrity": "sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - }, - "peerDependencies": { - "oxc-resolver": ">=11.0.0" - }, - "peerDependenciesMeta": { - "oxc-resolver": { - "optional": true - } - } - }, - "node_modules/empathic": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", - "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", - "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.2", - "@esbuild/android-arm": "0.28.2", - "@esbuild/android-arm64": "0.28.2", - "@esbuild/android-x64": "0.28.2", - "@esbuild/darwin-arm64": "0.28.2", - "@esbuild/darwin-x64": "0.28.2", - "@esbuild/freebsd-arm64": "0.28.2", - "@esbuild/freebsd-x64": "0.28.2", - "@esbuild/linux-arm": "0.28.2", - "@esbuild/linux-arm64": "0.28.2", - "@esbuild/linux-ia32": "0.28.2", - "@esbuild/linux-loong64": "0.28.2", - "@esbuild/linux-mips64el": "0.28.2", - "@esbuild/linux-ppc64": "0.28.2", - "@esbuild/linux-riscv64": "0.28.2", - "@esbuild/linux-s390x": "0.28.2", - "@esbuild/linux-x64": "0.28.2", - "@esbuild/netbsd-arm64": "0.28.2", - "@esbuild/netbsd-x64": "0.28.2", - "@esbuild/openbsd-arm64": "0.28.2", - "@esbuild/openbsd-x64": "0.28.2", - "@esbuild/openharmony-arm64": "0.28.2", - "@esbuild/sunos-x64": "0.28.2", - "@esbuild/win32-arm64": "0.28.2", - "@esbuild/win32-ia32": "0.28.2", - "@esbuild/win32-x64": "0.28.2" + "peerDependencies": { + "@types/react": "^18.0.0" } }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "node_modules/@vitest/expect": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "node_modules/@vitest/mocker": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/expect-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", - "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" + "@vitest/spy": "3.2.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "picomatch": "^3 || ^4" + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "peerDependenciesMeta": { - "picomatch": { + "msw": { + "optional": true + }, + "vite": { "optional": true } } }, - "node_modules/fflate": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", - "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "node_modules/@vitest/pretty-format": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/@vitest/runner": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "dependencies": { + "@vitest/utils": "3.2.7", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/get-tsconfig": { - "version": "5.0.0-beta.5", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.5.tgz", - "integrity": "sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==", + "node_modules/@vitest/snapshot": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", "dev": true, "license": "MIT", "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "engines": { - "node": ">=20.20.0" + "@vitest/pretty-format": "3.2.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" }, "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "url": "https://opencollective.com/vitest" } }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "node_modules/@vitest/spy": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" + "tinyspy": "^4.0.3" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/vitest" } }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "node_modules/@vitest/utils": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", "dev": true, "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" + "@vitest/pretty-format": "3.2.7", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/vitest" } }, - "node_modules/hookable": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", - "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==", + "node_modules/@xterm/addon-serialize": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0.tgz", + "integrity": "sha512-uteyTU1EkrQa2Ux6P/uFl2fzmXI46jy5uoQMKEOM0fKTyiW7cSn0WrFenHm5vO5uEXX/GpwW/FgILvv3r0WbkA==", "dev": true, "license": "MIT" }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "node_modules/@xterm/headless": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.0.0.tgz", + "integrity": "sha512-5Yj1QINYCyzrZtf8OFIHi47iQtI+0qYFPHmouEfG8dHNxbZ9Tb9YGSuLcsEwj9Z+OL75GJqPyJbyoFer80a2Hw==", "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "workspaces": [ + "addons/*" + ] }, - "node_modules/immer": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", - "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "node_modules/@yuku-codegen/binding-android-arm64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-android-arm64/-/binding-android-arm64-0.8.7.tgz", + "integrity": "sha512-C/0zV5IhgVdYhGJTwrY0v8dknxlhiKwtVJkMUaexu9/QvRmzlV4vfU3hZlUSgqc2BxQHntL1mCVbDq8j0FRFDw==", + "cpu": [ + "arm64" + ], + "deprecated": "yuku-codegen is pure JavaScript since 0.14", "dev": true, "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/import-without-cache": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/import-without-cache/-/import-without-cache-0.4.0.tgz", - "integrity": "sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==", + "node_modules/@yuku-codegen/binding-darwin-arm64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-darwin-arm64/-/binding-darwin-arm64-0.8.7.tgz", + "integrity": "sha512-/u+REDMI4a0/lsJXTM4c53/w31OGZLOReZIyg62uhgLs0kc8NHsj/nOcxTdlQjq5gi0zhdkccD9LaLTXcdzPvw==", + "cpu": [ + "arm64" + ], + "deprecated": "yuku-codegen is pure JavaScript since 0.14", "dev": true, "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "node_modules/@yuku-codegen/binding-darwin-x64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-darwin-x64/-/binding-darwin-x64-0.8.7.tgz", + "integrity": "sha512-sMMzFOwCo4WXR+/6zIBThOocSC50iIIZZdfiIDbaLvj0Ax/rWt/iavyfEAqajyvzydLyCqR/ZItdLWSRlu1umw==", + "cpu": [ + "x64" + ], + "deprecated": "yuku-codegen is pure JavaScript since 0.14", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/js-yaml": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", - "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/katex": { - "version": "0.16.47", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", - "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", - "dev": true, - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], - "license": "MIT", - "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/lightningcss": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", - "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", - "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", - "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", - "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "node_modules/@yuku-codegen/binding-freebsd-x64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-freebsd-x64/-/binding-freebsd-x64-0.8.7.tgz", + "integrity": "sha512-MpdpKXix9P+Y1rKgjvcNeNtGjXeL1CmttNhYINrWls8kRpm4xM/oBGTmn6w7to8lAlwj5jm8q03dQPl5mRv4Qw==", "cpu": [ "x64" ], + "deprecated": "yuku-codegen is pure JavaScript since 0.14", "dev": true, - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "freebsd" + ] }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", - "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "node_modules/@yuku-codegen/binding-linux-arm-gnu": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-arm-gnu/-/binding-linux-arm-gnu-0.8.7.tgz", + "integrity": "sha512-rr1srFLlPAmC1vtxfc9C1YLDe3iH09YjfSeeIidBqKhzx1MATjOAq4mjlRUOnhr/L27MotWIYOFKwVsd5JZFOg==", "cpu": [ - "x64" + "arm" ], + "deprecated": "yuku-codegen is pure JavaScript since 0.14", "dev": true, - "license": "MPL-2.0", + "libc": [ + "glibc" + ], + "license": "MIT", "optional": true, "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "linux" + ] }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", - "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "node_modules/@yuku-codegen/binding-linux-arm-musl": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-arm-musl/-/binding-linux-arm-musl-0.8.7.tgz", + "integrity": "sha512-eAufXh8qBRpiSO6ueaMDL+yyoXIGLhpUce72YbcACtZU2qhExwBIJyEtQf5kH2Ki0X2aqkSjSfog4OPtKXan3Q==", "cpu": [ "arm" ], + "deprecated": "yuku-codegen is pure JavaScript since 0.14", "dev": true, - "license": "MPL-2.0", + "libc": [ + "musl" + ], + "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + ] }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", - "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "node_modules/@yuku-codegen/binding-linux-arm64-gnu": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.8.7.tgz", + "integrity": "sha512-gw4w6wPoHObBrdIC4duVWLmJOvpdE25j5D7yrM5mACNlK4klRz/lv8hK+ssQk9EJHBgZjSaqZIJVFgqNYbfv7A==", "cpu": [ "arm64" ], + "deprecated": "yuku-codegen is pure JavaScript since 0.14", "dev": true, "libc": [ "glibc" ], - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + ] }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", - "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "node_modules/@yuku-codegen/binding-linux-arm64-musl": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.8.7.tgz", + "integrity": "sha512-L68N6Y4XkqcIaKo3Ra88JEvBEH4AHff44A4INcrxeVWZ8CZtu2tCpfVxe3hR8qQQMcvBSQlDn24KpkCKEhVvfA==", "cpu": [ "arm64" ], + "deprecated": "yuku-codegen is pure JavaScript since 0.14", "dev": true, "libc": [ "musl" ], - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + ] }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", - "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "node_modules/@yuku-codegen/binding-linux-x64-gnu": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.8.7.tgz", + "integrity": "sha512-dzyAbltJmf3Cqlb8HcFuYIf5Yn0fl1vTr3XJ9HiVNNvOlhqPSArOqtw9vI1p6/VTXTjrMLXlU+s+/kNHiIy/Cw==", "cpu": [ "x64" ], + "deprecated": "yuku-codegen is pure JavaScript since 0.14", "dev": true, "libc": [ "glibc" ], - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + ] }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", - "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "node_modules/@yuku-codegen/binding-linux-x64-musl": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-x64-musl/-/binding-linux-x64-musl-0.8.7.tgz", + "integrity": "sha512-Otw4MH3404q0Bbvl+YTdW9aoUV5vXmUw8260bWvt1XlaoIX/ceSgI4ygheRDMfBPoHt6FDayQaO9OLVUZAgkFA==", "cpu": [ "x64" ], + "deprecated": "yuku-codegen is pure JavaScript since 0.14", "dev": true, "libc": [ "musl" ], - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "linux" + ] + }, + "node_modules/@yuku-codegen/binding-win32-arm64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-win32-arm64/-/binding-win32-arm64-0.8.7.tgz", + "integrity": "sha512-qo/jyrzryiBuKEsFiuWaBCBe3tRMynQ0qFWFgOEjcCMQeZfBm+wKiVEUEFXXLc7bh8YezguAWp0Mtnhq4ARNyA==", + "cpu": [ + "arm64" ], - "engines": { - "node": ">= 12.0.0" + "deprecated": "yuku-codegen is pure JavaScript since 0.14", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@yuku-codegen/binding-win32-x64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-win32-x64/-/binding-win32-x64-0.8.7.tgz", + "integrity": "sha512-D5lDsVDx6m00E6bWySlWdH72Ca4TPSaphDqB6QjU6MpuNLIJqoGoatYyq2rOmBE8Zv/kunot/o58KGL03P3eiA==", + "cpu": [ + "x64" + ], + "deprecated": "yuku-codegen is pure JavaScript since 0.14", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@yuku-parser/binding-android-arm64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-android-arm64/-/binding-android-arm64-0.8.7.tgz", + "integrity": "sha512-eGKYiUDX7Y0V7tDTmg+JTVnXnjMqfXXsorZ+EDf5kxwchQ3Or1HS14MzI2fw+jFhHR85fCWt+mtX33Yao73hIQ==", + "cpu": [ + "arm64" + ], + "deprecated": "yuku-parser runs on yuku-core since 0.14", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@yuku-parser/binding-darwin-arm64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.8.7.tgz", + "integrity": "sha512-Re0RHelKLnjEURulY2/KxW+Ngb8zuNA4BRZuMwgGQNzVumT6u4U2N2hc01oeYVNVof0i7GrXE4UCNBgbpRRnjQ==", + "cpu": [ + "arm64" + ], + "deprecated": "yuku-parser runs on yuku-core since 0.14", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@yuku-parser/binding-darwin-x64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-darwin-x64/-/binding-darwin-x64-0.8.7.tgz", + "integrity": "sha512-Hn8DROtQkjlA1ACbPgj4a7eP9IuVOI504oiTwpkWPbpaDWD9KdmnVYCqW+1LfenNK/g7O9NhWGpXEdaCNX7lIA==", + "cpu": [ + "x64" + ], + "deprecated": "yuku-parser runs on yuku-core since 0.14", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@yuku-parser/binding-freebsd-x64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.8.7.tgz", + "integrity": "sha512-bAP2OV8wRuzplX/jYxv9+vvqQT8JxyNphI8fLfXGL054Xs+4/J5u33cIm3y4rxY8rdoLmmdiJs2Tq7r7lrDRfA==", + "cpu": [ + "x64" + ], + "deprecated": "yuku-parser runs on yuku-core since 0.14", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@yuku-parser/binding-linux-arm-gnu": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-arm-gnu/-/binding-linux-arm-gnu-0.8.7.tgz", + "integrity": "sha512-kTYwJQQgmZeAWdDIWabiReIZMpmfLueIj1tCmjStUtFGhR1Z0qwxonKVfUC4N7h/VhGGzLZ//7O1kgt1QKqgCg==", + "cpu": [ + "arm" + ], + "deprecated": "yuku-parser runs on yuku-core since 0.14", + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-parser/binding-linux-arm-musl": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-arm-musl/-/binding-linux-arm-musl-0.8.7.tgz", + "integrity": "sha512-uL4jE8HPT2BLlxAXyD10LqgPuXa9eDa0BKpCdSANmzIJghq/2eZo3/gQNtaxPZMupWoxjYzSad9IXrwu7aYPXQ==", + "cpu": [ + "arm" + ], + "deprecated": "yuku-parser runs on yuku-core since 0.14", + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-parser/binding-linux-arm64-gnu": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.8.7.tgz", + "integrity": "sha512-3gVN4pWSKZmXiNX7cU164dR9MPvesCHnlH6nPfpK+yQsCuYphjKKplcb4SnZBrhiqmXbgb2HR0c2TS0IZUPhgA==", + "cpu": [ + "arm64" + ], + "deprecated": "yuku-parser runs on yuku-core since 0.14", + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-parser/binding-linux-arm64-musl": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.8.7.tgz", + "integrity": "sha512-S0mwfEjoLpxzXeZw802Wa4RaELsQiPtWqG6INcy8j4GtvNFtl4LCX3eGO1XLn9pyLAISLzTRyU3zUCBUPin8lg==", + "cpu": [ + "arm64" + ], + "deprecated": "yuku-parser runs on yuku-core since 0.14", + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-parser/binding-linux-x64-gnu": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.8.7.tgz", + "integrity": "sha512-lnbWdPmerE5D1uH1G4IEZKnPzCrWCStRGrtgpSIe1RibAo5bZIjDbbbPYXmMHCEh4F+x/JaJpElh26a3r+BPbg==", + "cpu": [ + "x64" + ], + "deprecated": "yuku-parser runs on yuku-core since 0.14", + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-parser/binding-linux-x64-musl": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.8.7.tgz", + "integrity": "sha512-769uwndMvMzUvATWbAcEvyLHKA+DzhHSCl/obBUrRdYfRo26yxui6S8y3z7uJ+Naup7UKrDxrpK7OnQkxkl9KQ==", + "cpu": [ + "x64" + ], + "deprecated": "yuku-parser runs on yuku-core since 0.14", + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-parser/binding-win32-arm64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-win32-arm64/-/binding-win32-arm64-0.8.7.tgz", + "integrity": "sha512-mEB/9PlaAkisJ6KWGz0zvywXoU6+80dTlR2LwS7s/jcXXoU6fm2+sitBZXtqu3+Q4DcDgPxM45uWMCzPs0TSRw==", + "cpu": [ + "arm64" + ], + "deprecated": "yuku-parser runs on yuku-core since 0.14", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@yuku-parser/binding-win32-x64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-win32-x64/-/binding-win32-x64-0.8.7.tgz", + "integrity": "sha512-8vNB2DP0ou61nGb8tc/qfi41gfyDXz1MHr2zqL3nR+cJ6CEbiuWV/l/a/vv151gCgiZLLAyGkQGENpozdg716w==", + "cpu": [ + "x64" + ], + "deprecated": "yuku-parser runs on yuku-core since 0.14", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@yuku-toolchain/types": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-toolchain/types/-/types-0.8.7.tgz", + "integrity": "sha512-2Z53dNxAJL6UvFoIrDZvYf3zlO8s4VJK4O2hhaB4mXVwwpX/7ajtss3cmfqKvamlNLWyt9FSWs4eoYdlbxpnHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansis": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.5.0.tgz", + "integrity": "sha512-KwIVkfFfJSSzBmZhDGuTmkhWqTdyB5oNVzzznBJbBZ5bZtHiqSrcugRF0RKJKT4Ss3d7ZrcjnlFVDlQNmtWfWw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.1.0.tgz", + "integrity": "sha512-LMDU/U2yA4IPIY8KT0NsNPWEYZvAMkbgyspxJ6btJaYV+WMDOHAZCmB0lsTObiHaRS1V/ztKAl7+TSkTqXyeoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.2.tgz", + "integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "destroy": "1.2.0", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/dts-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dts-resolver/-/dts-resolver-3.0.0.tgz", + "integrity": "sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "oxc-resolver": ">=11.0.0" + }, + "peerDependenciesMeta": { + "oxc-resolver": { + "optional": true + } + } + }, + "node_modules/empathic": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.1.0.tgz", + "integrity": "sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/execa": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-10.2.1.tgz", + "integrity": "sha512-/Bofl3ZBh2Q4/PG0W63qFzP5vDAypi8Zc90UWTBzaDNIYiX7apPOd0N6L8hG8CojUdq4V51adu8BObP+iVsSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "figures": "^6.1.0", + "get-stream": "^9.0.1", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.3.1", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "which-command": "^0.1.0", + "yoctocolors": "^2.2.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", - "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", - "cpu": [ - "arm64" - ], + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" } }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", - "cpu": [ - "x64" - ], + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "license": "MPL-2.0", + "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "node_modules/get-tsconfig": { + "version": "5.0.0-beta.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.5.tgz", + "integrity": "sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==", "dev": true, "license": "MIT", "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" + "resolve-pkg-maps": "^1.0.0" }, - "bin": { - "loose-envify": "cli.js" + "engines": { + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "node_modules/hookable": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.2.tgz", + "integrity": "sha512-+abwxtiEA52GCVIsQqut3S/uKTbUwYIp4Pe/vv+6py5XiXBCqMZHg6pA6Y5qhgLSEys0/cYuPbO0z1QFj5ZCmg==", "dev": true, "license": "MIT" }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" } }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "node_modules/import-without-cache": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/import-without-cache/-/import-without-cache-0.4.1.tgz", + "integrity": "sha512-vXoV9PjKHEednCUu01e98TkImxy67e3BJXbTIOmIq4Hyzw3IAwYRcuPkfeTzJzwyyts4kjuA73x+pWJLc4f86A==", "dev": true, "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" + "engines": { + "node": "^22.18.0 || >=24.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", - "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "node_modules/ipaddr.js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz", + "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==", "dev": true, "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 10" } }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "engines": { + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" + "engines": { + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-gfm-footnote": { + "node_modules/is-unicode-supported": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" + "engines": { + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "argparse": "^2.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "node_modules/jsbi": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.2.tgz", + "integrity": "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==", "dev": true, + "license": "Apache-2.0" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/koffi": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-3.1.1.tgz", + "integrity": "sha512-mRX6AMeeKCxSOeOopqAcLAl5jcNvge7NAG8l7rF/8gGJATI0tdHFYjteIdE0mGOtWdsrJOij+PjnP8Q9c1gwgA==", + "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://liberapay.com/Koromix" + }, + "optionalDependencies": { + "@koromix/koffi-darwin-arm64": "3.1.1", + "@koromix/koffi-darwin-x64": "3.1.1", + "@koromix/koffi-freebsd-arm64": "3.1.1", + "@koromix/koffi-freebsd-ia32": "3.1.1", + "@koromix/koffi-freebsd-x64": "3.1.1", + "@koromix/koffi-linux-arm64": "3.1.1", + "@koromix/koffi-linux-ia32": "3.1.1", + "@koromix/koffi-linux-loong64": "3.1.1", + "@koromix/koffi-linux-riscv64": "3.1.1", + "@koromix/koffi-linux-x64": "3.1.1", + "@koromix/koffi-openbsd-ia32": "3.1.1", + "@koromix/koffi-openbsd-x64": "3.1.1", + "@koromix/koffi-win32-arm64": "3.1.1", + "@koromix/koffi-win32-ia32": "3.1.1", + "@koromix/koffi-win32-x64": "3.1.1" } }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, - "license": "MIT", + "license": "MPL-2.0", "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, - "node_modules/mdast-util-math": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", - "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "longest-streak": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.1.0", - "unist-util-remove-position": "^5.0.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "libc": [ + "glibc" ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "license": "MIT", "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "js-tokens": "^3.0.0 || ^4.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "bin": { + "loose-envify": "cli.js" } }, - "node_modules/micromark-extension-math": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", - "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@types/katex": "^0.16.0", - "devlop": "^1.0.0", - "katex": "^0.16.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + "license": "MIT" }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT", "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "node_modules/mime": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.2.tgz", + "integrity": "sha512-ikRzZ2RwbUVDyL9SsU0f2Phcmav4yq5r8faH5ZZKHtOZrMBLnrQOrj0uvqZ5R1uQnj+RO7Wcklw/lAf81mP9LQ==", "dev": true, "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "https://github.com/sponsors/broofa" ], "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" } }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "engines": { + "node": ">= 0.6" } }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT", "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } + "license": "MIT" }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT", "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "node_modules/node-addon-native-custom-loader": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/node-addon-native-custom-loader/-/node-addon-native-custom-loader-0.1.9.tgz", + "integrity": "sha512-Zh87aubSc1UF8CzZsRws1KtSwXiPDH0tbcg8V/2OA8Tcq9yGJRESFcEOC5NGLFama5IGfC3aJhubZbrj9Un2qA==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" + "engines": { + "node": ">=20" } }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "node_modules/node-addon-require-builtin": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/node-addon-require-builtin/-/node-addon-require-builtin-0.1.9.tgz", + "integrity": "sha512-BCxXCMZDs/VLLh64SwKNw/me7Qx1I7Be4qxWwYVRRm8tJoOB8fL7qg6CmXhSXeUqsKt72t8RaKCX4EN+5nocVg==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "node-addon-native-custom-loader": "0.1.9" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "node-addon-require-builtin-darwin-arm64": "0.1.9", + "node-addon-require-builtin-darwin-x64": "0.1.9", + "node-addon-require-builtin-linux-arm64-gnu": "0.1.9", + "node-addon-require-builtin-linux-arm64-musl": "0.1.9", + "node-addon-require-builtin-linux-x64-gnu": "0.1.9", + "node-addon-require-builtin-linux-x64-musl": "0.1.9", + "node-addon-require-builtin-win32-arm64-msvc": "0.1.9", + "node-addon-require-builtin-win32-ia32-msvc": "0.1.9", + "node-addon-require-builtin-win32-x64-msvc": "0.1.9" + } + }, + "node_modules/node-addon-require-builtin-darwin-arm64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/node-addon-require-builtin-darwin-arm64/-/node-addon-require-builtin-darwin-arm64-0.1.9.tgz", + "integrity": "sha512-n45xEILQVOLZ2hVhzRXesTMYS4odHquyI/B/IkPMPUCONa8PjNfBag6lYBH4wDrhgpLNNBwMsUD8d8O/13QPPA==", + "cpu": [ + "arm64" ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" + "node-addon-native-custom-loader": "0.1.9" + }, + "engines": { + "node": ">=20" } }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "node_modules/node-addon-require-builtin-darwin-x64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/node-addon-require-builtin-darwin-x64/-/node-addon-require-builtin-darwin-x64-0.1.9.tgz", + "integrity": "sha512-Sm4Liqh1uo9zgJB2F/pACPPZdHu+fj9Qniyr1Q99dDQB0v71VgZgkAgv3K0/E1iPQBlZq9rakFkY7MGUIfWc0A==", + "cpu": [ + "x64" ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" ], - "license": "MIT" + "dependencies": { + "node-addon-native-custom-loader": "0.1.9" + }, + "engines": { + "node": ">=20" + } }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "node_modules/node-addon-require-builtin-linux-arm64-gnu": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/node-addon-require-builtin-linux-arm64-gnu/-/node-addon-require-builtin-linux-arm64-gnu-0.1.9.tgz", + "integrity": "sha512-ToU4wGtOD/069i2y4fJ3cjDJiiWtcjSQTZMf0YKlotb5BmTM7FjhR3X1aub8J/YjlOwl6665J/XAYuvCaDO5sA==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "libc": [ + "glibc" ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "dependencies": { - "micromark-util-symbol": "^2.0.0" + "node-addon-native-custom-loader": "0.1.9" + }, + "engines": { + "node": ">=20" } }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "node_modules/node-addon-require-builtin-linux-arm64-musl": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/node-addon-require-builtin-linux-arm64-musl/-/node-addon-require-builtin-linux-arm64-musl-0.1.9.tgz", + "integrity": "sha512-85Z5Avp++wfa9iiTIH8LC/r6qvlC/CiwpifiwIz0gYQ9PzajTSkz06gX7YDqf1oUqWYxU9uwNEOmo7mktHOOfA==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "libc": [ + "musl" ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "dependencies": { - "micromark-util-types": "^2.0.0" + "node-addon-native-custom-loader": "0.1.9" + }, + "engines": { + "node": ">=20" } }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "node_modules/node-addon-require-builtin-linux-x64-gnu": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/node-addon-require-builtin-linux-x64-gnu/-/node-addon-require-builtin-linux-x64-gnu-0.1.9.tgz", + "integrity": "sha512-fh3K3/JvBfSYwU8vEOpp4Wdp4N/jX0EnZYq5GsaftlQin1FVqgve/yDNoh+asxBMOQyHOG8F4dXlV1WCierrfg==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "libc": [ + "glibc" ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" + "node-addon-native-custom-loader": "0.1.9" + }, + "engines": { + "node": ">=20" } }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "node_modules/node-addon-require-builtin-linux-x64-musl": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/node-addon-require-builtin-linux-x64-musl/-/node-addon-require-builtin-linux-x64-musl-0.1.9.tgz", + "integrity": "sha512-B6fu/5xmbRxnifM7nWz0IESkYnUl8dwFZmRDMsxdhzv2nb+EtBRFAXg4lf73qhX3yoZD4SnWJILBQMlIAwBCzw==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "libc": [ + "musl" ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "node-addon-native-custom-loader": "0.1.9" + }, + "engines": { + "node": ">=20" } }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "node_modules/node-addon-require-builtin-win32-arm64-msvc": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/node-addon-require-builtin-win32-arm64-msvc/-/node-addon-require-builtin-win32-arm64-msvc-0.1.9.tgz", + "integrity": "sha512-FOQuzNe8B1BANu886rJgdbQuRxPGUQmewkykw87wSr32U0ibQ3AWGuUM/Gh7LG4RekjFaifj7gEI/tq2+6GU6g==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" ], - "license": "MIT" + "dependencies": { + "node-addon-native-custom-loader": "0.1.9" + }, + "engines": { + "node": ">=20" + } }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "node_modules/node-addon-require-builtin-win32-ia32-msvc": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/node-addon-require-builtin-win32-ia32-msvc/-/node-addon-require-builtin-win32-ia32-msvc-0.1.9.tgz", + "integrity": "sha512-N29P0kmIBT6hnkNNFYzxn+o9ydT6OYZe9RFAi8dSxV1rNSul1FlxtuKWYe8w2sQpbQGYuGDEtpOwdeFtq2iFTg==", + "cpu": [ + "ia32" + ], "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" ], - "license": "MIT" + "dependencies": { + "node-addon-native-custom-loader": "0.1.9" + }, + "engines": { + "node": ">=20 <23" + } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/node-addon-require-builtin-win32-x64-msvc": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/node-addon-require-builtin-win32-x64-msvc/-/node-addon-require-builtin-win32-x64-msvc-0.1.9.tgz", + "integrity": "sha512-+i6N4A7m7txjbw8pDYESdWXiKO9EEVq0SzTzwPS0WZuV4F1MzQnaDJwJJnO7T6zAc0BvqHXBOkpharCrUjFhbQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "dependencies": { + "node-addon-native-custom-loader": "0.1.9" + }, + "engines": { + "node": ">=20" + } }, - "node_modules/nanoid": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/obug": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", - "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -4864,23 +5579,47 @@ "node": ">=12.20.0" } }, - "node_modules/oniguruma-parser": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", - "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", "dev": true, "license": "MIT" }, - "node_modules/oniguruma-to-es": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", - "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "dev": true, "license": "MIT", - "dependencies": { - "oniguruma-parser": "^0.12.2", - "regex": "^6.1.0", - "regex-recursion": "^6.0.2" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/pathe": { @@ -4921,9 +5660,9 @@ } }, "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "version": "8.5.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", + "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", "dev": true, "funding": [ { @@ -4941,23 +5680,28 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.17", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" } }, - "node_modules/property-information": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", - "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "node_modules/pretty-ms": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.1.tgz", + "integrity": "sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==", "dev": true, "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/quansync": { @@ -5004,33 +5748,16 @@ "react": "^18.3.1" } }, - "node_modules/regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", - "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-recursion": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", - "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/regex-utilities": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", - "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", - "dev": true, - "license": "MIT" - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -5041,14 +5768,31 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/resolve.exports": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.4.tgz", + "integrity": "sha512-zepuGL0ulf+nt+XaFrlKeaiTJeyPFPLLY62+ncq1/VTHBdt3HsfXOH60priZ6tnXQozdGcDeIF5JVTanvIcYWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "dev": true, + "license": "MIT" + }, "node_modules/rolldown": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.6.tgz", - "integrity": "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz", + "integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.147.0", + "@oxc-project/types": "=0.156.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -5058,21 +5802,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.6", - "@rolldown/binding-android-arm64": "1.2.6", - "@rolldown/binding-darwin-arm64": "1.2.6", - "@rolldown/binding-darwin-x64": "1.2.6", - "@rolldown/binding-freebsd-x64": "1.2.6", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.6", - "@rolldown/binding-linux-arm64-gnu": "1.2.6", - "@rolldown/binding-linux-arm64-musl": "1.2.6", - "@rolldown/binding-linux-ppc64-gnu": "1.2.6", - "@rolldown/binding-linux-s390x-gnu": "1.2.6", - "@rolldown/binding-linux-x64-gnu": "1.2.6", - "@rolldown/binding-linux-x64-musl": "1.2.6", - "@rolldown/binding-openharmony-arm64": "1.2.6", - "@rolldown/binding-win32-arm64-msvc": "1.2.6", - "@rolldown/binding-win32-x64-msvc": "1.2.6" + "@rolldown/binding-android-arm-eabi": "1.2.16", + "@rolldown/binding-android-arm64": "1.2.16", + "@rolldown/binding-darwin-arm64": "1.2.16", + "@rolldown/binding-darwin-x64": "1.2.16", + "@rolldown/binding-freebsd-x64": "1.2.16", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.16", + "@rolldown/binding-linux-arm64-gnu": "1.2.16", + "@rolldown/binding-linux-arm64-musl": "1.2.16", + "@rolldown/binding-linux-ppc64-gnu": "1.2.16", + "@rolldown/binding-linux-s390x-gnu": "1.2.16", + "@rolldown/binding-linux-x64-gnu": "1.2.16", + "@rolldown/binding-linux-x64-musl": "1.2.16", + "@rolldown/binding-openharmony-arm64": "1.2.16", + "@rolldown/binding-win32-arm64-msvc": "1.2.16", + "@rolldown/binding-win32-x64-msvc": "1.2.16" } }, "node_modules/rolldown-plugin-dts": { @@ -5118,13 +5862,13 @@ } }, "node_modules/rollup": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.0.tgz", - "integrity": "sha512-T5vnZ2y4QqC3/4P+w2+JO+Q/OVdnPsv4XcSYJYMEn0R9/jjl5AgLwO9LAZMzP2lN71O6pypn91rB7lDstUkfrQ==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.67.4.tgz", + "integrity": "sha512-Lsv3oKcz5jgOQJej/p7nfwbVEdAbDT+z6OPf/SCdI+nF+q2ENp0pTKT35ZwXAmH8g45mfrIiJeEyAqKYSYoL9Q==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.9" + "@types/estree": "^1.0.10" }, "bin": { "rollup": "dist/bin/rollup" @@ -5135,34 +5879,69 @@ }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", - "@rollup/rollup-android-arm-eabi": "4.63.0", - "@rollup/rollup-android-arm64": "4.63.0", - "@rollup/rollup-darwin-arm64": "4.63.0", - "@rollup/rollup-darwin-x64": "4.63.0", - "@rollup/rollup-freebsd-arm64": "4.63.0", - "@rollup/rollup-freebsd-x64": "4.63.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.63.0", - "@rollup/rollup-linux-arm-musleabihf": "4.63.0", - "@rollup/rollup-linux-arm64-gnu": "4.63.0", - "@rollup/rollup-linux-arm64-musl": "4.63.0", - "@rollup/rollup-linux-loong64-gnu": "4.63.0", - "@rollup/rollup-linux-loong64-musl": "4.63.0", - "@rollup/rollup-linux-ppc64-gnu": "4.63.0", - "@rollup/rollup-linux-ppc64-musl": "4.63.0", - "@rollup/rollup-linux-riscv64-gnu": "4.63.0", - "@rollup/rollup-linux-riscv64-musl": "4.63.0", - "@rollup/rollup-linux-s390x-gnu": "4.63.0", - "@rollup/rollup-linux-x64-gnu": "4.63.0", - "@rollup/rollup-linux-x64-musl": "4.63.0", - "@rollup/rollup-openbsd-x64": "4.63.0", - "@rollup/rollup-openharmony-arm64": "4.63.0", - "@rollup/rollup-win32-arm64-msvc": "4.63.0", - "@rollup/rollup-win32-ia32-msvc": "4.63.0", - "@rollup/rollup-win32-x64-gnu": "4.63.0", - "@rollup/rollup-win32-x64-msvc": "4.63.0", + "@rollup/rollup-android-arm-eabi": "4.67.4", + "@rollup/rollup-android-arm64": "4.67.4", + "@rollup/rollup-android-x64": "4.67.4", + "@rollup/rollup-darwin-arm64": "4.67.4", + "@rollup/rollup-darwin-x64": "4.67.4", + "@rollup/rollup-freebsd-arm64": "4.67.4", + "@rollup/rollup-freebsd-x64": "4.67.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.67.4", + "@rollup/rollup-linux-arm-musleabihf": "4.67.4", + "@rollup/rollup-linux-arm64-gnu": "4.67.4", + "@rollup/rollup-linux-arm64-musl": "4.67.4", + "@rollup/rollup-linux-loong64-gnu": "4.67.4", + "@rollup/rollup-linux-loong64-musl": "4.67.4", + "@rollup/rollup-linux-ppc64-gnu": "4.67.4", + "@rollup/rollup-linux-ppc64-musl": "4.67.4", + "@rollup/rollup-linux-riscv64-gnu": "4.67.4", + "@rollup/rollup-linux-riscv64-musl": "4.67.4", + "@rollup/rollup-linux-s390x-gnu": "4.67.4", + "@rollup/rollup-linux-x64-gnu": "4.67.4", + "@rollup/rollup-linux-x64-musl": "4.67.4", + "@rollup/rollup-openbsd-x64": "4.67.4", + "@rollup/rollup-openharmony-arm64": "4.67.4", + "@rollup/rollup-win32-arm64-msvc": "4.67.4", + "@rollup/rollup-win32-ia32-msvc": "4.67.4", + "@rollup/rollup-win32-x64-gnu": "4.67.4", + "@rollup/rollup-win32-x64-msvc": "4.67.4", "fsevents": "~2.3.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -5173,24 +5952,17 @@ "loose-envify": "^1.1.0" } }, - "node_modules/shiki": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.4.3.tgz", - "integrity": "sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==", + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/core": "4.4.3", - "@shikijs/engine-javascript": "4.4.3", - "@shikijs/engine-oniguruma": "4.4.3", - "@shikijs/langs": "4.4.3", - "@shikijs/themes": "4.4.3", - "@shikijs/types": "4.4.3", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.5" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=20" + "node": ">=10" } }, "node_modules/siginfo": { @@ -5200,25 +5972,27 @@ "dev": true, "license": "ISC" }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" } }, "node_modules/stackback": { @@ -5235,19 +6009,17 @@ "dev": true, "license": "MIT" }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" + "engines": { + "node": ">=18" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-literal": { @@ -5270,6 +6042,13 @@ "dev": true, "license": "MIT" }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "dev": true, + "license": "MIT" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -5278,9 +6057,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", - "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "dev": true, "license": "MIT", "engines": { @@ -5325,9 +6104,9 @@ } }, "node_modules/tinyspy": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.6.tgz", + "integrity": "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==", "dev": true, "license": "MIT", "engines": { @@ -5344,17 +6123,6 @@ "tree-kill": "cli.js" } }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/tsdown": { "version": "0.22.14", "resolved": "https://registry.npmjs.org/tsdown/-/tsdown-0.22.14.tgz", @@ -5428,6 +6196,13 @@ } } }, + "node_modules/tslib": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz", + "integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==", + "dev": true, + "license": "0BSD" + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -5456,6 +6231,17 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/undici": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz", + "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=22.19.0" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -5463,102 +6249,49 @@ "dev": true, "license": "MIT" }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-remove-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", - "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", "dev": true, "license": "MIT", "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" } }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" } }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" + "engines": { + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "engines": { + "node": ">= 0.8" } }, "node_modules/verkit": { @@ -5574,40 +6307,10 @@ "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/vite": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", - "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.7.tgz", + "integrity": "sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==", "dev": true, "license": "MIT", "dependencies": { @@ -5712,6 +6415,31 @@ "node": ">=8" } }, + "node_modules/vite-node/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vite-node/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/vitest": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", @@ -5785,6 +6513,31 @@ } } }, + "node_modules/vitest/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/vitest/node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", @@ -5792,6 +6545,22 @@ "dev": true, "license": "MIT" }, + "node_modules/which-command": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/which-command/-/which-command-0.1.0.tgz", + "integrity": "sha512-XZyoF5/5hZtXitIwzrU4NKK+Wtbb9aB9CezUEw2Q0wlYK8NUYQxC1rRXgNueYLtBAJwXIb+/tFVk4dozciNJMA==", + "dev": true, + "license": "MIT", + "bin": { + "which-command": "cli.js" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/which-command?sponsor=1" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -5810,9 +6579,9 @@ } }, "node_modules/ws": { - "version": "8.21.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", - "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "version": "8.22.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.1.tgz", + "integrity": "sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g==", "dev": true, "license": "MIT", "engines": { @@ -5831,6 +6600,42 @@ } } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yoctocolors": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yuku-ast": { "version": "0.8.7", "resolved": "https://registry.npmjs.org/yuku-ast/-/yuku-ast-0.8.7.tgz", @@ -5896,50 +6701,9 @@ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } - }, - "node_modules/zustand": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.7.tgz", - "integrity": "sha512-QFJWJMdlETcI69paJwhSMJz7PPWjVP8Sjhclxmxmxv/RYI7ZOvR5BHX+ktH0we9gTWQMxcne8q1OY8xxz604gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "use-sync-external-store": "1.2.0" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } } } } diff --git a/dsh-plugins/ccteam-ui/package.json b/dsh-plugins/ccteam-ui/package.json index 75d20b1bb..f6dd62a0b 100644 --- a/dsh-plugins/ccteam-ui/package.json +++ b/dsh-plugins/ccteam-ui/package.json @@ -1,6 +1,6 @@ { "name": "@ccteam/ccteam-ui", - "version": "0.13.0", + "version": "0.13.1", "description": "ccteam inside the DeepSeek Harness: the workbench in DSH web (cross-harness session tree, native-grade chat, one-click spawn), the six ccteam MCP tools for DSH agents, and the ACP transport ccteam hires DSH sessions through", "type": "module", "main": "lib/index.js", @@ -19,7 +19,7 @@ "README.md" ], "ccteam": { - "engine": "0.13.0" + "engine": "0.13.1" }, "dsh": { "bundle": { @@ -28,12 +28,12 @@ "client": { "platform": "web", "inject": [ - "@deepseek-ai/dsh-client-runtime", "@deepseek-ai/dsh-client-locale", "@deepseek-ai/dsh-client-ui-layout", "@deepseek-ai/dsh-client-ui-sidebar", "@deepseek-ai/dsh-client-ui-settings", - "@deepseek-ai/dsh-client-ui-settings-plugins" + "@deepseek-ai/dsh-client-ui-renderer", + "@deepseek-ai/dsh-client-ui-workspace" ] } }, @@ -46,33 +46,73 @@ "lint:scan": "bash scripts/no-persona-scan.sh" }, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/schemastery": "3.18.6-alpha.1" }, "optionalDependencies": { - "@ccteam/engine-darwin-arm64": "0.13.0", - "@ccteam/engine-darwin-x64": "0.13.0", - "@ccteam/engine-linux-arm64": "0.13.0", - "@ccteam/engine-linux-x64": "0.13.0" + "@ccteam/engine-darwin-arm64": "0.13.1", + "@ccteam/engine-darwin-x64": "0.13.1", + "@ccteam/engine-linux-arm64": "0.13.1", + "@ccteam/engine-linux-x64": "0.13.1" }, "bundledDependencies": [ "@deepseek-ai/schemastery" ], "devDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-api-remotes": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-connection": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-locale": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-layout": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-primitives": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-sidebar": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2", - "@deepseek-ai/dsh-client-ui-theme": "0.1.1-rc.2", - "@deepseek-ai/dsh-invariants": "0.1.1-rc.2", - "@deepseek-ai/dsh-settings": "0.1.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "0.1.1-rc.2", + "@deepseek-ai/cordis": "4.0.6-alpha.1", + "@deepseek-ai/dsh-agent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-agent-preset-registry": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-account-controller": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-gateway": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-job-controller": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-remote-access": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-remotes": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-session-controller": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-settings-controller": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-terminal-controller": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-workspace-controller": "0.2.2-alpha.2", + "@deepseek-ai/dsh-api-workspace-files": "0.2.2-alpha.2", + "@deepseek-ai/dsh-attachment": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-connection": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-file-upload": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-locale": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-product-analytics": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-shortcuts": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-store": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-conversation": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-layout": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-primitives": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-renderer": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-settings": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-settings-plugins": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-sidebar": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-slots": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-theme": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-trajectory": "0.2.2-alpha.2", + "@deepseek-ai/dsh-client-ui-workspace": "0.2.2-alpha.2", + "@deepseek-ai/dsh-command-feedback": "0.2.2-alpha.2", + "@deepseek-ai/dsh-commands": "0.2.2-alpha.2", + "@deepseek-ai/dsh-cordis-host-runner": "0.2.2-alpha.2", + "@deepseek-ai/dsh-file-reference": "0.2.2-alpha.2", + "@deepseek-ai/dsh-goal": "0.2.2-alpha.2", + "@deepseek-ai/dsh-host-plugin-inventory": "0.2.2-alpha.2", + "@deepseek-ai/dsh-host-webserver": "0.2.2-alpha.2", + "@deepseek-ai/dsh-message-feedback": "0.2.2-alpha.2", + "@deepseek-ai/dsh-office-to-pdf": "0.2.2-alpha.2", + "@deepseek-ai/dsh-permission-presets": "0.2.2-alpha.2", + "@deepseek-ai/dsh-plugin-manager": "0.2.2-alpha.2", + "@deepseek-ai/dsh-plugin-market": "0.2.2-alpha.2", + "@deepseek-ai/dsh-schedule": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session": "0.2.2-alpha.2", + "@deepseek-ai/dsh-session-reference": "0.2.2-alpha.2", + "@deepseek-ai/dsh-settings": "0.2.2-alpha.2", + "@deepseek-ai/dsh-subagent": "0.2.2-alpha.2", + "@deepseek-ai/dsh-tools": "0.2.2-alpha.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2", + "@deepseek-ai/dsh-user-questions": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-code-language": "0.2.2-alpha.2", + "@deepseek-ai/dsh-util-file-drag": "0.2.2-alpha.2", "@types/node": "^22.13.0", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", diff --git a/dsh-plugins/ccteam-ui/src/bff.ts b/dsh-plugins/ccteam-ui/src/bff.ts index d2b822c03..ef13c0a33 100644 --- a/dsh-plugins/ccteam-ui/src/bff.ts +++ b/dsh-plugins/ccteam-ui/src/bff.ts @@ -37,6 +37,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http' import { API_PREFIX, + API_RELATIVE_PREFIX, ATTACHMENT_PATH, EVENTS_PATH, UPLOAD_PATH, @@ -142,13 +143,7 @@ export interface BffOptions { sleep?: (ms: number, signal: AbortSignal) => Promise } -export interface WebServerLike { - register(route: { - kind: 'exact' | 'prefix' - path: string - handler: (req: IncomingMessage, res: ServerResponse) => void | Promise - }): () => void -} +export type WebServerLike = Pick export interface BffContext { webServer: WebServerLike @@ -958,11 +953,11 @@ function residencyOf(residency: string | undefined): Residency | undefined { } function attachmentUrlFor(project: string, name: string): string { - return `${API_PREFIX}${ATTACHMENT_PATH}?project=${encodeURIComponent(project)}&name=${encodeURIComponent(name)}` + return `${API_RELATIVE_PREFIX}${ATTACHMENT_PATH}?project=${encodeURIComponent(project)}&name=${encodeURIComponent(name)}` } function attachmentUrlForSid(sid: string, name: string): string { - return `${API_PREFIX}${ATTACHMENT_PATH}?sid=${encodeURIComponent(sid)}&name=${encodeURIComponent(name)}` + return `${API_RELATIVE_PREFIX}${ATTACHMENT_PATH}?sid=${encodeURIComponent(sid)}&name=${encodeURIComponent(name)}` } /** diff --git a/dsh-plugins/ccteam-ui/src/client/Chat.tsx b/dsh-plugins/ccteam-ui/src/client/Chat.tsx index b32168844..4113b5c33 100644 --- a/dsh-plugins/ccteam-ui/src/client/Chat.tsx +++ b/dsh-plugins/ccteam-ui/src/client/Chat.tsx @@ -11,15 +11,15 @@ import { useEffect, useRef, useState } from 'react' import clsx from 'clsx' import { Button, - IconApiOutline14, - IconChevronDownOutline14, - IconCodeOutline16, - IconEditOutline16, - IconGlobeOutline14, - IconLoadingOutline16, - IconPaperclipOutline16, - IconSparkle16, - IconThinkOutline16, + IconApiOutlineRegular, + IconChevronDownOutlineRegular, + IconCodeOutlineRegular, + IconEditOutlineRegular, + IconGlobeOutlineRegular, + IconLoadingOutlineRegular, + IconPaperclipOutlineRegular, + IconSparkleRegular, + IconThinkOutlineRegular, MarkdownText, Menu, StateDot, @@ -124,7 +124,7 @@ function ModelSwitch({ node, chat, models, t, onSwitch }: { {node.vendor} {currentModel !== null && · {currentModel}} {currentEffort !== null && · {currentEffort}} - + )} /> @@ -134,18 +134,18 @@ function ModelSwitch({ node, chat, models, t, onSwitch }: { function StepIcon({ kind }: { kind: string }) { switch (kind) { case 'command_exec': - return + return case 'file_change': - return + return case 'web_search': - return + return case 'thinking': - return + return case 'tool_call': case 'tool_result': - return + return default: - return + return } } @@ -169,7 +169,7 @@ function Steps({ steps, selected, t, onSelect }: { steps: Step[]; selected: stri {step.summary} {step.status === 'completed' ? - : } + : } ))}
@@ -192,7 +192,7 @@ function Attachments({ attachments }: { attachments: AttachmentRef[] | undefined rel="noreferrer" onClick={attachment.url === undefined ? event => event.preventDefault() : undefined} > - + {attachment.name} ) @@ -209,7 +209,7 @@ function ChoiceCard({ row, t, onResolve }: { const chosen = row.resolved === undefined ? undefined : row.options.find(o => o.id === row.resolved) return (
- {row.content !== '' &&
} + {row.content !== '' &&
} {chosen !== undefined ?
{t('chat.choice.resolved', { label: chosen.label })}
: ( @@ -282,7 +282,7 @@ function Row({ row, selectedStep, t, onSelectStep, onResolve }: { return (
- {row.content !== '' &&
} + {row.content !== '' &&
}
) diff --git a/dsh-plugins/ccteam-ui/src/client/Composer.tsx b/dsh-plugins/ccteam-ui/src/client/Composer.tsx index 99307103f..ae06fdcb1 100644 --- a/dsh-plugins/ccteam-ui/src/client/Composer.tsx +++ b/dsh-plugins/ccteam-ui/src/client/Composer.tsx @@ -10,11 +10,11 @@ import { useEffect, useRef, useState } from 'react' import type { ChangeEvent, KeyboardEvent, ReactNode } from 'react' import clsx from 'clsx' import { - IconCloseFill14, - IconLoadingOutline16, - IconPaperclipOutline16, - IconSendOutline16, - IconStopFill16, + IconCloseFillRegular, + IconLoadingOutlineRegular, + IconPaperclipOutlineRegular, + IconSendOutlineRegular, + IconStopFillRegular, Menu, } from '@deepseek-ai/dsh-client-ui-primitives' import type { MenuEntry } from '@deepseek-ai/dsh-client-ui-primitives' @@ -185,8 +185,8 @@ export function Composer(props: ComposerProps) { {props.attachments.map(attachment => ( {attachment.uploading === true - ? - : } + ? + : } {attachment.error !== undefined ? t('chat.attach.failed', { kind: attachment.error }) @@ -201,7 +201,7 @@ export function Composer(props: ComposerProps) { props.onRemoveAttachment?.(attachment.key) }} > - + )} @@ -234,7 +234,7 @@ export function Composer(props: ComposerProps) { fileRef.current?.click() }} > - + )} @@ -251,7 +251,7 @@ export function Composer(props: ComposerProps) { title={t('chat.stop')} onClick={onStop} > - + ) : ( @@ -263,7 +263,7 @@ export function Composer(props: ComposerProps) { disabled={!canSend} onClick={submit} > - {busy === true ? : } + {busy === true ? : } )}
diff --git a/dsh-plugins/ccteam-ui/src/client/Details.tsx b/dsh-plugins/ccteam-ui/src/client/Details.tsx index d4c197a11..bfcdab536 100644 --- a/dsh-plugins/ccteam-ui/src/client/Details.tsx +++ b/dsh-plugins/ccteam-ui/src/client/Details.tsx @@ -8,9 +8,9 @@ import { useEffect, useState } from 'react' import { Button, - IconCheckOutline14, - IconCloseOutline16, - IconCopyOutline16, + IconCheckOutlineRegular, + IconCloseOutlineRegular, + IconCopyOutlineRegular, Input, StateDot, writeClipboard, @@ -145,7 +145,7 @@ export function Details(props: DetailsProps) {
{step !== undefined ? t('details.step') : t('details.title')}
@@ -294,7 +294,7 @@ export function Details(props: DetailsProps) { ) @@ -118,7 +118,7 @@ export function EnginePanel({ t, status, pending, error, mode, onStart, onOpenSe
- {busy || status === null ? : } + {busy || status === null ? : } {title}
{reason !== null &&

{reason}

} @@ -139,7 +139,7 @@ export function EnginePanel({ t, status, pending, error, mode, onStart, onOpenSe
@@ -250,7 +250,7 @@ export function ProjectPanel({ t, busy, error, useWorkspaces, onCreate }: Projec
- + {t('firstrun.project.title')}

{t('firstrun.project.body')}

@@ -303,7 +303,7 @@ export function ProjectPanel({ t, busy, error, useWorkspaces, onCreate }: Projec size="md" type="button" disabled={busy} - icon={busy ? : undefined} + icon={busy ? : undefined} onClick={submit} > {busy ? t('firstrun.project.adding') : t('firstrun.project.add')} diff --git a/dsh-plugins/ccteam-ui/src/client/EntryButton.tsx b/dsh-plugins/ccteam-ui/src/client/EntryButton.tsx index 610eea92f..4cfe1eff1 100644 --- a/dsh-plugins/ccteam-ui/src/client/EntryButton.tsx +++ b/dsh-plugins/ccteam-ui/src/client/EntryButton.tsx @@ -7,7 +7,7 @@ * ccteam is not reachable; toggles the workbench through the one store. */ import clsx from 'clsx' -import { IconBranchOutline16, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives' +import { IconBranchOutlineRegular, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives' import type { EntryButtonProps } from './slots.js' import css from './workbench.module.css' @@ -34,7 +34,7 @@ export function EntryButton({ wide, useConsole, dispatch, t }: EntryButtonProps) dispatch({ type: 'toggle_panel' }) }} > - + {wide && {t('entry.title')}} {badge > 0 ? ( diff --git a/dsh-plugins/ccteam-ui/src/client/Hero.tsx b/dsh-plugins/ccteam-ui/src/client/Hero.tsx index a4dc26bd8..222d854ae 100644 --- a/dsh-plugins/ccteam-ui/src/client/Hero.tsx +++ b/dsh-plugins/ccteam-ui/src/client/Hero.tsx @@ -8,11 +8,11 @@ */ import { useState } from 'react' import { - IconAgentPresetOutline16, - IconBranchOutline16, - IconFolderOpen16, - IconSparkle16, - IconUserOutline16, + IconAgentPresetOutlineRegular, + IconBranchOutlineRegular, + IconFolderOpenRegular, + IconSparkleRegular, + IconUserOutlineRegular, } from '@deepseek-ai/dsh-client-ui-primitives' import type { ModelsCatalog, ProjectInfo, SpawnRequest, VendorAvailability } from '../shared/contract.js' import { titleFromTask } from './format.js' @@ -112,13 +112,13 @@ export function Hero(props: HeroProps) {
- + {t('hero.title')}
{noProjects ? t('hero.noProjects.body') : t('hero.subtitle')}
} + icon={} label={t('spawn.project')} value={draft.project} placeholder={noProjects ? t('hero.noProjects.title') : '…'} @@ -129,7 +129,7 @@ export function Hero(props: HeroProps) { }} /> } + icon={} label={t('spawn.vendor')} value={draft.vendor} placeholder="…" @@ -140,7 +140,7 @@ export function Hero(props: HeroProps) { }} /> } + icon={} label={t('spawn.role')} value={draft.role} placeholder={t('spawn.role.none')} @@ -168,7 +168,7 @@ export function Hero(props: HeroProps) { trailing={( <> } + icon={} label={t('spawn.model')} value={draft.model} placeholder={t('spawn.model.default')} diff --git a/dsh-plugins/ccteam-ui/src/client/Pickers.tsx b/dsh-plugins/ccteam-ui/src/client/Pickers.tsx index 3758acb5a..0b8575b28 100644 Binary files a/dsh-plugins/ccteam-ui/src/client/Pickers.tsx and b/dsh-plugins/ccteam-ui/src/client/Pickers.tsx differ diff --git a/dsh-plugins/ccteam-ui/src/client/TeamColumn.tsx b/dsh-plugins/ccteam-ui/src/client/TeamColumn.tsx index 106c3fa50..aff102e28 100644 --- a/dsh-plugins/ccteam-ui/src/client/TeamColumn.tsx +++ b/dsh-plugins/ccteam-ui/src/client/TeamColumn.tsx @@ -15,12 +15,12 @@ import { useEffect, useRef, useState } from 'react' import type { KeyboardEvent } from 'react' import { Button, - IconChevronDownOutline14, - IconEllipsisOutline16, - IconPlusOutline16, - IconSearchOutline16, - IconTreeCorner8x10, - IconWarningOutline16, + IconChevronDownOutlineRegular, + IconEllipsisOutlineRegular, + IconPlusOutlineRegular, + IconSearchOutlineRegular, + IconTreeCornerRegular, + IconWarningOutlineRegular, Menu, Modal, StateDot, @@ -145,7 +145,7 @@ function ProjectHead({ slug, collapsed, total, count, canSpawn, t, onToggle, onA }) { const [menuOpen, setMenuOpen] = useState(false) const items: MenuEntry[] = [ - { id: 'new', label: t('project.new'), icon: , disabled: !canSpawn }, + { id: 'new', label: t('project.new'), icon: , disabled: !canSpawn }, { id: 'copy', label: t('project.copySlug') }, { type: 'separator', id: 'sep-1' }, { id: 'solo', label: t('project.solo') }, @@ -161,7 +161,7 @@ function ProjectHead({ slug, collapsed, total, count, canSpawn, t, onToggle, onA onClick={onToggle} > {slug} {total !== null && {total}} @@ -193,7 +193,7 @@ function ProjectHead({ slug, collapsed, total, count, canSpawn, t, onToggle, onA setMenuOpen(previous => !previous) }} > - + )} /> @@ -207,7 +207,7 @@ function ProjectHead({ slug, collapsed, total, count, canSpawn, t, onToggle, onA onAction('new') }} > - +
@@ -295,7 +295,7 @@ function SessionRow({ node, depth, active, activity, now, t, renaming, onSelect, }} onKeyDown={onRowKey} > - {depth > 0 && } + {depth > 0 && } {renaming @@ -358,7 +358,7 @@ function SessionRow({ node, depth, active, activity, now, t, renaming, onSelect, event.stopPropagation() }} > - + )} /> @@ -425,14 +425,14 @@ export function TeamColumn(props: TeamColumnProps) { variant="outline" size="md" className={css.newBtn} - icon={} + icon={} disabled={!props.canSpawn} onClick={props.onNew} > {t('tree.spawn')} @@ -461,7 +461,7 @@ export function TeamColumn(props: TeamColumnProps) {
{props.graphError !== null && graph === null && (
- +
{t('tree.error')}
diff --git a/dsh-plugins/ccteam-ui/src/client/Workbench.tsx b/dsh-plugins/ccteam-ui/src/client/Workbench.tsx index eac0473e8..e165cac2b 100644 --- a/dsh-plugins/ccteam-ui/src/client/Workbench.tsx +++ b/dsh-plugins/ccteam-ui/src/client/Workbench.tsx @@ -22,14 +22,14 @@ import { useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { Button, - IconBranchOutline16, - IconChevronLeftOutline14, - IconCloseOutline16, - IconFullscreenOutline16, - IconInspectOutline12, - IconPanelLeftOutline16, - IconPlusOutline16, - IconWarningOutline16, + IconBranchOutlineRegular, + IconChevronLeftOutlineRegular, + IconCloseOutlineRegular, + IconFullscreenOutlineRegular, + IconInspectOutlineRegular, + IconPanelLeftOutlineRegular, + IconPlusOutlineRegular, + IconWarningOutlineRegular, StateDot, Tooltip, writeClipboard, @@ -131,7 +131,7 @@ function StateScreen({ state, dispatch, api, t }: { state: ConsoleState; dispatc if (phase === 'unconfigured') { return (
- +
{t('states.unconfigured.title')}
  1. {t('states.unconfigured.step1')}
  2. @@ -153,7 +153,7 @@ function StateScreen({ state, dispatch, api, t }: { state: ConsoleState; dispatc } return (
    - +
    {t('states.unreachable.title')}
    {t('states.unreachable.body')}
    @@ -647,7 +647,7 @@ export function Workbench({ useConsole, useWorkspaces, dispatch, api, t }: Workb dispatch({ type: 'clear_selection' }) }} > - + ) @@ -663,12 +663,12 @@ export function Workbench({ useConsole, useWorkspaces, dispatch, api, t }: Workb dispatch({ type: 'toggle_team' }) }} > - + )} - + {t('panel.title')} @@ -728,7 +728,7 @@ export function Workbench({ useConsole, useWorkspaces, dispatch, api, t }: Workb dispatch({ type: 'select_new' }) }} > - + @@ -741,7 +741,7 @@ export function Workbench({ useConsole, useWorkspaces, dispatch, api, t }: Workb dispatch({ type: 'toggle_details' }) }} > - + @@ -754,7 +754,7 @@ export function Workbench({ useConsole, useWorkspaces, dispatch, api, t }: Workb dispatch({ type: 'toggle_mode' }) }} > - + @@ -766,7 +766,7 @@ export function Workbench({ useConsole, useWorkspaces, dispatch, api, t }: Workb dispatch({ type: 'close_panel' }) }} > - +
    diff --git a/dsh-plugins/ccteam-ui/src/client/api.ts b/dsh-plugins/ccteam-ui/src/client/api.ts index ed0e89e8e..96cadf1e7 100644 --- a/dsh-plugins/ccteam-ui/src/client/api.ts +++ b/dsh-plugins/ccteam-ui/src/client/api.ts @@ -5,7 +5,7 @@ * URL or credential. Both transports are injectable so tests run without * fetch/EventSource. */ -import { API_PREFIX, ATTACHMENT_PATH, EVENTS_PATH, UPLOAD_PATH } from '../shared/contract.js' +import { API_RELATIVE_PREFIX, ATTACHMENT_PATH, EVENTS_PATH, UPLOAD_PATH } from '../shared/contract.js' import type { ApiMethod, EngineActionResult, @@ -114,7 +114,7 @@ export interface ApiClient { /** * Build the events URL for an optional sid filter. - * @param prefix - API prefix (contract API_PREFIX in production). + * @param prefix - API prefix (document-relative in production). * @param sid - session filter; omitted subscribes to the whole team stream. * @returns the subscription URL. */ @@ -152,7 +152,7 @@ export function attachmentUrl(prefix: string, project: string, name: string): st * @returns the client. */ export function createApi(deps: ApiDeps = {}): ApiClient { - const prefix = deps.prefix ?? API_PREFIX + const prefix = deps.prefix ?? API_RELATIVE_PREFIX const fetchFn = deps.fetchFn ?? ((input: string, init?: RequestInit) => fetch(input, init)) const createEventSource = deps.createEventSource ?? ((url: string): EventSourceLike => diff --git a/dsh-plugins/ccteam-ui/src/client/index.tsx b/dsh-plugins/ccteam-ui/src/client/index.tsx index b8089ad3e..70902c9ff 100644 --- a/dsh-plugins/ccteam-ui/src/client/index.tsx +++ b/dsh-plugins/ccteam-ui/src/client/index.tsx @@ -18,7 +18,7 @@ * Seats: * 1. `sidebar.footer.action` — the entry button beside DSH's Settings trigger * 2. `shell.overlay` — the workbench (team tree / conversation / details) - * 3. `settings.plugin.item` — the ccteam settings card + * 3. `settings.section` — the ccteam settings card * * All network traffic goes through the host BFF (src/shared/contract.ts). */ @@ -38,7 +38,7 @@ import { CCTEAM_CARD, SettingsCardController } from './settings/form.js' export const name = 'ccteam-ui' /** Services required by this plugin (cordis fiber inject). */ -export const inject = ['slots', 'locale', 'settingsScope'] +export const inject = ['slots', 'locale'] function browserStorage(): StorageLike | undefined { try { @@ -52,7 +52,7 @@ function browserStorage(): StorageLike | undefined { /** * Plugin body: wire the store/api/stream and contribute the three seats. - * @param ctx - client cordis context (slots + locale + settingsScope services injected). + * @param ctx - client cordis context (slots and locale). */ export function apply(ctx: CcteamClientContext): void { ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ccteam-ui: dictionaries') @@ -137,21 +137,20 @@ export function apply(ctx: CcteamClientContext): void { inject: face, }, Workbench)) - // One card for the one ccteam settings namespace — every face of this plugin - // is configured from it. The configurable-plugins tab dispatches by namespace - // and renders nothing for one the Host does not serve, so the card is inert - // wherever the host half did not load. - const card = new SettingsCardController( - ctx.settingsScope.bind({ namespace: CCTEAM_CARD.namespace }), - CCTEAM_CARD, - ) - ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({ - name: 'settings.plugin.item', - key: card.spec.namespace, - locale: NS, - // The card face plus the workbench store (its engine slice), the write - // path, and the BFF client — the 「引擎」 section is one seat of the same - // state the workbench header and first-run panel read. - inject: () => ({ ...card.inject(), hooks: { card, console: store }, dispatch: store.dispatch, api }), - }, SettingsCard)) + // Optional settings follow the native served-namespace and slot lifetimes. + ctx.inject(['configForms'], ctx => { + const card = new SettingsCardController( + ctx.configForms.get(CCTEAM_CARD.namespace), CCTEAM_CARD, ctx.configForms.describe(), + ) + ctx.effect(() => () => card.dispose(), 'ccteam-ui: settings form') + ctx.effect(() => ctx.configForms.whileServed([CCTEAM_CARD.namespace], () => + ctx.slots.inject('settings.section', () => ctx.slots.register({ + name: 'settings.section', + id: card.spec.namespace, + label: () => ctx.locale.bind(NS)('settings.card.title'), + locale: NS, + inject: () => ({ ...card.inject(), hooks: { card, console: store }, dispatch: store.dispatch, api }), + }, SettingsCard)), + ), 'ccteam-ui: settings section') + }) } diff --git a/dsh-plugins/ccteam-ui/src/client/locales.ts b/dsh-plugins/ccteam-ui/src/client/locales.ts index b8d4f5d81..ed8fe4d19 100644 --- a/dsh-plugins/ccteam-ui/src/client/locales.ts +++ b/dsh-plugins/ccteam-ui/src/client/locales.ts @@ -2,6 +2,8 @@ /** Simplified Chinese dictionary. */ export const zh = { + 'chat.footnotes': '脚注', + 'chat.copyCode': '复制代码', 'entry.title': 'ccteam', 'entry.label': '打开 ccteam 工作台', 'entry.badge': '{count} 个回合已完成', @@ -54,7 +56,7 @@ export const zh = { 'states.unreachable.title': '未连接到 ccteam', 'states.unreachable.body': '启动 daemon 后重试:', 'states.unconfigured.title': '还没配置 ccteam', - 'states.unconfigured.step1': '打开 DSH 设置 → 插件 → 插件配置 → ccteam-ui', + 'states.unconfigured.step1': '打开 DSH 设置 → ccteam-ui', 'states.unconfigured.step2': '填入 daemon URL 和你的 REST token(ccteam web → 设置 → 账户)', 'chat.working': '正在工作', 'chat.waiting': '等待你的决定', @@ -257,7 +259,7 @@ export const zh = { 'firstrun.engine.reason.ready': '引擎正常;启动、停止与日志在设置卡的「引擎」段。', 'firstrun.start': '启动引擎', 'firstrun.openSettings': '打开设置', - 'firstrun.settings.hint': '引擎设置在 DSH 设置 → 插件 → 插件配置 → ccteam-ui', + 'firstrun.settings.hint': '引擎设置在 DSH 设置 → ccteam-ui', 'firstrun.project.title': '添加工作区', 'firstrun.project.body': '把一个已有目录注册为 ccteam 项目;之后就能在这里为它新建会话。', 'firstrun.project.import': '从 DSH 导入', @@ -280,6 +282,8 @@ export type CcteamLocaleKey = keyof typeof zh /** English dictionary, checked complete against the zh key set. */ export const en = { + 'chat.footnotes': 'Footnotes', + 'chat.copyCode': 'Copy code', 'entry.title': 'ccteam', 'entry.label': 'Open the ccteam workbench', 'entry.badge': '{count} turns completed', @@ -332,7 +336,7 @@ export const en = { 'states.unreachable.title': 'Not connected to ccteam', 'states.unreachable.body': 'Start the daemon, then retry:', 'states.unconfigured.title': 'ccteam is not configured', - 'states.unconfigured.step1': 'Open DSH Settings → Plugins → Plugin configuration → ccteam-ui', + 'states.unconfigured.step1': 'Open DSH Settings → ccteam-ui', 'states.unconfigured.step2': 'Set the daemon URL and your REST token (ccteam web → Settings → Account)', 'chat.working': 'Working', 'chat.waiting': 'Waiting for your decision', @@ -535,7 +539,7 @@ export const en = { 'firstrun.engine.reason.ready': 'The engine is fine; start, stop and the log live in the settings card\'s Engine section.', 'firstrun.start': 'Start the engine', 'firstrun.openSettings': 'Open settings', - 'firstrun.settings.hint': 'Engine settings: DSH Settings → Plugins → Plugin configuration → ccteam-ui', + 'firstrun.settings.hint': 'Engine settings: DSH Settings → ccteam-ui', 'firstrun.project.title': 'Add a workspace', 'firstrun.project.body': 'Register an existing directory as a ccteam project; new sessions for it start here.', 'firstrun.project.import': 'Import from DSH', diff --git a/dsh-plugins/ccteam-ui/src/client/settings/EngineSection.tsx b/dsh-plugins/ccteam-ui/src/client/settings/EngineSection.tsx index 118502b77..c86af130e 100644 --- a/dsh-plugins/ccteam-ui/src/client/settings/EngineSection.tsx +++ b/dsh-plugins/ccteam-ui/src/client/settings/EngineSection.tsx @@ -10,9 +10,9 @@ import { useEffect, useState } from 'react' import { Button, DisclosureRow, - IconCodeOutline16, - IconRightUpOutline14, - IconSettingsOutline14, + IconCodeOutlineRegular, + IconRightUpOutlineRegular, + IconSettingsOutlineRegular, Modal, } from '@deepseek-ai/dsh-client-ui-primitives' import type { EngineLogResponse } from '../../shared/contract.js' @@ -131,7 +131,7 @@ export function EngineSection({ t, engine, api, dispatch, autoStart, onAutoStart {status?.reachable === true && ( {t('engine.openWeb')} - + )}

    @@ -205,7 +205,7 @@ export function EngineSection({ t, engine, api, dispatch, autoStart, onAutoStart /> } + icon={} title={t('engine.advanced')} open={advancedOpen} expandable @@ -255,7 +255,7 @@ export function EngineSection({ t, engine, api, dispatch, autoStart, onAutoStart
} + icon={} title={t('engine.log')} open={logOpen} expandable diff --git a/dsh-plugins/ccteam-ui/src/client/settings/SettingsCard.tsx b/dsh-plugins/ccteam-ui/src/client/settings/SettingsCard.tsx index 9fab0ed56..21e5b9d6b 100644 --- a/dsh-plugins/ccteam-ui/src/client/settings/SettingsCard.tsx +++ b/dsh-plugins/ccteam-ui/src/client/settings/SettingsCard.tsx @@ -13,7 +13,7 @@ */ import { useEffect, useId, useState } from 'react' import clsx from 'clsx' -import { Button, IconChevronDownOutline14 } from '@deepseek-ai/dsh-client-ui-primitives' +import { Button, IconChevronDownOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives' import type { SettingsCardProps } from '../slots.js' import type { T } from '../slots.js' import { EngineSection } from './EngineSection.js' @@ -114,7 +114,7 @@ export function SettingsCard({ spec, useCard, useConsole, dispatch, api, edit, r {t(spec.descriptionKey)} {state.dirty && {t('settings.unsaved')}} - + {open && (
diff --git a/dsh-plugins/ccteam-ui/src/client/settings/form.ts b/dsh-plugins/ccteam-ui/src/client/settings/form.ts index 2ae30075f..4f0a1f270 100644 --- a/dsh-plugins/ccteam-ui/src/client/settings/form.ts +++ b/dsh-plugins/ccteam-ui/src/client/settings/form.ts @@ -11,7 +11,10 @@ * `useCard` selector hook. Nothing here touches React or the wire directly; * the bound `SettingsScope` is the transport. */ -import type { SettingsScope } from '@deepseek-ai/dsh-client-runtime/client' +import type { ConfigForm, SettingsDescribeFace } from '@deepseek-ai/dsh-client-ui-settings/client' + +/** Only the operations this staged form consumes; upstream owns sync and persistence. */ +type SettingsScope = Pick, 'getSnapshot' | 'subscribe' | 'mutate'> import type { CcteamLocaleKey } from '../locales.js' /** One control of a card. */ @@ -140,6 +143,8 @@ export class SettingsCardController { private saving = false private failed = false private snapshot: CardState + private readonly disposers: Array<() => void> + private baseline: number | undefined /** * @param scope - the bound settings scope for this card's namespace. @@ -148,11 +153,20 @@ export class SettingsCardController { constructor( private readonly scope: SettingsScope
, readonly spec: CardSpec, + private readonly described: Pick, ) { this.snapshot = this.project() - scope.subscribe(() => { - this.publish() - }) + this.disposers = [scope.subscribe(() => this.publish()), described.subscribe(() => this.publish())] + } + + /** Release subscriptions when the plugin unloads. */ + dispose(): void { for (const dispose of this.disposers) dispose() } + + private stage(field: string, text: string): void { + if (this.staged.size === 0) this.baseline = this.scope.getSnapshot().revision + this.staged.set(field, text) + this.failed = false + this.publish() } /** @returns the current card state (stable reference until the next change). */ @@ -181,14 +195,10 @@ export class SettingsCardController { hooks: { card: this }, spec: this.spec, edit: (field, text) => { - this.staged.set(field, text) - this.failed = false - this.publish() + this.stage(field, text) }, reset: (field) => { - this.staged.set(field, '') - this.failed = false - this.publish() + this.stage(field, '') }, save: () => { void this.save() @@ -218,13 +228,13 @@ export class SettingsCardController { this.saving = true this.failed = false this.publish() - let threw = false + const before = this.scope.getSnapshot().revision try { - await this.scope.set(field, value) + this.failed = !await this.scope.mutate([{ op: 'set', path: [field], value }]) + if (!this.failed && this.baseline === before) this.baseline = this.scope.getSnapshot().revision } catch { - threw = true + this.failed = true } - this.failed = threw || booleanAt(this.scope.getSnapshot().value, field) !== value this.saving = false this.publish() } @@ -239,25 +249,15 @@ export class SettingsCardController { const plan = [...this.staged.entries()].map(([field, text]) => ({ field, text: text.trim(), kind: this.kindOf(field) })) this.saving = true this.publish() - let threw = false - for (const item of plan) { - if (item.kind === 'secret' && item.text === '') continue - try { - if (item.text === '') await this.scope.unset(item.field) - else await this.scope.set(item.field, item.text) - } catch { - // The scope's own contract is to reload Host state on a failed - // write; the read-back below is what decides whether this save landed. - threw = true - } + const ops = plan.filter(item => item.kind !== 'secret' || item.text !== '').map(item => + item.text === '' + ? { op: 'unset' as const, path: [item.field] } + : { op: 'set' as const, path: [item.field], value: item.text }) + try { + this.failed = ops.length > 0 && !await this.scope.mutate(ops, this.baseline) + } catch { + this.failed = true } - const after = this.scope.getSnapshot() - const landed = plan.every((item) => { - if (item.kind === 'secret') return item.text === '' || stringAt(after.value, item.field) !== '' - if (item.text === '') return !hasOwn(after.user, item.field) - return stringAt(after.value, item.field) === item.text - }) - this.failed = threw || !landed if (!this.failed) this.staged.clear() this.saving = false this.publish() @@ -284,7 +284,8 @@ export class SettingsCardController { const staged = this.staged.get(spec.field) const current = stringAt(snapshot.value, spec.field) if (spec.kind === 'secret') { - fields[spec.field] = { text: staged ?? '', overridden: false, configured: current !== '' } + const secrets = this.described.getSnapshot().view?.namespaces.find(row => row.ns === this.spec.namespace)?.secrets ?? [] + fields[spec.field] = { text: staged ?? '', overridden: false, configured: secrets.some(secret => secret.path.length === 1 && secret.path[0] === spec.field && secret.set) } continue } fields[spec.field] = { diff --git a/dsh-plugins/ccteam-ui/src/client/slots.ts b/dsh-plugins/ccteam-ui/src/client/slots.ts index b9476586f..5bbc0882a 100644 --- a/dsh-plugins/ccteam-ui/src/client/slots.ts +++ b/dsh-plugins/ccteam-ui/src/client/slots.ts @@ -8,17 +8,18 @@ * runtime edge (the client bundle purity gate only ever sees value imports). */ import type { InjectFace, PropsLocale, PropsRuntime, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots' -import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client' +import type { Context as ClientContext } from '@deepseek-ai/cordis' +import type {} from '@deepseek-ai/dsh-client-ui-renderer/client' +import type {} from '@deepseek-ai/dsh-client-ui-workspace/client' // ctx.locale (register / bind). import type {} from '@deepseek-ai/dsh-client-locale/client' // 'shell.overlay' — the frame-wide floating layer (declared by ui-layout's root entry). import type {} from '@deepseek-ai/dsh-client-ui-layout/client' // 'sidebar.footer.action' — the seat beside Settings at the sidebar foot (declared by ui-sidebar). import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client' -// ctx.settingsScope — the per-namespace settings scope binder. +// Native configuration forms and settings section slots. import type {} from '@deepseek-ai/dsh-client-ui-settings/client' -// 'settings.plugin.item' — one card per settings namespace (declared by ui-settings-plugins' configurable tab). -import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client' +// 'settings.section' — one card per settings namespace (declared by ui-settings-plugins' configurable tab). import type { CcteamLocaleKey } from './locales.js' import type { ApiClient } from './api.js' import type { Action, ConsoleStore } from './store.js' @@ -66,11 +67,11 @@ export type EntryButtonProps = PropsRuntime<'sidebar.footer.action'> & InjectFac /** Composed props of the `shell.overlay` entry: face + `t` (the layer passes no owner props). */ export type WorkbenchProps = PropsRuntime<'shell.overlay'> & InjectFace & PropsLocale<'ccteam'> -/** Composed props of one `settings.plugin.item` card: the card face + `t`. */ -export type SettingsCardProps = PropsRuntime<'settings.plugin.item'> & InjectFace & PropsLocale<'ccteam'> +/** Composed props of one `settings.section` card: the card face + `t`. */ +export type SettingsCardProps = PropsRuntime<'settings.section'> & InjectFace & PropsLocale<'ccteam'> /** DSH's own workspace list, as the framework's global seat hands it to every slot component. */ export type UseWorkspaces = WorkbenchProps['useWorkspaces'] -/** The client context as this plugin sees it (locale + settingsScope merges pulled above). */ +/** The client context as this plugin sees it (locale + configForms merges pulled above). */ export type CcteamClientContext = ClientContext diff --git a/dsh-plugins/ccteam-ui/src/credentials.ts b/dsh-plugins/ccteam-ui/src/credentials.ts index a0e336c8a..6dabfb937 100644 --- a/dsh-plugins/ccteam-ui/src/credentials.ts +++ b/dsh-plugins/ccteam-ui/src/credentials.ts @@ -15,8 +15,7 @@ export interface CcteamSessionMeta { /** Tool-approval posture for turns this transport owns. */ approvalMode?: 'skip' | 'hitl' /** - * DSH agent-preset id for `session/new` (`standard` | `code` | `minimal` | - * `cordis`). Absent = the vendor's own default. Never sent on + * Native DSH agent-preset id for `session/new`. Absent = the vendor's own default. Never sent on * `session/load`: a resumed session's preset is recovered from the vendor's * own storage, which stays authoritative if the human switched it. */ diff --git a/dsh-plugins/ccteam-ui/src/index.ts b/dsh-plugins/ccteam-ui/src/index.ts index 9f2bf594e..399bce5ff 100644 --- a/dsh-plugins/ccteam-ui/src/index.ts +++ b/dsh-plugins/ccteam-ui/src/index.ts @@ -32,9 +32,8 @@ import { import { createEnrollmentBootstrap } from './host/engine/bootstrap.js' import { DEFAULT_DAEMON_URL, - UNCHECKED_STATUS, - registerCcteamSettings, - type CcteamSettings, + bindCcteamSettings, + CCTEAM_SETTINGS_FIELDS, } from './settings.js' import { CcteamCompletionNotifier, CcteamMcpClientPool, registerCcteamTools } from './tools.js' import { startDshSocketTransport } from './transport.js' @@ -45,7 +44,7 @@ export const name = 'ccteam-ui' * Module-level services: only what EVERY face needs. Per-face services are * injected below (see the file header). */ -export const inject = ['settings'] +export const inject: string[] = [] /** Services the tool + transport faces cannot work without. */ const HOST_SERVICES = ['agents', 'tools', 'agentDefaultModel'] @@ -60,49 +59,22 @@ const HOST_SERVICES = ['agents', 'tools', 'agentDefaultModel'] */ const WEB_SERVICES = ['webServer'] -/** - * The profile ROW config, as ccteam's materializer writes it and Cordis - * validates it. Deliberately NARROWER than the settings card: `autoStart` and - * `enginePath` are card-only. - * - * The reason is the same trap the `resolve()` helper below documents, in its - * boolean form. Cordis fills every schema default before `apply`, so a key - * listed here arrives with a value whether or not the row mentioned it — which - * is survivable for a string (empty means "not pinned") but not for a boolean: - * `autoStart` would arrive `true` for every profile and the user's `false` - * could never win. - */ -export interface Config extends Partial> { - completionPollIntervalMs?: number - completionMaxPolls?: number - transportSocket?: string -} - -export const Config: Schema = Schema.object({ - daemonUrl: Schema.string().default(DEFAULT_DAEMON_URL).description('ccteam daemon URL.'), - enrollment: Schema.string() - .default('') - .role('secret') - .description('Enrollment credential from ccteam config (tool surface).'), - restToken: Schema.string() - .default('') - .role('secret') - .description('Personal ccteam REST API token (ccteam web console → Account).'), - defaultProject: Schema.string() - .default('') - .description('Project slug new sessions land in when the panel does not name one.'), - connectionStatus: Schema.string().default(UNCHECKED_STATUS), +/** Runtime-only fields are static; user preferences are native live Config references. */ +export const Config = Schema.object({ + ...CCTEAM_SETTINGS_FIELDS, completionPollIntervalMs: Schema.number().default(5000), completionMaxPolls: Schema.number().default(720), - transportSocket: Schema.string().description('Unix socket path this plugin serves ACP on for ccteam. Empty = tool surface only.'), + transportSocket: Schema.string(), + engineManaged: Schema.boolean().default(false), }) +export type Config = ReturnType /** * Reported as ACP `agentInfo.version` and as the MCP client version. ccteam's * Rust side gates the handshake on it (`MIN_DSH_CLIENT_VERSION`), so it must * stay in step with package.json — asserted by tests/host-exports.test.ts. */ -export const PACKAGE_VERSION = '0.13.0' +export const PACKAGE_VERSION = '0.13.1' /** * The engine version this plugin is published against — package.json @@ -114,93 +86,26 @@ export const PACKAGE_VERSION = '0.13.0' * repairs — the engine is older, so update the engine; the plugin is older, so * update the plugin. */ -export const ENGINE_VERSION = '0.13.0' +export const ENGINE_VERSION = '0.13.1' export interface ApplyContext extends BffContext { - /** - * Cordis's own "run once these services exist" hook (`ctx.inject`). Each - * face activates through it, so a profile without `webServer` still gets the - * tools and a profile without `agents` still gets the workbench. - */ inject(deps: readonly string[], body: (ctx: never) => void): unknown - settings?: { - register( - ns: string, - schema: Schema, - options?: { applies?: 'live' | 'restart'; base?: Partial }, - ): { get(): T } - } -} - -/** - * Resolve one field: the value the profile's patch row PINNED wins, otherwise - * the user's settings card decides. - * - * What counts as "pinned" is the whole function, and it has two halves — - * because Cordis validates the row against this plugin's `Config` schema and - * fills in every default BEFORE `apply` runs, so a row that mentioned nothing - * still arrives fully populated: - * - * - an EMPTY value is a blank, not a pin (shipped as a bug once: every - * credential field defaults to `''`, so a plain `??` never fell through and - * the settings card — the documented way a hand-started `dsh web` supplies - * its credentials — was silently dead); - * - a value equal to the SCHEMA DEFAULT is also a blank, for exactly the same - * reason. This half only bites fields whose default is not empty, which - * today means `daemonUrl`: its default is a real URL, so without this the - * card's daemon URL could never win either, and every profile would report - * itself as one whose engine somebody else owns. - * - * @param pinned - the value from the row config, if any. - * @param stored - the value from the settings card, if any. - * @param schemaDefault - what this field's schema fills a blank row with. - * @returns the effective value, or `undefined` when neither layer has one. - */ -function resolve( - pinned: string | undefined, - stored: string | undefined, - schemaDefault = '', -): string | undefined { - const named = (value: string | undefined): string | undefined => - typeof value === 'string' && value.trim() !== '' ? value : undefined - const row = named(pinned) - if (row !== undefined && row.trim() !== schemaDefault) return row - return named(stored) ?? row + get?(name: string): unknown + fiber?: { entry?: { id: string } } } /** * Plugin body: register the one settings namespace, then hand each face the * closures that resolve its configuration. * - * Both layers are read through closures on every call, so editing the card - * takes effect without a restart. Credentials never leave these closures. + * Native Config references are read through closures on every call, so edits + * take effect without a restart. Credentials never leave these closures. */ -export function apply(ctx: ApplyContext, config: Config = {}): void { - const settings = registerCcteamSettings(ctx, { - daemonUrl: config.daemonUrl, - enrollment: config.enrollment, - restToken: config.restToken, - defaultProject: config.defaultProject, - connectionStatus: config.connectionStatus, - }) - // The engine's lifetime is NOT this plugin's (PRD D1), so nothing below ever - // stops a daemon. `managed` and `pinnedDaemonUrl` are the two runtimes where - // it must not even start one — see host/engine/supervisor.ts's header. +export function apply(ctx: ApplyContext, config: Config = Config({})): void { + const settings = bindCcteamSettings(ctx, config) const environment = defaultEnvironment() const managed = isPinned(config.transportSocket) - /** - * The row describes an engine somebody else set up. A ccteam-materialized - * profile is recognized by the CREDENTIALS in its row — ccteam writes them, - * a human's `dsh plugin --profile add` never does — rather than by the daemon URL, - * whose schema default is present in every row and would otherwise mark - * every profile as somebody else's. - */ - const externallyOwned = - !managed && - (isPinned(config.restToken) || - isPinned(config.enrollment) || - (isPinned(config.daemonUrl) && - config.daemonUrl!.trim().replace(/\/+$/, '') !== DEFAULT_DAEMON_URL)) + const externallyOwned = config.engineManaged /** * The console token, bootstrapped from `$CCTEAM_HOME/secrets/web-token` when @@ -224,7 +129,7 @@ export function apply(ctx: ApplyContext, config: Config = {}): void { * and a daemon does not move that often. */ const configuredDaemonUrl = (): string | undefined => { - const value = (resolve(config.daemonUrl, settings.get().daemonUrl, DEFAULT_DAEMON_URL) ?? '') + const value = (settings.get().daemonUrl ?? '') .trim() .replace(/\/+$/, '') // The compiled default is not an instruction — it is what a blank resolves @@ -239,7 +144,7 @@ export function apply(ctx: ApplyContext, config: Config = {}): void { logger: ctx.logger, }) const restToken = (): string => { - const supplied = resolve(config.restToken, settings.get().restToken) + const supplied = settings.get().restToken.trim() || undefined if (supplied !== undefined) return supplied return isLoopbackUrl(daemonUrl()) ? tokens.read() ?? '' : '' } @@ -255,14 +160,14 @@ export function apply(ctx: ApplyContext, config: Config = {}): void { const enrollmentBootstrap = createEnrollmentBootstrap({ daemonUrl, authorization: () => authorizationFor(restToken()), - held: () => resolve(config.enrollment, settings.get().enrollment), + held: () => settings.get().enrollment.trim() || undefined, persist: async (bearer: string) => { await settings.update?.({ enrollment: bearer }) }, logger: ctx.logger, }) const enrollment = (): string | undefined => - resolve(config.enrollment, settings.get().enrollment) ?? enrollmentBootstrap.value() + settings.get().enrollment.trim() || enrollmentBootstrap.value() // One identity per ccteam session, never one per process: this runtime serves // many hires plus the human at the DSH UI. @@ -296,7 +201,7 @@ export function apply(ctx: ApplyContext, config: Config = {}): void { registerBff(webContext, { daemonUrl, restToken, - defaultProject: () => resolve(config.defaultProject, settings.get().defaultProject) ?? '', + defaultProject: () => settings.get().defaultProject ?? '', engine: supervisor, logger: webContext.logger, }) diff --git a/dsh-plugins/ccteam-ui/src/settings.ts b/dsh-plugins/ccteam-ui/src/settings.ts index 6f04980cf..8879a9acc 100644 --- a/dsh-plugins/ccteam-ui/src/settings.ts +++ b/dsh-plugins/ccteam-ui/src/settings.ts @@ -4,7 +4,6 @@ export const DEFAULT_DAEMON_URL = 'http://127.0.0.1:7331' export const SETTINGS_NAMESPACE = 'ccteam-ui' export const UNCHECKED_STATUS = 'Not checked. If the daemon is unreachable, run: ccteam start' -const NO_SERVICE_STATUS = 'Settings service unavailable. If the daemon is unreachable, run: ccteam start' /** * ONE flat namespace for all three faces of this plugin. The base URL is @@ -21,7 +20,7 @@ const NO_SERVICE_STATUS = 'Settings service unavailable. If the daemon is unreac * * Keys are FLAT on purpose: this schema is also the shape of the row `config` * that ccteam's materializer writes into a profile's `cordis.patch.yml`, and - * that config reaches `apply(ctx, config)` verbatim. Nesting a key under a + * Cordis supplies live references to `apply(ctx, config)`. Nesting a key under a * namespace makes the value silently undefined (shipped once, v0.10.0). */ export interface CcteamSettings { @@ -44,76 +43,45 @@ export interface CcteamSettings { enginePath: string } -export const CcteamSettingsSchema: Schema = Schema.object({ - daemonUrl: Schema.string().default(DEFAULT_DAEMON_URL).description('ccteam daemon URL.'), - enrollment: Schema.string() - .default('') - .role('secret') - .description('Enrollment credential from ccteam config (tool surface).'), - restToken: Schema.string() - .default('') - .role('secret') - .description('Personal ccteam REST API token (ccteam web console → Account).'), - defaultProject: Schema.string() - .default('') - .description('Project slug new sessions land in when the panel does not name one.'), - connectionStatus: Schema.string().default(UNCHECKED_STATUS), - autoStart: Schema.boolean() - .default(true) - .description('Install the ccteam engine and start its daemon when this plugin loads.'), - enginePath: Schema.string() - .default('') - .description('Advanced: path to the ccteam binary. Empty = PATH, then the canonical install path.'), -}) +/** Native Config is the single authority; DSH projects volatile fields into its forms. */ +export const CCTEAM_SETTINGS_FIELDS = { + daemonUrl: Schema.string().default(DEFAULT_DAEMON_URL).description('ccteam daemon URL.').volatile(), + enrollment: Schema.string().default('').role('secret').description('ccteam enrollment credential.').volatile(), + restToken: Schema.string().default('').role('secret').description('Personal ccteam REST API token.').volatile(), + defaultProject: Schema.string().default('').description('Default ccteam project slug.').volatile(), + connectionStatus: Schema.string().default(UNCHECKED_STATUS).volatile(), + autoStart: Schema.boolean().default(true).description('Install and start the local ccteam engine.').volatile(), + enginePath: Schema.string().default('').description('Path to the ccteam binary; empty uses PATH.').volatile(), +} +export const CcteamSettingsSchema = Schema.object(CCTEAM_SETTINGS_FIELDS) + +export type LiveSettings = ReturnType export interface SettingsScope { get(): T - watch?(callback: (next: T, prev: T) => void | Promise): () => void - /** - * Merge a patch into the user layer. Present on the real service - * (`@deepseek-ai/dsh-settings`); optional here because the no-service - * fallback below has nowhere to write. - */ update?(patch: object): Promise } -export interface SettingsContext { - settings?: { - register( - ns: string, - schema: Schema, - options?: { applies?: 'live' | 'restart'; base?: Partial }, - ): SettingsScope - } -} - -/** - * Register the settings card. Credentials live here and in closure scope only: - * they are never written to `process.env`, never logged, and never leave the - * host half (see bff.ts and tools.ts). - */ -export function registerCcteamSettings( - ctx: SettingsContext, - base?: Partial, +/** Read the framework-owned references on every operation; no second settings registry. */ +export function bindCcteamSettings( + ctx: { get?(name: string): unknown; fiber?: { entry?: { id: string } } }, + config: LiveSettings, ): SettingsScope { - if (ctx.settings === undefined) { - return { - get: () => ({ - daemonUrl: base?.daemonUrl ?? DEFAULT_DAEMON_URL, - enrollment: base?.enrollment ?? '', - restToken: base?.restToken ?? '', - defaultProject: base?.defaultProject ?? '', - connectionStatus: base?.connectionStatus ?? NO_SERVICE_STATUS, - autoStart: base?.autoStart ?? true, - enginePath: base?.enginePath ?? '', - }), - } + return { + get: () => ({ + daemonUrl: config.daemonUrl.get(), + enrollment: config.enrollment.get(), + restToken: config.restToken.get(), + defaultProject: config.defaultProject.get(), + connectionStatus: config.connectionStatus.get(), + autoStart: config.autoStart.get(), + enginePath: config.enginePath.get(), + }), + async update(patch) { + const service = ctx.get?.('settings') as import('@deepseek-ai/dsh-settings').SettingsForms | undefined + const entryId = ctx.fiber?.entry?.id + if (service === undefined || entryId === undefined) throw new Error('DSH profile settings are unavailable') + await service.update(entryId, patch) + }, } - return ctx.settings.register(SETTINGS_NAMESPACE, CcteamSettingsSchema, { - // `live` for the panel's own reads (the BFF resolves through closures on - // every request); the tool and transport faces read their credential per - // call, so an edit reaches them without a restart too. - applies: 'live', - base, - }) } diff --git a/dsh-plugins/ccteam-ui/src/shared/contract.ts b/dsh-plugins/ccteam-ui/src/shared/contract.ts index f52c471aa..4921dce57 100644 --- a/dsh-plugins/ccteam-ui/src/shared/contract.ts +++ b/dsh-plugins/ccteam-ui/src/shared/contract.ts @@ -12,6 +12,8 @@ */ export const API_PREFIX = '/ccteam/api' +/** Browser URLs follow the DSH document mount, at / or behind a path proxy. */ +export const API_RELATIVE_PREFIX = API_PREFIX.slice(1) /** Sub-paths under {@link API_PREFIX} that are not JSON methods. */ export const EVENTS_PATH = '/events' diff --git a/dsh-plugins/ccteam-ui/src/tools.ts b/dsh-plugins/ccteam-ui/src/tools.ts index a645fa9d1..d31225222 100644 --- a/dsh-plugins/ccteam-ui/src/tools.ts +++ b/dsh-plugins/ccteam-ui/src/tools.ts @@ -1,3 +1,6 @@ +import type { Agent } from '@deepseek-ai/dsh-agent' +import type { ToolsService, ToolDefinition, ToolRunContext as NativeToolRunContext } from '@deepseek-ai/dsh-tools' +import { createUserMessage } from '@deepseek-ai/dsh-llm/message' import { randomUUID } from 'node:crypto' import type { SessionCredentialStore } from './credentials.js' @@ -270,37 +273,21 @@ export function sessionIdOfAgent(agent: DshAgent | undefined): string | undefine return typeof sessionId === 'string' && sessionId !== '' ? sessionId : undefined } -export interface ToolRunContext { - agent?: DshAgent - signal?: AbortSignal - [key: string]: unknown -} +export type ToolRunContext = Partial> & { agent?: DshAgent } -export interface DshAgent { +/** Narrow the native live handle to the capabilities this bridge consumes. */ +export type DshAgent = Partial> & { id?: string - session?: { id?: string; events?: unknown[] } + session?: { id?: string } inbox?: { remove?(messageId: string): boolean } - followup?(message: unknown): void - whenIdle?(): Promise - cancel?(cause: { kind: string }, options?: { keepInbox?: boolean }): void - [key: string]: unknown } export interface ToolRegistryContext { - tools: { - register(definition: DshToolDefinition): () => void - } + tools: Pick effect? void | Promise) | void>(setup: () => T, label?: string): () => void } -export interface DshToolDefinition { - name: string - description: string - parameters: unknown - output: { - schema: Record - render(args: unknown, value: McpToolResult): ContentBlock[] - } +export type DshToolDefinition = Omit & { execute(args: unknown, exec: ToolRunContext): Promise } @@ -314,7 +301,7 @@ export type McpClientForExec = (exec: ToolRunContext) => CcteamMcpClient interface McpToolDefinition { name: string description: string - inputSchema: unknown + inputSchema: Record } export const CCTEAM_TOOL_DEFINITIONS: McpToolDefinition[] = [ @@ -378,7 +365,7 @@ export const CCTEAM_TOOL_DEFINITIONS: McpToolDefinition[] = [ enum: ['full', 'read', 'none'], description: 'New session\'s ccteam tool face (default full).', }, - mode: { type: 'string', description: 'Vendor session mode. DSH only: standard|ptc|minimal|creator.' }, + mode: { type: 'string', description: 'Native DSH agent-preset id; omitted uses the DSH default. creator selects cordis.' }, permission_mode: { type: 'string', enum: ['skip', 'hitl'], @@ -444,7 +431,10 @@ function toDshTool(definition: McpToolDefinition, clientFor: McpClientForExec, n output: { schema: {}, render(_args, value) { - return value.content + return (isRecord(value) && Array.isArray(value.content) ? value.content : [value]).map(block => ({ + type: 'text' as const, + text: isRecord(block) && typeof block.text === 'string' ? block.text : JSON.stringify(block), + })) }, }, async execute(args, exec) { @@ -511,21 +501,9 @@ export class CcteamCompletionNotifier implements DelegationNotifier { } } -/** A ccteam-minted user turn; its `id` is what turn attribution binds on. */ -export interface UserTextMessage { - readonly id: string - readonly role: 'user' - readonly content: readonly [{ readonly type: 'text'; readonly text: string }] - readonly source: { readonly kind: 'user' } -} - -export function createUserTextMessage(text: string): UserTextMessage { - return Object.freeze({ - id: randomUUID(), - role: 'user', - content: Object.freeze([Object.freeze({ type: 'text', text })]), - source: Object.freeze({ kind: 'user' }), - }) as UserTextMessage +/** Construct user turns through DSH's public immutable-message factory. */ +export function createUserTextMessage(text: string) { + return createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'user' } }) } function extractDelegatedSid(args: unknown, result: McpToolResult): string | undefined { diff --git a/dsh-plugins/ccteam-ui/src/transport.ts b/dsh-plugins/ccteam-ui/src/transport.ts index 1f2bbada3..de21f539a 100644 --- a/dsh-plugins/ccteam-ui/src/transport.ts +++ b/dsh-plugins/ccteam-ui/src/transport.ts @@ -1,3 +1,10 @@ +import type {} from '@deepseek-ai/dsh-agent-default-model' +import type { PermissionPresetService } from '@deepseek-ai/dsh-permission-presets' +import type { WorkspaceRegistry } from '@deepseek-ai/dsh-workspace' +import type { Context } from '@deepseek-ai/cordis' +import type { AgentRegistry, AgentHandle, AgentOptions, AgentSetup, Agent, AssistantStreamFrame } from '@deepseek-ai/dsh-agent' +import type { Session, SessionId, SessionEvent, SessionEventMap } from '@deepseek-ai/dsh-session' +import type { AgentPresetRegistry } from '@deepseek-ai/dsh-agent-preset-registry' import { randomUUID } from 'node:crypto' import { mkdirSync, unlinkSync } from 'node:fs' import { createServer, type Server, type Socket } from 'node:net' @@ -5,29 +12,10 @@ import { dirname } from 'node:path' import { parseCcteamMeta, type SessionCredentialStore } from './credentials.js' import { createUserTextMessage, sessionIdOfAgent, type ContentBlock, type DshAgent } from './tools.js' -export interface DshAgents { - create(options: { - sessionId: string - meta?: { cwd?: string; agentPreset?: string } - agentOptions?: unknown - setup?: (agentCtx: unknown) => Promise - }): Promise - resume(options: { - resumeSessionId: string - agentOptions?: unknown - setup?: (agentCtx: unknown) => Promise - }): Promise - get?(id: string): DshAgent | undefined -} - -export interface DshAgentHandle { - agent: DshAgent - dispose?(): Promise | void -} +export type DshAgents = Pick +export type DshAgentHandle = AgentHandle -export interface DshWorkspace { - attachSession(sessionId: string): Promise -} +export type DshWorkspace = Awaited> /** * DSH's agent-preset roster (Cordis service `agentPresets`, @@ -35,16 +23,9 @@ export interface DshWorkspace { * vendor tools and moves them behind presets, so an agent created without * `setup: mount(...)` has no bash/read/write at all — only globally-registered * tools like ccteam's own (real-machine `unknown tool "bash"` regression). - * Shipped ids: `standard` | `code` (PTC) | `minimal` | `cordis` (creator). + * The vendor owns its roster and default; ccteam forwards native preset ids. */ -export interface DshAgentPresets { - resolve(id?: string): Promise<{ id?: string } | undefined> - mount(agentCtx: unknown, id?: string): Promise -} - -interface DshSessionPersistence { - inspect(sessionId: string): Promise<{ meta?: unknown; events?: unknown[] }> -} +export type DshAgentPresets = Pick /** * DSH's permission-preset roster (Cordis service `permissionPresets`). @@ -53,36 +34,13 @@ interface DshSessionPersistence { * approval prompts — the DSH counterpart of ccteam's `skip` posture); a * `hitl` hire keeps the vendor's pinned default so approvals keep flowing. */ -interface DshPermissionPresets { - set(session: unknown, name: string): void -} +type DshPermissionPresets = Pick const HIRE_PERMISSION_PRESET = 'danger-full-access' -export interface DshWorkspaceRegistry { - create(path: string, title?: string): Promise -} +export type DshWorkspaceRegistry = Pick -export interface TransportContext { - agents: DshAgents - /** - * Optional service lookup (`ctx.get`). Cordis THROWS on `ctx.workspaceRegistry` - * when the service is not in this plugin's `inject` list — and it cannot be: - * `dsh-workspace` ships in the web-app bundle only, so a hard inject would - * dead-lock plugin activation on every non-web profile (mode 2). `ctx.get` - * is the vendor's own optional accessor (dsh-host-apiproxy uses it for - * `sessionPersistence`). - */ - get?(name: string): unknown - agentDefaultModel?: { - currentSelection(): { provider?: string; model?: string } | undefined - } - on?(event: string, handler: (...args: never[]) => unknown): () => void - effect? void | Promise) | void>(setup: () => T, label?: string): () => void - logger?: { - warn(message: string): void - } -} +export type TransportContext = Pick export interface DshSocketTransportOptions { version: string @@ -126,6 +84,8 @@ interface Inflight { interface SessionRecord { agent: DshAgent + stream?: { attemptId: string; revision: number; turn: number } + thoughtStreamed?: boolean /** Turn currently open on this session, ccteam-owned or human-owned. */ openTurn?: number inflight?: Inflight @@ -180,7 +140,7 @@ export class WorkspaceMounter { const operation = this.chain.then(() => registry.create(cwd)) this.chain = operation.then(() => undefined, () => undefined) const workspace = await operation - await workspace.attachSession(sessionId) + await workspace.attachSession(sessionId as SessionId) } } @@ -330,9 +290,12 @@ export class DshAcpServer { this.input.on('end', onClose) this.input.on('error', onClose) - const offSession = this.ctx.on?.('session/event', ((session: unknown, event: unknown) => { + const offSession = this.ctx.on('session/event', (session, event) => { this.onSessionEvent(session, event) - }) as never) + }) + const offStream = this.ctx.on('agent/assistant-stream', ({ agent, frame }) => { + this.onAssistantStream(agent, frame) + }) const offError = this.ctx.on?.('agent/error', ((payload: unknown) => { this.onAgentError(payload) }) as never) @@ -346,6 +309,7 @@ export class DshAcpServer { this.input.off('end', onClose) this.input.off('error', onClose) offSession?.() + offStream?.() offError?.() offApproval?.() // Agents stay live and un-cancelled: the human at the DSH UI owns them too. @@ -474,7 +438,7 @@ export class DshAcpServer { private async composeCreatePreset( requested: string | undefined, sessionId: string, - ): Promise<{ id?: string; setup?: (agentCtx: unknown) => Promise }> { + ): Promise<{ id?: string; setup?: (agentCtx: unknown, agent: DshAgent) => Promise }> { const presets = this.presetService() if (presets === undefined) { if (requested !== undefined) { @@ -498,76 +462,36 @@ export class DshAcpServer { return { id, setup: async agentCtx => { - await presets.mount(agentCtx, id) + await presets.mount(agentCtx as Parameters[0], id) }, } } - /** Re-mount the STORED preset when resuming a persisted session. */ - private async composeResumeSetup( - sessionId: string, - ): Promise<((agentCtx: unknown) => Promise) | undefined> { + /** DSH owns persisted preset selection, including switches made in its UI. */ + private composeResumeSetup(): AgentSetup | undefined { const presets = this.presetService() if (presets === undefined) return undefined - const stored = await this.storedPreset(sessionId) - return async agentCtx => { - await presets.mount(agentCtx, stored) - } - } - - /** - * The preset a persisted session was using: the newest - * `agent-preset/selected` event wins (a blank-session switch in the DSH UI), - * else the creation header's `agentPreset`; `undefined` mounts the vendor - * default. Mirrors the vendor's own `resolveSessionPreset` fold — the - * package is not importable from this plugin, so the two-field fold is - * reimplemented against the same durable data. - */ - private async storedPreset(sessionId: string): Promise { - const svc = typeof this.ctx.get === 'function' ? this.ctx.get('sessionPersistence') : undefined - const persistence = svc as DshSessionPersistence | undefined - if (typeof persistence?.inspect !== 'function') return undefined - try { - const inspected = await persistence.inspect(sessionId) - const events = Array.isArray(inspected?.events) ? inspected.events : [] - for (let i = events.length - 1; i >= 0; i--) { - const event = asRecord(events[i]) - if (event.type !== 'agent-preset/selected') continue - const chosen = asRecord(event.data).agentPreset - if (typeof chosen === 'string' && chosen.trim() !== '') return chosen - } - const header = asRecord(inspected?.meta).agentPreset - return typeof header === 'string' && header.trim() !== '' ? header : undefined - } catch (error) { - this.ctx.logger?.warn( - `ccteam dsh transport could not read the stored preset of ${sessionId}: ${errorMessage(error)}`, - ) - return undefined + return async (agentCtx, agent) => { + const stored = presets.presetForSession(agent.session as Session) ?? undefined + await presets.mount(agentCtx as Parameters[0], stored) } } private async newSession(params: unknown): Promise<{ sessionId: string }> { const body = requireRecord(params, 'session/new params') const cwd = requireString(body, 'cwd') - const sessionId = randomUUID() + const sessionId = randomUUID() as SessionId const agentOptions = this.resolveAgentOptions(body.agentOptions) const meta = parseCcteamMeta(body) if (meta !== undefined) this.credentials?.set(sessionId, meta) let handle: DshAgentHandle try { const preset = await this.composeCreatePreset(meta?.agentPreset, sessionId) - const request: { - sessionId: string - meta: { cwd: string; agentPreset?: string } - agentOptions?: unknown - setup?: (agentCtx: unknown) => Promise - } = { + const request: Parameters[0] = { sessionId, meta: { cwd, ...(preset.id === undefined ? {} : { agentPreset: preset.id }) }, } - if (agentOptions !== undefined) request.agentOptions = agentOptions - if (preset.setup !== undefined) request.setup = preset.setup - handle = await this.ctx.agents.create(request) + handle = await this.ctx.agents.create({ ...request, ...agentOptions === undefined ? {} : { agentOptions }, ...preset.setup === undefined ? {} : { setup: preset.setup } }) } catch (error) { this.credentials?.delete(sessionId) throw errorToRpc(error) @@ -595,7 +519,7 @@ export class DshAcpServer { return } try { - presets.set(agent.session, HIRE_PERMISSION_PRESET) + presets.set(agent.session as Session, HIRE_PERMISSION_PRESET) } catch (error) { this.ctx.logger?.warn( `ccteam dsh transport could not set ${HIRE_PERMISSION_PRESET} on ${sessionId}: ${errorMessage(error)}`, @@ -605,7 +529,7 @@ export class DshAcpServer { private async loadSession(params: unknown): Promise<{ sessionId: string }> { const body = requireRecord(params, 'session/load params') - const sessionId = requireString(body, 'sessionId') + const sessionId = requireString(body, 'sessionId') as SessionId const agentOptions = this.resolveAgentOptions(body.agentOptions) const meta = parseCcteamMeta(body) if (meta !== undefined) this.credentials?.set(sessionId, meta) @@ -619,7 +543,7 @@ export class DshAcpServer { // The stored preset is authoritative on resume (the human may have // switched it in the DSH UI); `_meta.ccteam.agentPreset` is a // creation-only field and deliberately ignored here. - const setup = await this.composeResumeSetup(sessionId) + const setup = this.composeResumeSetup() const handle = await this.ctx.agents.resume({ resumeSessionId: sessionId, ...agentOptions === undefined ? {} : { agentOptions }, @@ -636,7 +560,7 @@ export class DshAcpServer { private async prompt(params: unknown): Promise { const body = requireRecord(params, 'session/prompt params') - const sessionId = requireString(body, 'sessionId') + const sessionId = requireString(body, 'sessionId') as SessionId const record = this.sessions.get(sessionId) if (record === undefined) throw new RpcError(`unknown session: ${sessionId}`, -32602) if (record.inflight !== undefined) throw new RpcError('a prompt is already in flight for this session', -32602) @@ -665,18 +589,15 @@ export class DshAcpServer { }) } - private onSessionEvent(session: unknown, event: unknown): void { + private onSessionEvent(session: Session, event: SessionEvent): void { const sessionId = sessionIdFromSession(session) if (sessionId === undefined) return const record = this.sessions.get(sessionId) if (record === undefined) return - const ev = asRecord(event) - const type = typeof ev.type === 'string' ? ev.type : '' - const data = asRecord(ev.data) - - switch (type) { + switch (event.type) { case 'turn/start': { - const turn = numberField(data, 'turn') + const data = event.data + const turn = data.turn record.openTurn = turn const inflight = record.inflight if (inflight !== undefined && inflight.bindNextTurn && turn !== undefined) { @@ -686,9 +607,10 @@ export class DshAcpServer { break } case 'user/message': { + const data = event.data const inflight = record.inflight if (inflight === undefined || inflight.ownedTurn !== undefined) break - if (stringField(data, 'id') !== inflight.messageId) break + if (data.id !== inflight.messageId) break if (record.openTurn === undefined) { inflight.bindNextTurn = true break @@ -696,25 +618,24 @@ export class DshAcpServer { inflight.ownedTurn = record.openTurn break } - case 'assistant/message': - if (!this.ownsTurn(record, numberField(data, 'turn'))) break + case 'assistant/message': { + const data = event.data + if (!this.ownsTurn(record, data.turn)) break this.onAssistantMessage(sessionId, data, record) break - case 'assistant/chunk': - if (!this.ownsTurn(record, numberField(data, 'turn'))) break - this.onAssistantChunk(sessionId, data, record) - break + } case 'tool/call': { - if (!this.ownsTurn(record, numberField(data, 'turn'))) break - const callId = stringField(data, 'callId') + const data = event.data + if (!this.ownsTurn(record, data.turn)) break + const callId = data.callId if (callId !== undefined) record.inflight?.toolCalls.add(callId) this.notify({ sessionId, update: { sessionUpdate: 'tool_call', toolCallId: callId ?? 'tool', - name: stringField(data, 'name') ?? 'tool', - title: stringField(data, 'name') ?? 'tool', + name: data.name ?? 'tool', + title: data.name ?? 'tool', rawInput: parseMaybeJson(data.arguments), status: 'pending', }, @@ -722,22 +643,25 @@ export class DshAcpServer { break } case 'tool/result': { - const callId = stringField(asRecord(asRecord(data.message).source), 'callId') - if (!this.ownsToolResult(record, numberField(data, 'turn'), callId)) break + const data = event.data + const callId = data.message.source.callId + if (!this.ownsToolResult(record, data.turn, callId)) break + const failed = data.message.isError === true this.notify({ sessionId, update: { sessionUpdate: 'tool_call_update', toolCallId: callId ?? 'tool', - status: 'completed', + status: failed ? 'failed' : 'completed', content: toolResultText(data), - isError: toolResultIsError(data), + isError: failed, }, }) break } case 'turn/end': { - const turn = numberField(data, 'turn') + const data = event.data + const turn = data.turn if (turn === undefined || record.openTurn === turn) record.openTurn = undefined const inflight = record.inflight if (inflight === undefined || !this.ownsTurn(record, turn)) break @@ -776,47 +700,39 @@ export class DshAcpServer { return callId !== undefined && record.inflight?.toolCalls.has(callId) === true } - private onAssistantMessage(sessionId: string, data: Record, record: SessionRecord): void { - const message = asRecord(data.message) - const content = message.content - if (Array.isArray(content)) { - for (const block of content) { - const normalized = asRecord(block) - if (normalized.type === 'text' && typeof normalized.text === 'string' && normalized.text.length > 0) { - this.notify({ - sessionId, - update: { - sessionUpdate: 'agent_message_chunk', - content: { type: 'text', text: normalized.text }, - }, - }) - } + private onAssistantMessage(sessionId: string, data: SessionEventMap['assistant/message'], record: SessionRecord): void { + for (const block of data.message.content) { + if (block.type === 'text' && block.text !== '') { + this.notify({ sessionId, update: { + sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: block.text }, + } }) + } else if (block.type === 'reasoning' && block.text !== '' && !record.thoughtStreamed) { + this.notify({ sessionId, update: { + sessionUpdate: 'agent_thought_chunk', content: { type: 'text', text: block.text }, + } }) } } + record.thoughtStreamed = false accumulateUsage(record.inflight?.usage, asRecord(data.usage)) } - private onAssistantChunk(sessionId: string, data: Record, record: SessionRecord): void { - const chunk = asRecord(data.chunk) - if (chunk.type === 'reasoning-delta' && typeof chunk.text === 'string' && chunk.text.length > 0) { - this.notify({ - sessionId, - update: { - sessionUpdate: 'agent_thought_chunk', - content: { type: 'text', text: chunk.text }, - }, - }) - } else if (chunk.type === 'usage') { - accumulateUsage(record.inflight?.usage, asRecord(chunk.usage)) - const usage = normalizeUsage(asRecord(chunk.usage)) - this.notify({ - sessionId, - update: { - sessionUpdate: 'usage_update', - used: usage.inputTokens, - size: usage.contextWindow, - }, - }) + /** Follow the public live-stream frames; settled messages own token accounting. */ + private onAssistantStream(agent: Agent, frame: AssistantStreamFrame): void { + const record = this.sessions.get(agent.id) + if (record === undefined || record.agent !== agent) return + if (frame.type === 'start') { + record.stream = { attemptId: frame.attemptId, revision: frame.revision, turn: frame.turn } + return + } + const stream = record.stream + if (stream === undefined || stream.attemptId !== frame.attemptId || stream.revision !== frame.revision + || !this.ownsTurn(record, stream.turn)) return + if (frame.type === 'end') { record.stream = undefined; return } + if (frame.chunk.type === 'reasoning-delta' && frame.chunk.text !== '') { + record.thoughtStreamed = true + this.notify({ sessionId: agent.id, update: { + sessionUpdate: 'agent_thought_chunk', content: { type: 'text', text: frame.chunk.text }, + } }) } } @@ -903,11 +819,11 @@ export class DshAcpServer { }) } - private resolveAgentOptions(requested: unknown): unknown | undefined { + private resolveAgentOptions(requested: unknown): AgentOptions | undefined { const selection = this.ctx.agentDefaultModel?.currentSelection() const selected = isModelSelection(selection) ? selection : undefined if (requested === undefined) return selected - if (!isRecord(requested)) return requested + if (!isRecord(requested)) throw new RpcError('agentOptions must be an object', -32602) const merged: Record = { ...requested } if (stringField(merged, 'provider') === undefined && selected?.provider !== undefined) { @@ -916,7 +832,7 @@ export class DshAcpServer { if (stringField(merged, 'model') === undefined && selected?.model !== undefined) { merged.model = selected.model } - return Object.keys(merged).length === 0 ? undefined : merged + return Object.keys(merged).length === 0 ? undefined : merged as AgentOptions } } @@ -1027,21 +943,8 @@ function sessionIdFromSession(session: unknown): string | undefined { return stringField(header, 'id') ?? stringField(asRecord(session), 'id') } -function toolResultText(data: Record): ContentBlock { - const content = asRecord(asRecord(data.message).content) - if (Array.isArray(asRecord(data.message).content)) { - return { type: 'text', text: JSON.stringify(asRecord(data.message).content) } - } - return { type: 'text', text: JSON.stringify(content) } -} - -function toolResultIsError(data: Record): boolean { - const message = asRecord(data.message) - const content = message.content - if (Array.isArray(content)) { - return content.some(block => asRecord(block).isError === true) - } - return data.error !== undefined +function toolResultText(data: SessionEventMap['tool/result']): ContentBlock { + return { type: 'text', text: JSON.stringify(data.message.content) } } function isErrorReason(reason: unknown): boolean { diff --git a/dsh-plugins/ccteam-ui/tests/client-api.test.ts b/dsh-plugins/ccteam-ui/tests/client-api.test.ts index 972e8c7c0..d988b330d 100644 --- a/dsh-plugins/ccteam-ui/tests/client-api.test.ts +++ b/dsh-plugins/ccteam-ui/tests/client-api.test.ts @@ -1,9 +1,10 @@ /** BFF client: method→URL mapping, JSON wire shape, events URL, frame parsing. */ import { describe, expect, it } from 'vitest' -import { API_PREFIX } from '../src/shared/contract.js' +import { API_RELATIVE_PREFIX } from '../src/shared/contract.js' import { createApi, eventsUrl } from '../src/client/api.js' import type { EventSourceLike } from '../src/client/api.js' import type { PanelEvent } from '../src/shared/contract.js' +import { attachmentRefs } from '../src/bff.js' interface RecordedRequest { url: string @@ -42,7 +43,7 @@ describe('api.call', () => { expect(result).toEqual({ connected: true }) expect(requests).toHaveLength(1) const request = requests[0]! - expect(request.url).toBe(`${API_PREFIX}/status`) + expect(request.url).toBe(`${API_RELATIVE_PREFIX}/status`) expect(request.init?.method).toBe('POST') expect(request.init?.headers).toEqual({ 'content-type': 'application/json' }) expect(JSON.parse(String(request.init?.body))).toEqual({}) @@ -56,10 +57,10 @@ describe('api.call', () => { await api.call('session.send', { sid: 's7', text: 'hi' }) await api.call('session.spawn', { vendor: 'claude' }) expect(requests.map(r => r.url)).toEqual([ - `${API_PREFIX}/team.graph`, - `${API_PREFIX}/session.history`, - `${API_PREFIX}/session.send`, - `${API_PREFIX}/session.spawn`, + `${API_RELATIVE_PREFIX}/team.graph`, + `${API_RELATIVE_PREFIX}/session.history`, + `${API_RELATIVE_PREFIX}/session.send`, + `${API_RELATIVE_PREFIX}/session.spawn`, ]) expect(JSON.parse(String(requests[1]!.init?.body))).toEqual({ sid: 's7' }) }) @@ -85,9 +86,9 @@ describe('api.events', () => { api.events({ onEvent: () => {} }, 's12') api.events({ onEvent: () => {} }, 's 9') expect(sources.map(s => s.url)).toEqual([ - `${API_PREFIX}/events`, - `${API_PREFIX}/events?sid=s12`, - `${API_PREFIX}/events?sid=s%209`, + `${API_RELATIVE_PREFIX}/events`, + `${API_RELATIVE_PREFIX}/events?sid=s12`, + `${API_RELATIVE_PREFIX}/events?sid=s%209`, ]) }) @@ -143,3 +144,26 @@ describe('eventsUrl', () => { expect(eventsUrl('/x', 's1')).toBe('/x/events?sid=s1') }) }) + +describe('DSH mount paths', () => { + it.each(['http://localhost:7332/', 'https://example.com/dsh/'])( + 'keeps RPC, events and attachments inside %s', async (base) => { + const { requests, fetchFn } = fakeFetch({ connected: true }) + const sources: FakeEventSource[] = [] + const api = createApi({ fetchFn, createEventSource: url => { + const source = new FakeEventSource(url) + sources.push(source) + return source + } }) + await api.call('status', {}) + await api.upload('p', { name: 'report.txt', body: new Uint8Array([1]) }) + api.events({ onEvent: () => {} }) + const history = attachmentRefs([{ kind: 'file', path: '/tmp/report.pdf' }], 's1')! + for (const path of [...requests.map(r => r.url), sources[0]!.url, api.attachmentUrl('p', 'report.pdf'), history[0]!.url!]) { + const url = new URL(path, base) + expect(url.origin).toBe(new URL(base).origin) + expect(url.pathname.startsWith(`${new URL(base).pathname}ccteam/api/`)).toBe(true) + } + }, + ) +}) diff --git a/dsh-plugins/ccteam-ui/tests/client-apply.test.ts b/dsh-plugins/ccteam-ui/tests/client-apply.test.ts index afad94cea..9cf3c7599 100644 --- a/dsh-plugins/ccteam-ui/tests/client-apply.test.ts +++ b/dsh-plugins/ccteam-ui/tests/client-apply.test.ts @@ -21,17 +21,17 @@ vi.mock('@deepseek-ai/dsh-client-ui-primitives', () => { StateDot: stub, Tooltip: stub, writeClipboard: stub, - IconBranchOutline16: stub, - IconCheckOutline14: stub, - IconChevronDownOutline14: stub, - IconChevronLeftOutline14: stub, - IconCloseOutline16: stub, - IconCopyOutline16: stub, - IconPlusOutline16: stub, - IconSendOutline16: stub, - IconSettingsOutline14: stub, - IconTreeCorner8x10: stub, - IconWarningOutline16: stub, + IconBranchOutlineRegular: stub, + IconCheckOutlineRegular: stub, + IconChevronDownOutlineRegular: stub, + IconChevronLeftOutlineRegular: stub, + IconCloseOutlineRegular: stub, + IconCopyOutlineRegular: stub, + IconPlusOutlineRegular: stub, + IconSendOutlineRegular: stub, + IconSettingsOutlineRegular: stub, + IconTreeCornerRegular: stub, + IconWarningOutlineRegular: stub, } }) @@ -64,19 +64,19 @@ function fakeScope(namespace: string) { mode: 'host' as const, }), subscribe: () => () => {}, - set: () => Promise.resolve(), - unset: () => Promise.resolve(), + mutate: () => Promise.resolve(true), } } /** A recording client context: every service the plugin injects, no React. */ -function recordingCtx() { +function recordingCtx(settings = true) { const effects: Array<{ label: string | undefined; dispose: (() => void) | void }> = [] const dictionaries: Array<{ ns: string; locales: string[] }> = [] const waits: Wait[] = [] const registrations: Registration[] = [] const bound: string[] = [] const ctx = { + inject(deps: string[], body: (ctx: unknown) => void) { if (settings || !deps.includes('configForms')) body(ctx) }, effect(setup: () => (() => void) | void, label?: string) { const dispose = setup() effects.push({ label, dispose }) @@ -104,10 +104,12 @@ function recordingCtx() { } }, }, - settingsScope: { - bind(spec: { namespace: string }) { - bound.push(spec.namespace) - return fakeScope(spec.namespace) + configForms: { + describe() { return { getSnapshot: () => ({ view: { namespaces: [] } }), subscribe: () => () => {} } }, + whileServed(_namespaces: string[], register: () => () => void) { return register() }, + get(namespace: string) { + bound.push(namespace) + return fakeScope(namespace) }, }, } @@ -126,9 +128,16 @@ function declareAll(waits: Wait[]): void { } describe('plugin face', () => { + it('keeps the workbench and entry active without the optional settings provider', () => { + const { ctx, waits, registrations } = recordingCtx(false) + apply(ctx as never) + declareAll(waits) + expect(registrations.map(row => row.options.name)).toEqual(['sidebar.footer.action', 'shell.overlay']) + }) + it('is a DSH client plugin: name, required services, apply', () => { expect(name).toBe('ccteam-ui') - expect(inject).toEqual(['slots', 'locale', 'settingsScope']) + expect(inject).toEqual(['slots', 'locale']) expect(typeof apply).toBe('function') }) }) @@ -143,13 +152,15 @@ describe('apply', () => { 'ccteam-ui: persistence', 'ccteam-ui: team event stream', 'ccteam-ui: engine poller', + 'ccteam-ui: settings form', + 'ccteam-ui: settings section', ]) }) it('contributes every seat through slots.inject, never a direct register at apply time', () => { const { ctx, waits, registrations } = recordingCtx() apply(ctx as never) - expect(waits.map(wait => wait.key)).toEqual(['sidebar.footer.action', 'shell.overlay', 'settings.plugin.item']) + expect(waits.map(wait => wait.key)).toEqual(['sidebar.footer.action', 'shell.overlay', 'settings.section']) // Nothing is registered until the framework reports the slot declared. expect(registrations).toEqual([]) }) @@ -168,8 +179,8 @@ describe('apply', () => { expect(panel?.component).toBe(Workbench) expect(panel?.options).toMatchObject({ id: 'ccteam', order: 0, locale: 'ccteam' }) - const cards = byName('settings.plugin.item') - expect(cards.map(card => card.options.key)).toEqual(['ccteam-ui']) + const cards = byName('settings.section') + expect(cards.map(card => card.options.id)).toEqual(['ccteam-ui']) for (const card of cards) { expect(card.component).toBe(SettingsCard) expect(card.options.locale).toBe('ccteam') @@ -203,7 +214,7 @@ describe('apply', () => { const { ctx, waits, registrations } = recordingCtx() apply(ctx as never) declareAll(waits) - for (const card of registrations.filter(r => r.options.name === 'settings.plugin.item')) { + for (const card of registrations.filter(r => r.options.name === 'settings.section')) { const face = (card.options.inject as () => { hooks: { card: { getSnapshot(): { available: boolean; fields: Record }; subscribe: unknown } } spec: { namespace: string; fields: Array<{ field: string; kind: string }> } @@ -212,7 +223,7 @@ describe('apply', () => { discard: unknown reset: unknown })() - expect(face.spec.namespace).toBe(card.options.key) + expect(face.spec.namespace).toBe(card.options.id) expect(typeof face.hooks.card.subscribe).toBe('function') const state = face.hooks.card.getSnapshot() expect(state.available).toBe(true) diff --git a/dsh-plugins/ccteam-ui/tests/client-chat.test.tsx b/dsh-plugins/ccteam-ui/tests/client-chat.test.tsx index d27da5be8..acd5a4efd 100644 --- a/dsh-plugins/ccteam-ui/tests/client-chat.test.tsx +++ b/dsh-plugins/ccteam-ui/tests/client-chat.test.tsx @@ -16,18 +16,18 @@ vi.mock('@deepseek-ai/dsh-client-ui-primitives', async () => { MarkdownText: ({ text }: { text: string }) => h('p', { 'data-md': '' }, text), Menu: ({ anchor }: { anchor: unknown }) => anchor as never, StateDot: ({ state }: { state: string }) => h('i', { 'data-dot': state }), - IconApiOutline14: icon, - IconChevronDownOutline14: icon, - IconCloseFill14: icon, - IconCodeOutline16: icon, - IconEditOutline16: icon, - IconGlobeOutline14: icon, - IconLoadingOutline16: icon, - IconPaperclipOutline16: icon, - IconSendOutline16: icon, - IconSparkle16: icon, - IconStopFill16: icon, - IconThinkOutline16: icon, + IconApiOutlineRegular: icon, + IconChevronDownOutlineRegular: icon, + IconCloseFillRegular: icon, + IconCodeOutlineRegular: icon, + IconEditOutlineRegular: icon, + IconGlobeOutlineRegular: icon, + IconLoadingOutlineRegular: icon, + IconPaperclipOutlineRegular: icon, + IconSendOutlineRegular: icon, + IconSparkleRegular: icon, + IconStopFillRegular: icon, + IconThinkOutlineRegular: icon, } }) diff --git a/dsh-plugins/ccteam-ui/tests/client-engine-ui.test.tsx b/dsh-plugins/ccteam-ui/tests/client-engine-ui.test.tsx index 7506becd7..bcb4e844a 100644 --- a/dsh-plugins/ccteam-ui/tests/client-engine-ui.test.tsx +++ b/dsh-plugins/ccteam-ui/tests/client-engine-ui.test.tsx @@ -23,18 +23,18 @@ vi.mock('@deepseek-ai/dsh-client-ui-primitives', async () => { Input: (props: Record) => h('input', props), Pill: ({ children }: { children: unknown }) => h('span', null, children as never), writeClipboard: () => {}, - IconBranchOutline16: icon, - IconCheckOutline14: icon, - IconCloseOutline16: icon, - IconCodeOutline16: icon, - IconCopyOutline16: icon, - IconFolderOpen16: icon, - IconLoadingOutline16: icon, - IconPlayOutline16: icon, - IconRightUpOutline14: icon, - IconSettingsOutline14: icon, - IconWarningOutline16: icon, - IconChevronDownOutline14: icon, + IconBranchOutlineRegular: icon, + IconCheckOutlineRegular: icon, + IconCloseOutlineRegular: icon, + IconCodeOutlineRegular: icon, + IconCopyOutlineRegular: icon, + IconFolderOpenRegular: icon, + IconLoadingOutlineRegular: icon, + IconPlayOutlineRegular: icon, + IconRightUpOutlineRegular: icon, + IconSettingsOutlineRegular: icon, + IconWarningOutlineRegular: icon, + IconChevronDownOutlineRegular: icon, } }) @@ -205,7 +205,7 @@ describe('first-run engine panel', () => { // The host sentence repeats the reason in other words: hidden. expect(html).not.toContain('the daemon is not running.') expect(button(html, '启动引擎').disabled).toBe(false) - expect(html).toContain('插件配置 → ccteam-ui') + expect(html).toContain('DSH 设置 → ccteam-ui') }) it('installing / starting: a spinner title and no start button', () => { diff --git a/dsh-plugins/ccteam-ui/tests/client-settings.test.ts b/dsh-plugins/ccteam-ui/tests/client-settings.test.ts index d7a26a1fb..eef0e67b3 100644 --- a/dsh-plugins/ccteam-ui/tests/client-settings.test.ts +++ b/dsh-plugins/ccteam-ui/tests/client-settings.test.ts @@ -18,6 +18,7 @@ interface ScopeState { /** A SettingsScope double whose Host answers are scripted per test. */ function fakeScope(initial: Partial = {}) { let state: ScopeState = { status: 'ready', value: {}, user: undefined, writable: true, ...initial } + let revision = 1 const listeners = new Set<() => void>() const writes: Array<{ op: 'set' | 'unset'; field: string; value?: unknown }> = [] /** How the Host treats a write: `accept` folds it into value+user, `reject` leaves state alone, `throw` rejects. */ @@ -25,38 +26,43 @@ function fakeScope(initial: Partial = {}) { const notify = () => { for (const listener of [...listeners]) listener() } + const secrets = new Map() + const redact = () => { + for (const field of ['restToken', 'enrollment']) { + if (Object.hasOwn(state.value ?? {}, field)) secrets.set(field, Boolean(state.value?.[field])) + } + state = { ...state, value: { ...state.value }, user: { ...state.user } } + for (const field of ['restToken', 'enrollment']) { delete state.value?.[field]; delete state.user?.[field] } + } + redact() const scope = { - getSnapshot: () => ({ ...state, base: undefined, revision: 1, mode: 'host' as const }), - subscribe(listener: () => void) { - listeners.add(listener) - return () => { - listeners.delete(listener) - } - }, - set(field: string, value: unknown) { - writes.push({ op: 'set', field, value }) - if (behaviour === 'throw') return Promise.reject(new Error('wire down')) - if (behaviour === 'accept') { - state = { ...state, value: { ...state.value, [field]: value }, user: { ...state.user, [field]: value } } - notify() + getSnapshot: () => ({ ...state, base: undefined, revision }), + subscribe(listener: () => void) { listeners.add(listener); return () => { listeners.delete(listener) } }, + async mutate(ops: readonly ({ op: 'set'; path: readonly string[]; value: unknown } | { op: 'unset'; path: readonly string[] })[], expectedRevision?: number) { + for (const op of ops) writes.push({ op: op.op, field: op.path[0]!, ...op.op === 'set' ? { value: op.value } : {} }) + if (behaviour === 'throw') throw new Error('wire down') + if (behaviour === 'reject' || (expectedRevision !== undefined && expectedRevision !== revision)) return false + for (const op of ops) { + const field = op.path[0]! + if (op.op === 'set') state = { ...state, value: { ...state.value, [field]: op.value }, user: { ...state.user, [field]: op.value } } + else { delete state.value?.[field]; delete state.user?.[field]; secrets.delete(field) } } - return Promise.resolve() - }, - unset(field: string) { - writes.push({ op: 'unset', field }) - if (behaviour === 'throw') return Promise.reject(new Error('wire down')) - if (behaviour === 'accept') { - const value = { ...state.value } - const user = { ...state.user } - delete value[field] - delete user[field] - state = { ...state, value, user } - notify() - } - return Promise.resolve() + revision++ + redact() + notify() + return true }, } + const mirror = { + getSnapshot: () => ({ status: 'ready' as const, error: null, view: { + namespaces: [{ ns: 'ccteam-ui', autoGenerate: false, schema: {}, value: {}, revision: 1, applies: 'live' as const, + secrets: [...secrets].map(([field, set]) => ({ path: [field], set })) }], + writable: true, hasDocument: true, + } }), + subscribe: (_listener: () => void) => () => {}, + } return { + mirror, scope, writes, behave(next: typeof behaviour) { @@ -64,26 +70,64 @@ function fakeScope(initial: Partial = {}) { }, host(next: Partial) { state = { ...state, ...next } + revision++ + redact() notify() }, } } +describe('revision fences', () => { + it('saves staged text after its own live toggle advances the form revision', async () => { + const { scope, mirror } = fakeScope() + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) + card.inject().edit('enginePath', '/opt/ccteam') + await card.setToggle('autoStart', false) + await card.save() + expect(card.getSnapshot().failed).toBe(false) + expect(scope.getSnapshot().value?.enginePath).toBe('/opt/ccteam') + }) + + it('keeps the stale fence when another editor changed the form before the toggle', async () => { + const { scope, mirror, host } = fakeScope() + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) + card.inject().edit('defaultProject', 'my-project') + host({ value: { defaultProject: 'external-project' } }) + await card.setToggle('autoStart', false) + await card.save() + expect(card.getSnapshot().failed).toBe(true) + expect(scope.getSnapshot().value?.defaultProject).toBe('external-project') + }) +}) + +describe('redacted native configuration', () => { + it('accepts a secret write without receiving the secret in the Host response', async () => { + const { scope, mirror } = fakeScope() + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) + card.inject().edit('restToken', 'private-token') + await card.save() + expect(scope.getSnapshot().value).not.toHaveProperty('restToken') + expect(card.getSnapshot().fields.restToken).toEqual({ text: '', overridden: false, configured: true }) + expect(card.getSnapshot().failed).toBe(false) + expect(card.getSnapshot().dirty).toBe(false) + }) +}) + describe('projection', () => { it('is unavailable (renders nothing) until the Host serves the namespace', () => { - const { scope, host } = fakeScope({ status: 'loading' }) - const card = new SettingsCardController(scope, CCTEAM_CARD) + const { mirror, scope, host } = fakeScope({ status: 'loading' }) + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) expect(card.getSnapshot().available).toBe(false) host({ status: 'ready' }) expect(card.getSnapshot().available).toBe(true) }) it('shows the effective value, marks user-layer fields overridden, and never echoes a secret', () => { - const { scope } = fakeScope({ + const { mirror, scope } = fakeScope({ value: { daemonUrl: 'http://127.0.0.1:7331', restToken: 'ccteam:abc', defaultProject: 'demo' }, user: { defaultProject: 'demo' }, }) - const card = new SettingsCardController(scope, CCTEAM_CARD) + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) const { fields } = card.getSnapshot() expect(fields.daemonUrl).toEqual({ text: 'http://127.0.0.1:7331', overridden: false, configured: true }) expect(fields.defaultProject).toEqual({ text: 'demo', overridden: true, configured: true }) @@ -92,14 +136,14 @@ describe('projection', () => { }) it('reports an unset secret as not configured', () => { - const { scope } = fakeScope({ value: { daemonUrl: 'http://127.0.0.1:7331' } }) - const card = new SettingsCardController(scope, CCTEAM_CARD) + const { mirror, scope } = fakeScope({ value: { daemonUrl: 'http://127.0.0.1:7331' } }) + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) expect(card.getSnapshot().fields.enrollment?.configured).toBe(false) }) it('publishes a new snapshot (new reference) whenever the scope changes, and keeps the reference otherwise', () => { - const { scope, host } = fakeScope() - const card = new SettingsCardController(scope, CCTEAM_CARD) + const { mirror, scope, host } = fakeScope() + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) const first = card.getSnapshot() expect(card.getSnapshot()).toBe(first) let notified = 0 @@ -115,8 +159,8 @@ describe('projection', () => { describe('staging', () => { it('edit stages a draft: dirty, overridden preview, no write yet', () => { - const { scope, writes } = fakeScope() - const card = new SettingsCardController(scope, CCTEAM_CARD) + const { mirror, scope, writes } = fakeScope() + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) const face = card.inject() face.edit('daemonUrl', 'http://10.0.0.1:7331') expect(card.getSnapshot().dirty).toBe(true) @@ -125,8 +169,8 @@ describe('staging', () => { }) it('discard drops every draft; reset stages a clear', () => { - const { scope } = fakeScope({ value: { daemonUrl: 'x' }, user: { daemonUrl: 'x' } }) - const card = new SettingsCardController(scope, CCTEAM_CARD) + const { mirror, scope } = fakeScope({ value: { daemonUrl: 'x' }, user: { daemonUrl: 'x' } }) + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) const face = card.inject() face.edit('daemonUrl', 'y') face.discard() @@ -140,8 +184,8 @@ describe('staging', () => { describe('save', () => { it('writes set for text, unset for blank, skips a blank secret, then reads the outcome back', async () => { - const { scope, writes } = fakeScope({ value: { daemonUrl: 'old', defaultProject: 'p' }, user: { defaultProject: 'p' } }) - const card = new SettingsCardController(scope, CCTEAM_CARD) + const { mirror, scope, writes } = fakeScope({ value: { daemonUrl: 'old', defaultProject: 'p' }, user: { defaultProject: 'p' } }) + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) const face = card.inject() face.edit('daemonUrl', ' http://new:7331 ') face.edit('defaultProject', '') @@ -160,8 +204,8 @@ describe('save', () => { }) it('writes a secret and reports it configured afterwards', async () => { - const { scope, writes } = fakeScope() - const card = new SettingsCardController(scope, CCTEAM_CARD) + const { mirror, scope, writes } = fakeScope() + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) const face = card.inject() face.edit('enrollment', 'ccteam-enroll:id:secret') await card.save() @@ -170,9 +214,9 @@ describe('save', () => { }) it('keeps the drafts and flags failure when the Host did not take the value', async () => { - const { scope, behave } = fakeScope({ value: { daemonUrl: 'old' } }) + const { mirror, scope, behave } = fakeScope({ value: { daemonUrl: 'old' } }) behave('reject') - const card = new SettingsCardController(scope, CCTEAM_CARD) + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) const face = card.inject() face.edit('daemonUrl', 'new') await card.save() @@ -184,17 +228,17 @@ describe('save', () => { }) it('treats a rejected write as a failed save rather than throwing', async () => { - const { scope, behave } = fakeScope() + const { mirror, scope, behave } = fakeScope() behave('throw') - const card = new SettingsCardController(scope, CCTEAM_CARD) + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) card.inject().edit('daemonUrl', 'new') await expect(card.save()).resolves.toBeUndefined() expect(card.getSnapshot().failed).toBe(true) }) it('is a no-op with nothing staged and while a save is in flight', async () => { - const { scope, writes } = fakeScope() - const card = new SettingsCardController(scope, CCTEAM_CARD) + const { mirror, scope, writes } = fakeScope() + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) await card.save() expect(writes).toEqual([]) card.inject().edit('daemonUrl', 'a') @@ -208,8 +252,8 @@ describe('save', () => { describe('toggles', () => { it('projects a live toggle from the effective value, defaulting per spec, apart from the staged fields', () => { - const { scope, host } = fakeScope() - const card = new SettingsCardController(scope, CCTEAM_CARD) + const { mirror, scope, host } = fakeScope() + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) expect(card.getSnapshot().toggles).toEqual({ autoStart: true }) expect(card.getSnapshot().fields.autoStart).toBeUndefined() host({ value: { autoStart: false } }) @@ -217,8 +261,8 @@ describe('toggles', () => { }) it('setToggle writes at once (no staging) and reads the outcome back', async () => { - const { scope, writes } = fakeScope() - const card = new SettingsCardController(scope, CCTEAM_CARD) + const { mirror, scope, writes } = fakeScope() + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) await card.setToggle('autoStart', false) expect(writes).toEqual([{ op: 'set', field: 'autoStart', value: false }]) expect(card.getSnapshot()).toMatchObject({ failed: false, saving: false, dirty: false }) @@ -226,9 +270,9 @@ describe('toggles', () => { }) it('setToggle reports failure when the Host keeps the old value', async () => { - const { scope, behave } = fakeScope() + const { mirror, scope, behave } = fakeScope() behave('reject') - const card = new SettingsCardController(scope, CCTEAM_CARD) + const card = new SettingsCardController(scope, CCTEAM_CARD, mirror) await card.setToggle('autoStart', false) expect(card.getSnapshot().failed).toBe(true) expect(card.getSnapshot().toggles.autoStart).toBe(true) diff --git a/dsh-plugins/ccteam-ui/tests/host-apply.test.ts b/dsh-plugins/ccteam-ui/tests/host-apply.test.ts index 8d341e7bf..733ceb7dd 100644 --- a/dsh-plugins/ccteam-ui/tests/host-apply.test.ts +++ b/dsh-plugins/ccteam-ui/tests/host-apply.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import { apply } from '../src/index.js' +import { apply, Config } from '../src/index.js' import { AcpClient, makeFakeCtx, shortSocketPath } from './host-cordis-fakes.js' interface WireCall { @@ -51,7 +51,7 @@ afterEach(() => { describe('apply', () => { it('registers exactly the six ccteam tool names', () => { const h = makeFakeCtx() - apply(h.ctx as never) + apply(h.ctx as never, Config({ enrollment: 'ccteam-enroll:e1:secret', daemonUrl: 'http://daemon.test' })) expect(h.tools.map(tool => tool.name).sort()).toEqual([ 'agent', @@ -66,7 +66,7 @@ describe('apply', () => { it('runs tool-surface-only (mode 2) on the enrollment credential with no listeners', async () => { const calls = stubDaemon() const h = makeFakeCtx({ settings: { enrollment: 'ccteam-enroll:e1:secret' } }) - apply(h.ctx as never) + apply(h.ctx as never, Config({ enrollment: 'ccteam-enroll:e1:secret', daemonUrl: 'http://daemon.test' })) expect(h.ctx.on).not.toHaveBeenCalled() @@ -84,7 +84,7 @@ describe('apply', () => { process.env.CCTEAM_DSH_APPROVAL = 'hitl' const calls = stubDaemon() const h = makeFakeCtx({ settings: { enrollment: 'ccteam-enroll:e1:secret' } }) - apply(h.ctx as never) + apply(h.ctx as never, Config({ enrollment: 'ccteam-enroll:e1:secret', daemonUrl: 'http://daemon.test' })) // No transport: the env switch is gone, only `transportSocket` starts one. expect(h.ctx.on).not.toHaveBeenCalled() @@ -101,10 +101,10 @@ describe('apply', () => { * (schema default), so the enrollment a human pasted into the DSH settings * card must still win. */ - it('takes the enrollment from the settings card when the row config carries the schema default', async () => { + it('uses the native live enrollment reference', async () => { const calls = stubDaemon() const h = makeFakeCtx({ settings: { enrollment: 'ccteam-enroll:from-card:secret' } }) - apply(h.ctx as never, { enrollment: '', daemonUrl: '' }) + apply(h.ctx as never, Config({ enrollment: 'ccteam-enroll:from-card:secret', daemonUrl: 'http://daemon.test' })) const status = h.tools.find(tool => tool.name === 'status')! await status.execute({}, { agent: { id: 'dsh-1', session: { id: 'dsh-1' }, followup: vi.fn() } }) @@ -119,7 +119,7 @@ describe('apply', () => { const calls = stubDaemon() const socketPath = shortSocketPath() const h = makeFakeCtx({ settings: { enrollment: 'ccteam-enroll:e1:secret' } }) - apply(h.ctx as never, { transportSocket: socketPath }) + apply(h.ctx as never, Config({ transportSocket: socketPath, enrollment: 'ccteam-enroll:e1:secret', daemonUrl: 'http://daemon.test' })) const client = await AcpClient.connect(socketPath) closers.push(() => client.close()) diff --git a/dsh-plugins/ccteam-ui/tests/host-build.test.ts b/dsh-plugins/ccteam-ui/tests/host-build.test.ts index 2f321f58e..5ef48542f 100644 --- a/dsh-plugins/ccteam-ui/tests/host-build.test.ts +++ b/dsh-plugins/ccteam-ui/tests/host-build.test.ts @@ -73,7 +73,7 @@ describe('client bundle loader contract', () => { expect(typeof exported).toBe('object') expect(exported.name).toBe('ccteam-ui') expect(typeof exported.apply).toBe('function') - expect(exported.inject).toEqual(['slots', 'locale', 'settingsScope']) + expect(exported.inject).toEqual(['slots', 'locale']) // Whatever the client half requires must be answerable by the module table. expect(required.length).toBeGreaterThan(0) for (const spec of required) expect(isClientExternal(spec)).toBe(true) @@ -108,12 +108,12 @@ describe('client bundle loader contract', () => { // convention every DSH client package follows (ui-goal lists // ui-conversation, ui-settings-general lists ui-sidebar). expect(pkg.dsh.client.inject).toEqual([ - '@deepseek-ai/dsh-client-runtime', '@deepseek-ai/dsh-client-locale', '@deepseek-ai/dsh-client-ui-layout', '@deepseek-ai/dsh-client-ui-sidebar', '@deepseek-ai/dsh-client-ui-settings', - '@deepseek-ai/dsh-client-ui-settings-plugins', + '@deepseek-ai/dsh-client-ui-renderer', + '@deepseek-ai/dsh-client-ui-workspace', ]) expect(pkg.files).toContain('lib') expect(pkg.files).toContain('cordis.patch.yml') @@ -131,10 +131,9 @@ describe('client bundle loader contract', () => { }) describe('client externals', () => { - it('is exactly the platform seed table plus the preloaded runtime', () => { + it('is exactly only the shared platform modules used by this plugin', () => { expect([...CLIENT_EXTERNALS].sort()).toEqual([ '@deepseek-ai/cordis', - '@deepseek-ai/dsh-client-runtime/client', '@deepseek-ai/dsh-client-ui-primitives', '@deepseek-ai/dsh-client-ui-slots', 'react', diff --git a/dsh-plugins/ccteam-ui/tests/host-cordis-fakes.ts b/dsh-plugins/ccteam-ui/tests/host-cordis-fakes.ts index 9d510d406..5d734e6c6 100644 --- a/dsh-plugins/ccteam-ui/tests/host-cordis-fakes.ts +++ b/dsh-plugins/ccteam-ui/tests/host-cordis-fakes.ts @@ -93,6 +93,7 @@ export function makeFakeCtx(options?: { // Mirrors the vendor roster: resolve(undefined) falls back to the default id. resolve: vi.fn(async (id?: string) => ({ id: id ?? 'standard' })), mount: vi.fn(async (_agentCtx: unknown, _id?: string) => undefined), + presetForSession: vi.fn((_session: unknown) => 'cordis'), } const persistenceInspect = vi.fn(async (_sessionId: string) => options?.persistence ?? {}) const permissionPresets = { set: vi.fn((_session: unknown, _name: string) => undefined) } @@ -212,7 +213,13 @@ export function makeFakeCtx(options?: { emit('session/event', { id: sessionId }, { type, seq: 1, time: new Date().toISOString(), data }) } + const assistantStream = (sessionId: string, data: { turn: number; step?: number; chunk: unknown }) => { + const agent = agents.get(sessionId) + emit('agent/assistant-stream', { agent, frame: { type: 'start', attemptId: 'attempt-1', revision: 1, turn: data.turn, step: 0 } }) + emit('agent/assistant-stream', { agent, frame: { type: 'chunk', attemptId: 'attempt-1', revision: 1, index: 0, time: 0, chunk: data.chunk } }) + } return { + assistantStream, ctx, inject, tools, diff --git a/dsh-plugins/ccteam-ui/tests/host-engine-bff.test.ts b/dsh-plugins/ccteam-ui/tests/host-engine-bff.test.ts index 8205fe9d8..d09502fd5 100644 --- a/dsh-plugins/ccteam-ui/tests/host-engine-bff.test.ts +++ b/dsh-plugins/ccteam-ui/tests/host-engine-bff.test.ts @@ -11,7 +11,7 @@ import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' import { describe, expect, it } from 'vitest' import { createBff, type EngineFace } from '../src/bff.js' -import { ENGINE_VERSION, apply } from '../src/index.js' +import { ENGINE_VERSION, apply, Config } from '../src/index.js' import { DEFAULT_DAEMON_URL } from '../src/settings.js' import { API_PREFIX, type ApiMethod, type EngineStatus } from '../src/shared/contract.js' import { FakeFetch, FakeRequest, FakeResponse, FakeWebServer, hostCtx } from './host-fakes.js' @@ -160,24 +160,8 @@ describe('engine methods through the BFF', () => { describe('a schema default is a blank row, not a pin', () => { function applied(config: Record, card: Record = {}) { const server = new FakeWebServer() - const ctx = hostCtx({ - webServer: server, - settings: { - register: () => ({ - get: () => ({ - daemonUrl: DEFAULT_DAEMON_URL, - enrollment: '', - restToken: '', - defaultProject: '', - connectionStatus: '', - autoStart: false, - enginePath: '', - ...card, - }), - }), - }, - }) - apply(ctx as never, config as never) + const ctx = hostCtx({ webServer: server }) + apply(ctx as never, Config({ autoStart: false, ...config, ...card })) return server } @@ -208,9 +192,9 @@ describe('a schema default is a blank row, not a pin', () => { expect(status.supervised).toBe(true) }) - it('does read a ccteam-materialized row — one carrying credentials — as exactly that', async () => { + it('does read a ccteam-materialized row — explicitly marked engineManaged — as exactly that', async () => { const status = await engineStatusOf( - applied({ daemonUrl: 'http://127.0.0.1:7331', restToken: 'ccteam:deadbeef' }), + applied({ engineManaged: true, daemonUrl: 'http://127.0.0.1:7331', restToken: 'ccteam:deadbeef' }), ) expect(status.supervised).toBe(false) diff --git a/dsh-plugins/ccteam-ui/tests/host-settings.test.ts b/dsh-plugins/ccteam-ui/tests/host-settings.test.ts index 10aea5556..0e6415f25 100644 --- a/dsh-plugins/ccteam-ui/tests/host-settings.test.ts +++ b/dsh-plugins/ccteam-ui/tests/host-settings.test.ts @@ -1,287 +1,61 @@ -import Schema from '@deepseek-ai/schemastery' -import { describe, expect, it, vi } from 'vitest' -import { apply } from '../src/index.js' -import { - DEFAULT_DAEMON_URL, - SETTINGS_NAMESPACE, - registerCcteamSettings, - type CcteamSettings, -} from '../src/settings.js' -import { FakeFetch, FakeRequest, FakeResponse, FakeWebServer, hostCtx } from './host-fakes.js' - -interface Registration { - ns: string - schema: Schema - options: { applies?: string; base?: Record } | undefined -} - -interface SchemaNode { - type: string - meta?: { default?: unknown; role?: string; description?: string } - dict?: Record -} - -/** - * `Schema.toJSON()` serializes a ref graph — `{uid, refs}` where an object - * node's `dict` maps each key to a ref id — so field assertions resolve - * through `refs` rather than reading `dict` as inline nodes. - */ -function describeSchema(schema: Schema): { - type: string - fields: Record -} { - const json = schema.toJSON() as unknown as { uid: number; refs: Record } - const root = json.refs[String(json.uid)]! - const fields: Record = {} - for (const [key, ref] of Object.entries(root.dict ?? {})) { - fields[key] = json.refs[String(ref)]! - } - return { type: root.type, fields } -} - -function fakeSettings(stored: Partial = {}) { - const registrations: Registration[] = [] - const service = { - register( - ns: string, - schema: Schema, - options?: { applies?: 'live' | 'restart'; base?: Partial }, - ) { - registrations.push({ - ns, - schema: schema as Schema, - options: options as Registration['options'], - }) - return { - get: () => ({ - daemonUrl: DEFAULT_DAEMON_URL, - enrollment: '', - restToken: '', - defaultProject: '', - connectionStatus: '', - ...stored, - }) as T, - } - }, - } - return { service, registrations } -} - -describe('settings registration', () => { - it('registers one namespace whose schema carries the documented fields', () => { - const { service, registrations } = fakeSettings() - registerCcteamSettings({ settings: service }) - - expect(registrations).toHaveLength(1) - const [registration] = registrations - expect(registration!.ns).toBe(SETTINGS_NAMESPACE) - expect(registration!.ns).toBe('ccteam-ui') - - // Structural assertion on the serialized schema, not a substring match: - // the bug class here is "right text, wrong shape". - const { type, fields } = describeSchema(registration!.schema) - expect(type).toBe('object') - expect(Object.keys(fields).sort()).toEqual([ - 'autoStart', - 'connectionStatus', - 'daemonUrl', - 'defaultProject', - 'enginePath', - 'enrollment', - 'restToken', - ]) - expect(fields.daemonUrl!.type).toBe('string') - expect(fields.daemonUrl!.meta!.default).toBe(DEFAULT_DAEMON_URL) - }) - - /** - * `autoStart` is ON by default because `dsh plugin --profile add @ccteam/ccteam-ui` is - * meant to be the whole install (PRD G2). It is also the one key whose - * default is load-bearing in the OTHER direction: it lives on the settings - * card and NOT in the profile row schema, because Cordis fills every row - * default before `apply` and a row-level boolean would arrive `true` for - * every profile, leaving the user's `false` unreachable. - */ - it('defaults auto-start on and leaves the engine path unset', () => { - const { service, registrations } = fakeSettings() - registerCcteamSettings({ settings: service }) - - const { fields } = describeSchema(registrations[0]!.schema) - expect(fields.autoStart!.type).toBe('boolean') - expect(fields.autoStart!.meta!.default).toBe(true) - expect(fields.enginePath!.type).toBe('string') - expect(fields.enginePath!.meta!.default).toBe('') - }) - - it('marks both credentials secret so the settings UI never renders them in the clear', () => { - const { service, registrations } = fakeSettings() - registerCcteamSettings({ settings: service }) - - const { fields } = describeSchema(registrations[0]!.schema) - for (const field of ['restToken', 'enrollment'] as const) { - expect(fields[field]!.meta!.role).toBe('secret') - expect(fields[field]!.meta!.default).toBe('') - } - // Exactly the credentials claim the secret role — nothing else, and nothing missing. - const secrets = Object.entries(fields) - .filter(([, field]) => field.meta?.role === 'secret') - .map(([key]) => key) - .sort() - expect(secrets).toEqual(['enrollment', 'restToken']) - }) - - it('passes plugin config through as the settings base', () => { - const { service, registrations } = fakeSettings() - registerCcteamSettings({ settings: service }, { - daemonUrl: 'http://10.0.0.4:7331', - restToken: 'ccteam:abc', - }) - - expect(registrations[0]!.options?.base).toEqual({ - daemonUrl: 'http://10.0.0.4:7331', - restToken: 'ccteam:abc', - }) - }) - - it('still resolves values when the settings service is absent', () => { - const scope = registerCcteamSettings({}, { daemonUrl: 'http://elsewhere:1234' }) - const value = scope.get() - - expect(value.daemonUrl).toBe('http://elsewhere:1234') - expect(value.restToken).toBe('') - expect(value.connectionStatus).toContain('ccteam start') - }) - - it('defaults the daemon URL when neither config nor settings supply one', () => { - expect(registerCcteamSettings({}).get().daemonUrl).toBe(DEFAULT_DAEMON_URL) - }) +import { updateVolatile } from '@deepseek-ai/cosmokit' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { Config, apply } from '../src/index.js' +import { bindCcteamSettings, DEFAULT_DAEMON_URL } from '../src/settings.js' +import { FakeRequest, FakeResponse, FakeWebServer, hostCtx } from './host-fakes.js' +import { API_PREFIX } from '../src/shared/contract.js' + +afterEach(async () => { + vi.unstubAllGlobals() }) -describe('config-over-settings precedence', () => { - /** - * Drives the REAL `apply()` wiring: stub the global fetch, run the route the - * plugin registered, and read back which daemon URL and token it used. This - * exercises the precedence closures rather than restating them. - */ - async function probe( - config: Partial, - stored: Partial, - payload: unknown = {}, - method = 'status', - ): Promise { - const fake = new FakeFetch() - .on('/api/v1/capabilities', { json: { harnesses: [] } }) - .on('/sessions', { status: 201, json: { sid: 's1' } }) - vi.stubGlobal('fetch', fake.fetch) - try { - const server = new FakeWebServer() - const { service } = fakeSettings(stored) - apply(hostCtx({ webServer: server, settings: service }) as never, config) - const res = new FakeResponse() - await server.handler()( - new FakeRequest('POST', `/ccteam/api/${method}`, payload).asIncomingMessage(), - res.asServerResponse(), - ) - } finally { - vi.unstubAllGlobals() - } - return fake - } - - it('prefers a config value over the settings value', async () => { - const fake = await probe( - { daemonUrl: 'http://from-config:7331', restToken: 'ccteam:from-config' }, - { - daemonUrl: 'http://from-settings:7331', - restToken: 'ccteam:from-settings', - defaultProject: 'settings-project', - }, - ) - - expect(fake.calls[0]!.url).toBe('http://from-config:7331/api/v1/capabilities') - expect(fake.calls[0]!.authorization).toBe('Bearer ccteam:from-config') - }) - - it('falls back to the settings value for anything config does not pin', async () => { - const fake = await probe( - { daemonUrl: 'http://from-config:7331' }, - { restToken: 'ccteam:from-settings', defaultProject: 'settings-project' }, - ) - - expect(fake.calls[0]!.url).toBe('http://from-config:7331/api/v1/capabilities') - expect(fake.calls[0]!.authorization).toBe('Bearer ccteam:from-settings') - }) - - it('takes the spawn project from the settings card when config is silent', async () => { - const fake = await probe( - {}, - { defaultProject: 'settings-project' }, - { vendor: 'claude' }, - 'session.spawn', - ) - - expect(fake.calls[0]!.url).toContain('/api/v1/projects/settings-project/sessions') - }) - - /** - * The regression this exists for: Cordis validates the row config against - * this plugin's `Config` schema BEFORE apply, so a key the row omits arrives - * as its schema default (`''`), not as undefined. Under a plain `??` that - * empty string wins and the settings card is dead — which is exactly how a - * hand-started `dsh web` is documented to supply its credentials (real - * machine, DSH 0.1.1-rc.2: "ccteam MCP credential is not configured" with a - * perfectly good enrollment string in DSH Settings). - */ - it('treats an empty config value as unpinned, so the settings card still decides', async () => { - const fake = await probe( - { daemonUrl: '', restToken: ' ' }, - { daemonUrl: 'http://from-settings:7331', restToken: 'ccteam:from-settings' }, - ) - - expect(fake.calls[0]!.url).toBe('http://from-settings:7331/api/v1/capabilities') - expect(fake.calls[0]!.authorization).toBe('Bearer ccteam:from-settings') - }) - - it('falls back to the built-in daemon URL when neither layer has one', async () => { - const fake = await probe({ daemonUrl: '' }, { daemonUrl: '' }) - - expect(fake.calls[0]!.url).toBe(`${DEFAULT_DAEMON_URL}/api/v1/capabilities`) - }) - - it('apply() wires the settings card and the BFF route together, once', () => { - const server = new FakeWebServer() - const { service, registrations } = fakeSettings({ restToken: 'ccteam:stored' }) - apply(hostCtx({ webServer: server, settings: service }) as never) - - expect(registrations).toHaveLength(1) - expect(server.routes).toHaveLength(1) - expect(server.routes[0]!.path).toBe('/ccteam/api') - }) - - it('reads the token live, so editing the settings card needs no restart', async () => { - const fake = new FakeFetch().on('/api/v1/capabilities', { json: { harnesses: [] } }) - let token = 'ccteam:first' - const { createBff } = await import('../src/bff.js') - const bff = createBff({ - daemonUrl: () => 'http://127.0.0.1:7331', - restToken: () => token, - fetchImpl: fake.fetch, +describe('native live Config', () => { + it('resolves defaults and exposes one volatile reference per preference', () => { + const config = Config({}) + expect(bindCcteamSettings({}, config).get()).toEqual({ + daemonUrl: DEFAULT_DAEMON_URL, enrollment: '', restToken: '', defaultProject: '', + connectionStatus: expect.any(String), autoStart: true, enginePath: '', }) - - const call = async (): Promise => { - const res = new FakeResponse() - await bff.handle( - new FakeRequest('POST', '/ccteam/api/status', {}).asIncomingMessage(), - res.asServerResponse(), - ) - } - await call() - token = 'ccteam:second' - await call() - - expect(fake.calls.map(entry => entry.authorization)).toEqual([ - 'Bearer ccteam:first', - 'Bearer ccteam:second', - ]) + expect(config.engineManaged).toBe(false) + expect(config.transportSocket).toBeUndefined() + }) + + it('reads the same native references after a Loader volatile update', () => { + const config = Config({ enrollment: 'first', autoStart: false }) + const settings = bindCcteamSettings({}, config) + // This is the reference commit operation used by Loader Entry.update. + updateVolatile(config.enrollment, Config({ enrollment: 'second' }).enrollment) + expect(settings.get().enrollment).toBe('second') + }) + + it('persists bootstrap enrollment into its actual Loader entry', async () => { + const update = vi.fn(async () => {}) + const settings = bindCcteamSettings({ + get: name => name === 'settings' ? { update } : undefined, + fiber: { entry: { id: 'user-selected-ccteam-entry' } }, + }, Config({})) + await settings.update?.({ enrollment: 'ccteam-enroll:e1:secret' }) + expect(update).toHaveBeenCalledWith('user-selected-ccteam-entry', { enrollment: 'ccteam-enroll:e1:secret' }) + }) + + it('serves the workbench without a settings service or agent runtime', async () => { + const fetch = vi.fn(async () => new Response(JSON.stringify({ projects: [] }))) + vi.stubGlobal('fetch', fetch) + const webServer = new FakeWebServer() + apply(hostCtx({ webServer }) as never, Config({ + autoStart: false, daemonUrl: 'http://daemon.test', restToken: 'personal', + })) + const response = new FakeResponse() + await webServer.handler()(new FakeRequest('POST', `${API_PREFIX}/catalog.projects`, {}).asIncomingMessage(), response.asServerResponse()) + expect(fetch).toHaveBeenCalled() + expect(response.statusCode).toBe(200) + expect(JSON.stringify(response.json())).not.toContain('personal') + }) + + it('declares credentials as secret volatile fields in the native schema', () => { + const schema = Config.toJSON() as unknown as { refs: Record } + const secrets = Object.values(schema.refs).filter(node => node.meta?.role === 'secret') + expect(secrets).toHaveLength(2) + expect(secrets.every(node => node.meta?.volatile === true)).toBe(true) }) }) diff --git a/dsh-plugins/ccteam-ui/tests/host-transport.test.ts b/dsh-plugins/ccteam-ui/tests/host-transport.test.ts index 9c2311f76..68cbc35fc 100644 --- a/dsh-plugins/ccteam-ui/tests/host-transport.test.ts +++ b/dsh-plugins/ccteam-ui/tests/host-transport.test.ts @@ -97,11 +97,11 @@ describe('DSH ACP socket transport', () => { h.sessionEvent(sessionId, 'turn/start', { turn: 1 }) h.sessionEvent(sessionId, 'user/message', { id: messageId, role: 'user', content: [{ type: 'text', text: 'hello' }] }) - h.sessionEvent(sessionId, 'assistant/chunk', { turn: 1, step: 0, chunk: { type: 'reasoning-delta', text: 'thinking' } }) + h.assistantStream(sessionId, { turn: 1, step: 0, chunk: { type: 'reasoning-delta', text: 'thinking' } }) h.sessionEvent(sessionId, 'assistant/message', { turn: 1, step: 0, - message: { content: [{ type: 'text', text: 'partial answer' }] }, + message: { content: [{ type: 'reasoning', text: 'thinking' }, { type: 'text', text: 'partial answer' }] }, usage: { inputTokens: 4, outputTokens: 2 }, }) h.sessionEvent(sessionId, 'turn/end', { turn: 1, reason: { kind: 'max-tokens' } }) @@ -115,6 +115,7 @@ describe('DSH ACP socket transport', () => { sessionId, update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: 'partial answer' } }, }) + expect(client.updates.filter(update => (update.update as { sessionUpdate: string }).sessionUpdate === 'agent_thought_chunk')).toHaveLength(1) expect(client.updates).toContainEqual({ sessionId, update: { sessionUpdate: 'agent_thought_chunk', content: { type: 'text', text: 'thinking' } }, @@ -200,15 +201,7 @@ describe('DSH ACP socket transport', () => { }) it('re-mounts the STORED preset on resume, ignoring the _meta request', async () => { - const h = await startTransport({ - persistence: { - meta: { agentPreset: 'minimal' }, - events: [ - { type: 'agent-preset/selected', data: { agentPreset: 'cordis' } }, - { type: 'turn/start', data: { turn: 1 } }, - ], - }, - }) + const h = await startTransport() const client = await connectClient(h.socketPath) await client.request('session/load', { @@ -216,11 +209,12 @@ describe('DSH ACP socket transport', () => { _meta: { ccteam: { agentPreset: 'code' } }, }) - const request = h.resume.mock.calls[0]![0] as { setup?: (agentCtx: unknown) => Promise } + const request = h.resume.mock.calls[0]![0] as { setup?: (agentCtx: unknown, agent: unknown) => Promise } expect(request.setup).toBeTypeOf('function') - await request.setup!('agent-ctx') - // The newest agent-preset/selected event outranks the creation header, - // and the ccteam-side request never overrides vendor storage. + const agent = h.agents.get('persisted-1')! + await request.setup!('agent-ctx', agent) + expect(h.agentPresets.presetForSession).toHaveBeenCalledWith(agent.session) + // DSH resolves its persisted selection; the bridge never replays its log. expect(h.agentPresets.mount).toHaveBeenCalledWith('agent-ctx', 'cordis') }) @@ -305,7 +299,7 @@ describe('DSH ACP socket transport', () => { // A human types in the DSH web UI while our message waits in the inbox. h.sessionEvent(sessionId, 'turn/start', { turn: 1 }) h.sessionEvent(sessionId, 'user/message', { id: 'human-message', role: 'user', content: [{ type: 'text', text: 'hi' }] }) - h.sessionEvent(sessionId, 'assistant/chunk', { turn: 1, step: 0, chunk: { type: 'reasoning-delta', text: 'human thought' } }) + h.assistantStream(sessionId, { turn: 1, step: 0, chunk: { type: 'reasoning-delta', text: 'human thought' } }) h.sessionEvent(sessionId, 'assistant/message', { turn: 1, step: 0, @@ -326,11 +320,11 @@ describe('DSH ACP socket transport', () => { h.sessionEvent(sessionId, 'assistant/message', { turn: 2, step: 0, - message: { content: [{ type: 'text', text: 'ccteam answer' }] }, + message: { content: [{ type: 'reasoning', text: 'settled thought' }, { type: 'text', text: 'ccteam answer' }] }, usage: { inputTokens: 7, outputTokens: 3 }, }) h.sessionEvent(sessionId, 'tool/call', { turn: 2, step: 0, callId: 'ours', name: 'bash', arguments: '{"a":1}' }) - h.sessionEvent(sessionId, 'tool/result', { turn: 2, step: 0, message: { source: { callId: 'ours' }, content: [] } }) + h.sessionEvent(sessionId, 'tool/result', { turn: 2, step: 0, message: { source: { callId: 'ours' }, content: [], isError: true } }) h.sessionEvent(sessionId, 'turn/end', { turn: 2, reason: { kind: 'completed' } }) const result = await prompt @@ -338,8 +332,10 @@ describe('DSH ACP socket transport', () => { stopReason: 'end_turn', _meta: { inputTokens: 7, outputTokens: 3 }, }) + expect(client.updates).toContainEqual({ sessionId, update: expect.objectContaining({ sessionUpdate: 'tool_call_update', toolCallId: 'ours', status: 'failed', isError: true }) }) const texts = JSON.stringify(client.updates) expect(texts).toContain('ccteam answer') + expect(texts).toContain('settled thought') expect(texts).not.toContain('human answer') expect(texts).not.toContain('human thought') expect(texts).not.toContain('human-call') diff --git a/dsh-plugins/pack-assets.sh b/dsh-plugins/pack-assets.sh index a21c3cb1f..c612a66c3 100755 --- a/dsh-plugins/pack-assets.sh +++ b/dsh-plugins/pack-assets.sh @@ -75,7 +75,12 @@ for plugin in "${plugins[@]}"; do # Two-path reproducibility gate: same source, different directory, same bytes. mirror="$staging/mirror-$plugin" rm -rf "$mirror" - cp -al "$src" "$mirror" 2>/dev/null || cp -a "$src" "$mirror" + if ! cp -al "$src" "$mirror" 2>/dev/null; then + # A cross-filesystem hardlink failure can leave a partial destination. + # Recreate it so the fallback cannot nest the source beneath that directory. + rm -rf "$mirror" + cp -a "$src" "$mirror" + fi (cd "$mirror" && npm run build >/dev/null) mirror_out="$staging/$plugin-mirror" mkdir -p "$mirror_out"