@piagent/platform 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  This file records release-facing changes for Pi Agent Platform. Copy the relevant version block into GitHub Releases when publishing a tag.
4
4
 
5
+ ## v1.1.1 - 2026-07-26
6
+
7
+ Fixes for defects found reviewing v1.1.0 after it shipped.
8
+
9
+ ### Fixed
10
+
11
+ - `piagent-setup --no-mcp` installs no MCP again. Setup only ever appended `--with-mcp` and relied on the installer defaulting MCP off; v1.1.0 changed that default to on, so the opt-out reached the installer as silence and the installer's default overrode the operator's explicit choice. It now passes `--no-mcp` through. The other install flags stay one-sided because the installer still defaults them off, which is the condition that made the pattern safe in the first place.
12
+ - `npm run site:check` no longer hangs when a response exceeds the 4 MB body cap. Hitting the cap destroys the response, and a destroyed response emits neither `end` nor `error` — it goes to `aborted` and `close` — so the promise stayed pending forever, and the request timeout could not rescue it because the socket was already gone. A release gate that hangs is worse than one that fails, because nothing reports it. A body cut short is also now reported as cut short rather than as a page that does not mention the version, which is a different claim.
13
+ - `npm run site:check` no longer exits `0` when it could not check an address. Unreachable addresses were printed as `UNVERIFIED` and then the run still ended in `PASS`, so a gate that verified fewer addresses than it resolved reported that it had verified them all. An unchecked address now ends the run; `--allow-unverified` accepts the gap deliberately and names it in the summary.
14
+ - `piagent_document_read` refuses bytes that are not valid UTF-8 or UTF-16 instead of decoding them into replacement characters. The binary check was a scan for a NUL byte, which plenty of binary formats do not carry in their first bytes, and everything past it was decoded leniently — so a binary file renamed to `.txt` came back as content-shaped garbage and was handed to the model. Decoding is now strict in both encodings, which is what the surrounding code already claimed to do.
15
+ - Two CodeQL high alerts came from building a regular expression out of a version string and escaping only `.`. Those assertions were comparing literal text, so they are substring checks now and need no escaping at all. Two further alerts were reviewed and dismissed with written reasons: extracting `&lt;script&gt;` from a document as `<script>` is the required result and the sink is a terminal tool result, not HTML; and the SHA-256 over a normalised shell command is a content identifier, not a password hash.
16
+
17
+ ### Changed
18
+
19
+ - The release checklist now requires the **Code scanning results** check on the `main` ruleset, separately from the `analyze (...)` jobs. Requiring those jobs proves only that the analysis ran and uploaded its results — they succeed just as well when the analysis found new high-severity alerts, which is how v1.1.0 merged green with open ones.
20
+
5
21
  ## v1.1.0 - 2026-07-26
6
22
 
7
23
  A document downloaded outside the repository can now be read by the agent, and the two documented install commands stop disagreeing about MCP.
package/README.md CHANGED
@@ -283,7 +283,7 @@ This repository intentionally excludes:
283
283
 
284
284
  ## Maturity
285
285
 
286
- The current package version is read from package metadata and release tags. Personal machines may follow the unpinned package source when accepting ongoing updates; production/team quickstarts and committed project settings should pin an explicit tag such as `v1.1.0` or a reviewed commit.
286
+ The current package version is read from package metadata and release tags. Personal machines may follow the unpinned package source when accepting ongoing updates; production/team quickstarts and committed project settings should pin an explicit tag such as `v1.1.1` or a reviewed commit.
287
287
 
288
288
  Ready for:
289
289
 
@@ -419,8 +419,8 @@ Các lệnh này chạy ngoài Pi.
419
419
  | Command | Dùng khi nào |
420
420
  |---|---|
421
421
  | `npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0` | Cài Pi CLI tương thích với release hiện tại. |
422
- | `npm install -g --ignore-scripts @piagent/platform@1.1.0` | Cài terminal helper `piagent-*` từ release tag hiện tại. |
423
- | `pi install git:github.com/Vt-mmm/piagent@v1.1.0` | Cài pinned release khi cần reproducible team setup. |
422
+ | `npm install -g --ignore-scripts @piagent/platform@1.1.1` | Cài terminal helper `piagent-*` từ release tag hiện tại. |
423
+ | `pi install git:github.com/Vt-mmm/piagent@v1.1.1` | Cài pinned release khi cần reproducible team setup. |
424
424
  | `pi install git:github.com/Vt-mmm/piagent` | Cài latest package platform cho máy cá nhân/sandbox. |
425
425
  | Cài exact Pi host của release, rồi `npm install -g --ignore-scripts @piagent/platform@X.Y.Z` và `piagent-install --stable` | Full update: đồng bộ host, npm-global helper và Pi package. Mỗi release pin một Pi host chính xác; lấy đúng version của release đang cài trong [release/install policy](release-install-policy.md). |
