diff --git a/AGENTS.md b/AGENTS.md index bfe94417..61e69326 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,7 +37,7 @@ AgentMessage[] → transform_context() → convert_to_llm() → LangChain BaseMe | `packages/pi-mcp/` | Optional extension `pi-mcp-py` (entry point `pi-mcp`): connects the MCP servers (stdio, http, sse) that an ACP client passes in `session/new`, one runner task per server, tools named `mcp____`, `/mcp` command. Core, harness and CLI have no MCP client; they hand the servers over as data (`ClientMcpServer`, `ExtensionAPI.client_mcp_servers`). Design: `docs/specs/2026-10-23-mcp-extension-design.md`. Its tests are left out of a run that did not install the package (the header says so) | — | | `packaging/agent/` | Builds the agent as a one-file executable (PyInstaller; `build.py`, `zycode-agent.spec`, `entry.py`) and the smoke test over ACP with a fake model server (`smoke.py`). `.github/workflows/agent-binaries.yml` runs both on five platforms and attaches the archives to the GitHub Release after the `Release` workflow. A frozen build has no interpreter: `host.py` starts a frozen `child.py`, and only the OpenAI-format provider is in it. Design and the registry entry draft: `docs/specs/2026-10-31-agent-binaries.md` | — | | `packages/pi-agent-cli/` | Standard-ACP CLI (`python -m pi_agent_cli`); config `agent.example.toml`, home `~/.pi-python`; TUI binary `zypi` under `tui/`. See [`packages/pi-agent-cli/AGENTS.md`](packages/pi-agent-cli/AGENTS.md) for spawn, config, first run (`--setup` and the terminal login that `initialize` offers to a client that can run one; `zypi` still gets `authMethods: []`), prompt pipeline, and the three permission modes `ask`, `auto` and `always-approve` (Auto: rules, and a model step for a `bash` call that no rule decides: `auto_rules.py`, `auto_classifier.py`, `auto_mode.py`, `permission_modes.py`; design `docs/specs/2026-11-02-auto-mode-design.md`; the pager offers all three, and a clean start is `ask`). The items on the ACP wire beyond standard ACP (`pi/session/delete`, `_meta.sessionId`, and others) are in the register `docs/specs/2026-11-05-acp-extension-registry.md` | — | -| `vscode/` | The VS Code extension `zycode.zycode` (TypeScript, `src/core/` has no `vscode` import). It is an ACP client and a chat view for zycode in VS Code: it starts the agent as a child process, shows permission questions and a diff for `write`/`edit`, runs the first-run setup in a terminal (a walkthrough, a banner and hints guide the first start; the agent's own `--setup` and `auth_required` carry it, no wire item and no agent change), lists past chats of the folder in a view (`session/list`, `session/load`, `pi/session/delete`), and offers the three permission modes `ask`, `auto` and `always-approve` in the chat menu (`always-approve` only after a modal warning, never restored for a new chat, a past chat or a restart, and not in an untrusted workspace; the `zycode.permissionMode` setting stays `ask`/`auto`; it moves the agent with the agent's own `pi/yolo_mode_changed`). The extension never writes `always-approve` through `set_config_option`. A Thinking menu shows `thinking_level` of `agent.toml`; agent 0.9.1 cannot change it live, so a change edits that one line (checked by parsing, after a modal), restarts the agent and reopens the chat (design section 7c). One VSIX per platform, each with the bundled `zycode-agent` binary of one Release (pin and sha256 in `agent-assets.json`). Not a Python package: pytest does not read it, and ruff reads only its three Python helpers (`scripts/python_licenses.py`, `test/support/fake_model.py`, `test/support/pty_answers.py`). `.github/workflows/vscode-extension.yml` runs `npm run check` and a `linux-x64` VSIX smoke test for a PR that changes it. `ci.yml` (Ruff, pytest) skips a PR that touches only `vscode/`, `docs/` and that workflow, with no `.py` file (a `paths` filter with `!` patterns; no check is required on `main`, so a skipped run blocks no merge). The same workflow publishes (Trusted Publishing, by hand on `main`, environment `vscode-publish`). Version 1.0.0 is the first stable release (the pre-release 0.1.0 was uploaded by hand; the 1.0.0 run, run `43181052994` on 2026-11-12, was the first real OIDC publish and worked on the first try on Open VSX and the Marketplace, five targets, no token; the record is in `vscode/RELEASING.md`; stable = even minor, pre-release = odd minor). Do not rename that file or the environment: the registries name them. Local check: `cd vscode; npm run check`. Design: `docs/specs/2026-11-04-vscode-extension-design.md`; make and test a VSIX: `vscode/PACKAGING.md`; releases: `vscode/RELEASING.md` | — | +| `vscode/` | The VS Code extension `zycode.zycode` (TypeScript, `src/core/` has no `vscode` import). It is an ACP client and a chat view for zycode in VS Code: it starts the agent as a child process, shows permission questions and a diff for `write`/`edit`, runs the first-run setup in a terminal (a walkthrough, a banner and hints guide the first start; the agent's own `--setup` and `auth_required` carry it, no wire item and no agent change), lists past chats of the folder in a view (`session/list`, `session/load`, `pi/session/delete`), and offers the three permission modes `ask`, `auto` and `always-approve` in the chat menu (`always-approve` only after a modal warning, never restored for a new chat, a past chat or a restart, and not in an untrusted workspace; the `zycode.permissionMode` setting stays `ask`/`auto`; it moves the agent with the agent's own `pi/yolo_mode_changed`). The extension never writes `always-approve` through `set_config_option`. A Thinking menu shows `thinking_level` of `agent.toml`; agent 0.9.1 cannot change it live, so a change edits that one line (checked by parsing, after a modal), restarts the agent and reopens the chat (design section 7c). One VSIX per platform, each with the bundled `zycode-agent` binary of one Release (pin and sha256 in `agent-assets.json`). Not a Python package: pytest does not read it, and ruff reads only its three Python helpers (`scripts/python_licenses.py`, `test/support/fake_model.py`, `test/support/pty_answers.py`). `.github/workflows/vscode-extension.yml` runs `npm run check` and a `linux-x64` VSIX smoke test for a PR that changes it. `ci.yml` (Ruff, pytest) skips a PR that touches only `vscode/`, `docs/` and that workflow, with no `.py` file (a `paths` filter with `!` patterns; no check is required on `main`, so a skipped run blocks no merge). The same workflow publishes (Trusted Publishing, by hand on `main`, environment `vscode-publish`). 1.0.0 was the first stable release (the pre-release 0.1.0 was uploaded by hand; the 1.0.0 run, run `43181052994` on 2026-11-12, was the first real OIDC publish and worked on the first try on Open VSX and the Marketplace, five targets, no token; the record is in `vscode/RELEASING.md`; the owner tested the install of 1.0.0 and the update from 0.1.0 on Windows (2026-11-12), and the rest of the Windows flow, macOS and Linux in a real VS Code are not verified; stable = even minor, pre-release = odd minor). Do not rename that file or the environment: the registries name them. Local check: `cd vscode; npm run check`. Design: `docs/specs/2026-11-04-vscode-extension-design.md`; make and test a VSIX: `vscode/PACKAGING.md`; releases: `vscode/RELEASING.md` | — | | `web/` | Product site for zycode, live at `https://zycode.dev` (static Astro, Node ≥ 22.23.4; English at `/`, Chinese at `/zh/`; Cloudflare Workers static assets, no server of ours). Not a Python package: ruff and pytest do not read it; `.github/workflows/web-ci.yml` runs `npm run check` and `npm run build` for a PR that changes `web/`. A merge to `main` that changes `web/` deploys to `zycode.dev` by itself (Cloudflare Workers Builds, not GitHub Actions). Its claims must stay equal to the README table "Status" and to the benchmark report, and `en.ts` / `zh.ts` keep the same keys. See [`web/README.md`](web/README.md) for commands, deploy, the domain, and the Cloudflare settings that are not in the repository | — | ### Invariants diff --git a/README.md b/README.md index f9b0d946..c2d2de41 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ This repository is a monorepo containing: | `pi-dynamic-workflows-py` | **Beta** | [PyPI](https://pypi.org/project/pi-dynamic-workflows-py/) | | `pi-mcp-py` | **Alpha** | [PyPI](https://pypi.org/project/pi-mcp-py/) (from 0.6.0) | | `zypi` (Rust TUI) | **Beta** | [GitHub Releases](https://github.com/zy1233/pi-python/releases) (Linux x86_64) | -| `zycode.zycode` (VS Code extension) | **Beta** | [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=zycode.zycode) and [Open VSX](https://open-vsx.org/extension/zycode/zycode) (version 1.0.0, five platforms; Windows and macOS not verified) | +| `zycode.zycode` (VS Code extension) | **Beta** | [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=zycode.zycode) and [Open VSX](https://open-vsx.org/extension/zycode/zycode) (five platforms; the project owner tested the install of 1.0.0 and the update from 0.1.0 to 1.0.0 on Windows on 2026-11-12; the rest of the Windows flow, macOS and Linux in a real VS Code are not verified) | ## Features @@ -157,10 +157,10 @@ The extension `zycode.zycode` ([`vscode`](vscode/README.md)) puts the zycode age - **VS Code:** install it from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=zycode.zycode), or run `code --install-extension zycode.zycode`. - **VSCodium and other editors that use Open VSX:** install it from [Open VSX](https://open-vsx.org/extension/zycode/zycode). -- **Platforms:** version 1.0.0 is published on both registries for five platforms: `linux-x64`, `linux-arm64`, `darwin-arm64`, `darwin-x64` and `win32-x64`. Each one has the agent program inside. +- **Platforms:** the extension is published on both registries for five platforms: `linux-x64`, `linux-arm64`, `darwin-arm64`, `darwin-x64` and `win32-x64`. Each one has the agent program inside. - **What it has:** a chat view with a question card for each tool call that is not read-only; a **Show diff** button for `write` and `edit`; a getting-started guide and help for the first start (the setup runs in a VS Code terminal); a **Past chats** view; a mode menu (**Ask**, **Auto** and **Always approve**); and a **Thinking** menu. - **Auto is not a sandbox.** It guards what a call writes or runs. It does not guard what a call reads or sends. Use a container or a virtual machine if you need a boundary. See the [extension README](vscode/README.md) for the three modes. -- **Limits:** the bundled agent is not code-signed, and it has only the OpenAI-format provider. The repository lists Windows and macOS as not verified. An install of 1.0.0 in a real VS Code is not measured yet. The release record is in [`RELEASING.md`](vscode/RELEASING.md). +- **Limits:** the bundled agent is not code-signed, and it has only the OpenAI-format provider. The project owner tested the install of 1.0.0 and the update from 0.1.0 to 1.0.0 on Windows (2026-11-12). The rest of the Windows flow (the first-run setup in a terminal, a chat with a model, the unsigned agent on other machines or under an antivirus), all macOS flows, and Linux in a real VS Code are not verified. The release record is in [`RELEASING.md`](vscode/RELEASING.md). ### In Zed (custom agent) diff --git a/docs/specs/2026-11-04-vscode-extension-design.md b/docs/specs/2026-11-04-vscode-extension-design.md index 4e180328..bdb3089d 100644 --- a/docs/specs/2026-11-04-vscode-extension-design.md +++ b/docs/specs/2026-11-04-vscode-extension-design.md @@ -59,7 +59,7 @@ Rules: 2. The webview has no access to the file system, the agent or the network. It is a view: it gets state and events, and it sends intents. Its content security policy allows only the extension's own script and style, with a nonce, and no remote content. 3. The extension runs in the workspace host (`extensionKind: ["workspace"]`). With Remote-SSH, WSL or a dev container, the agent then runs next to the files it edits, and VS Code installs the platform VSIX of the remote machine. 4. One agent process for each window. One active chat session at a time. A new chat is a new `session/new` on the same process. -5. Text that the agent or the model sends is data. The webview builds the page with DOM nodes and `textContent`. It never sets `innerHTML` from agent text. A Markdown parser (`markdown-it`) reads the text and gives tokens. `core/markdown.ts` changes the tokens to plain data (blocks and inline items), and `webview/dom.ts` makes DOM nodes from that data. The HTML option of the parser is off, so raw HTML in the text stays text. The webview never loads an image: it shows an image as a link. A link opens through the host, and only `http`, `https` and `mailto` (D34). +5. Text that the agent or the model sends is data. The webview builds the page with DOM nodes and `textContent`. It never sets `innerHTML` from agent text. A Markdown parser (`markdown-it`) reads the text and gives tokens. `core/markdown.ts` changes the tokens to plain data (blocks and inline items), and `webview/dom.ts` makes DOM nodes from that data. The HTML option of the parser is off, so raw HTML in the text stays text. The webview never loads an image: it shows an image as a link. A link opens through the host, and only `http`, `https` and `mailto` (D34). The reader keeps at most 32 marks open; a deeper mark is dropped and its text stays, so a hostile text cannot make a deep DOM tree. ### 2.1 Files @@ -257,7 +257,7 @@ Other facts: - The key is typed in the terminal. It never goes through the webview, the host's own variables or the log. The extension does not use `SecretStorage` in Phase 1: the agent and `zypi` read `~/.pi-python`, and a second place for the key would make the two disagree (report section 5). - The command is also available when a session exists, to change the model or key later. It then ends with a restart of the agent and a new chat. While a setup terminal is open, a second click does nothing. -- `shellPath` with a binary and `shellArgs` makes VS Code run the binary directly in the pty. On Windows the agent binary is a console program, and this works the same. (Not verified on Windows or macOS: section 13.) +- `shellPath` with a binary and `shellArgs` makes VS Code run the binary directly in the pty. On Windows the agent binary is a console program, and this works the same. (Not verified on Windows or macOS: section 13. The owner's Windows test covers the install and the update only.) ## 7a. The first-run guide (1.0.0) @@ -530,7 +530,7 @@ From the report, section 3.3 (Phase 2 and later), and from the limits above: **Commands** (run from `vscode`, and pasted into each pull request): `npm ci`, `npm run typecheck`, `npm run lint`, `npm test`, and for the real agent `npm run test:agent`. The workflow in section 11 runs the same checks on a pull request that changes the extension. -**Result (2026-11-12): the Open VSX and Marketplace publish path is now measured (1.0.0, five targets, OIDC, no token); an install of 1.0.0 in a real VS Code is not.** **Not verified on any machine of ours:** Windows and macOS (the extension host, the terminal for `--setup`, the exec bit after a VS Code install, the first start of an unsigned binary under Gatekeeper and SmartScreen, a Windows antivirus on a PyInstaller file), Remote-SSH, WSL and dev containers, the Marketplace scan of the VSIX, Open VSX. The README lists the limits that remain. +**Result (2026-11-12): the Open VSX and Marketplace publish path is now measured (1.0.0, five targets, OIDC, no token); the project owner tested, on Windows, the install of 1.0.0 and the update from 0.1.0 to 1.0.0 (his word, 2026-11-12, 18:17 Asia/Shanghai; nothing else is claimed from that test).** **Not verified on any machine of ours:** the rest of the Windows flow (the extension host, the terminal for `--setup`, a chat with a model, the first start of an unsigned binary under SmartScreen on other machines, a Windows antivirus on a PyInstaller file) and all macOS flows (the extension host, the terminal for `--setup`, the exec bit after a VS Code install, the first start of an unsigned binary under Gatekeeper), Linux in a real VS Code, Remote-SSH, WSL and dev containers, the Marketplace scan of the VSIX, Open VSX. The README lists the limits that remain. ## 14. Decisions @@ -579,5 +579,5 @@ From the report, section 3.3 (Phase 2 and later), and from the limits above: | D35 (built) | The webview draws the text of a streamed answer at most once each 80 ms. The first piece is drawn at once. The pieces that come in a pause are joined into one draw at the end of the pause, counted from the end of the last draw. The last piece is always drawn. A reset of the chat cancels a draw that is planned. | A draw makes the DOM of the whole answer again, so its cost grows with the length of the answer. We measured it in headless Chrome, with the text already N characters long (the median of 25 pieces of 4 characters, two runs): 7 ms at 3,000 characters, 16 ms at 8,000 and 37 ms at 20,000. The reader of 1.0.0 took 5, 12 and 26 ms, so the new reader is about 1.4 times slower for each draw. The host sends each piece at once. If a model sends 50 pieces each second (an assumption: we did not measure a real model), the time between two pieces is 20 ms, and a draw of 37 ms does not fit in it. With the pause there are at most 12 draws each second, so a draw of 37 ms uses less than half of the time of the page. A change that draws only the last block again is a later option, if 80 ms is not enough. | | D36 (built) | **Recommended: as built.** The context and the cost in the status row come only from the standard `usage_update` of the agent. The extension counts no tokens, has no setting for the size of the window, and shows nothing when the agent sends nothing. Options not taken: **B**, an estimate that the extension makes (for example characters ÷ 4); **C**, a setting `zycode.contextWindow`; **D**, change the agent now (the user said: write down the investigation only). | Agent 0.9.1 does not know the real window (`Model.context_window` is 128,000 for every model, and no key sets it) and has no price (`cost.total` is 0 in all 21 messages that we read), so a number from the extension would be a guess that looks like a fact. The pager made the same decision on 2026-10-13: it shows no context and no cost, and no number is invented. The row shows by itself when an agent sends `usage_update`, with no change in the extension. The findings and the agent change that is needed are in `docs/specs/2026-11-13-agent-context-usage.md`. | | D37 (built) | **Recommended: as built.** The time of the turn is measured by the clock of the host, and the folder comes from the session. Both show in the header (and in the tooltip of the status bar item). The page counts the running time with its own timer, and changes one element each 500 ms. Options not taken: a clock in the status bar item (it needs a timer in the extension host each second); the tokens of the last turn (the agent does not send them). | The agent sends no times, and the pager draws clocks of its own for the same reason (2026-10-13). A tick that changes one element leaves a menu that is open alone; a new `state` message for each second would close it. The host sends a fresh `elapsedMs` in each `state` message, so a clock that has drifted is set right at the next one. | -| D38 (built) | **Recommended: as built.** The extension lives in `vscode/`, at the root of the repository, and not in `packages/pi-vscode/`. The workflow file `vscode-extension.yml` and the environment `vscode-publish` keep their names. Options not taken: **B**, keep `packages/pi-vscode/`; **C**, a longer name (`zycode-vscode/`, `extensions/vscode/`). | `packages/` holds the six Python packages. Each has a `pyproject.toml`, and the uv workspace and the pytest paths in the root `pyproject.toml` name them one by one. The extension is not Python and is in neither list. `web/` and `tui/` are at the root for the same reason, and each has its own CI, as the extension has (`vscode-extension.yml`). **The move does not change which CI runs for a change.** `ci.yml` (Ruff and pytest) has no `paths` filter, so it runs for every pull request, also for one that only touches `web/`. What the move changed: `REPO_ROOT` in `scripts/lib.mjs` and the three `../../..` in the opt-in tests; the test that reads the workflow; `repository.directory`; and the workflow (8 `working-directory`, the artifact path, the trigger path, and three `../../vsix/` that have no `pi-vscode` in the text, so a search by name misses them; two of them are in the publish job, which does not run in a pull request). A test now checks all of these against the real layout (`points every path of the workflow at the real layout`). The two registries name the repository, the workflow file and the environment, and not a directory, so the trusted publishers stay valid. Old links to `packages/pi-vscode/...` on GitHub do not work any more. The README of 1.0.0 names the old folder as text only, with no link (the next release says `vscode`), and its CHANGELOG and walkthrough have no link into it. | +| D38 (built) | **Recommended: as built.** The extension lives in `vscode/`, at the root of the repository, and not in `packages/pi-vscode/`. The workflow file `vscode-extension.yml` and the environment `vscode-publish` keep their names. Options not taken: **B**, keep `packages/pi-vscode/`; **C**, a longer name (`zycode-vscode/`, `extensions/vscode/`). | `packages/` holds the six Python packages. Each has a `pyproject.toml`, and the uv workspace and the pytest paths in the root `pyproject.toml` name them one by one. The extension is not Python and is in neither list. `web/` and `tui/` are at the root for the same reason, and each has its own CI, as the extension has (`vscode-extension.yml`). **The move itself does not change which CI runs for a change.** Before D39, `ci.yml` (Ruff and pytest) had no `paths` filter, so it ran for every pull request, also for one that only touches `web/`; D39 adds the filter, in a separate commit. What the move changed: `REPO_ROOT` in `scripts/lib.mjs`, the three `../../..` in the opt-in tests, and `parents[4]` in `test/support/fake_model.py` (the first commit of the move missed it; the review found it, because `npm run test:agent` failed 7 tests with `fake model exited with 1` until it was `parents[3]`; those tests are off in CI); the test that reads the workflow; `repository.directory`; and the workflow (8 `working-directory`, the artifact path, the trigger path, and three `../../vsix/` that have no `pi-vscode` in the text, so a search by name misses them; two of them are in the publish job, which does not run in a pull request). A test now checks all of these against the real layout (`points every path of the workflow at the real layout`). The two registries name the repository, the workflow file and the environment, and not a directory, so the trusted publishers stay valid. Old links to `packages/pi-vscode/...` on GitHub do not work any more. The README of 1.0.0 names the old folder as text only, with no link (the next release says `vscode`), and its CHANGELOG and walkthrough have no link into it. | | D39 (built) | **Recommended: as built.** `ci.yml` (Ruff and pytest) does not run for a change that touches only `vscode/`, `docs/` and `vscode-extension.yml`, with no `.py` file. It is a `paths` filter with `!` patterns, on `push` and on `pull_request`. Options not taken: a `paths-ignore` filter with `vscode/**` (it would also skip the three Python helpers in `vscode/`, which Ruff reads); also leaving out `web/**` and `tui/**` (the same idea, but not asked for, and not checked against their tests). | A change of the extension started the whole Python CI (Ruff, and pytest on three Python versions, about 15 minutes of runner time), because `ci.yml` had no filter. For a pull request that only changes the extension and its documents, no Python result can change. No test reads `vscode/` or `docs/`, `docs` is in `extend-exclude` of Ruff, and `docs/` has Markdown only. Ruff does read the three Python files in `vscode/` (`scripts/python_licenses.py`, `test/support/fake_model.py`, `test/support/pty_answers.py`), so the last pattern, `**/*.py`, puts every Python file back (GitHub reads the patterns in order, and a positive pattern after a `!` pattern puts a file back). The workflow still runs when any other file is in the change (for example `AGENTS.md`, `README.md`, or `ci.yml` itself). No check is required on `main` (the ruleset only forbids a deletion and a force push), so a skipped run blocks no merge. Not verified: the effect on GitHub. The pull request that adds the filter changes `ci.yml`, so it runs the workflow itself. The first pull request that touches only the extension shows the effect. | diff --git a/vscode/CHANGELOG.md b/vscode/CHANGELOG.md index 46b4d42a..e9045853 100644 --- a/vscode/CHANGELOG.md +++ b/vscode/CHANGELOG.md @@ -1,10 +1,12 @@ # Changelog -## Unreleased +## 1.2.0 (2026-11-13) + +Stable release. It follows 1.0.0 and has the same bundled agent (0.9.1). A user of 1.0.0 gets it as an update. ### New -- The chat shows Markdown as Markdown. Before, only paragraphs, lists, code and links worked, and a heading, a table or bold text showed its marks (`##`, `|---|`, `**`). Now the chat shows headings, bold, italic, strikethrough, nested lists, quotes, rules and tables (with column alignment, and a scroll bar for a wide table). Bold text next to Chinese, Japanese or Korean characters works (`**重要:**文本`). Raw HTML in an answer stays text, an image is not loaded (it shows as a link), and a link with an address that the extension does not open (for example `javascript:`) shows as plain text with its address. +- The chat shows Markdown as Markdown. Before, only paragraphs, lists, code and links worked, and a heading, a table or bold text showed its marks (`##`, `|---|`, `**`). Now the chat shows headings, bold, italic, strikethrough, nested lists, quotes, rules and tables (with column alignment, and a scroll bar for a wide table). Bold text next to Chinese, Japanese or Korean characters works (`**重要:**文本`). Raw HTML in an answer stays text, an image is not loaded (it shows as a link), and a link with an address that the extension does not open (for example `javascript:`) shows as plain text with its address. Bold, italic, strikethrough and links nest at most 32 levels deep, and a deeper one shows only its text. - The status row of the chat is richer. It shows the time of the turn (a clock while the agent works, then `last turn 8.4 s`) and the folder of the chat. When the agent sends the standard ACP message `usage_update`, a **Context** row shows a bar, the percent, the tokens and the cost, and the VS Code status bar shows `· context 42%`. The bar turns to a warning colour from 80 % and to a danger colour from 90 %. **Agent 0.9.1, which is bundled, does not send `usage_update`, so the Context row does not show with it yet.** The extension does not count tokens and does not guess the size of the window. The agent was not changed: [an investigation record](https://github.com/zy1233/pi-python/blob/main/docs/specs/2026-11-13-agent-context-usage.md) says what a change of the agent needs. diff --git a/vscode/PACKAGING.md b/vscode/PACKAGING.md index c7a7c0c6..83ca9c3b 100644 --- a/vscode/PACKAGING.md +++ b/vscode/PACKAGING.md @@ -23,7 +23,7 @@ The files in `out/` are ready for a hand upload. Do not edit them. ## Test a VSIX ``` -node scripts/smoke-vsix.mjs out/zycode-linux-x64-1.0.0.vsix +node scripts/smoke-vsix.mjs out/zycode-linux-x64-*.vsix ``` It unpacks the agent from the VSIX and starts it with a new home folder for each of three checks: @@ -34,7 +34,7 @@ It unpacks the agent from the VSIX and starts it with a new home folder for each Checks 2 and 3 are what the extension does for its mode menu and its Past chats view. Use a VSIX of the platform that you run on. -To try a VSIX in VS Code: `code --install-extension out/zycode-linux-x64-1.0.0.vsix`. +To try a VSIX in VS Code: `code --install-extension out/zycode-linux-x64-*.vsix`. ## Publish diff --git a/vscode/README.md b/vscode/README.md index 50363108..bb0bbeb2 100644 --- a/vscode/README.md +++ b/vscode/README.md @@ -95,7 +95,7 @@ The extension has no telemetry and sends nothing to its authors. The agent sends - The thinking level is a line of `agent.toml`, and a change restarts the agent (the chat is opened again; a running answer must end first; Always approve is off again after the restart). A live setting needs a change in the agent. - The **Context** row shows only when the agent sends the standard ACP message `usage_update`. Agent 0.9.1, which is bundled, does not send it, so the row does not show yet. The extension does not count tokens and does not guess the size of the window. What an agent change needs is in [the investigation record](https://github.com/zy1233/pi-python/blob/main/docs/specs/2026-11-13-agent-context-usage.md). - One chat at a time (open a past chat to switch). No model choice, no images, no `@file` mentions yet. -- Markdown in an answer: raw HTML shows as text, and an image is not loaded (it shows as a link). There is no math, no footnote and no task-list box: their marks show as text (but the line of a footnote definition, `[^1]: …`, does not show, because Markdown reads it as the definition of a link). A link with an address that the extension does not open (for example `javascript:`) shows its text and its address as plain text. +- Markdown in an answer: raw HTML shows as text, and an image is not loaded (it shows as a link). There is no math, no footnote and no task-list box: their marks show as text (but the line of a footnote definition, `[^1]: …`, does not show, because Markdown reads it as the definition of a link). A link with an address that the extension does not open (for example `javascript:`) shows its text and its address as plain text. Bold, italic, strikethrough and links nest at most 32 levels deep (all kinds count together), and a deeper one shows only its text. ## Third-party software diff --git a/vscode/RELEASING.md b/vscode/RELEASING.md index 4239685d..fd051ff7 100644 --- a/vscode/RELEASING.md +++ b/vscode/RELEASING.md @@ -11,7 +11,8 @@ This file is the checklist for a release. How the files are made is in `PACKAGIN - Open VSX took the five VSIX files by Trusted Publishing (OIDC), from 08:18:55Z to 08:19:17Z. Then the Marketplace took them with `vsce publish --oidc`, from 08:19:24Z to 08:19:57Z. Both registries accepted the first try with no token. So the OIDC path and both trusted publisher registrations are proven. - Time until the version was visible: Open VSX 08:23Z to 08:25Z (target by target, the scan queue). Marketplace 08:26Z to 08:28Z (validation, about 7 to 9 minutes after the upload). - Checked with the public APIs: five targets at 1.0.0 on both registries. `preRelease` is false on Open VSX. The Marketplace has no PreRelease property for 1.0.0 (0.1.0 had it). The sha256 of the five Open VSX files equals the `sha256sum` in the Publish log (the CI artifacts). -- Not verified: an install of 1.0.0 in a real VS Code, the update from 0.1.0 to 1.0.0, and the Windows and macOS flows. +- 2026-11-12, 18:17 Asia/Shanghai: the project owner tested, on Windows, the install of 1.0.0 and the update from 0.1.0 to 1.0.0. His words: "I tested the install of 1.0 and the update from 0.1 on Windows." Nothing else is claimed from this test. +- Not verified: the rest of the Windows flow (the first-run setup in a terminal, a chat with a model, the unsigned agent on other machines or under an antivirus), all macOS flows, and Linux in a real VS Code. ## Where things stand before the 1.0.0 run (read-only check on 2026-11-12, kept as history) diff --git a/vscode/package-lock.json b/vscode/package-lock.json index 9cd10e3b..2172f897 100644 --- a/vscode/package-lock.json +++ b/vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "zycode", - "version": "1.0.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zycode", - "version": "1.0.0", + "version": "1.2.0", "license": "MIT", "devDependencies": { "@agentclientprotocol/sdk": "2.4.0", diff --git a/vscode/package.json b/vscode/package.json index 2b750492..5de231e7 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -2,7 +2,7 @@ "name": "zycode", "displayName": "zycode", "description": "Chat with the zycode coding agent in VS Code. Unofficial; not affiliated with pi or Microsoft.", - "version": "1.0.0", + "version": "1.2.0", "publisher": "zycode", "icon": "media/icon.png", "galleryBanner": { diff --git a/vscode/scripts/check-vsix.mjs b/vscode/scripts/check-vsix.mjs index 35f9268c..e1365580 100644 --- a/vscode/scripts/check-vsix.mjs +++ b/vscode/scripts/check-vsix.mjs @@ -1,5 +1,5 @@ // Opens a VSIX and checks what the packaging promises (docs/specs/2026-11-04-vscode-extension-design.md, section 11). -// node scripts/check-vsix.mjs out/zycode-linux-x64-0.1.0.vsix +// node scripts/check-vsix.mjs out/zycode-linux-x64-*.vsix import { readFileSync } from "node:fs"; import path from "node:path"; diff --git a/vscode/scripts/smoke-vsix.mjs b/vscode/scripts/smoke-vsix.mjs index c01a877c..6013ab95 100644 --- a/vscode/scripts/smoke-vsix.mjs +++ b/vscode/scripts/smoke-vsix.mjs @@ -11,7 +11,7 @@ // process with the replay of the history, `_pi/yolo_mode_changed` (the mode must follow, and a // new session must not start in it after the extension puts `ask` back), and `_pi/session/delete`. // This is what the session list and the mode menu of the extension use. -// node scripts/smoke-vsix.mjs out/zycode-linux-x64-1.0.0.vsix +// node scripts/smoke-vsix.mjs out/zycode-linux-x64-*.vsix import { spawn } from "node:child_process"; import http from "node:http"; import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; diff --git a/vscode/src/core/markdown.ts b/vscode/src/core/markdown.ts index a92ee741..53c641e5 100644 --- a/vscode/src/core/markdown.ts +++ b/vscode/src/core/markdown.ts @@ -229,9 +229,12 @@ function withoutFinalNewline(text: string): string { return text.endsWith("\n") ? text.slice(0, -1) : text; } +/** Marks nest only this deep. A deeper mark is dropped and its text stays, so a hostile text cannot make a deep DOM tree. */ +const MAX_INLINE_DEPTH = 32; + type Frame = - | { kind: "strong" | "em" | "del"; children: Inline[] } - | { kind: "link"; url: string; children: Inline[] }; + | { kind: "strong" | "em" | "del"; children: Inline[]; flat?: true } + | { kind: "link"; url: string; children: Inline[]; flat?: true }; /** Reads the children of an `inline` token. A single newline is a line break, as in most chat apps. */ function readInline(token: Token | undefined): Inline[] { @@ -245,9 +248,12 @@ function readInline(token: Token | undefined): Inline[] { if (last?.t === "text") last.text += text; else target.push({ t: "text", text }); }; + const open = (frame: Frame) => { + stack.push(stack.length >= MAX_INLINE_DEPTH ? { ...frame, children: here(), flat: true } : frame); + }; const closeTop = () => { const frame = stack.pop(); - if (!frame) return; + if (!frame || frame.flat) return; if (frame.kind === "link") here().push({ t: "link", url: frame.url, children: frame.children }); else if (frame.kind === "strong") here().push({ t: "strong", children: frame.children }); else if (frame.kind === "em") here().push({ t: "em", children: frame.children }); @@ -270,25 +276,25 @@ function readInline(token: Token | undefined): Inline[] { here().push({ t: "br" }); break; case "strong_open": - stack.push({ kind: "strong", children: [] }); + open({ kind: "strong", children: [] }); break; case "strong_close": close("strong"); break; case "em_open": - stack.push({ kind: "em", children: [] }); + open({ kind: "em", children: [] }); break; case "em_close": close("em"); break; case "s_open": - stack.push({ kind: "del", children: [] }); + open({ kind: "del", children: [] }); break; case "s_close": close("del"); break; case "link_open": - stack.push({ kind: "link", url: attr(child, "href"), children: [] }); + open({ kind: "link", url: attr(child, "href"), children: [] }); break; case "link_close": close("link"); diff --git a/vscode/test/markdown.test.ts b/vscode/test/markdown.test.ts index 3ab3abbe..14b647f2 100644 --- a/vscode/test/markdown.test.ts +++ b/vscode/test/markdown.test.ts @@ -302,6 +302,26 @@ function checkBlocks(blocks: Block[]): void { } describe("parseMarkdown: hostile and random input", () => { + it("nests marks to a bounded depth and keeps the text", () => { + // Not recursive: the test must fail on an assertion, not on the stack of its own helper. + const depth = (inline: readonly Inline[]): number => { + let deepest = 0; + const todo: Array<[readonly Inline[], number]> = [[inline, 0]]; + for (let item = todo.pop(); item; item = todo.pop()) { + deepest = Math.max(deepest, item[1]); + for (const part of item[0]) if ("children" in part) todo.push([part.children, item[1] + 1]); + } + return deepest; + }; + for (const [open, close] of [["*a ", "a* "], ["**a ", "a** "], ["~~a ", "a~~ "]] as const) { + const [block] = parseMarkdown(open.repeat(5000) + close.repeat(5000)); + expect(block?.t).toBe("p"); + const inline = (block as Extract).inline; + expect(depth(inline)).toBeLessThanOrEqual(33); + expect(inlineText(inline).replace(/\s/g, "")).toBe("a".repeat(10000)); + } + }); + it("returns for text that is built to be slow or deep, and the result has a valid shape", () => { const inputs = [ "[".repeat(20000), diff --git a/vscode/test/support/fake_model.py b/vscode/test/support/fake_model.py index ad2ab314..9c1d99f0 100644 --- a/vscode/test/support/fake_model.py +++ b/vscode/test/support/fake_model.py @@ -19,7 +19,7 @@ from http.server import ThreadingHTTPServer from pathlib import Path -REPO = Path(__file__).resolve().parents[4] +REPO = Path(__file__).resolve().parents[3] sys.path.insert(0, str(REPO / "packaging" / "agent")) import smoke # noqa: E402 diff --git a/vscode/test/webview.test.ts b/vscode/test/webview.test.ts index 2199f2bb..4b321f56 100644 --- a/vscode/test/webview.test.ts +++ b/vscode/test/webview.test.ts @@ -77,6 +77,19 @@ describe.skipIf(!chrome || !built)("webview in headless Chrome", () => { expect(out.codeBlock).toBe("code"); }); + it("draws the items that follow a deeply nested answer when the chat reloads", () => { + const deep = "*a ".repeat(5000) + "a* ".repeat(5000); + const out = run(`${state} + state({}); + host({ type: "reset", transcript: [ + { type: "agent", id: "a1", text: ${lit(deep)}, thought: "" }, + { type: "notice", level: "info", text: "NOTICE-AFTER" }, + ] }); + window.__result = { text: document.querySelector("[data-testid=transcript]").textContent };`); + expect(out.ok).toBe(true); + expect(out.text).toContain("NOTICE-AFTER"); + }); + it("posts send on Enter, not on Shift+Enter, and only when ready", () => { const out = run(`${state} const input = document.querySelector("textarea"); diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index deda8ded..39fcd495 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -232,10 +232,10 @@ zypi`, code --install-extension ${links.vscodeExtensionId}`, notes: [ `The extension \`${links.vscodeExtensionId}\` puts the zycode agent in a chat view in VS Code. It has the agent program inside, so you do not install the Python agent first. Install it from the [Visual Studio Marketplace](${links.vscodeMarketplace}). For VSCodium and other editors that use Open VSX, install it from [Open VSX](${links.openVsx}).`, - 'Version 1.0.0 is published on both registries for five platforms: `linux-x64`, `linux-arm64`, `darwin-arm64`, `darwin-x64` and `win32-x64`.', + 'The extension is published on both registries for five platforms: `linux-x64`, `linux-arm64`, `darwin-arm64`, `darwin-x64` and `win32-x64`.', 'The chat view asks a question for each tool call that is not read-only. It has a **Show diff** button for `write` and `edit`, a getting-started guide, a Past chats view, a mode menu (Ask, Auto and Always approve) and a Thinking menu.', 'Auto is not a sandbox. It guards what a call writes or runs. It does not guard what a call reads or sends. Use a container or a virtual machine if you need a boundary.', - 'The bundled agent is not code-signed, and it has only the OpenAI-format provider. The repository lists Windows and macOS as not verified. An install of 1.0.0 in a real VS Code is not measured yet.', + 'The bundled agent is not code-signed, and it has only the OpenAI-format provider. The project owner tested the install of 1.0.0 and the update from 0.1.0 to 1.0.0 on Windows (2026-11-12). The rest of the Windows flow (the first-run setup in a terminal, a chat with a model, the unsigned agent on other machines or under an antivirus), all macOS flows, and Linux in a real VS Code are not verified.', ], }, zed: { @@ -289,7 +289,7 @@ PI_USE_MOCK=1 python -m pi_agent_cli -p "hello" --cwd .`, harness: 'Sessions, compaction, skills, execution environment', cli: 'ACP agent and headless mode', zypi: 'Terminal UI (prebuilt for Linux x86_64)', - vscode: 'VS Code extension. Version 1.0.0 is published on both registries for five platforms. The bundled agent is Beta. Windows and macOS are not verified.', + vscode: 'VS Code extension. It is published on both registries for five platforms. The bundled agent is Beta. The project owner tested the install of 1.0.0 and the update from 0.1.0 to 1.0.0 on Windows (2026-11-12). The rest of the Windows flow, macOS and Linux are not verified.', webAccess: 'Web search and fetch', goalX: 'Goal planning', workflows: 'Workflows', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index fedb296e..f995cae5 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -232,10 +232,10 @@ zypi`, code --install-extension ${links.vscodeExtensionId}`, notes: [ `扩展 \`${links.vscodeExtensionId}\` 把 zycode 智能体放进 VS Code 的聊天视图。它自带智能体程序,所以不必先安装 Python 智能体。请从 [Visual Studio Marketplace](${links.vscodeMarketplace}) 安装。VSCodium 和其他使用 Open VSX 的编辑器,请从 [Open VSX](${links.openVsx}) 安装。`, - '1.0.0 版已在两个注册表上发布,覆盖五个平台:`linux-x64`、`linux-arm64`、`darwin-arm64`、`darwin-x64` 和 `win32-x64`。', + '扩展已在两个注册表上发布,覆盖五个平台:`linux-x64`、`linux-arm64`、`darwin-arm64`、`darwin-x64` 和 `win32-x64`。', '对每个非只读的工具调用,聊天视图都会提问。它有用于 `write` 和 `edit` 的 **Show diff** 按钮、新手引导、Past chats 视图、模式菜单(Ask、Auto 和 Always approve)以及 Thinking 菜单。', 'Auto 不是沙箱。它管住调用写入或运行的内容,不管调用读取或发送的内容。需要边界时,请使用容器或虚拟机。', - '内置的智能体没有代码签名,并且只有 OpenAI 格式的提供方。仓库把 Windows 和 macOS 列为尚未验证。1.0.0 在真实 VS Code 中的安装还没有测量过。', + '内置的智能体没有代码签名,并且只有 OpenAI 格式的提供方。项目所有者在 Windows 上测试了 1.0.0 的安装,以及从 0.1.0 到 1.0.0 的更新(2026-11-12)。Windows 上的其余流程(在终端中进行首次运行设置、与模型聊天、未签名的智能体在其他机器上或在杀毒软件下的表现)、所有 macOS 流程,以及 Linux 上真实 VS Code 中的使用,都尚未验证。', ], }, zed: { @@ -289,7 +289,7 @@ PI_USE_MOCK=1 python -m pi_agent_cli -p "hello" --cwd .`, harness: '会话、压缩、技能、执行环境', cli: 'ACP 智能体与无界面模式', zypi: '终端界面(预编译版:Linux x86_64)', - vscode: 'VS Code 扩展。1.0.0 版已在两个注册表上发布,覆盖五个平台。内置的智能体为测试级。Windows 和 macOS 尚未验证。', + vscode: 'VS Code 扩展。已在两个注册表上发布,覆盖五个平台。内置的智能体为测试级。项目所有者在 Windows 上测试了 1.0.0 的安装,以及从 0.1.0 到 1.0.0 的更新(2026-11-12)。Windows 上的其余流程、macOS 和 Linux 尚未验证。', webAccess: '联网搜索与抓取', goalX: '目标规划', workflows: '工作流',