@piagent/platform 1.1.2 → 1.1.4
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 +23 -0
- package/README.md +1 -1
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +7 -7
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +2 -0
- package/docs/release-install-policy.md +7 -7
- package/docs/security-threat-model.md +1 -1
- package/docs/team-onboarding.md +2 -2
- package/docs-site/index.html +2 -2
- package/package.json +1 -1
- package/packages/piagent-core/extensions/document-intake.ts +11 -1
- package/packages/piagent-core/extensions/runtime-evidence.js +38 -20
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/security/sensitive-data.js +72 -25
- package/scripts/check-published-site.mjs +6 -0
- package/scripts/install-global.sh +8 -5
- package/scripts/team-doctor.sh +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
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.4 - 2026-07-27
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- A credential passed as a command-line option is recognised as one. `--token=abc123`, `--token abc123`, `--password`, `--api-key`, `--client-secret` and the rest went unrecognised because the assignment patterns require a key that starts with a letter, and `-` is not one. v1.1.3 made the redactor's verdict the gate that decides whether a command may serve as verify evidence, so anything it missed was stored in the clear *and* accepted as proof — the same invariant v1.1.3 was written to enforce, defeated by the syntax most command-line tools actually use. Env-style assignments were the only shape covered.
|
|
10
|
+
- An `Authorization` header is redacted wherever it sits among the arguments. The value ran to the end of the line, so the same credential survived or not depending on how much text happened to follow it: `curl URL -H "Authorization: Token abc123"` left the header value one character under the length bar, while moving the header ahead of the URL swept the URL into the value and pushed it over. The value is now the single whitespace-free token that follows the scheme, and under a known authentication scheme its length is not consulted at all. Prose such as `Authorization: not required for local runs` is still left alone.
|
|
11
|
+
|
|
12
|
+
## v1.1.3 - 2026-07-27
|
|
13
|
+
|
|
14
|
+
Fixes for defects found reviewing v1.1.2 after it shipped, including two introduced by v1.1.2 itself. Every fix was reproduced first.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Verify evidence identifies the command exactly again. v1.1.2 moved the digest onto the redacted text to stop it confirming guesses at a secret, and in doing so gave every secret the same identity: an observation of `DATABASE_PASSWORD=CorrectHorse42 npm test` satisfied a claim about `DATABASE_PASSWORD=DifferentHorse99 npm test`, so a run against one database could be recorded as proof for another. That contradicts the exact-match rule the gate is built on. Neither text is safe to hash, so a command with anything to redact now gets no digest at all and cannot be evidence; the gate says so instead of matching loosely. Commands with no secret in them — every legitimate verify command — are identified by their own text as before.
|
|
19
|
+
- A document replaced by a named pipe is refused instead of hanging. The identity check that would have rejected it runs after the file is opened, and opening a pipe waits for a writer, so the substitution did not have to defeat the check — it only had to stop it from running. The read blocked indefinitely. The file is now opened without blocking and without following a symbolic link at the final component.
|
|
20
|
+
- Short credentials are redacted. Values under a key that names a secret outright were kept in the clear below eight or twelve characters, so `TOKEN=hunter2` was stored verbatim in the evidence ledger. Length is only ambiguous after a bare `:`, where English uses the same shape — `password: short` is prose, and `Authorization: Token abc` puts a scheme name where a value would go. Under `=`, inside quotes, in a query string, or as a field in a structure, any value that is not a placeholder is now redacted at any length.
|
|
21
|
+
- A redirect must land on the page being verified. `npm run site:check` compared the hostname, scheme, port and credentials of the redirect target but not its path, so `/missing` or `/?release=old` passed while the run only ever fetched `/`.
|
|
22
|
+
- `piagent-install` reports a missing Node runtime as a missing Node runtime. Validating the MCP preset early, added in v1.1.2, put a Node script ahead of the Node version check, so on a machine without Node the installer failed with `node: command not found` and blamed the preset.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- `docs/release-install-policy.md` named the wrong release for the code-scanning gap. A pull request last merged with a failing CodeQL check and an open high alert on `v1.1.0`; on `v1.1.2` the results gate blocked the pull request until the alerts were cleared.
|
|
27
|
+
|
|
5
28
|
## v1.1.2 - 2026-07-26
|
|
6
29
|
|
|
7
30
|
Security and release-gating fixes found reviewing v1.1.1 after it shipped. Every fix in this release was reproduced as a working exploit or failure first.
|
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.
|
|
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.4` 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.
|
|
423
|
-
| `pi install git:github.com/Vt-mmm/piagent@v1.1.
|
|
422
|
+
| `npm install -g --ignore-scripts @piagent/platform@1.1.4` | Cài terminal helper `piagent-*` từ release tag hiện tại. |
|
|
423
|
+
| `pi install git:github.com/Vt-mmm/piagent@v1.1.4` | 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.
|
|
17
|
+
| Team pinned release | `git:github.com/Vt-mmm/piagent@v1.1.4` |
|
|
18
18
|
| Personal/sandbox dev | `git:github.com/Vt-mmm/piagent` |
|
|
19
|
-
| Registry distribution | `npm:@piagent/platform@1.1.
|
|
19
|
+
| Registry distribution | `npm:@piagent/platform@1.1.4` |
|
|
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.
|
|
29
|
+
| `stable` | `bash scripts/install-global.sh --stable` | Helper release tag resolved to commit SHA, currently `v1.1.4` | 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.
|
|
56
|
+
pi install git:github.com/Vt-mmm/piagent@v1.1.4
|
|
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.
|
|
68
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.4
|
|
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.
|
|
73
|
+
`pi install git:github.com/Vt-mmm/piagent@v1.1.4` 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.4`.
|
|
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.
|
|
106
|
+
--package-source git:github.com/Vt-mmm/piagent@v1.1.4 \
|
|
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.
|
|
21
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.4
|
|
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.
|
|
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.4`. 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.
|
|
102
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.4
|
|
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.
|
|
272
|
+
--package-source git:github.com/Vt-mmm/piagent@v1.1.4 \
|
|
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.
|
|
846
|
-
| `pi install git:github.com/Vt-mmm/piagent@v1.1.
|
|
845
|
+
| `npm install -g --ignore-scripts @piagent/platform@1.1.4` | Cài terminal helper `piagent-*` từ release tag hiện tại. |
|
|
846
|
+
| `pi install git:github.com/Vt-mmm/piagent@v1.1.4` | 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.
|
|
943
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.4
|
|
944
944
|
command -v piagent-install
|
|
945
945
|
```
|
|
946
946
|
|
|
@@ -45,6 +45,8 @@ pi install /path/to/piagent
|
|
|
45
45
|
- `1.0.2`: đủ security review, MCP registry, adapter schema versioned.
|
|
46
46
|
- `1.1.0`: đọc document ngoài project qua `additionalReadRoots`, MCP baseline bật mặc định ở cả hai lệnh install, pin Pi host `0.82.0`.
|
|
47
47
|
- `1.1.2`: đóng TOCTOU của document reader, hash evidence tính trên text đã redact, publish chờ đủ verify matrix.
|
|
48
|
+
- `1.1.3`: command mang secret không còn được dùng làm verify evidence, document reader từ chối FIFO thay vì treo, redact secret ngắn, redirect phải về đúng trang được kiểm.
|
|
49
|
+
- `1.1.4`: nhận diện secret dạng CLI option (`--token`, `--password`, `--api-key`) và Authorization header ở mọi vị trí argument.
|
|
48
50
|
|
|
49
51
|
## Không publish
|
|
50
52
|
|
|
@@ -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.
|
|
13
|
+
| Surface | Status for v1.1.4 | 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.
|
|
36
|
+
| `stable` | `git:github.com/Vt-mmm/piagent@<resolved-commit-sha>` from `v1.1.4` | 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.
|
|
56
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.4
|
|
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.
|
|
65
|
-
tag: v1.1.
|
|
64
|
+
currentRelease: v1.1.4 (helper package version)
|
|
65
|
+
tag: v1.1.4
|
|
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.
|
|
82
|
+
pi install git:github.com/Vt-mmm/piagent@v1.1.4
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
Direct `pi install` does not create `piagent-*` commands on `PATH`.
|
|
@@ -192,7 +192,7 @@ 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.
|
|
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.0`: its pull request carried a failing `CodeQL` check run and a high alert open since the day before, and merged anyway because the rule did not yet require the result. 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.
|
|
196
196
|
|
|
197
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.
|
|
198
198
|
|
|
@@ -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.
|
|
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.4, 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
|
package/docs/team-onboarding.md
CHANGED
|
@@ -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.
|
|
12
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.4
|
|
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.
|
|
45
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.4
|
|
46
46
|
piagent-install --stable --dry-run
|
|
47
47
|
piagent-install --stable
|
|
48
48
|
```
|
package/docs-site/index.html
CHANGED
|
@@ -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.
|
|
801
|
+
<span class="pill"><span class="pill-dot"></span>v1.1.4 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.
|
|
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.4</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
|
@@ -510,14 +510,24 @@ export type ResolvedDocument = Extract<DocumentResolution, { status: "ok" }>;
|
|
|
510
510
|
|
|
511
511
|
const DOCUMENT_CHANGED = "document changed between the safety checks and the read, so nothing was read";
|
|
512
512
|
|
|
513
|
+
// Opening blocks until a writer appears when the name has become a FIFO, and the
|
|
514
|
+
// identity check below never runs — the read hangs instead of being refused.
|
|
515
|
+
// O_NONBLOCK returns a descriptor immediately for any file type, and has no
|
|
516
|
+
// effect on reads from a regular file, which is the only type that gets past the
|
|
517
|
+
// check. O_NOFOLLOW refuses a symbolic link at the final component: the path was
|
|
518
|
+
// already canonicalised, so a link there means the name was swapped after it was
|
|
519
|
+
// checked, which is the substitution this open exists to defeat.
|
|
520
|
+
const OPEN_FLAGS = fs.constants.O_RDONLY | fs.constants.O_NONBLOCK | fs.constants.O_NOFOLLOW;
|
|
521
|
+
|
|
513
522
|
function openVerifiedDocument(
|
|
514
523
|
resolution: ResolvedDocument
|
|
515
524
|
): { status: "ok"; fd: number; size: number } | { status: "error"; reason: string } {
|
|
516
525
|
let fd: number;
|
|
517
526
|
try {
|
|
518
|
-
fd = fs.openSync(resolution.absolutePath,
|
|
527
|
+
fd = fs.openSync(resolution.absolutePath, OPEN_FLAGS);
|
|
519
528
|
} catch (error) {
|
|
520
529
|
const code = error && typeof error === "object" && "code" in error ? String((error as { code?: unknown }).code) : "";
|
|
530
|
+
if (code === "ELOOP") return { status: "error", reason: DOCUMENT_CHANGED };
|
|
521
531
|
return {
|
|
522
532
|
status: "error",
|
|
523
533
|
reason: code === "ENOENT"
|
|
@@ -9,24 +9,27 @@ export function normalizeEvidenceCommand(command) {
|
|
|
9
9
|
.trim();
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// A command that carries a secret gets no digest at all, and cannot be evidence.
|
|
13
13
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
14
|
+
// Neither text is safe to hash. Hashing the raw command published a template:
|
|
15
|
+
// the ledger stored `export TOKEN= [REDACTED_SECRET] && npm test` beside a
|
|
16
|
+
// SHA-256 of the real line, so the only unknown left was the secret, and SHA-256
|
|
17
|
+
// is fast enough to walk a candidate list offline. Hashing the redacted text
|
|
18
|
+
// instead gives every secret the same digest, which is worse in a different
|
|
19
|
+
// direction: the gate promises the observed command matches `verifyCommands`
|
|
20
|
+
// exactly, and two runs differing only in a credential would satisfy each
|
|
21
|
+
// other's claim. Verifying against database A and recording it as proof for
|
|
22
|
+
// database B is exactly what the exact-match rule exists to prevent.
|
|
20
23
|
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
+
// So the digest covers the raw command and is withheld when there is anything to
|
|
25
|
+
// redact. Withholding it costs nothing real: a verify command in a task plan has
|
|
26
|
+
// no business carrying an inline credential, and a command that does gets refused
|
|
27
|
+
// rather than matched loosely.
|
|
24
28
|
export function hashEvidenceCommand(command) {
|
|
25
29
|
const normalized = normalizeEvidenceCommand(command);
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
.digest("hex");
|
|
30
|
+
if (!normalized) return "";
|
|
31
|
+
if (redactSensitiveText(normalized).redacted) return "";
|
|
32
|
+
return crypto.createHash("sha256").update(normalized).digest("hex");
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
export function commandMatchesVerifyPlan(command, verifyCommands) {
|
|
@@ -88,8 +91,14 @@ export function observedBashResultFromToolResultEvent(event, cwd, nowMs = Date.n
|
|
|
88
91
|
|
|
89
92
|
function canonicalObservedEntry(entry) {
|
|
90
93
|
const rawNormalizedCommand = normalizeEvidenceCommand(entry?.normalizedCommand ?? entry?.command);
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
if (!rawNormalizedCommand) return undefined;
|
|
95
|
+
// `??`, not `||`: an entry read back from the ledger carries an empty digest
|
|
96
|
+
// when its command held a secret, and that emptiness has to survive. Recomputing
|
|
97
|
+
// it here would hash the stored text, which is redacted, and hand the entry the
|
|
98
|
+
// one digest this module refuses to produce.
|
|
99
|
+
const commandHash = typeof entry?.commandHash === "string"
|
|
100
|
+
? entry.commandHash
|
|
101
|
+
: hashEvidenceCommand(rawNormalizedCommand);
|
|
93
102
|
const command = typeof entry?.command === "string" ? redactSensitiveText(entry.command).text : "";
|
|
94
103
|
const normalizedCommand = redactSensitiveText(rawNormalizedCommand).text;
|
|
95
104
|
const recordedAtMs = parseTimeMs(entry?.recordedAtMs ?? entry?.recordedAt);
|
|
@@ -106,9 +115,11 @@ function canonicalObservedEntry(entry) {
|
|
|
106
115
|
};
|
|
107
116
|
}
|
|
108
117
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
// Identity is the digest and nothing else. Falling back to comparing the stored
|
|
119
|
+
// text would compare redacted text, which is the collision this module refuses.
|
|
120
|
+
function observedCommandsMatch(entry, commandHash) {
|
|
121
|
+
if (!commandHash || !entry.commandHash) return false;
|
|
122
|
+
return entry.commandHash === commandHash;
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
export function findMatchingObservedBashResult(entries, { cwd, command, notBefore, exitCode }) {
|
|
@@ -119,12 +130,19 @@ export function findMatchingObservedBashResult(entries, { cwd, command, notBefor
|
|
|
119
130
|
if (!normalizedCommand) {
|
|
120
131
|
return { ok: false, reason: "Verify command is empty after normalization." };
|
|
121
132
|
}
|
|
133
|
+
if (!commandHash) {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
reason: "Verify command carries a secret, so it cannot be matched against observed evidence. "
|
|
137
|
+
+ "Move the credential into the environment and keep it out of the command."
|
|
138
|
+
};
|
|
139
|
+
}
|
|
122
140
|
|
|
123
141
|
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
124
142
|
const entry = canonicalObservedEntry(entries[index]);
|
|
125
143
|
if (!entry) continue;
|
|
126
144
|
if (entry.cwd !== normalizedCwd) continue;
|
|
127
|
-
if (!observedCommandsMatch(entry,
|
|
145
|
+
if (!observedCommandsMatch(entry, commandHash)) continue;
|
|
128
146
|
if (entry.recordedAtMs < notBeforeMs) continue;
|
|
129
147
|
if (!claimedExitMatchesObserved(exitCode, entry)) {
|
|
130
148
|
return {
|
|
@@ -15,7 +15,27 @@ const TOKEN_PATTERNS = [
|
|
|
15
15
|
const CONNECTION_URL_PATTERN = /\b((?:https?|ftp|postgres(?:ql)?|mysql|mariadb|mongodb(?:\+srv)?|redis):\/\/[^:\s/"']+:)([^@\s/"']+)(@[^\s"'<>]+)/gi;
|
|
16
16
|
const BEARER_PATTERN = /\b(Bearer\s+)([A-Za-z0-9._~+/=-]{20,})\b/gi;
|
|
17
17
|
const BASIC_AUTH_PATTERN = /\b(Basic\s+)([A-Za-z0-9+/]{12,}={0,2})(?=$|[\s,;}])/gi;
|
|
18
|
-
|
|
18
|
+
// The value is one whitespace-free token, not the rest of the line. Taking the
|
|
19
|
+
// rest of the line made the result depend on where the header sat among the
|
|
20
|
+
// arguments: `curl URL -H "Authorization: Token abc123"` yielded `abc123"`, short
|
|
21
|
+
// enough to keep, while moving the header before the URL swept the URL into the
|
|
22
|
+
// value and pushed it past the length bar. Same header, same credential, opposite
|
|
23
|
+
// outcome. Credentials carry no spaces, so stopping at the first one also keeps
|
|
24
|
+
// prose like "Authorization: not required for local runs" intact.
|
|
25
|
+
const AUTHORIZATION_HEADER_PATTERN = /\b(Authorization\s*:\s*)([A-Za-z][A-Za-z0-9_-]{0,31})(\s+)([^\s\r\n"'`]+)/gi;
|
|
26
|
+
|
|
27
|
+
// A credential passed as a command-line option, which is how most CLIs take one.
|
|
28
|
+
// The assignment patterns below require a key starting with a letter, so every
|
|
29
|
+
// `--token=...` and `--token ...` walked straight past them.
|
|
30
|
+
const CLI_OPTION_SECRET_PATTERN = /(^|[\s({[])(--[A-Za-z][A-Za-z0-9_.-]{0,80})(=|\s+)("[^"\r\n]*"|'[^'\r\n]*'|[^\s"'`;,)}]+)/g;
|
|
31
|
+
|
|
32
|
+
// Under one of these schemes the header carries an opaque credential, so its
|
|
33
|
+
// length says nothing. Anything else after `Authorization:` is more likely prose
|
|
34
|
+
// — "Authorization: not required" — and keeps the ambiguous-syntax length bar.
|
|
35
|
+
const AUTHORIZATION_SCHEMES = new Set([
|
|
36
|
+
"bearer", "basic", "token", "digest", "negotiate", "ntlm",
|
|
37
|
+
"oauth", "hawk", "signature", "apikey", "jwt", "aws4-hmac-sha256"
|
|
38
|
+
]);
|
|
19
39
|
const QUERY_SECRET_PATTERN = /([?&])([A-Za-z][A-Za-z0-9_.-]{0,80})(=)([^&#\s]+)/gi;
|
|
20
40
|
const DOUBLE_QUOTED_SECRET_ASSIGNMENT_PATTERN = /(^|[\s{[,(;])((?:"|')?[A-Za-z][A-Za-z0-9_.-]{0,80}(?:"|')?\s*)(:|=(?!=))\s*"((?:\\.|[^"\\\r\n])*)"/gim;
|
|
21
41
|
const SINGLE_QUOTED_SECRET_ASSIGNMENT_PATTERN = /(^|[\s{[,(;])((?:"|')?[A-Za-z][A-Za-z0-9_.-]{0,80}(?:"|')?\s*)(:|=(?!=))\s*'((?:\\.|[^'\\\r\n])*)'/gim;
|
|
@@ -54,14 +74,6 @@ const SENSITIVE_PLURAL_KEYS = new Set([
|
|
|
54
74
|
"signing_keys",
|
|
55
75
|
"authorizations"
|
|
56
76
|
]);
|
|
57
|
-
const LOWER_THRESHOLD_PLURAL_KEYS = new Set([
|
|
58
|
-
"passwords",
|
|
59
|
-
"secrets",
|
|
60
|
-
"credentials",
|
|
61
|
-
"passphrases",
|
|
62
|
-
"private_keys",
|
|
63
|
-
"signing_keys"
|
|
64
|
-
]);
|
|
65
77
|
|
|
66
78
|
function normalizeSecretValue(value) {
|
|
67
79
|
return String(value ?? "")
|
|
@@ -97,13 +109,6 @@ function keyLooksSensitive(key) {
|
|
|
97
109
|
|| normalized.endsWith(`_${term}`));
|
|
98
110
|
}
|
|
99
111
|
|
|
100
|
-
function keyRequiresLowerThreshold(key) {
|
|
101
|
-
const normalized = normalizeSecretKey(key);
|
|
102
|
-
return LOWER_THRESHOLD_PLURAL_KEYS.has(normalized)
|
|
103
|
-
|| ["password", "passwd", "pwd", "secret", "credential", "passphrase", "private_key", "signing_key"]
|
|
104
|
-
.some((term) => normalized === term || normalized.endsWith(`_${term}`));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
112
|
function valueLooksPlaceholder(value) {
|
|
108
113
|
const clean = normalizeSecretValue(value);
|
|
109
114
|
if (!clean) return true;
|
|
@@ -115,12 +120,40 @@ function valueLooksPlaceholder(value) {
|
|
|
115
120
|
return false;
|
|
116
121
|
}
|
|
117
122
|
|
|
118
|
-
|
|
123
|
+
// Length is only evidence where the syntax is ambiguous, and `=` is not.
|
|
124
|
+
//
|
|
125
|
+
// A short value used to be kept in the clear whatever the syntax, so `TOKEN=hunter2`
|
|
126
|
+
// and `PASSWORD=short7` went into the ledger verbatim. Every name in
|
|
127
|
+
// SENSITIVE_KEY_TERMS states outright what it holds — there is no `key` or `id`
|
|
128
|
+
// among them — so under an `=`, or inside quotes, a non-placeholder value is the
|
|
129
|
+
// secret the key says it is, at any length.
|
|
130
|
+
//
|
|
131
|
+
// A bare `:` is the ambiguous case, because English uses it too: `password: short`
|
|
132
|
+
// and `The secret: always run tests` are prose, and `Authorization: Token abc123`
|
|
133
|
+
// puts the scheme name where a value would go. Those keep a length bar.
|
|
134
|
+
const SHORT_VALUE_MIN_LENGTH = 8;
|
|
135
|
+
const AMBIGUOUS_VALUE_MIN_LENGTH = 12;
|
|
136
|
+
|
|
137
|
+
function valueLooksSensitive(key, value, syntax = "unquoted-colon") {
|
|
119
138
|
const clean = normalizeSecretValue(value);
|
|
120
139
|
if (!clean || valueLooksPlaceholder(clean)) return false;
|
|
121
140
|
if (looksLikeKnownSecret(clean)) return true;
|
|
122
|
-
if (
|
|
123
|
-
|
|
141
|
+
if (!keyLooksSensitive(key)) return false;
|
|
142
|
+
if (syntax !== "unquoted-colon") return true;
|
|
143
|
+
return clean.length >= (keyNamesASecretOutright(key) ? SHORT_VALUE_MIN_LENGTH : AMBIGUOUS_VALUE_MIN_LENGTH);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Under a bare `:`, these names carry enough intent on their own to redact a short
|
|
147
|
+
// value; the broader terms wait for length before touching prose.
|
|
148
|
+
function keyNamesASecretOutright(key) {
|
|
149
|
+
const normalized = normalizeSecretKey(key);
|
|
150
|
+
return ["password", "passwd", "pwd", "secret", "credential", "passphrase", "private_key", "signing_key"]
|
|
151
|
+
.some((term) => normalized === term || normalized.endsWith(`_${term}`))
|
|
152
|
+
|| ["passwords", "secrets", "credentials", "passphrases", "private_keys", "signing_keys"].includes(normalized);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function assignmentSyntax(separator) {
|
|
156
|
+
return separator === "=" ? "assignment" : "unquoted-colon";
|
|
124
157
|
}
|
|
125
158
|
|
|
126
159
|
export function redactSensitiveText(input) {
|
|
@@ -131,29 +164,41 @@ export function redactSensitiveText(input) {
|
|
|
131
164
|
text = text.replace(BEARER_PATTERN, (_match, prefix) => `${prefix}${REDACTION}`);
|
|
132
165
|
text = text.replace(BASIC_AUTH_PATTERN, (_match, prefix) => `${prefix}${REDACTION}`);
|
|
133
166
|
text = text.replace(AUTHORIZATION_HEADER_PATTERN, (match, prefix, scheme, spacing, value) => {
|
|
134
|
-
|
|
167
|
+
const syntax = AUTHORIZATION_SCHEMES.has(scheme.toLowerCase()) ? "assignment" : "unquoted-colon";
|
|
168
|
+
if (!valueLooksSensitive("authorization", value, syntax)) return match;
|
|
135
169
|
return `${prefix}${scheme}${spacing}${REDACTION}`;
|
|
136
170
|
});
|
|
137
171
|
|
|
172
|
+
// Over-redaction is the safe direction here: a flag whose name says "secret"
|
|
173
|
+
// and whose argument is a plain name — `docker service create --secret build` —
|
|
174
|
+
// loses that name from the record and cannot serve as verify evidence. Reading
|
|
175
|
+
// the name as the credential it is called is the cheaper mistake.
|
|
176
|
+
text = text.replace(CLI_OPTION_SECRET_PATTERN, (match, prefix, flag, separator, value) => {
|
|
177
|
+
// A following option is the next flag, not this flag's argument.
|
|
178
|
+
if (separator !== "=" && value.startsWith("-")) return match;
|
|
179
|
+
if (!keyLooksSensitive(flag) || !valueLooksSensitive(flag, value, "assignment")) return match;
|
|
180
|
+
return `${prefix}${flag}${separator === "=" ? "= " : " "}${REDACTION}`;
|
|
181
|
+
});
|
|
182
|
+
|
|
138
183
|
for (const pattern of TOKEN_PATTERNS) text = text.replace(pattern, REDACTION);
|
|
139
184
|
|
|
140
185
|
text = text.replace(QUERY_SECRET_PATTERN, (match, prefix, key, separator, value) => {
|
|
141
|
-
if (!keyLooksSensitive(key) || !valueLooksSensitive(key, value)) return match;
|
|
186
|
+
if (!keyLooksSensitive(key) || !valueLooksSensitive(key, value, "assignment")) return match;
|
|
142
187
|
return `${prefix}${key}${separator}${REDACTION}`;
|
|
143
188
|
});
|
|
144
189
|
|
|
145
190
|
text = text.replace(DOUBLE_QUOTED_SECRET_ASSIGNMENT_PATTERN, (match, prefix, key, separator, value) => {
|
|
146
|
-
if (!keyLooksSensitive(key) || !valueLooksSensitive(key, value)) return match;
|
|
191
|
+
if (!keyLooksSensitive(key) || !valueLooksSensitive(key, value, "quoted")) return match;
|
|
147
192
|
return `${prefix}${key}${separator} "${REDACTION}"`;
|
|
148
193
|
});
|
|
149
194
|
|
|
150
195
|
text = text.replace(SINGLE_QUOTED_SECRET_ASSIGNMENT_PATTERN, (match, prefix, key, separator, value) => {
|
|
151
|
-
if (!keyLooksSensitive(key) || !valueLooksSensitive(key, value)) return match;
|
|
196
|
+
if (!keyLooksSensitive(key) || !valueLooksSensitive(key, value, "quoted")) return match;
|
|
152
197
|
return `${prefix}${key}${separator} '${REDACTION}'`;
|
|
153
198
|
});
|
|
154
199
|
|
|
155
200
|
text = text.replace(SECRET_ASSIGNMENT_PATTERN, (match, prefix, key, separator, value) => {
|
|
156
|
-
if (!keyLooksSensitive(key) || !valueLooksSensitive(key, value)) return match;
|
|
201
|
+
if (!keyLooksSensitive(key) || !valueLooksSensitive(key, value, assignmentSyntax(separator))) return match;
|
|
157
202
|
return `${prefix}${key}${separator} ${REDACTION}`;
|
|
158
203
|
});
|
|
159
204
|
|
|
@@ -173,7 +218,9 @@ function redactStorageValue(value, key) {
|
|
|
173
218
|
if (typeof value === "string") {
|
|
174
219
|
const redacted = redactSensitiveText(value);
|
|
175
220
|
if (redacted.redacted) return redacted.text;
|
|
176
|
-
|
|
221
|
+
// A field name in a structure is machine syntax, never prose, so length says
|
|
222
|
+
// nothing here either.
|
|
223
|
+
return key && keyLooksSensitive(key) && valueLooksSensitive(key, value, "assignment") ? REDACTION : value;
|
|
177
224
|
}
|
|
178
225
|
if (Array.isArray(value)) return value.map((item) => redactStorageValue(item, key));
|
|
179
226
|
if (!value || typeof value !== "object") return value;
|
|
@@ -185,6 +185,12 @@ export function judge(host, hosts, expect, result, family) {
|
|
|
185
185
|
if (target.protocol !== "https:") return `HTTP ${result.status} redirecting away from HTTPS to ${target.href}`;
|
|
186
186
|
if (target.port !== "" && target.port !== "443") return `HTTP ${result.status} redirecting to a non-standard port at ${target.href}`;
|
|
187
187
|
if (target.username !== "" || target.password !== "") return `HTTP ${result.status} redirecting to a URL carrying credentials`;
|
|
188
|
+
// The check that follows a redirect is the check of the other host's `/`, which
|
|
189
|
+
// is the only page this run verifies. A redirect to `/missing` or to `/?release=old`
|
|
190
|
+
// was accepted on the strength of the hostname alone, so the run reported the
|
|
191
|
+
// version served at a page it never fetched.
|
|
192
|
+
if (target.pathname !== "/") return `HTTP ${result.status} redirecting to ${target.pathname}, which is not the page being verified`;
|
|
193
|
+
if (target.search !== "" || target.hash !== "") return `HTTP ${result.status} redirecting to ${target.href}, which is not the page being verified`;
|
|
188
194
|
return null;
|
|
189
195
|
}
|
|
190
196
|
if (result.status !== 200) return `HTTP ${result.status}`;
|
|
@@ -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.
|
|
18
|
+
scripts/install-global.sh --version v1.1.4 --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.
|
|
130
|
+
printf '%s/%s (outside v1.1.4 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.
|
|
138
|
+
*"supported target"*|*"experimental"*|*"not release-gated"*|*"outside v1.1.4 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
|
;;
|
|
@@ -344,11 +344,15 @@ if [[ "$WITH_MCP" == false && "$MCP_PRESET_EXPLICIT" == true ]]; then
|
|
|
344
344
|
exit 2
|
|
345
345
|
fi
|
|
346
346
|
|
|
347
|
+
require_node_version
|
|
348
|
+
|
|
347
349
|
# The preset is only read at the very last step, after the platform package and
|
|
348
350
|
# the MCP adapter are already installed. A name with a typo therefore failed
|
|
349
351
|
# after two installs had landed, leaving a half-configured machine, and the
|
|
350
352
|
# dry-run still exited 0 because it never reached the check. Validating here
|
|
351
|
-
# costs one process and makes a bad preset cost nothing.
|
|
353
|
+
# costs one process and makes a bad preset cost nothing. It has to come after the
|
|
354
|
+
# Node check, not before: the validator is a Node script, so on a machine without
|
|
355
|
+
# Node it reported a missing interpreter as a bad preset.
|
|
352
356
|
if [[ "$WITH_MCP" == true ]]; then
|
|
353
357
|
if ! bash "$PLATFORM_ROOT/scripts/configure-mcp.sh" --scope global --preset "$MCP_PRESET" --validate-preset >/dev/null; then
|
|
354
358
|
echo "Nothing was installed." >&2
|
|
@@ -356,7 +360,6 @@ if [[ "$WITH_MCP" == true ]]; then
|
|
|
356
360
|
fi
|
|
357
361
|
fi
|
|
358
362
|
|
|
359
|
-
require_node_version
|
|
360
363
|
CURRENT_RUNTIME_SURFACE="$(detect_runtime_surface)"
|
|
361
364
|
warn_runtime_surface_if_needed "$CURRENT_RUNTIME_SURFACE"
|
|
362
365
|
CURRENT_RELEASE_TAG="$(node -e 'const fs = require("node:fs"); const p = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.stdout.write(`v${p.version}`);' "$PLATFORM_ROOT/package.json")"
|
package/scripts/team-doctor.sh
CHANGED
|
@@ -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.
|
|
140
|
+
note: "This runtime is outside the documented v1.1.4 support matrix."
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
143
|
|