426
426
  | Cài exact Pi host ghi trong release cũ, rồi helper `vPREVIOUS` và `piagent-install --stable` | Full rollback; đánh giá dependency risk của host cũ trước khi hạ version. |
@@ -14,9 +14,9 @@ Repo này phải chạy được cho nhiều project/domain khác nhau. Vì vậ
14
14
 
15
15
  | Use case | Package source |
16
16
  |---|---|
17
- | Team pinned release | `git:github.com/Vt-mmm/piagent@v1.1.0` |
17
+ | Team pinned release | `git:github.com/Vt-mmm/piagent@v1.1.1` |
18
18
  | Personal/sandbox dev | `git:github.com/Vt-mmm/piagent` |
19
- | Registry distribution | `npm:@piagent/platform@1.1.0` |
19
+ | Registry distribution | `npm:@piagent/platform@1.1.1` |
20
20
  | Enterprise npm (fork under another scope) | `npm:@your-scope/platform@x.y.z` |
21
21
  | Local platform dev | `/path/to/piagent` |
22
22
 
@@ -26,7 +26,7 @@ Latest tiện cho máy cá nhân muốn nhận cập nhật nhanh. Pin tag/commi
26
26
 
27
27
  | Channel | Command | Target | Policy |
28
28
  |---|---|---|---|
29
- | `stable` | `bash scripts/install-global.sh --stable` | Helper release tag resolved to commit SHA, currently `v1.1.0` | Install the Pi package matching the helper. |
29
+ | `stable` | `bash scripts/install-global.sh --stable` | Helper release tag resolved to commit SHA, currently `v1.1.1` | Install the Pi package matching the helper. |
30
30
  | `exact` | `bash scripts/install-global.sh --version vX.Y.Z --resolve-tag` | Requested tag resolved to commit SHA | Pi-package-only rollout, rollback, and incident recovery. |
31
31
  | `dev` | `bash scripts/install-global.sh --dev` | Moving Git source | Personal/sandbox only; do not commit into project settings. |
32
32
  | `local` | `bash scripts/install-global.sh --local` | Current checkout path | Platform development only. |
@@ -53,7 +53,7 @@ Root `package.json` có `pi` manifest trỏ tới:
53
53
  Do đó team có thể:
54
54
 
55
55
  ```bash
56
- pi install git:github.com/Vt-mmm/piagent@v1.1.0
56
+ pi install git:github.com/Vt-mmm/piagent@v1.1.1
57
57
  ```
58
58
 
59
59
  Không cần biết internal folder `packages/piagent-core`. Source không pin chỉ dành cho personal/sandbox như bảng channel ở trên.
@@ -65,12 +65,12 @@ Team nên install global package một lần:
65
65
  ```bash
66
66
  node --version # >= 22.19.0
67
67
  npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
68
- npm install -g --ignore-scripts @piagent/platform@1.1.0
68
+ npm install -g --ignore-scripts @piagent/platform@1.1.1
69
69
  piagent-install --stable --dry-run
70
70
  piagent-install --stable
71
71
  ```
72
72
 
73
- `pi install git:github.com/Vt-mmm/piagent@v1.1.0` vẫn hợp lệ nếu chỉ cần cài Pi package. Lệnh đó không tự tạo các binary terminal `piagent-*`; muốn có helper global thì dùng `npm install -g --ignore-scripts @piagent/platform@1.1.0`.
73
+ `pi install git:github.com/Vt-mmm/piagent@v1.1.1` vẫn hợp lệ nếu chỉ cần cài Pi package. Lệnh đó không tự tạo các binary terminal `piagent-*`; muốn có helper global thì dùng `npm install -g --ignore-scripts @piagent/platform@1.1.1`.
74
74
 
75
75
  Support matrix hiện tại: macOS Apple Silicon + Bash và Linux x64 + Bash đã được verify; macOS Intel + Bash và Linux ARM64 + Bash là supported target cần chạy smoke trước rollout; native Windows chưa phải target rollout team; WSL2 experimental. Chi tiết và version runtime nằm trong [release/install policy](release-install-policy.md).
76
76
 
@@ -103,7 +103,7 @@ Nếu muốn commit sẵn `.pi/piagent-profile.json` vào repo hoặc bootstrap
103
103
  ```bash
104
104
  bash scripts/setup.sh /path/to/project \
105
105
  --profile be-readonly-fe \
106
- --package-source git:github.com/Vt-mmm/piagent@v1.1.0 \
106
+ --package-source git:github.com/Vt-mmm/piagent@v1.1.1 \
107
107
  --mcp-preset core \
108
108
  --subagents-preset safe
109
109
  ```
@@ -18,12 +18,12 @@ Support matrix release hiện tại: macOS Apple Silicon + Bash và Linux x64 +
18
18
  ```bash
19
19
  node --version # >= 22.19.0
20
20
  npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
21
- npm install -g --ignore-scripts @piagent/platform@1.1.0
21
+ npm install -g --ignore-scripts @piagent/platform@1.1.1
22
22
  piagent-install --stable --dry-run
23
23
  piagent-install --stable
24
24
  ```
25
25
 
26
- Khi seed `.pi/settings.json` cho team/repo cần audit lặp lại, giữ package source dạng pinned tag như `git:github.com/Vt-mmm/piagent@v1.1.0`. Máy cá nhân có thể dùng `git:github.com/Vt-mmm/piagent` để theo latest.
26
+ Khi seed `.pi/settings.json` cho team/repo cần audit lặp lại, giữ package source dạng pinned tag như `git:github.com/Vt-mmm/piagent@v1.1.1`. Máy cá nhân có thể dùng `git:github.com/Vt-mmm/piagent` để theo latest.
27
27
 
28
28
  Nếu đang ở source checkout của platform, dùng helper theo channel để preview trước khi đổi:
29
29
 
@@ -99,7 +99,7 @@ install package once
99
99
 
100
100
  ```bash
101
101
  npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
102
- npm install -g --ignore-scripts @piagent/platform@1.1.0
102
+ npm install -g --ignore-scripts @piagent/platform@1.1.1
103
103
  piagent-install --stable --dry-run
104
104
  piagent-install --stable
105
105
  ```
@@ -269,7 +269,7 @@ Không phải daily default. Dùng khi muốn tạo sẵn `.pi` files cho projec
269
269
  bash /path/to/piagent/scripts/setup.sh /path/to/project \
270
270
  --project-only \
271
271
  --profile auto \
272
- --package-source git:github.com/Vt-mmm/piagent@v1.1.0 \
272
+ --package-source git:github.com/Vt-mmm/piagent@v1.1.1 \
273
273
  --mcp-preset core \
274
274
  --subagents-preset safe
275
275
  ```
@@ -842,8 +842,8 @@ Watchdog là optional adversarial reviewer ở cuối turn, không bật mặc
842
842
 
843
843
  | Command | Dùng để |
844
844
  |---|---|
845
- | `npm install -g --ignore-scripts @piagent/platform@1.1.0` | Cài terminal helper `piagent-*` từ release tag hiện tại. |
846
- | `pi install git:github.com/Vt-mmm/piagent@v1.1.0` | Install pinned release cho reproducible team setup. |
845
+ | `npm install -g --ignore-scripts @piagent/platform@1.1.1` | Cài terminal helper `piagent-*` từ release tag hiện tại. |
846
+ | `pi install git:github.com/Vt-mmm/piagent@v1.1.1` | Install pinned release cho reproducible team setup. |
847
847
  | `pi install git:github.com/Vt-mmm/piagent` | Install latest platform package cho máy cá nhân/sandbox. |
848
848
  | Cài exact Pi host của release, rồi `npm install -g --ignore-scripts @piagent/platform@X.Y.Z` và `piagent-install --stable` | Full update: đồng bộ host, terminal helper và Pi package. Mỗi release pin một Pi host chính xác; lấy đúng version của release đang cài trong [release/install policy](release-install-policy.md). |
849
849
  | Cài exact Pi host ghi trong release cũ, rồi helper `vPREVIOUS` và `piagent-install --stable` | Full rollback; đánh giá lại dependency findings của host cũ trước khi hạ version. |
@@ -940,7 +940,7 @@ Mở lại Pi session sau khi install.
940
940
  Cài lại terminal helper đúng release rồi kiểm tra `PATH`:
941
941
 
942
942
  ```bash
943
- npm install -g --ignore-scripts @piagent/platform@1.1.0
943
+ npm install -g --ignore-scripts @piagent/platform@1.1.1
944
944
  command -v piagent-install
945
945
  ```
946
946
 
@@ -10,7 +10,7 @@ This file is the canonical install, update, rollback, and release checklist. Oth
10
10
 
11
11
  All supported environments require Node.js `>=22.19.0` and Pi Coding Agent `0.82.0`. The Pi host is installed as a Node CLI; Pi Agent Platform still defines its own release matrix because the terminal helpers and shell policy rely on Bash/POSIX behavior.
12
12
 
13
- | Surface | Status for v1.1.0 | Rollout guidance |
13
+ | Surface | Status for v1.1.1 | Rollout guidance |
14
14
  |---|---|---|
15
15
  | macOS Apple Silicon (`darwin/arm64`) + Bash | Verified for this release. | Safe default for team rollout after normal project smoke tests. |
16
16
  | Linux x64 + Bash | Verified in GitHub Actions. | Safe default for CI/server usage after normal project smoke tests. |
@@ -33,7 +33,7 @@ These three components are versioned independently. A full install, update, or r
33
33
 
34
34
  | Channel | Source shape | Mutability | Use when |
35
35
  |---|---|---:|---|
36
- | `stable` | `git:github.com/Vt-mmm/piagent@<resolved-commit-sha>` from `v1.1.0` | Fixed after resolution | Default for team rollout. |
36
+ | `stable` | `git:github.com/Vt-mmm/piagent@<resolved-commit-sha>` from `v1.1.1` | Fixed after resolution | Default for team rollout. |
37
37
  | `exact` | Tag, reviewed commit, or a tag resolved with `--resolve-tag` | Fixed when using a commit SHA | Pi-package-only roll forward, rollback, or reproduction. |
38
38
  | `dev` | `git:github.com/Vt-mmm/piagent` | Moving | Personal machine or sandbox only. |
39
39
  | `local` | `/path/to/piagent` | Local workspace | Platform development and dry-run validation. |
@@ -53,7 +53,7 @@ Use this flow when the team needs both terminal commands and the Pi package:
53
53
  ```bash
54
54
  node --version # >= 22.19.0
55
55
  npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
56
- npm install -g --ignore-scripts @piagent/platform@1.1.0
56
+ npm install -g --ignore-scripts @piagent/platform@1.1.1
57
57
  piagent-install --stable --dry-run
58
58
  piagent-install --stable
59
59
  ```
@@ -61,8 +61,8 @@ piagent-install --stable
61
61
  The stable preview and apply output includes:
62
62
 
63
63
  ```text
64
- currentRelease: v1.1.0 (helper package version)
65
- tag: v1.1.0
64
+ currentRelease: v1.1.1 (helper package version)
65
+ tag: v1.1.1
66
66
  resolvedCommit: <40-char-sha>
67
67
  source: git:github.com/Vt-mmm/piagent@<40-char-sha>
68
68
  ```
@@ -79,7 +79,7 @@ bash scripts/install-global.sh --stable
79
79
  Use this only when terminal commands are not needed:
80
80
 
81
81
  ```bash
82
- pi install git:github.com/Vt-mmm/piagent@v1.1.0
82
+ pi install git:github.com/Vt-mmm/piagent@v1.1.1
83
83
  ```
84
84
 
85
85
  Direct `pi install` does not create `piagent-*` commands on `PATH`.
@@ -192,9 +192,12 @@ Before broad team access, a repository administrator must enable these external
192
192
  3. Enable Dependency graph, Dependabot alerts, and Dependabot security updates. Keep `.github/dependabot.yml` enabled for scheduled npm and GitHub Actions update pull requests.
193
193
  4. Enable GitHub private vulnerability reporting so the form linked by `SECURITY.md` works before public rollout.
194
194
  5. Keep secret scanning and push protection enabled. Review CodeQL results after the workflow's first successful run.
195
+ 6. Add the **Code scanning results** check to the `main` branch ruleset and set its alert threshold so that a new high-severity CodeQL alert blocks the merge. The `analyze (...)` jobs required above only prove the analysis ran and uploaded its SARIF; they succeed just as well when the analysis found new high-severity alerts. Without this separate check, a pull request merges green with open high alerts, which is what happened on `v1.1.1`. Requiring the `analyze` jobs is still necessary — it catches an analysis that failed to run — but it is not the results gate and must not be mistaken for one.
195
196
 
196
197
  Verify these settings in GitHub **Settings → Rules** and **Settings → Code security and analysis**. Treat any missing control as an explicit rollout blocker, not as a passing source-code gate. Repository rules also close the remaining mutability risk of bootstrap-by-tag; tag-to-SHA resolution inside `piagent-install` alone cannot protect the helper package before it starts.
197
198
 
199
+ Alerts dismissed as false positives carry their reason in the dismissal comment, and the reason belongs in the source as well when the flagged shape is deliberate. An alert dismissed without a written reason is indistinguishable from an alert nobody read.
200
+
198
201
  ### Required npm publishing controls
199
202
 
200
203
  The publish workflow authenticates with the `NPM_TOKEN` repository secret. That token needs three things, and read-and-write access alone is not enough:
@@ -270,7 +273,9 @@ Re-running the workflow from a branch instead skips publishing, because the publ
270
273
  npm run site:check
271
274
  ```
272
275
 
273
- Opening the site in a browser does not answer this. The domain has several address records, so one can be dead or looping while every manual look lands on a healthy one. This resolves both hosts, asks each address directly with the right `Host` header and SNI, and requires each to return the released version. A redirect from an address back to its own host is reported as a loop rather than followed. An address the machine has no route to is reported as unverified, not as a pass.
276
+ Opening the site in a browser does not answer this. The domain has several address records, so one can be dead or looping while every manual look lands on a healthy one. This resolves both hosts, asks each address directly with the right `Host` header and SNI, and requires each to return the released version. A redirect from an address back to its own host is reported as a loop rather than followed.
277
+
278
+ An address the machine has no route to ends the run. It is reported separately from a site failure, because the machine is what is missing rather than the site, but it is not a pass either — the release gate must not report that it verified every address in a run where it verified fewer. Run the check from a machine with routes to every address family the domain publishes. `--allow-unverified` accepts the gap deliberately and names it in the summary; use it only when the missing route is understood and the remaining addresses were verified.
274
279
  9. Publish the already-reviewed draft only after production verification passes:
275
280
 
276
281
  ```bash
@@ -21,7 +21,7 @@ This document covers the packaged guard, project profiles, capability locks, ins
21
21
  - The operator, operating-system account, Pi host, and explicitly trusted project repository are trusted to execute code.
22
22
  - Project-local Pi resources are loaded only after Pi project trust or an explicit operator override.
23
23
  - A human remains available to approve destructive and external-provider actions.
24
- - Supported release verification covers macOS Apple Silicon + Bash, Linux x64 + Bash, Node.js `>=22.19.0`, and the exact Pi host version declared by the release. macOS Intel + Bash and Linux ARM64 + Bash are supported targets that need local smoke verification before broad rollout. Native Windows is not a team-rollout target for v1.1.0, and WSL2 is experimental/unverified.
24
+ - Supported release verification covers macOS Apple Silicon + Bash, Linux x64 + Bash, Node.js `>=22.19.0`, and the exact Pi host version declared by the release. macOS Intel + Bash and Linux ARM64 + Bash are supported targets that need local smoke verification before broad rollout. Native Windows is not a team-rollout target for v1.1.1, and WSL2 is experimental/unverified.
25
25
  - Registry, GitHub, model-provider, MCP-provider, and Vercel controls are external dependencies. Their account security and platform guarantees are not replaced by this repository.
26
26
 
27
27
  ## Threat actors and failure modes
@@ -9,7 +9,7 @@ Một thành viên mới không cần biết local path của maintainer. Luồn
9
9
  ```bash
10
10
  node --version # >= 22.19.0
11
11
  npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
12
- npm install -g --ignore-scripts @piagent/platform@1.1.0
12
+ npm install -g --ignore-scripts @piagent/platform@1.1.1
13
13
  piagent-install --stable
14
14
  cd /path/to/project
15
15
  pi
@@ -42,7 +42,7 @@ Mặc định team dùng stable helper:
42
42
  ```bash
43
43
  node --version # >= 22.19.0
44
44
  npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
45
- npm install -g --ignore-scripts @piagent/platform@1.1.0
45
+ npm install -g --ignore-scripts @piagent/platform@1.1.1
46
46
  piagent-install --stable --dry-run
47
47
  piagent-install --stable
48
48
  ```
@@ -798,7 +798,7 @@
798
798
  <span>Pi Agent Platform</span>
799
799
  </a>
800
800
  <div class="top-actions">
801
- <span class="pill"><span class="pill-dot"></span>v1.1.0 docs</span>
801
+ <span class="pill"><span class="pill-dot"></span>v1.1.1 docs</span>
802
802
  <a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
803
803
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
804
804
  <path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
@@ -934,7 +934,7 @@
934
934
  <article class="step">
935
935
  <div>
936
936
  <h3>Cài — hai lệnh</h3>
937
- <p>Cần Node.js 22.19.0 trở lên. Không cần cài Pi Coding Agent trước: <span class="inline-code">piagent-setup</span> tự cài đúng bản <span class="inline-code">0.82.0</span> mà release <span class="inline-code">v1.1.0</span> đang pin. Chạy nó ngay trong thư mục project — nó cài Pi package, khởi tạo <span class="inline-code">.pi/</span>, cài MCP baseline cùng subagents, rồi chạy doctor. Thêm <span class="inline-code">--no-mcp</span>, <span class="inline-code">--no-subagents</span>, <span class="inline-code">--no-herdr</span> hoặc <span class="inline-code">--global-only</span> để bỏ bớt.</p>
937
+ <p>Cần Node.js 22.19.0 trở lên. Không cần cài Pi Coding Agent trước: <span class="inline-code">piagent-setup</span> tự cài đúng bản <span class="inline-code">0.82.0</span> mà release <span class="inline-code">v1.1.1</span> đang pin. Chạy nó ngay trong thư mục project — nó cài Pi package, khởi tạo <span class="inline-code">.pi/</span>, cài MCP baseline cùng subagents, rồi chạy doctor. Thêm <span class="inline-code">--no-mcp</span>, <span class="inline-code">--no-subagents</span>, <span class="inline-code">--no-herdr</span> hoặc <span class="inline-code">--global-only</span> để bỏ bớt.</p>
938
938
  <div class="table-wrap compact-table">
939
939
  <table>
940
940
  <thead>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/platform",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -288,6 +288,17 @@ function decodeXmlEntities(value: string): string {
288
288
  .replace(/&amp;/g, "&");
289
289
  }
290
290
 
291
+ // Stripping markup and then decoding entities is the shape of an incomplete
292
+ // sanitizer, and static analysis flags it as one: a document containing the
293
+ // literal text `&lt;script&gt;` comes out as `<script>`. That is not a defect
294
+ // here, it is the required result — the document shows those characters to a
295
+ // reader, so extraction has to reproduce them. What makes it safe is the sink.
296
+ // This text becomes a Pi tool result, `{ type: "text" }`, rendered in a
297
+ // terminal; the shipped package contains no HTML sink at all. Untrusted
298
+ // document content is separately redacted and wrapped in a region delimited by
299
+ // a marker the document cannot predict. If any renderer that interprets markup
300
+ // is ever added downstream, escaping belongs at that boundary, not here, where
301
+ // it would corrupt the extracted text for every existing caller.
291
302
  export function extractDocxText(buffer: Buffer): DocumentExtraction {
292
303
  const document = readZipEntry(buffer, "word/document.xml");
293
304
  if (document.status === "error") return document;
@@ -337,6 +348,20 @@ function finish(text: string, kind: "text" | "docx" | "pdf"): DocumentExtraction
337
348
  // UTF-16 is half NUL bytes by design, so the binary check has to know about it
338
349
  // before it runs, and a byte-order mark is the only signal available without
339
350
  // guessing. Everything without a mark is treated as UTF-8.
351
+ //
352
+ // Decoding is strict in both encodings. Buffer.toString substitutes U+FFFD for
353
+ // anything it cannot decode, which turns a binary file renamed to .txt into
354
+ // "text" made of replacement characters — the exact outcome the extension gate
355
+ // exists to prevent. A NUL scan does not catch it either: plenty of binary
356
+ // formats carry no NUL in their first bytes.
357
+ function decodeStrict(bytes: Buffer, encoding: "utf-8" | "utf-16le"): string | undefined {
358
+ try {
359
+ return new TextDecoder(encoding, { fatal: true }).decode(bytes);
360
+ } catch {
361
+ return undefined;
362
+ }
363
+ }
364
+
340
365
  function decodeTextBuffer(buffer: Buffer): string | undefined {
341
366
  if (buffer.length >= 4 && buffer[0] === 0xff && buffer[1] === 0xfe && buffer[2] === 0x00 && buffer[3] === 0x00) {
342
367
  return undefined;
@@ -346,13 +371,13 @@ function decodeTextBuffer(buffer: Buffer): string | undefined {
346
371
  if (utf16Little || utf16Big) {
347
372
  const body = buffer.subarray(2);
348
373
  if (body.length % 2 !== 0) return undefined;
349
- return utf16Big ? Buffer.from(body).swap16().toString("utf16le") : body.toString("utf16le");
374
+ return decodeStrict(utf16Big ? Buffer.from(body).swap16() : body, "utf-16le");
350
375
  }
351
376
  if (buffer.includes(0)) return undefined;
352
377
  if (buffer.length >= 3 && buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf) {
353
- return buffer.subarray(3).toString("utf8");
378
+ return decodeStrict(buffer.subarray(3), "utf-8");
354
379
  }
355
- return buffer.toString("utf8");
380
+ return decodeStrict(buffer, "utf-8");
356
381
  }
357
382
 
358
383
  export function extractTextDocument(buffer: Buffer): DocumentExtraction {
@@ -360,7 +385,10 @@ export function extractTextDocument(buffer: Buffer): DocumentExtraction {
360
385
  // otherwise rather than emit replacement characters that read like content.
361
386
  const decoded = decodeTextBuffer(buffer);
362
387
  if (decoded === undefined) {
363
- return { status: "error", reason: "file has a text extension but contains binary data" };
388
+ return {
389
+ status: "error",
390
+ reason: "file has a text extension but its bytes are not valid UTF-8 or UTF-16 text; it is binary or in some other encoding"
391
+ };
364
392
  }
365
393
  return finish(decoded.replace(/\r\n?/g, "\n"), "text");
366
394
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/core",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -30,15 +30,20 @@ function fail(message) {
30
30
  }
31
31
 
32
32
  function parseArguments(values) {
33
- const options = { hosts: [], expect: null };
33
+ const options = { hosts: [], expect: null, allowUnverified: false };
34
34
  for (let index = 0; index < values.length; index += 1) {
35
35
  const option = values[index];
36
36
  if (option === "-h" || option === "--help") {
37
- process.stdout.write("Usage: node scripts/check-published-site.mjs [--host <name>]... [--expect <text>]\n");
37
+ process.stdout.write("Usage: node scripts/check-published-site.mjs [--host <name>]... [--expect <text>] [--allow-unverified]\n");
38
38
  process.stdout.write("Verify every resolved address of the docs site serves the released version.\n");
39
39
  process.stdout.write("Defaults to piagent.io.vn and www.piagent.io.vn, expecting the root package version.\n");
40
+ process.stdout.write("--allow-unverified downgrades addresses this machine cannot route to from a failure to a note.\n");
40
41
  process.exit(0);
41
42
  }
43
+ if (option === "--allow-unverified") {
44
+ options.allowUnverified = true;
45
+ continue;
46
+ }
42
47
  const value = values[index + 1];
43
48
  if (option === "--host") {
44
49
  if (!value || value.startsWith("--")) fail("--host requires a value");
@@ -76,14 +81,30 @@ async function resolveAddresses(host) {
76
81
  return addresses;
77
82
  }
78
83
 
79
- function request(host, { address, family }) {
84
+ // `port` and `tlsOptions` exist so the body-cap path can be driven against a
85
+ // local server in a test. Nothing on the command line reaches them, and the
86
+ // defaults are what every real run uses: port 443 and Node's own trust store.
87
+ export function request(host, { address, family, port = 443 }, tlsOptions = {}) {
80
88
  return new Promise((resolve) => {
89
+ // Every path out of here has to settle exactly once. Capping the body
90
+ // destroys the response, and a destroyed response emits neither `end` nor
91
+ // `error` — it goes straight to `aborted` and `close`. Without a `close`
92
+ // handler the promise stays pending forever, and the request timeout cannot
93
+ // rescue it because the socket is already gone. That is a hung release gate,
94
+ // not a failed one.
95
+ let settled = false;
96
+ const settle = (value) => {
97
+ if (settled) return;
98
+ settled = true;
99
+ resolve(value);
100
+ };
81
101
  const call = https.request(
82
102
  {
103
+ ...tlsOptions,
83
104
  host: address,
84
105
  family,
85
106
  servername: host,
86
- port: 443,
107
+ port,
87
108
  path: "/",
88
109
  method: "GET",
89
110
  headers: { Host: host, "User-Agent": "piagent-site-check" },
@@ -92,24 +113,31 @@ function request(host, { address, family }) {
92
113
  (response) => {
93
114
  let body = "";
94
115
  let bytes = 0;
116
+ let truncated = false;
95
117
  response.setEncoding("utf8");
96
118
  response.on("data", (chunk) => {
97
119
  bytes += Buffer.byteLength(chunk, "utf8");
98
120
  if (bytes > MAX_BODY_BYTES) {
121
+ truncated = true;
99
122
  response.destroy();
100
123
  return;
101
124
  }
102
125
  body += chunk;
103
126
  });
104
- response.on("end", () => resolve({ status: response.statusCode, location: response.headers.location, body }));
105
- response.on("error", (error) => resolve({ error: error.message, code: error.code }));
127
+ response.on("end", () =>
128
+ settle({ status: response.statusCode, location: response.headers.location, body, truncated })
129
+ );
130
+ response.on("error", (error) => settle({ error: error.message, code: error.code }));
131
+ response.on("close", () =>
132
+ settle({ status: response.statusCode, location: response.headers.location, body, truncated })
133
+ );
106
134
  }
107
135
  );
108
136
  call.on("timeout", () => {
109
137
  call.destroy();
110
- resolve({ error: `no response within ${REQUEST_TIMEOUT_MS}ms` });
138
+ settle({ error: `no response within ${REQUEST_TIMEOUT_MS}ms` });
111
139
  });
112
- call.on("error", (error) => resolve({ error: error.message, code: error.code }));
140
+ call.on("error", (error) => settle({ error: error.message, code: error.code }));
113
141
  call.end();
114
142
  });
115
143
  }
@@ -137,8 +165,13 @@ export function judge(host, hosts, expect, result, family) {
137
165
  return null;
138
166
  }
139
167
  if (result.status !== 200) return `HTTP ${result.status}`;
140
- if (!result.body.includes(expect)) return `HTTP 200 but the page does not mention ${expect}`;
141
- return null;
168
+ if (result.body.includes(expect)) return null;
169
+ // Not finding the version in a body that was cut short is not the same claim
170
+ // as not finding it in the page.
171
+ if (result.truncated) {
172
+ return `HTTP 200 but the response exceeded ${MAX_BODY_BYTES} bytes and the part read does not mention ${expect}`;
173
+ }
174
+ return `HTTP 200 but the page does not mention ${expect}`;
142
175
  }
143
176
 
144
177
  async function main() {
@@ -170,18 +203,38 @@ async function main() {
170
203
  }
171
204
  }
172
205
 
173
- for (const entry of unverified) process.stdout.write(`UNVERIFIED: ${entry}\n`);
206
+ const summary = summarize({ checked, failures, unverified, expect: options.expect, allowUnverified: options.allowUnverified });
207
+ process.stdout.write(summary.out);
208
+ process.stderr.write(summary.err);
209
+ process.exit(summary.code);
210
+ }
211
+
212
+ // An address nobody checked is not an address that passed. Reporting it as
213
+ // unverified and then exiting 0 makes the gate say "verified every address"
214
+ // about a run that did not, so an unchecked address ends the run unless the
215
+ // operator accepts the gap deliberately.
216
+ export function summarize({ checked, failures, unverified, expect, allowUnverified }) {
217
+ let out = "";
218
+ for (const entry of unverified) out += `UNVERIFIED: ${entry}\n`;
174
219
 
175
220
  if (failures.length > 0) {
176
- process.stderr.write(`FAIL: ${failures.length} of ${checked} addresses do not serve ${options.expect}\n`);
177
- for (const failure of failures) process.stderr.write(` ${failure}\n`);
178
- process.stderr.write("Each address is a real path users take. Fix the DNS records or the deployment behind them.\n");
179
- process.exit(1);
221
+ let err = `FAIL: ${failures.length} of ${checked} addresses do not serve ${expect}\n`;
222
+ for (const failure of failures) err += ` ${failure}\n`;
223
+ err += "Each address is a real path users take. Fix the DNS records or the deployment behind them.\n";
224
+ return { code: 1, out, err };
225
+ }
226
+
227
+ if (unverified.length > 0 && !allowUnverified) {
228
+ let err = `FAIL: ${unverified.length} of ${checked} addresses could not be checked from this machine\n`;
229
+ for (const entry of unverified) err += ` ${entry}\n`;
230
+ err += "Run the check from a machine with a route to them, or pass --allow-unverified to accept the gap.\n";
231
+ return { code: 1, out, err };
180
232
  }
181
233
 
182
234
  const verified = checked - unverified.length;
183
- const note = unverified.length > 0 ? ` (${unverified.length} not reachable from this machine)` : "";
184
- process.stdout.write(`PASS: ${verified} of ${checked} addresses serve ${options.expect}${note}\n`);
235
+ const note = unverified.length > 0 ? ` (${unverified.length} accepted as not reachable from this machine)` : "";
236
+ out += `PASS: ${verified} of ${checked} addresses serve ${expect}${note}\n`;
237
+ return { code: 0, out, err: "" };
185
238
  }
186
239
 
187
240
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
@@ -15,7 +15,7 @@ Package source examples:
15
15
  # Installs the stable tag after resolving it to a commit SHA.
16
16
 
17
17
  # Pin a specific release:
18
- scripts/install-global.sh --version v1.1.0 --resolve-tag
18
+ scripts/install-global.sh --version v1.1.1 --resolve-tag
19
19
 
20
20
  # Preview the planned install/update without changing user config:
21
21
  scripts/install-global.sh --stable --dry-run
@@ -127,7 +127,7 @@ detect_runtime_surface() {
127
127
  printf 'native-windows/%s (not release-gated; terminal helpers require Bash semantics)' "$arch_name"
128
128
  ;;
129
129
  *)
130
- printf '%s/%s (outside v1.1.0 release matrix)' "$os_name" "$arch_name"
130
+ printf '%s/%s (outside v1.1.1 release matrix)' "$os_name" "$arch_name"
131
131
  ;;
132
132
  esac
133
133
  }
@@ -135,7 +135,7 @@ detect_runtime_surface() {
135
135
  warn_runtime_surface_if_needed() {
136
136
  local surface="$1"
137
137
  case "$surface" in
138
- *"supported target"*|*"experimental"*|*"not release-gated"*|*"outside v1.1.0 release matrix"*)
138
+ *"supported target"*|*"experimental"*|*"not release-gated"*|*"outside v1.1.1 release matrix"*)
139
139
  echo "WARN: runtime surface is $surface." >&2
140
140
  echo "WARN: For team rollout, run piagent-doctor plus the project smoke/verify suite on this machine before relying on it." >&2
141
141
  ;;
package/scripts/setup.sh CHANGED
@@ -378,8 +378,13 @@ echo
378
378
  if [[ "$DO_GLOBAL" == true ]]; then
379
379
  ensure_pi_cli
380
380
  install_args=("$PLATFORM_ROOT/scripts/install-global.sh" "--package-source" "$PACKAGE_SOURCE")
381
+ # The opt-out has to be passed explicitly. The installer defaults MCP on, so
382
+ # saying nothing here would install it against the operator's choice; the
383
+ # flags below can stay one-sided only because the installer defaults them off.
381
384
  if [[ "$WITH_MCP" == true ]]; then
382
385
  install_args+=("--with-mcp" "--mcp-preset" "$MCP_PRESET")
386
+ else
387
+ install_args+=("--no-mcp")
383
388
  fi
384
389
  if [[ "$WITH_SUBAGENTS" == true ]]; then
385
390
  install_args+=("--with-subagents" "--subagents-preset" "$SUBAGENTS_PRESET" "--subagents-model-scope" "$SUBAGENTS_MODEL_SCOPE")
@@ -137,7 +137,7 @@ function runtimeMatrixStatus() {
137
137
  surface: `${platform}/${arch}`,
138
138
  status: "outside-release-matrix",
139
139
  teamRolloutReady: false,
140
- note: "This runtime is outside the documented v1.1.0 support matrix."
140
+ note: "This runtime is outside the documented v1.1.1 support matrix."
141
141
  };
142
142
  }
143
143