diff --git a/PARITY.md b/PARITY.md
index ecc8a34..c9a1162 100644
--- a/PARITY.md
+++ b/PARITY.md
@@ -2,7 +2,7 @@
Updated 2026-10-14 KST. Python reference: **1.0.2**,
[`47c2371`](https://github.com/abruption/session-peer/tree/47c23713d0a2a3c11ebde6186afd8c43489b8b65/session_peer_core).
-This matrix describes the **TS 0.3.5 source/package contract**: the 0.2.0 unified listing, safe home selection,
+This matrix describes the **TS 0.3.6 source/package contract**: the 0.2.0 unified listing, safe home selection,
read-only doctor, text/help/positional input, Unicode 14.0.0 name matching and
separately managed TS skill guidance (#16/#17/#18/#19/#25), the
[0.2.1 reliability and hardening](#021-reliability-and-hardening) changes, and the
@@ -15,6 +15,12 @@ between releases, operators should also align reviewed commits; the wire does no
verify a source commit. `referenceVersion: "1.0.2"` identifies the comparison baseline,
not complete parity. See [VALIDATION.md](VALIDATION.md) for dated evidence.
+## 0.3.6 documentation patch
+
+This source/package carries the [published skill 0.2.1 guidance](#published-skill-021-adoption--2026-10-14-kst) adopted in PR #146, merge source `602cfc09b31ce8aad23e1dd9382a2448a61143f9`. All four guides use immutable skill source `0f494cabd05901c03161dc91792dcf89f8e96631`; skill metadata/full-version and Python reference remain unchanged. TS 0.3.6 adds only its explicitly reviewed finite row with the same skill 0.1.0/0.2.0/0.2.1 acceptance as public 0.3.5. Historical rows stay unchanged and unreviewed future versions remain incompatible.
+
+The patch updates package/banner/current examples and the stable release baseline to previous public 0.3.5. SSH still requires exact matching TS versions; 0.3.5 and 0.3.6 refuse each other. No native delivery behavior, platform qualification, automatic ACK/wait, retry, installation manager, skill publication or 0.4 experiment is added. Public availability must be checked in the registry; this source description is not publication evidence. Dated preparation and publication checks belong in VALIDATION.md and release notes.
+
## Published skill 0.2.1 adoption — 2026-10-14 KST
The four guides now pin the independently published [TS skill 0.2.1 release](https://github.com/abruption/session-peer-skill/releases/tag/session-peer-ts-v0.2.1) to immutable source [`0f494cabd05901c03161dc91792dcf89f8e96631`](https://github.com/abruption/session-peer-skill/tree/0f494cabd05901c03161dc91792dcf89f8e96631/session-peer-ts). Annotated tag object `4e7df7d8f99863e86b477eacce958033d7280246` peels to that source. The complete `SKILL.md` is **13,996 raw UTF-8 bytes**, SHA-256 `8c3520f95e0d6807303be96bfa06df69887ddaf76be05f1905505d0a956820c3`. The tag, source and file identities were independently rechecked after publication; the reviewed PR head `dc303b513d9da862f2ce21d648e168fdd847ae95` and published source have identical SKILL bytes.
diff --git a/README.ja.md b/README.ja.md
index a038730..d8ff55a 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -15,7 +15,7 @@
-**文書・ソースのバージョン: 0.3.5。** インストール前に、選択したリリースとレジストリのメタデータを確認してください。日付付きの準備・検証記録は [VALIDATION.md](VALIDATION.md)、リリース手順は [RELEASING.md](RELEASING.md) にあります。SSH の両端には同じ TypeScript クライアントのバージョンが必要で、0.3.4 と 0.3.5 は互いの SSH 事前確認を拒否します。
+**文書・ソースのバージョン: 0.3.6。** インストール前に、選択したリリースとレジストリのメタデータを確認してください。日付付きの準備・検証記録は [VALIDATION.md](VALIDATION.md)、リリース手順は [RELEASING.md](RELEASING.md) にあります。SSH の両端には同じ TypeScript クライアントのバージョンが必要で、0.3.5 と 0.3.6 は互いの SSH 事前確認を拒否します。
**実行中の Claude Code・Codex セッションを見つけ、ローカルまたは SSH でメッセージを送信します。**
Python 不要の Node.js クライアントです。パッケージ名とコマンド名は `session-peer` です。
@@ -56,15 +56,15 @@ SSH は既存の鍵・ホスト信頼設定と両端の**同じバージョン
`Get-Command session-peer -All` で PATH を確認してください。利用する実装を選び、
別の管理ツールのファイルを `--force` で上書きしないでください。
-**0.3.5 のインストール:** npm の導入・更新例を実行する前に、レジストリで公開状況と選択したリリースを確認してください。利用できない場合は[レビュー済みソースビルド](docs/guide.ja.md#ソースからビルド)を使います。
+**0.3.6 のインストール:** npm の導入・更新例を実行する前に、レジストリで公開状況と選択したリリースを確認してください。利用できない場合は[レビュー済みソースビルド](docs/guide.ja.md#ソースからビルド)を使います。
```sh
-npm view session-peer@0.3.5 version dist.integrity
-npm install --global --ignore-scripts session-peer@0.3.5
+npm view session-peer@0.3.6 version dist.integrity
+npm install --global --ignore-scripts session-peer@0.3.6
session-peer --version
```
-期待する出力: `session-peer 0.3.5 (typescript)`。分離インストール・ソースビルド・
+期待する出力: `session-peer 0.3.6 (typescript)`。分離インストール・ソースビルド・
Windows・削除の手順は以下の詳細ガイドを参照してください。
任意の `sp` 短縮名は 0.3.0 以降に含まれ(0.2.1 以前にはありません)、自動では有効になりません。
@@ -73,12 +73,12 @@ Windows・削除の手順は以下の詳細ガイドを参照してください
### Update
npm で導入したクライアントは npm で更新します。タグと対象バージョンを確認してから
-正確なバージョンを指定してください。以下は古い npm 版を選択した 0.3.5 に更新する例です。
+正確なバージョンを指定してください。以下は古い npm 版を選択した 0.3.6 に更新する例です。
CLI は更新をインストールせず、`session-peer update --check` は確認のみ行います。
```sh
npm view session-peer dist-tags
-npm install --global --ignore-scripts session-peer@0.3.5
+npm install --global --ignore-scripts session-peer@0.3.6
session-peer --version
```
diff --git a/README.ko.md b/README.ko.md
index 7625a45..0071713 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -20,7 +20,7 @@ Python을 설치하지 않아도 Node.js에서 실행되는 TypeScript 클라이
-**문서·소스 기준 버전: 0.3.5.** 설치 전에 선택한 릴리스와 레지스트리 메타데이터를 확인하세요. 날짜별 준비·검증 기록은 [VALIDATION.md](VALIDATION.md), 릴리스 절차는 [RELEASING.md](RELEASING.md)에 있습니다. SSH 양쪽에는 같은 TypeScript 클라이언트 버전이 필요하며, 0.3.4와 0.3.5는 서로의 SSH 사전 확인을 거부합니다.
+**문서·소스 기준 버전: 0.3.6.** 설치 전에 선택한 릴리스와 레지스트리 메타데이터를 확인하세요. 날짜별 준비·검증 기록은 [VALIDATION.md](VALIDATION.md), 릴리스 절차는 [RELEASING.md](RELEASING.md)에 있습니다. SSH 양쪽에는 같은 TypeScript 클라이언트 버전이 필요하며, 0.3.5와 0.3.6는 서로의 SSH 사전 확인을 거부합니다.
## 시연
@@ -65,15 +65,15 @@ PowerShell에서는 `Get-Command session-peer -All`로 PATH(명령 검색 경로
실행 파일을 찾는지 확인하세요. 사용할 구현을 선택하고 다른 패키지 관리자가 설치한
파일을 `--force`로 덮어쓰지 마세요. 설치와 갱신은 사용자가 npm으로 직접 수행합니다.
-**0.3.5 설치:** npm 설치·업데이트 예시를 실행하기 전에 레지스트리 제공 여부와 선택한 릴리스를 확인하세요. 해당 버전이 제공되지 않으면 [검토한 소스 빌드](docs/guide.ko.md#소스에서-빌드)를 사용하세요.
+**0.3.6 설치:** npm 설치·업데이트 예시를 실행하기 전에 레지스트리 제공 여부와 선택한 릴리스를 확인하세요. 해당 버전이 제공되지 않으면 [검토한 소스 빌드](docs/guide.ko.md#소스에서-빌드)를 사용하세요.
```sh
-npm view session-peer@0.3.5 version dist.integrity
-npm install --global --ignore-scripts session-peer@0.3.5
+npm view session-peer@0.3.6 version dist.integrity
+npm install --global --ignore-scripts session-peer@0.3.6
session-peer --version
```
-예상 결과는 `session-peer 0.3.5 (typescript)`입니다. 별도 경로에 설치하거나,
+예상 결과는 `session-peer 0.3.6 (typescript)`입니다. 별도 경로에 설치하거나,
소스에서 빌드하거나, Windows에서 설치·제거하는 방법은 [사용자 가이드](docs/guide.ko.md#설치)를 참고하세요.
선택 사항인 `sp` 단축 명령은 0.3.0 이상에 포함되며(0.2.1 이하에는 없음) 자동으로
@@ -84,13 +84,13 @@ session-peer --version
### 업데이트
npm으로 설치한 클라이언트는 사용자가 직접 npm으로 업데이트합니다. 사용 가능한 배포 태그
-(dist-tag)와 대상 버전을 확인한 뒤 해당 버전을 지정해 설치하세요. 아래 예시는 이전 npm 설치본을 선택한 0.3.5로
+(dist-tag)와 대상 버전을 확인한 뒤 해당 버전을 지정해 설치하세요. 아래 예시는 이전 npm 설치본을 선택한 0.3.6로
업데이트합니다. `session-peer` CLI는 업데이트를 설치하지 않습니다. `session-peer
update --check`는 새 버전이 있는지 확인할 뿐입니다.
```sh
npm view session-peer dist-tags
-npm install --global --ignore-scripts session-peer@0.3.5
+npm install --global --ignore-scripts session-peer@0.3.6
session-peer --version
```
diff --git a/README.md b/README.md
index ee8dec9..21eca50 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
-**Documentation and source version: 0.3.5.** Review the selected release and registry metadata before installation. Dated preparation and validation records are in [VALIDATION.md](VALIDATION.md); release procedures are in [RELEASING.md](RELEASING.md). SSH endpoints require the same TypeScript client version; 0.3.4 and 0.3.5 refuse each other’s SSH preflight.
+**Documentation and source version: 0.3.6.** Review the selected release and registry metadata before installation. Dated preparation and validation records are in [VALIDATION.md](VALIDATION.md); release procedures are in [RELEASING.md](RELEASING.md). SSH endpoints require the same TypeScript client version; 0.3.5 and 0.3.6 refuse each other’s SSH preflight.
**Find and message running Claude Code and Codex sessions, locally or over SSH.**
A Node.js client that runs without Python. Package and command: `session-peer`.
@@ -56,15 +56,15 @@ Check `type -a session-peer` on macOS/Linux or `Get-Command session-peer -All`
in PowerShell before installing. Choose the intended PATH entry; do not use
`--force` to overwrite another manager’s files.
-**Version 0.3.5 installation:** check registry availability and review the chosen release before running the npm install/update examples. If that version is unavailable, use the [reviewed source build](docs/guide.md#build-from-source).
+**Version 0.3.6 installation:** check registry availability and review the chosen release before running the npm install/update examples. If that version is unavailable, use the [reviewed source build](docs/guide.md#build-from-source).
```sh
-npm view session-peer@0.3.5 version dist.integrity
-npm install --global --ignore-scripts session-peer@0.3.5
+npm view session-peer@0.3.6 version dist.integrity
+npm install --global --ignore-scripts session-peer@0.3.6
session-peer --version
```
-Expected: `session-peer 0.3.5 (typescript)`. For isolated installation, source
+Expected: `session-peer 0.3.6 (typescript)`. For isolated installation, source
builds, Windows and removal, see the detailed guide below.
Optional `sp` shorthand ships with 0.3.0 and later (not 0.2.1 or earlier) and is
@@ -74,12 +74,12 @@ never enabled automatically. See [explicit activation and collisions](docs/short
Use npm for an npm-managed installation. Check the available tags and review
the target version, then install that exact version; this example updates an
-older npm installation to the selected 0.3.5 release. The CLI does not install updates;
+older npm installation to the selected 0.3.6 release. The CLI does not install updates;
`session-peer update --check` only reports them.
```sh
npm view session-peer dist-tags
-npm install --global --ignore-scripts session-peer@0.3.5
+npm install --global --ignore-scripts session-peer@0.3.6
session-peer --version
```
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 836e0f9..e1de074 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -15,7 +15,7 @@
-**文档和源码版本:0.3.5。** 安装前,请审核所选发布版本和注册表元数据。按日期记录的准备与验证结果见 [VALIDATION.md](VALIDATION.md),发布流程见 [RELEASING.md](RELEASING.md)。SSH 两端必须使用相同的 TypeScript 客户端版本;0.3.4 与 0.3.5 会拒绝彼此的 SSH 预检。
+**文档和源码版本:0.3.6。** 安装前,请审核所选发布版本和注册表元数据。按日期记录的准备与验证结果见 [VALIDATION.md](VALIDATION.md),发布流程见 [RELEASING.md](RELEASING.md)。SSH 两端必须使用相同的 TypeScript 客户端版本;0.3.5 与 0.3.6 会拒绝彼此的 SSH 预检。
**发现正在运行的 Claude Code 和 Codex 会话,并在本机或通过 SSH 发送消息。**
这是不需要 Python 的 Node.js 客户端。包名与命令名均为 `session-peer`。
@@ -54,15 +54,15 @@ session-peer send --to CLAUDE_PID --message '请检查 API 契约并回复。' -
`Get-Command session-peer -All` 检查 PATH。选择所需实现,不要用 `--force`
覆盖其他安装管理器的文件。
-**安装 0.3.5:** 运行 npm 安装或更新示例前,请确认注册表可用性并审核所选发布版本。如该版本不可用,请使用[已审核的源码构建](docs/guide.zh-CN.md#从源码构建)。
+**安装 0.3.6:** 运行 npm 安装或更新示例前,请确认注册表可用性并审核所选发布版本。如该版本不可用,请使用[已审核的源码构建](docs/guide.zh-CN.md#从源码构建)。
```sh
-npm view session-peer@0.3.5 version dist.integrity
-npm install --global --ignore-scripts session-peer@0.3.5
+npm view session-peer@0.3.6 version dist.integrity
+npm install --global --ignore-scripts session-peer@0.3.6
session-peer --version
```
-预期输出:`session-peer 0.3.5 (typescript)`。隔离安装、源码构建、Windows 和
+预期输出:`session-peer 0.3.6 (typescript)`。隔离安装、源码构建、Windows 和
卸载步骤见下方详细指南。
可选的 `sp` 简写包含在 0.3.0 及更高版本中(0.2.1 及更早版本没有),不会自动启用。
@@ -71,11 +71,11 @@ session-peer --version
### Update
使用 npm 更新由 npm 管理的安装。先检查标签并审阅目标版本,再安装指定版本。
-下面示例将旧 npm 安装更新至所选的 0.3.5。CLI 不会安装更新,`session-peer update --check` 只做检查。
+下面示例将旧 npm 安装更新至所选的 0.3.6。CLI 不会安装更新,`session-peer update --check` 只做检查。
```sh
npm view session-peer dist-tags
-npm install --global --ignore-scripts session-peer@0.3.5
+npm install --global --ignore-scripts session-peer@0.3.6
session-peer --version
```
diff --git a/RELEASING.md b/RELEASING.md
index 31226b2..42a34b8 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -9,6 +9,23 @@ the historical 0.3.2 settings-read exception is recorded below. The workflow
uses OIDC and no npm token. The protected GitHub environment requires a reviewer
and permits deployments from `main` only.
+## 0.3.6 stable release procedure
+
+This patch carries PR #146's published TS skill 0.2.1 pin and multilingual guidance from main `602cfc09b31ce8aad23e1dd9382a2448a61143f9`. It preserves the skill's immutable source/hash and all historical release evidence. Package/banner/examples use 0.3.6; its finite metadata row accepts the same three profiles as public 0.3.5. Draft #106–#109, #82 and #142 remain excluded.
+
+Read-only preparation baseline on 2026-10-14 KST: latest=0.3.5, preview=0.1.0-preview.1, target 0.3.6 absent. Previous stable integrity: `sha512-EJc2B5tcruzHXiHDd7tyJjv2DBLYE7IHJiPVHBzLY9fSVasEQRdwx+ZZmiyBF8WHpQ7+pI3SO2VXwsQTgOWP+Q==`; attestation metadata https://registry.npmjs.org/-/npm/v1/attestations/session-peer@0.3.5. Recheck before staging. Historical settings-read exceptions do not carry forward.
+
+At the 2026-10-14 preparation checkpoint, the user explicitly skipped the fresh Trusted Publisher lookup for this 0.3.6 publication. The current mapping/permissions are therefore not freshly confirmed; prior stage-only evidence is historical. Pending stages and ownership were checked after npm login. The user separately skipped the web package publish-MFA policy lookup for this 0.3.6 publication; that current policy is also not freshly confirmed. GitHub environment protection and actual final npm stage 2FA approval remain required. No settings, secrets, tokens or protections were changed.
+
+1. Review/merge the release preparation and require exact merged-main CLI/release gate, Windows Node 22/24 and CodeQL success.
+2. Fresh authenticated reads verify ownership, pending stages, stage-only Trusted Publisher mapping, package publish-MFA and GitHub npm environment reviewer/main-only protections. Any unread gate remains incomplete unless the user explicitly grants a current exception; settings/protections are never bypassed.
+3. Verify exact-source reproducible packing, isolated installation/removal and every packaged Markdown document under the [artifact documentation gate](#documentation-gate-for-the-next-release). Historical and public skill pins remain intact.
+4. Obtain separate approval naming the exact merged 40-character main SHA, `session-peer@0.3.6`, `stable-stage` and eventual latest promotion to 0.3.6.
+5. Dispatch publish.yml only with that approved SHA/version/mode/confirmation. Review retained Actions bytes before protected environment approval; verify staged bytes/provenance and obtain separate final npm 2FA approval. Never retry an uncertain upload.
+6. Verify public bytes/integrity/provenance/signatures and isolated version/install/removal. Preserve preview. Append dated evidence and create the user-requested GitHub v0.3.6 tag/release against the verified publication source, with exact-version SSH and separate skill/runtime lifecycle notes. No fleet install is authorized.
+
+The historical 0.3.5 preparation and settings exceptions below apply to that completed publication only.
+
## 0.3.5 publication checkpoint — 2026-10-13 KST
PR #144 is merged as `5409d0acd4b559c09920a63c5fd7f11fd10f5737`; its tree matches reviewed preparation `6fdf9d65fb0b62cca3801a3733af07add5fb6553`. Exact-main CLI and CodeQL runs passed. Publication has not started: artifact review found stale current-facing unmerged/candidate text, which requires a document-only correction, human merge and new exact-source/artifact approval. The dated preparation sections below retain their historical scope.
diff --git a/VALIDATION.md b/VALIDATION.md
index 3c3420b..ab6e14f 100644
--- a/VALIDATION.md
+++ b/VALIDATION.md
@@ -1,4 +1,12 @@
-# Validation record — updated 2026-10-13 KST
+# Validation record — updated 2026-10-14 KST
+
+## 0.3.6 preparation checks — 2026-10-14 KST
+
+This patch starts from merged PR #146/main `602cfc09b31ce8aad23e1dd9382a2448a61143f9`, whose exact-main CLI38493362484 and CodeQL38493362496 passed. Source/package/banner/current examples are 0.3.6 and its explicit finite row carries the same three skill profiles as public 0.3.5; the published skill0.2.1 source/hash and Python1.0.2 oracle remain unchanged. Draft #106–#109, #82 and #142 are excluded.
+
+Local macOS arm64 / Node24.16.0 validation: npm test193total/188passed/0failed/5Windows-onlyskips; build/types, metadata/guidance, release baseline, repository links/anchors and diff checks passed. The archived Python oracle was verified byte-for-byte against public1.0.2 source `47c23713d0a2a3c11ebde6186afd8c43489b8b65` before conformance tests. Package smoke passed reproducible packing and isolated install/uninstall plus bash/zsh shorthand contract/lifecycle. npm audit reported0vulnerabilities. An initial scoped run had two stale expected update-banner regex failures; both were corrected before the passing final full run. Package hashes before this record was added are not final release artifacts. Exact candidate-source/retained artifact hashes and final CI/review gates are recorded in the preparation PR; this record itself is not publication evidence.
+
+Read-only registry baseline was latest0.3.5, preview0.1.0-preview.1 and target0.3.6absent. Public0.3.5 bytes/integrity/provenance source/run matched the historical publication; those are not fresh publisher/MFA-setting audits. After npm login, pending-stage list was empty and sole maintainer was abruption. The user explicitly skipped the TrustedPublisher lookup for this0.3.6 release. The user separately skipped the fresh web package publish-MFA policy read for this 0.3.6 publication; neither skipped setting is freshly confirmed. Protected environment approval and actual final npm stage 2FA remain required. GitHub npm environment required reviewer abruption, disallowed administrator bypass, allowed main only and held zero secrets, as did repository Actions. No settings/token/protection change, native/model input, actual delivery/ACK, fleet/global install or publication occurred in these preparation checks.
## 0.3.5 pre-staging document correction — 2026-10-13 KST
diff --git a/docs/api.md b/docs/api.md
index 8e014b7..c5d094c 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -1,18 +1,18 @@
# API reference
-This reference describes source/package version **0.3.5**. Check registry availability and review the selected release before installation. Dated release evidence is recorded in [VALIDATION.md](../VALIDATION.md); before publication use a reviewed source artifact.
+This reference describes source/package version **0.3.6**. Check registry availability and review the selected release before installation. Dated release evidence is recorded in [VALIDATION.md](../VALIDATION.md); before publication use a reviewed source artifact.
[Back to README](../README.md) · [User guide](guide.md)
The package exposes a small ESM API with TypeScript declarations. Install it
-locally with `npm install --ignore-scripts session-peer@0.3.5` before importing it.
+locally with `npm install --ignore-scripts session-peer@0.3.6` before importing it.
Importing the package does not execute the CLI or submit a message.
```js
import { VERSION, VERSION_LINE, reply, envelope } from 'session-peer';
-console.log(VERSION); // 0.3.5
-console.log(VERSION_LINE); // session-peer 0.3.5 (typescript)
+console.log(VERSION); // 0.3.6
+console.log(VERSION_LINE); // session-peer 0.3.6 (typescript)
const address = 'session-peer://v1/reply?agent=claude&session=12345&transport=local';
console.log(reply(address)); // { to: '12345' }
console.log(envelope('Please review and reply.', true, address));
diff --git a/docs/design/typescript-skill-compatibility.md b/docs/design/typescript-skill-compatibility.md
index b590fe2..dfa2e2d 100644
--- a/docs/design/typescript-skill-compatibility.md
+++ b/docs/design/typescript-skill-compatibility.md
@@ -2,6 +2,10 @@
This document retains the original TS source proposal and dated design review for [#118](https://github.com/abruption/session-peer-ts/issues/118), based on the companion [Draft #20 at `963c3d7`](https://github.com/abruption/session-peer-skill/blob/963c3d748a11b1164013ee26c9c0ef285c52b285/docs/typescript-compatibility.md). Companion [design acceptance at `699a325`](https://github.com/abruption/session-peer-skill/blob/699a3251830c30c62d0f812363faf4d0eb0c8e0c/docs/typescript-compatibility.md) records the full static review of the exact candidates below on 2026-10-09. This design-only acceptance does not authorize runtime merge/release, successor skill metadata/publication, installation or pin changes, content authenticity, or live delivery support.
+## 0.3.6 finite row
+
+TS source/package 0.3.6 explicitly carries the same three reviewed skill profiles as public 0.3.5: 0.1.0, 0.2.0 and published 0.2.1. The immutable 0.2.1 source/hash below are unchanged. Default/undefined/outer inspection use compiled VERSION=0.3.6; an explicit 0.3.5 helper still returns its historical reviewed verdict. 0.3.7 and other unreviewed identities remain incompatible. Parser/reader behavior and all malformed/unsupported envelopes remain unchanged; this is an explicit finite row, not a SemVer range or a new skill release.
+
## Published skill 0.2.1 adoption — 2026-10-14 KST
The four guides now pin the independently published [TS skill 0.2.1 release](https://github.com/abruption/session-peer-skill/releases/tag/session-peer-ts-v0.2.1) to immutable source [`0f494cabd05901c03161dc91792dcf89f8e96631`](https://github.com/abruption/session-peer-skill/tree/0f494cabd05901c03161dc91792dcf89f8e96631/session-peer-ts). Annotated tag object `4e7df7d8f99863e86b477eacce958033d7280246` peels to that source. The complete `SKILL.md` is **13,996 raw UTF-8 bytes**, SHA-256 `8c3520f95e0d6807303be96bfa06df69887ddaf76be05f1905505d0a956820c3`. The tag, source and file identities were independently rechecked after publication; the reviewed PR head `dc303b513d9da862f2ce21d648e168fdd847ae95` and published source have identical SKILL bytes.
@@ -14,7 +18,7 @@ The published bytes are retained as an offline guidance fixture, distinct from t
The skill repository tag, Python skill revision, TS skill revision, and npm runtime version are independent. `runtime-min-version` describes the skill's baseline CLI compatibility floor; `runtime-full-version` describes its documented published CLI feature coverage reference. Neither is a security recommendation, a doctor-acceptance rule, permission to send, or evidence of platform/native compatibility. Optional operations still need their actual resolved TS executable's version/help and prerequisites.
-The published 0.3.4 checkpoint at [`afed4a5a8945012faf1b23d52cffecb70470a5b3`](https://github.com/abruption/session-peer-ts/tree/afed4a5a8945012faf1b23d52cffecb70470a5b3) has the following finite runtime/profile matrix. The current 0.3.5 source/package extension is documented in the successor-rollout section below; it does not change that immutable published artifact:
+The published 0.3.4 checkpoint at [`afed4a5a8945012faf1b23d52cffecb70470a5b3`](https://github.com/abruption/session-peer-ts/tree/afed4a5a8945012faf1b23d52cffecb70470a5b3) has the following finite runtime/profile matrix. The current 0.3.6 source/package row is documented above; the dated 0.3.5 successor rollout below is historical and does not change that immutable published artifact:
| Inspecting runtime | Skill version | Implementation | Minimum | Full | Policy | Verdict |
|---|---|---|---|---|---|---|
@@ -99,7 +103,7 @@ The preparation notes and rollout conditions in this section are retained from 2
[#139](https://github.com/abruption/session-peer-ts/issues/139) tracks the separate successor profile and rollout for updated TS skill documentation. The 0.3.5 source/package adds exactly the skill 0.2.1 profile below while retaining legacy 0.1.0 and published 0.2.0. Its complete candidate bytes/profile were reviewed for guard preparation as recorded below; final skill publication wording, immutable release and installation pin remain separate. Source/package support does not itself establish npm or skill publication.
-At the original [Draft #140 checkpoint `b917769`](https://github.com/abruption/session-peer-ts/tree/b917769047db41dedcb4aa1367a1b89915beb000), compiled VERSION was 0.3.4 and candidate 0.3.5 support was exercised only through helper arguments. [PR #144](https://github.com/abruption/session-peer-ts/pull/144) integrated that implementation and the compiled-default/full-file tests; its merge source is `5409d0acd4b559c09920a63c5fd7f11fd10f5737`. The current compiled VERSION is 0.3.5. Public 0.3.4 remains immutable; at that preparation checkpoint, no successor skill installation pin was approved.
+At the original [Draft #140 checkpoint `b917769`](https://github.com/abruption/session-peer-ts/tree/b917769047db41dedcb4aa1367a1b89915beb000), compiled VERSION was 0.3.4 and candidate 0.3.5 support was exercised only through helper arguments. [PR #144](https://github.com/abruption/session-peer-ts/pull/144) integrated that implementation and the compiled-default/full-file tests; its merge source is `5409d0acd4b559c09920a63c5fd7f11fd10f5737`. At that release-preparation checkpoint, compiled VERSION was 0.3.5. Public 0.3.4 remains immutable; at that preparation checkpoint, no successor skill installation pin was approved.
| Inspecting source/package runtime | Skill version | Implementation | Minimum | Full | Policy | Verdict |
| --- | --- | --- | --- | --- | --- | --- |
diff --git a/docs/guide.ja.md b/docs/guide.ja.md
index 0232226..9bac729 100644
--- a/docs/guide.ja.md
+++ b/docs/guide.ja.md
@@ -4,13 +4,13 @@
[English](guide.md) | [한국어](guide.ko.md) | [日本語](guide.ja.md) | [简体中文](guide.zh-CN.md)
-**文書・ソースのバージョン: 0.3.5。** インストール前に、選択したリリースとレジストリのメタデータを確認してください。日付付きの準備・検証記録は [VALIDATION.md](../VALIDATION.md)、リリース手順は [RELEASING.md](../RELEASING.md) にあります。SSH の両端には同じ TypeScript クライアントのバージョンが必要で、0.3.4 と 0.3.5 は互いの SSH 事前確認を拒否します。
+**文書・ソースのバージョン: 0.3.6。** インストール前に、選択したリリースとレジストリのメタデータを確認してください。日付付きの準備・検証記録は [VALIDATION.md](../VALIDATION.md)、リリース手順は [RELEASING.md](../RELEASING.md) にあります。SSH の両端には同じ TypeScript クライアントのバージョンが必要で、0.3.5 と 0.3.6 は互いの SSH 事前確認を拒否します。
-0.3.5 の詳細ガイドです([0.3.1 の信頼性の修正](../PARITY.md#031-reliability-fixes))。0.3.0 は複数ホスト SSH、送信者情報と返信経路、更新確認、任意の `sp` 短縮名を追加しました([0.3.0 の変更点](../PARITY.md#030-remote-usability-and-maintenance))。以前の公開記録は [0.3.1 公開リリースの証拠](../VALIDATION.md#public-031--2026-10-04-kst) にあります。以下の 0.1.0 の説明は旧版との比較です。
+0.3.6 の詳細ガイドです([0.3.1 の信頼性の修正](../PARITY.md#031-reliability-fixes))。0.3.0 は複数ホスト SSH、送信者情報と返信経路、更新確認、任意の `sp` 短縮名を追加しました([0.3.0 の変更点](../PARITY.md#030-remote-usability-and-maintenance))。以前の公開記録は [0.3.1 公開リリースの証拠](../VALIDATION.md#public-031--2026-10-04-kst) にあります。以下の 0.1.0 の説明は旧版との比較です。
-## 移行時の境界 (0.3.5)
+## 移行時の境界 (0.3.6)
[0.3.2 の公開記録](../VALIDATION.md#public-032--2026-10-07-kst)はリリースのソースと成果物を固定しています。SSH の事前確認は TS のバージョン表示の完全一致を検査しますが、コミットハッシュは検査しません。ソースビルドのレビュー済みコミットは運用者が別途揃える必要があります。
@@ -40,7 +40,7 @@
実行中の **Claude Code と Codex セッション**に、ローカルまたは SSH 経由でメッセージを送る TypeScript クライアントです。Node.js で動作し、Python は不要です。
-**0.3.5 の利用ガイドです。**パッケージ名は `session-peer`、CLI コマンドは **`session-peer`**。Relay サーバーやホスティングサービスは提供しません。
+**0.3.6 の利用ガイドです。**パッケージ名は `session-peer`、CLI コマンドは **`session-peer`**。Relay サーバーやホスティングサービスは提供しません。
## 機能と範囲
@@ -57,7 +57,7 @@ Relay 通信、MCP、wake/resume、Antigravity、自動更新は未実装です
ビルド後の `node dist/cli.js list --json` は Claude/Codex をまとめて表示し、`list --agent codex --json` は既知の home を検索します。公開 npm **0.1.0** では引き続き agent と Codex 一覧の home を明示します。0.2.0 でも明示的なコマンド形式を利用できます。
-対象は既定の `~/.codex`、`CODEX_HOME`、macOS Orca 直下のアカウント home、JSON 配列 `SESSION_PEER_CODEX_HOMES` のみです。`--codex-home` は Codex 一覧を固定して無関係な設定エラーを回避し、`--agent claude` は Codex 探索を省略します。同じ home の別名は統合し、異なる home の同じ UUID は保持します。送信には各行の `codexHome` を使ってください。任意 home の不在はエラーではなく、明示した home の不在・不正は読み取れた行を保持して終了コード 1 を返します。一覧は writer を選択せず、メッセージを提出しません。[一覧契約](../PARITY.md#source-unified-listing-contract--16--020)に順序・診断・`--all`・SSH を記載しています。0.2.0の Codex 送信は `--codex-home` 省略時に一意で安定した live writer を選択します。明示 home でも既知の競合 home をすべて検査します。非アクティブなキュー送信には保存済みスレッド、全候補の非アクティブ検証、および `--codex-home HOME --allow-inactive-codex-home` が必要で、wake/resume は実行しません。Dry-run は提出しません。JSON に診断 `codexHomeResolution` と、ネイティブ出力にある場合のみ `queueId` を追加しますが、消費確認ではありません。[選択契約](../PARITY.md#source-codex-home-selection--17--020)を参照してください。公開 **0.1.0** には明示 live home が必要で、非アクティブ許可オプションはありません。SSH の両端には同じ TypeScript クライアントのバージョン(0.3.5)が必要です。
+対象は既定の `~/.codex`、`CODEX_HOME`、macOS Orca 直下のアカウント home、JSON 配列 `SESSION_PEER_CODEX_HOMES` のみです。`--codex-home` は Codex 一覧を固定して無関係な設定エラーを回避し、`--agent claude` は Codex 探索を省略します。同じ home の別名は統合し、異なる home の同じ UUID は保持します。送信には各行の `codexHome` を使ってください。任意 home の不在はエラーではなく、明示した home の不在・不正は読み取れた行を保持して終了コード 1 を返します。一覧は writer を選択せず、メッセージを提出しません。[一覧契約](../PARITY.md#source-unified-listing-contract--16--020)に順序・診断・`--all`・SSH を記載しています。0.2.0の Codex 送信は `--codex-home` 省略時に一意で安定した live writer を選択します。明示 home でも既知の競合 home をすべて検査します。非アクティブなキュー送信には保存済みスレッド、全候補の非アクティブ検証、および `--codex-home HOME --allow-inactive-codex-home` が必要で、wake/resume は実行しません。Dry-run は提出しません。JSON に診断 `codexHomeResolution` と、ネイティブ出力にある場合のみ `queueId` を追加しますが、消費確認ではありません。[選択契約](../PARITY.md#source-codex-home-selection--17--020)を参照してください。公開 **0.1.0** には明示 live home が必要で、非アクティブ許可オプションはありません。SSH の両端には同じ TypeScript クライアントのバージョン(0.3.6)が必要です。
### 0.2.0 CLI の使いやすさ
@@ -80,15 +80,15 @@ macOS / Linux / Windows native、Node **22.x の 22.13 以上、または 24.x**
Node 22.x の 22.13 以上または 24.x を使用し、インストールする正確なバージョンの公開状況を確認してください。Python CLI も同じコマンド名を使うため、既存のインストールがあれば先に PATH を確認してください。
-**0.3.5 のインストール:** npm の導入・更新例を実行する前に、レジストリで公開状況と選択したリリースを確認してください。利用できない場合は[レビュー済みソースビルド](#ソースからビルド)を使います。
+**0.3.6 のインストール:** npm の導入・更新例を実行する前に、レジストリで公開状況と選択したリリースを確認してください。利用できない場合は[レビュー済みソースビルド](#ソースからビルド)を使います。
```sh
-npm view session-peer@0.3.5 version dist.integrity
-npm install --global --ignore-scripts session-peer@0.3.5
+npm view session-peer@0.3.6 version dist.integrity
+npm install --global --ignore-scripts session-peer@0.3.6
session-peer --version
```
-期待値は `session-peer 0.3.5 (typescript)`。
+期待値は `session-peer 0.3.6 (typescript)`。
1. `session-peer list --agent claude --json` で宛先を探し、正確な PID を選びます。
2. `session-peer send --to CLAUDE_PID --message 'Please reply after checking.' --dry-run --json` で提出せずに確認します。
@@ -107,7 +107,7 @@ npm run build
node dist/cli.js --version
npm pack --ignore-scripts
# 任意のグローバルインストール前に PATH の既存コマンドを確認
-npm install --global --ignore-scripts ./session-peer-0.3.5.tgz
+npm install --global --ignore-scripts ./session-peer-0.3.6.tgz
session-peer --version
```
@@ -117,7 +117,7 @@ session-peer --version
インストール前後に `type -a session-peer` と `command -v session-peer` を確認します。他の実装も同名コマンドを提供するため、PATH 上の一つを選ぶか `node /absolute/path/dist/cli.js` を使ってください。`--force` で他の管理ツールのファイルを上書きしないでください。Python パッケージ・スキル・サービスを自動変更しません。削除は `npm uninstall --global session-peer` を使い、PATH を再確認します。
-Windows PowerShell では `Get-Command session-peer -All` で既存コマンドを確認します。Python CLI を置換せずに試すには、`npm ci --ignore-scripts`、`npm run build`、`npm pack --ignore-scripts` の後、`npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.3.5.tgz` を実行し、`& "$env:TEMP\session-peer-ts-source\node_modules\.bin\session-peer.cmd" --version` で確認します。同じ prefix の `npm uninstall --prefix "$env:TEMP\session-peer-ts-source" session-peer` で削除します。
+Windows PowerShell では `Get-Command session-peer -All` で既存コマンドを確認します。Python CLI を置換せずに試すには、`npm ci --ignore-scripts`、`npm run build`、`npm pack --ignore-scripts` の後、`npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.3.6.tgz` を実行し、`& "$env:TEMP\session-peer-ts-source\node_modules\.bin\session-peer.cmd" --version` で確認します。同じ prefix の `npm uninstall --prefix "$env:TEMP\session-peer-ts-source" session-peer` で削除します。
## 使い方
@@ -208,13 +208,13 @@ Python は開発時の互換検証基準のみです(v1.0.2、`47c23713d0a2a3c
## npm リリース
-公開 **0.3.2** は 2026-10-07 KST に検証されました([固定された公開記録](../VALIDATION.md#public-032--2026-10-07-kst))。以下のレジストリ照会で 0.3.5 の現在の公開状況と npm タグを確認してください。日付付きの準備記録は、その後の公開結果を証明しません。
+公開 **0.3.2** は 2026-10-07 KST に検証されました([固定された公開記録](../VALIDATION.md#public-032--2026-10-07-kst))。以下のレジストリ照会で 0.3.6 の現在の公開状況と npm タグを確認してください。日付付きの準備記録は、その後の公開結果を証明しません。
0.2.1 は 2026-09-29 KST、0.2.0 は 2026-09-28 KST に公開・検証されました。
[0.2.1 公開記録](../VALIDATION.md#public-021--2026-09-29-kst)を参照し(以降のリリースはそれぞれ日付付きの記録を追加します)、導入前に正確なバージョンと現在のタグを確認してください。
```sh
-npm view session-peer@0.3.5 version dist.integrity
+npm view session-peer@0.3.6 version dist.integrity
npm view session-peer dist-tags
```
@@ -256,7 +256,7 @@ session-peer update --check --channel preview --output-format text
実行中の CLI のパスからインストールの管理者を確実に特定できた場合にだけ示します。
対象は、自身の `session-peer` ランチャーがこのパッケージを指す npm グローバル prefix
(既定、Homebrew、nvm、nvm-windows、fnm。例:
-`npm install --global --ignore-scripts session-peer@0.3.5`)、マニフェストで
+`npm install --global --ignore-scripts session-peer@0.3.6`)、マニフェストで
`session-peer` を宣言している pnpm・Yarn・Bun のグローバルストア、Volta、npx キャッシュ
です。プロジェクトへのインストール(`npm_project`、`pnpm_project`)、ソースチェック
アウト(`source`)、それ以外(`unknown`)では `updateCommand: null` と `guidance` の文だけを
diff --git a/docs/guide.ko.md b/docs/guide.ko.md
index 0eaa633..ab46653 100644
--- a/docs/guide.ko.md
+++ b/docs/guide.ko.md
@@ -4,13 +4,13 @@
[English](guide.md) | [한국어](guide.ko.md) | [日本語](guide.ja.md) | [简体中文](guide.zh-CN.md)
-**문서·소스 기준 버전: 0.3.5.** 설치 전에 선택한 릴리스와 레지스트리 메타데이터를 확인하세요. 날짜별 준비·검증 기록은 [VALIDATION.md](../VALIDATION.md), 릴리스 절차는 [RELEASING.md](../RELEASING.md)에 있습니다. SSH 양쪽에는 같은 TypeScript 클라이언트 버전이 필요하며, 0.3.4와 0.3.5는 서로의 SSH 사전 확인을 거부합니다.
+**문서·소스 기준 버전: 0.3.6.** 설치 전에 선택한 릴리스와 레지스트리 메타데이터를 확인하세요. 날짜별 준비·검증 기록은 [VALIDATION.md](../VALIDATION.md), 릴리스 절차는 [RELEASING.md](../RELEASING.md)에 있습니다. SSH 양쪽에는 같은 TypeScript 클라이언트 버전이 필요하며, 0.3.5와 0.3.6는 서로의 SSH 사전 확인을 거부합니다.
-이 가이드는 0.3.5를 다룹니다([0.3.1 신뢰성 수정](../PARITY.md#031-reliability-fixes)). 0.3.0에는 여러 호스트를 통한 SSH 전송, 발신자 정보와 회신 경로, 업데이트 확인, 선택 기능인 `sp` 단축 명령이 추가됐습니다([0.3.0 변경 사항](../PARITY.md#030-remote-usability-and-maintenance)). [0.3.1 공개 릴리스 기록](../VALIDATION.md#public-031--2026-10-04-kst)은 이전 버전의 발행 내역입니다. 아래에서 0.1.0을 언급할 때는 이전 버전을 설명합니다.
+이 가이드는 0.3.6를 다룹니다([0.3.1 신뢰성 수정](../PARITY.md#031-reliability-fixes)). 0.3.0에는 여러 호스트를 통한 SSH 전송, 발신자 정보와 회신 경로, 업데이트 확인, 선택 기능인 `sp` 단축 명령이 추가됐습니다([0.3.0 변경 사항](../PARITY.md#030-remote-usability-and-maintenance)). [0.3.1 공개 릴리스 기록](../VALIDATION.md#public-031--2026-10-04-kst)은 이전 버전의 발행 내역입니다. 아래에서 0.1.0을 언급할 때는 이전 버전을 설명합니다.
-## 마이그레이션 경계 (0.3.5)
+## 마이그레이션 경계 (0.3.6)
[0.3.2 공개 기록](../VALIDATION.md#public-032--2026-10-07-kst)은 릴리스 소스와 산출물을 고정합니다. SSH 사전 확인은 정확히 같은 TS 버전 표시를 검사하며 커밋 해시는 검사하지 않습니다. 소스 빌드의 검토 커밋 일치는 운영자가 별도로 확인해야 합니다.
@@ -92,7 +92,7 @@ JSON에는 정제된 `codexHomeResolution`이 포함되며, 네이티브 응답
`queueId`도 포함됩니다. 둘 다 메시지를 읽거나 처리했음을 확인하지 않습니다.
[홈 선택 계약](../PARITY.md#source-codex-home-selection--17--020)을 참고하세요.
공개 **0.1.0**은 여전히 실행 중인 홈을 명시해야 하며 비활성 세션 허용 옵션이 없습니다.
-SSH를 사용할 때는 발신·수신 양쪽에 같은 TypeScript 클라이언트 버전(0.3.5)을 설치해야
+SSH를 사용할 때는 발신·수신 양쪽에 같은 TypeScript 클라이언트 버전(0.3.6)을 설치해야
합니다.
@@ -132,20 +132,20 @@ Claude Code에는 받은 메시지함에 접근할 수 있는 실행 중 TUI(터
Node 22.x의 22.13 이상 또는 24.x를 사용하세요. Python CLI도 같은 `session-peer`
명령 이름을 쓰므로, 설치 전에 PATH(명령 검색 경로)에서 어떤 실행 파일을 찾는지
-확인하세요. 아래 명령은 선택한 0.3.5를 npm으로 설치하는 예입니다.
+확인하세요. 아래 명령은 선택한 0.3.6를 npm으로 설치하는 예입니다.
CLI 자체는 패키지를 설치하거나 업데이트하지 않습니다.
-**0.3.5 설치:** npm 설치·업데이트 예시를 실행하기 전에 레지스트리 제공 여부와 선택한 릴리스를 확인하세요. 해당 버전이 제공되지 않으면 [검토한 소스 빌드](#소스에서-빌드)를 사용하세요.
+**0.3.6 설치:** npm 설치·업데이트 예시를 실행하기 전에 레지스트리 제공 여부와 선택한 릴리스를 확인하세요. 해당 버전이 제공되지 않으면 [검토한 소스 빌드](#소스에서-빌드)를 사용하세요.
```sh
-npm view session-peer@0.3.5 version dist.integrity
-npm install --global --ignore-scripts session-peer@0.3.5
-session-peer --version # session-peer 0.3.5 (typescript)
+npm view session-peer@0.3.6 version dist.integrity
+npm install --global --ignore-scripts session-peer@0.3.6
+session-peer --version # session-peer 0.3.6 (typescript)
session-peer list --agent claude --json
session-peer send --to CLAUDE_PID --message 'Please review the API contract.' --dry-run --json
```
-예상 결과는 `session-peer 0.3.5 (typescript)`입니다.
+예상 결과는 `session-peer 0.3.6 (typescript)`입니다.
1. 세션을 조회하고 `CLAUDE_PID`를 대상으로 선택한 정확한 PID로 바꾸세요.
2. `--dry-run`으로 검증합니다. 이 단계에서는 메시지를 제출하지 않습니다.
@@ -173,11 +173,11 @@ npm run build
node dist/cli.js --version
npm pack --ignore-scripts
# Optional global install: first check which session-peer your PATH selects.
-npm install --global --ignore-scripts ./session-peer-0.3.5.tgz
+npm install --global --ignore-scripts ./session-peer-0.3.6.tgz
session-peer --version
```
-0.3.5에서 예상되는 결과는 `session-peer 0.3.5 (typescript)`입니다. 로컬 빌드
+0.3.6에서 예상되는 결과는 `session-peer 0.3.6 (typescript)`입니다. 로컬 빌드
산출물을 선택하려면 `./...tgz` 경로를 그대로 두세요. 이후 버전에 이 명령을 사용할
때는 먼저 체크아웃한 소스의 버전을 확인하세요.
@@ -198,7 +198,7 @@ Python CLI를 덮어쓰지 않으려면 격리 prefix에 설치·제거할 수
npm ci --ignore-scripts
npm run build
npm pack --ignore-scripts
-npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.3.5.tgz
+npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.3.6.tgz
& "$env:TEMP\session-peer-ts-source\node_modules\.bin\session-peer.cmd" --version
# Later: npm uninstall --prefix "$env:TEMP\session-peer-ts-source" session-peer
```
@@ -441,7 +441,7 @@ Windows x64/Node 24에서 실제 TUI로 확인한 ACK는 별도의 일회성 증
## npm 릴리스
-아래 레지스트리 조회로 0.3.5의 현재 제공 여부와 npm 태그를 확인하세요. 날짜가 기록된 준비 근거는 이후 발행 결과를 입증하지 않습니다.
+아래 레지스트리 조회로 0.3.6의 현재 제공 여부와 npm 태그를 확인하세요. 날짜가 기록된 준비 근거는 이후 발행 결과를 입증하지 않습니다.
0.3.2는 2026-10-07 KST에, 0.3.1은 2026-10-04 KST에 공개·검증했습니다.
[0.3.2 공개 기록](../VALIDATION.md#public-032--2026-10-07-kst)과
@@ -452,7 +452,7 @@ Windows x64/Node 24에서 실제 TUI로 확인한 ACK는 별도의 일회성 증
설치 전에는 정확한 버전과 현재 npm 태그를 확인하세요.
```sh
-npm view session-peer@0.3.5 version dist.integrity
+npm view session-peer@0.3.6 version dist.integrity
npm view session-peer dist-tags
```
@@ -507,7 +507,7 @@ session-peer update --check --channel preview --output-format text
`updateCommand`는 실행 중인 CLI 경로로 설치 주체를 확인할 수 있을 때 사용자에게 안내합니다.
해당하는 경우는
자체 `session-peer` 실행기가 이 패키지를 가리키는 npm 전역 prefix(기본, Homebrew, nvm,
-nvm-windows, fnm. 예: `npm install --global --ignore-scripts session-peer@0.3.5`),
+nvm-windows, fnm. 예: `npm install --global --ignore-scripts session-peer@0.3.6`),
매니페스트에 `session-peer`가 선언된 pnpm·Yarn·Bun 전역 저장소, Volta, npx 캐시입니다.
프로젝트 설치(`npm_project`, `pnpm_project`), 소스 체크아웃(`source`), 그 밖의
경우(`unknown`)에는 `updateCommand: null`과 `guidance` 문장만 돌려주므로, 관련 없는 현재
diff --git a/docs/guide.md b/docs/guide.md
index 9c32085..3086398 100644
--- a/docs/guide.md
+++ b/docs/guide.md
@@ -4,13 +4,13 @@
[English](guide.md) | [한국어](guide.ko.md) | [日本語](guide.ja.md) | [简体中文](guide.zh-CN.md)
-**Documentation and source version: 0.3.5.** Review the selected release and registry metadata before installation. Dated preparation and validation records are in [VALIDATION.md](../VALIDATION.md); release procedures are in [RELEASING.md](../RELEASING.md). SSH endpoints require the same TypeScript client version; 0.3.4 and 0.3.5 refuse each other’s SSH preflight.
+**Documentation and source version: 0.3.6.** Review the selected release and registry metadata before installation. Dated preparation and validation records are in [VALIDATION.md](../VALIDATION.md); release procedures are in [RELEASING.md](../RELEASING.md). SSH endpoints require the same TypeScript client version; 0.3.5 and 0.3.6 refuse each other’s SSH preflight.
-This guide covers version 0.3.5 ([0.3.1 reliability fixes](../PARITY.md#031-reliability-fixes)). Version 0.3.0 introduced multi-host SSH, sender context and return routes, update checks and the optional `sp` shorthand ([0.3.0 changes](../PARITY.md#030-remote-usability-and-maintenance)). [Public 0.3.1 release evidence](../VALIDATION.md#public-031--2026-10-04-kst) records a prior publication. References to 0.1.0 below describe the older release.
+This guide covers version 0.3.6 ([0.3.1 reliability fixes](../PARITY.md#031-reliability-fixes)). Version 0.3.0 introduced multi-host SSH, sender context and return routes, update checks and the optional `sp` shorthand ([0.3.0 changes](../PARITY.md#030-remote-usability-and-maintenance)). [Public 0.3.1 release evidence](../VALIDATION.md#public-031--2026-10-04-kst) records a prior publication. References to 0.1.0 below describe the older release.
-## Migration boundaries (0.3.5)
+## Migration boundaries (0.3.6)
The [public 0.3.2 record](../VALIDATION.md#public-032--2026-10-07-kst) fixes the
release source and artifact. SSH preflight checks the exact TS version banner,
@@ -73,7 +73,7 @@ Relay/MCP are outside this migration comparison.
**Find and message running Claude Code and Codex sessions, locally or over SSH.**
This TypeScript client runs on Node.js without Python.
-This guide describes **version 0.3.5**. The npm package and CLI command are both **`session-peer`**. This
+This guide describes **version 0.3.6**. The npm package and CLI command are both **`session-peer`**. This
project does not provide a Relay server or hosted service.
## Quick start
@@ -81,12 +81,12 @@ project does not provide a Relay server or hosted service.
Use Node **22.13+ within 22.x or 24.x**. Check which `session-peer` your PATH
selects if the Python CLI is already installed; both packages use that command.
-**Version 0.3.5 installation:** check registry availability and review the chosen release before running the npm install/update examples. If that version is unavailable, use the [reviewed source build](#build-from-source).
+**Version 0.3.6 installation:** check registry availability and review the chosen release before running the npm install/update examples. If that version is unavailable, use the [reviewed source build](#build-from-source).
```sh
-npm view session-peer@0.3.5 version dist.integrity
-npm install --global --ignore-scripts session-peer@0.3.5
-session-peer --version # session-peer 0.3.5 (typescript)
+npm view session-peer@0.3.6 version dist.integrity
+npm install --global --ignore-scripts session-peer@0.3.6
+session-peer --version # session-peer 0.3.6 (typescript)
session-peer list --agent claude --json
session-peer send --to CLAUDE_PID --message 'Please review the API contract.' --dry-run --json
```
@@ -153,7 +153,7 @@ submits nothing. JSON adds sanitized `codexHomeResolution` and, when supplied by
native queue output, `queueId`; neither confirms consumption. See the
[selection contract](../PARITY.md#source-codex-home-selection--17--020).
Published **0.1.0** still requires an explicit live home and has no inactive opt-in.
-On SSH, both ends need the same TypeScript client version (0.3.5).
+On SSH, both ends need the same TypeScript client version (0.3.6).
## Requirements
@@ -177,11 +177,11 @@ npm run build
node dist/cli.js --version
npm pack --ignore-scripts
# Optional global install: first check which session-peer your PATH selects.
-npm install --global --ignore-scripts ./session-peer-0.3.5.tgz
+npm install --global --ignore-scripts ./session-peer-0.3.6.tgz
session-peer --version
```
-Expected for version 0.3.5: `session-peer 0.3.5 (typescript)`. Keep the `./...tgz` path to
+Expected for version 0.3.6: `session-peer 0.3.6 (typescript)`. Keep the `./...tgz` path to
select the locally built artifact. Check the checkout version before using
these commands for a later release.
@@ -201,7 +201,7 @@ On Windows PowerShell, inspect competing commands with
npm ci --ignore-scripts
npm run build
npm pack --ignore-scripts
-npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.3.5.tgz
+npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.3.6.tgz
& "$env:TEMP\session-peer-ts-source\node_modules\.bin\session-peer.cmd" --version
# Later: npm uninstall --prefix "$env:TEMP\session-peer-ts-source" session-peer
```
@@ -464,7 +464,7 @@ separate approval. Licensed under [MIT](../LICENSE).
## Package release
-Public **0.3.2** was verified on 2026-10-07 KST ([immutable checkpoint](../VALIDATION.md#public-032--2026-10-07-kst)). Check current 0.3.5 availability and npm tags with the registry queries below; dated preparation evidence does not establish a later publication outcome.
+Public **0.3.2** was verified on 2026-10-07 KST ([immutable checkpoint](../VALIDATION.md#public-032--2026-10-07-kst)). Check current 0.3.6 availability and npm tags with the registry queries below; dated preparation evidence does not establish a later publication outcome.
Version 0.2.1 was published and verified on 2026-09-29 KST and 0.2.0 on
2026-09-28 KST; see the [0.2.1 public release record](../VALIDATION.md#public-021--2026-09-29-kst).
@@ -472,7 +472,7 @@ Later releases add their own dated records. Check the exact version and current
tags before installing:
```sh
-npm view session-peer@0.3.5 version dist.integrity
+npm view session-peer@0.3.6 version dist.integrity
npm view session-peer dist-tags
```
@@ -524,7 +524,7 @@ and reports `current`, `latest`, `channel` (`latest` by default, or `preview`),
installation's owner is positively identified from the running CLI's path:
an npm global prefix whose own `session-peer` launcher points at this package
(default, Homebrew, nvm, nvm-windows and fnm prefixes), for example
-`npm install --global --ignore-scripts session-peer@0.3.5`; a pnpm, Yarn or Bun
+`npm install --global --ignore-scripts session-peer@0.3.6`; a pnpm, Yarn or Bun
global store whose manifest declares `session-peer`; Volta; or the npx cache.
Project installs (`npm_project`, `pnpm_project`), source checkouts (`source`)
and anything else (`unknown`) get `updateCommand: null` and a `guidance`
diff --git a/docs/guide.zh-CN.md b/docs/guide.zh-CN.md
index 2c97354..1a13b10 100644
--- a/docs/guide.zh-CN.md
+++ b/docs/guide.zh-CN.md
@@ -4,13 +4,13 @@
[English](guide.md) | [한국어](guide.ko.md) | [日本語](guide.ja.md) | [简体中文](guide.zh-CN.md)
-**文档和源码版本:0.3.5。** 安装前,请审核所选发布版本和注册表元数据。按日期记录的准备与验证结果见 [VALIDATION.md](../VALIDATION.md),发布流程见 [RELEASING.md](../RELEASING.md)。SSH 两端必须使用相同的 TypeScript 客户端版本;0.3.4 与 0.3.5 会拒绝彼此的 SSH 预检。
+**文档和源码版本:0.3.6。** 安装前,请审核所选发布版本和注册表元数据。按日期记录的准备与验证结果见 [VALIDATION.md](../VALIDATION.md),发布流程见 [RELEASING.md](../RELEASING.md)。SSH 两端必须使用相同的 TypeScript 客户端版本;0.3.5 与 0.3.6 会拒绝彼此的 SSH 预检。
-这是 0.3.5 详细指南([0.3.1 可靠性修复](../PARITY.md#031-reliability-fixes))。0.3.0 新增了多主机 SSH、发送者信息与回复路由、更新检查以及可选的 `sp` 简写([0.3.0 变更](../PARITY.md#030-remote-usability-and-maintenance))。此前的发布记录见 [0.3.1 公开发布证据](../VALIDATION.md#public-031--2026-10-04-kst);下文 0.1.0 的说明用于旧版本对比。
+这是 0.3.6 详细指南([0.3.1 可靠性修复](../PARITY.md#031-reliability-fixes))。0.3.0 新增了多主机 SSH、发送者信息与回复路由、更新检查以及可选的 `sp` 简写([0.3.0 变更](../PARITY.md#030-remote-usability-and-maintenance))。此前的发布记录见 [0.3.1 公开发布证据](../VALIDATION.md#public-031--2026-10-04-kst);下文 0.1.0 的说明用于旧版本对比。
-## 迁移边界 (0.3.5)
+## 迁移边界 (0.3.6)
[0.3.2 公开记录](../VALIDATION.md#public-032--2026-10-07-kst)固定了发布源码和产物。SSH 预检验证 TS 版本标识完全一致,不检查提交哈希;源码构建使用的已审核提交需要由操作人员另行对齐。
@@ -40,7 +40,7 @@
向正在运行的 **Claude Code 和 Codex 会话**发送消息,支持本机和跨机器 SSH。这是运行于 Node.js 的 TypeScript 客户端,不需要 Python。
-**这是 0.3.5 使用指南。**包名为 `session-peer`,CLI 命令为 **`session-peer`**。本项目不提供 Relay 服务器或托管服务。
+**这是 0.3.6 使用指南。**包名为 `session-peer`,CLI 命令为 **`session-peer`**。本项目不提供 Relay 服务器或托管服务。
## 功能与边界
@@ -57,7 +57,7 @@
构建后,`node dist/cli.js list --json` 合并列出 Claude/Codex,`list --agent codex --json` 查找已知 home。公开 npm **0.1.0** 仍要求显式 agent,以及 Codex 列表所需的 home;0.2.0 也支持显式命令格式。
-范围仅限默认 `~/.codex`、`CODEX_HOME`、macOS Orca 下的直接账户 home,以及 JSON 数组 `SESSION_PEER_CODEX_HOMES`。`--codex-home` 固定 Codex 列表并绕过无关配置错误;`--agent claude` 完全跳过 Codex 探索。同一 home 的别名合并,不同 home 的相同 UUID 保留。发送时使用每行的 `codexHome`。可选 home 缺失不是错误;显式 home 缺失或无效会保留成功读取的行,并返回退出码 1。列表不会选择 writer 或提交消息。[列表契约](../PARITY.md#source-unified-listing-contract--16--020)说明排序、诊断、`--all` 和 SSH 行为。0.2.0的 Codex 发送在省略 `--codex-home` 时选择唯一且稳定的 live writer;显式 home 仍会检查所有已知竞争 home。非活动队列提交需要已保存的线程、所有候选均确认非活动,以及 `--codex-home HOME --allow-inactive-codex-home`,不会执行 wake/resume。Dry-run 不提交。JSON 新增诊断 `codexHomeResolution`,且仅在原生输出提供时包含 `queueId`;两者均不代表消费确认。请参阅[选择契约](../PARITY.md#source-codex-home-selection--17--020)。公开 **0.1.0** 仍需要显式 live home,且没有非活动许可选项。SSH 两端需要相同的 TypeScript 客户端版本(0.3.5)。
+范围仅限默认 `~/.codex`、`CODEX_HOME`、macOS Orca 下的直接账户 home,以及 JSON 数组 `SESSION_PEER_CODEX_HOMES`。`--codex-home` 固定 Codex 列表并绕过无关配置错误;`--agent claude` 完全跳过 Codex 探索。同一 home 的别名合并,不同 home 的相同 UUID 保留。发送时使用每行的 `codexHome`。可选 home 缺失不是错误;显式 home 缺失或无效会保留成功读取的行,并返回退出码 1。列表不会选择 writer 或提交消息。[列表契约](../PARITY.md#source-unified-listing-contract--16--020)说明排序、诊断、`--all` 和 SSH 行为。0.2.0的 Codex 发送在省略 `--codex-home` 时选择唯一且稳定的 live writer;显式 home 仍会检查所有已知竞争 home。非活动队列提交需要已保存的线程、所有候选均确认非活动,以及 `--codex-home HOME --allow-inactive-codex-home`,不会执行 wake/resume。Dry-run 不提交。JSON 新增诊断 `codexHomeResolution`,且仅在原生输出提供时包含 `queueId`;两者均不代表消费确认。请参阅[选择契约](../PARITY.md#source-codex-home-selection--17--020)。公开 **0.1.0** 仍需要显式 live home,且没有非活动许可选项。SSH 两端需要相同的 TypeScript 客户端版本(0.3.6)。
### 0.2.0 CLI 易用性
@@ -79,15 +79,15 @@ macOS、Linux 或 Windows native;Node **22.x 中的 22.13 及以上,或 24.x
使用 Node 22.x 的 22.13 及以上或 24.x,并确认要安装的确切版本是否已发布。Python CLI 也使用同一个命令名;若已有安装,请先检查 PATH。
-**安装 0.3.5:** 运行 npm 安装或更新示例前,请确认注册表可用性并审核所选发布版本。如该版本不可用,请使用[已审核的源码构建](#从源码构建)。
+**安装 0.3.6:** 运行 npm 安装或更新示例前,请确认注册表可用性并审核所选发布版本。如该版本不可用,请使用[已审核的源码构建](#从源码构建)。
```sh
-npm view session-peer@0.3.5 version dist.integrity
-npm install --global --ignore-scripts session-peer@0.3.5
+npm view session-peer@0.3.6 version dist.integrity
+npm install --global --ignore-scripts session-peer@0.3.6
session-peer --version
```
-预期输出:`session-peer 0.3.5 (typescript)`。
+预期输出:`session-peer 0.3.6 (typescript)`。
1. 用 `session-peer list --agent claude --json` 查找目标并选择准确 PID。
2. 用 `session-peer send --to CLAUDE_PID --message 'Please reply after checking.' --dry-run --json` 验证,不提交。
@@ -106,7 +106,7 @@ npm run build
node dist/cli.js --version
npm pack --ignore-scripts
# 可选:全局安装前先检查 PATH 选择的现有命令
-npm install --global --ignore-scripts ./session-peer-0.3.5.tgz
+npm install --global --ignore-scripts ./session-peer-0.3.6.tgz
session-peer --version
```
@@ -116,7 +116,7 @@ session-peer --version
安装前后用 `type -a session-peer` 和 `command -v session-peer` 检查实际执行项。其他实现也可能提供同名命令;请选择 PATH 上的一种,或显式运行 `node /absolute/path/dist/cli.js`。不要用 `--force` 覆盖其他管理器的文件。本包不会自动安装、删除或调整 Python 包、技能或服务。卸载使用 `npm uninstall --global session-peer`,随后再次检查 PATH。
-在 Windows PowerShell 中用 `Get-Command session-peer -All` 检查已有命令。为了不替换 Python CLI,可先执行 `npm ci --ignore-scripts`、`npm run build`、`npm pack --ignore-scripts`,再用 `npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.3.5.tgz` 安装到隔离目录。以 `& "$env:TEMP\session-peer-ts-source\node_modules\.bin\session-peer.cmd" --version` 验证,并可用 `npm uninstall --prefix "$env:TEMP\session-peer-ts-source" session-peer` 卸载。
+在 Windows PowerShell 中用 `Get-Command session-peer -All` 检查已有命令。为了不替换 Python CLI,可先执行 `npm ci --ignore-scripts`、`npm run build`、`npm pack --ignore-scripts`,再用 `npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.3.6.tgz` 安装到隔离目录。以 `& "$env:TEMP\session-peer-ts-source\node_modules\.bin\session-peer.cmd" --version` 验证,并可用 `npm uninstall --prefix "$env:TEMP\session-peer-ts-source" session-peer` 卸载。
## 使用
@@ -207,14 +207,14 @@ Python 只用作开发时的兼容性基准(v1.0.2,`47c23713d0a2a3c11ebde618
## npm 版本
-公开 **0.3.2** 于 2026-10-07 KST 完成验证([不可变公开记录](../VALIDATION.md#public-032--2026-10-07-kst))。请用下方注册表查询确认 0.3.5 的当前可用性和 npm 标签;带日期的准备证据不能证明后续发布结果。
+公开 **0.3.2** 于 2026-10-07 KST 完成验证([不可变公开记录](../VALIDATION.md#public-032--2026-10-07-kst))。请用下方注册表查询确认 0.3.6 的当前可用性和 npm 标签;带日期的准备证据不能证明后续发布结果。
0.2.1 已于 2026-09-29 KST、0.2.0 已于 2026-09-28 KST 公开并验证。请参阅
[0.2.1 公开记录](../VALIDATION.md#public-021--2026-09-29-kst)(之后的版本各自追加带日期的记录),
安装前核对准确版本与当前标签。
```sh
-npm view session-peer@0.3.5 version dist.integrity
+npm view session-peer@0.3.6 version dist.integrity
npm view session-peer dist-tags
```
@@ -253,7 +253,7 @@ dist-tag(3 秒超时,不重试),并报告 `current`、`latest`、`channe
`ahead`)、`managedBy`、`updateCommand` 和 `guidance`。只有根据当前 CLI 路径能确切识别
安装的管理方时才给出命令:其自身 `session-peer` 启动器指向本包的 npm 全局 prefix
(默认、Homebrew、nvm、nvm-windows、fnm,例如
-`npm install --global --ignore-scripts session-peer@0.3.5`),清单中声明了
+`npm install --global --ignore-scripts session-peer@0.3.6`),清单中声明了
`session-peer` 的 pnpm、Yarn、Bun 全局存储,Volta,或 npx 缓存。项目安装
(`npm_project`、`pnpm_project`)、源码检出(`source`)以及其他情况(`unknown`)只返回
`updateCommand: null` 和一句 `guidance`,因此不会给出可能修改无关当前目录的命令。它只报告
diff --git a/docs/shorthand.md b/docs/shorthand.md
index 2e8107d..be1bc78 100644
--- a/docs/shorthand.md
+++ b/docs/shorthand.md
@@ -1,6 +1,6 @@
# Optional `sp` shorthand
-This reference describes source/package version 0.3.5. Check registry availability before using a version-pinned npm installation; before publication use a reviewed source artifact. The shorthand forwards to the explicitly selected CLI and does not install or upgrade it.
+This reference describes source/package version 0.3.6. Check registry availability before using a version-pinned npm installation; before publication use a reviewed source artifact. The shorthand forwards to the explicitly selected CLI and does not install or upgrade it.
[README](../README.md) · [User guide](guide.md)
diff --git a/package-lock.json b/package-lock.json
index aa67054..b53331b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "session-peer",
- "version": "0.3.5",
+ "version": "0.3.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "session-peer",
- "version": "0.3.5",
+ "version": "0.3.6",
"license": "MIT",
"os": [
"darwin",
diff --git a/package.json b/package.json
index 8ff42c0..4269ed1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "session-peer",
- "version": "0.3.5",
+ "version": "0.3.6",
"private": false,
"publishConfig": {
"registry": "https://registry.npmjs.org/",
diff --git a/scripts/release.mjs b/scripts/release.mjs
index e17f350..a03e175 100644
--- a/scripts/release.mjs
+++ b/scripts/release.mjs
@@ -8,9 +8,9 @@ import { fileURLToPath } from 'node:url';
export const repository = 'abruption/session-peer-ts';
export const registry = 'https://registry.npmjs.org';
-const stableVersion = '0.3.5';
-const previousStable = '0.3.4';
-const previousStableIntegrity = 'sha512-O9Uwpa0x1NZ8MNZCk0a1Z4hj7S+v0F0QeOJa+rKfbwcAYzBCSMwqmEWQWZQMWM36SbxVu3RowTSvpwPiKd/BDA==';
+const stableVersion = '0.3.6';
+const previousStable = '0.3.5';
+const previousStableIntegrity = 'sha512-EJc2B5tcruzHXiHDd7tyJjv2DBLYE7IHJiPVHBzLY9fSVasEQRdwx+ZZmiyBF8WHpQ7+pI3SO2VXwsQTgOWP+Q==';
const verifiedPreview = '0.1.0-preview.1';
const verifiedPreviewIntegrity = 'sha512-h4SMvrQ/LWA9osd4EHIs9rSTqv1u+S3MXQAmn+yG/gZ9+7NwYMutq+Oa5K/0ggIq61Wfwdh11Cv+5YdEffiNMA==';
export const packageFiles = ['UNICODE-LICENSE.txt', 'dist/casefold.js', 'dist/casefold.d.ts', 'dist/help.js', 'dist/help.d.ts', 'dist/output.js', 'dist/output.d.ts', 'CONTRIBUTING.md', 'LICENSE', 'PARITY.md', 'README.ja.md', 'README.ko.md',
@@ -30,7 +30,7 @@ export function validatePackage(pkg) {
assert.deepEqual(pkg.exports, { '.': { types: './dist/index.d.ts', import: './dist/index.js' } });
assert.equal(pkg.private, false);
assert.ok(pkg.version === stableVersion || /^\d+\.\d+\.\d+-preview\.\d+$/.test(pkg.version),
- 'only reviewed preview versions and 0.3.5 stable are supported');
+ 'only reviewed preview versions and 0.3.6 stable are supported');
assert.deepEqual(pkg.bin, { 'session-peer': 'dist/cli.js' });
assert.equal(pkg.repository.url, `git+https://github.com/${repository}.git`);
assert.deepEqual(pkg.publishConfig, { registry: `${registry}/`, access: 'public',
diff --git a/src/protocol.ts b/src/protocol.ts
index 9514918..810651f 100644
--- a/src/protocol.ts
+++ b/src/protocol.ts
@@ -1,7 +1,7 @@
import { isIPv6 } from 'node:net';
import { Refusal } from './discovery.js';
import { uuid } from './writer.js';
-export const VERSION = '0.3.5';
+export const VERSION = '0.3.6';
export const VERSION_LINE = `session-peer ${VERSION} (typescript)`;
// Return the OpenSSH destination: [user@]name, or an IPv6 literal with any
// brackets removed (`ssh` does not strip `[...]` outside ssh:// URIs). Zone IDs
diff --git a/src/skill-metadata.ts b/src/skill-metadata.ts
index 1efc9f7..765faa8 100644
--- a/src/skill-metadata.ts
+++ b/src/skill-metadata.ts
@@ -11,14 +11,13 @@ const verdict = (compatible: boolean): SkillCheck => ({ status: compatible ? 'co
code: compatible ? 'skill_contract_compatible' : 'skill_contract_mismatch', verification: 'metadata_only' });
export const SKILL_METADATA_LIMIT = 65536;
const keys = ['version', 'runtime-implementation', 'runtime-min-version', 'runtime-full-version', 'runtime-capability-policy'] as const;
-// Preserve published 0.3.2/0.3.3/0.3.4 results. The 0.3.5 / skill 0.2.1
-// row is the #139 proposal activated by this 0.3.5 release candidate, not a
-// published companion profile. Candidate VERSION accepts this finite row;
-// this does not publish a companion skill or authenticate its content.
+// Preserve each published runtime/profile result. The 0.3.6 release candidate
+// carries forward the exact three profiles reviewed for published 0.3.5.
+// Metadata compatibility does not authenticate content or publish/install a skill.
// Every later runtime/profile requires separate review; no version ranges.
const matrix: Record = {
'0.3.2': ['0.1.0'], '0.3.3': ['0.1.0', '0.2.0'], '0.3.4': ['0.1.0', '0.2.0'],
- '0.3.5': ['0.1.0', '0.2.0', '0.2.1']
+ '0.3.5': ['0.1.0', '0.2.0', '0.2.1'], '0.3.6': ['0.1.0', '0.2.0', '0.2.1']
};
const profiles: Record = {
'0.1.0': ['0.1.0', 'typescript', '0.1.0', '0.1.0', 'probe-help'],
diff --git a/test/integration-multihost-updates.test.ts b/test/integration-multihost-updates.test.ts
index ae1e661..4b5bf18 100644
--- a/test/integration-multihost-updates.test.ts
+++ b/test/integration-multihost-updates.test.ts
@@ -21,7 +21,7 @@ async function fixture(t: TestContext) {
const requests: string[] = [];
const server = createServer((request, response) => {
requests.push(request.url ?? '');
- response.writeHead(200, { 'content-type': 'application/json' }); response.end(JSON.stringify({ latest: '0.3.6' }));
+ response.writeHead(200, { 'content-type': 'application/json' }); response.end(JSON.stringify({ latest: '0.3.7' }));
});
server.listen(0, '127.0.0.1'); await once(server, 'listening');
t.after(async () => {
@@ -38,7 +38,7 @@ const args=process.argv.slice(2);
if(args.includes('-G')){console.log('hostname x\\nuser config-user\\nport 22');process.exit(0);}
const dest=args[args.indexOf('--')+1];
if((process.env.FAKE_DOWN||'').split(',').includes(dest)){console.error('ssh: connect to host: Connection refused');process.exit(255);}
-if(args.at(-1).endsWith('--version')){console.log('session-peer 0.3.5 (typescript)');process.exit(0);}
+if(args.at(-1).endsWith('--version')){console.log('session-peer 0.3.6 (typescript)');process.exit(0);}
const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input:fs.readFileSync(0,'utf8'),encoding:'utf8',
env:{...process.env,SESSION_PEER_CACHE_DIR:${JSON.stringify(receiver)},SESSION_PEER_UPDATE_NOTICE:'1'}});
// FAKE_INJECT: a receiver response carrying a forged client-only field.
@@ -58,7 +58,7 @@ process.stdout.write(out);process.exit(r.status);`, { mode: 0o700 });
};
const seed = (age = 0) => {
mkdirSync(cache, { recursive: true, mode: 0o700 });
- writeFileSync(file, JSON.stringify({ schemaVersion: 1, package: 'session-peer', source: 'npm_registry', channel: 'latest', latest: '0.3.6', checkedAt: Date.now() - age }), { mode: 0o600 });
+ writeFileSync(file, JSON.stringify({ schemaVersion: 1, package: 'session-peer', source: 'npm_registry', channel: 'latest', latest: '0.3.7', checkedAt: Date.now() - age }), { mode: 0o600 });
};
const settled = async (count: number) => {
for (let i = 0; i < 200 && !(requests.length >= count && existsSync(file) && !existsSync(lock)); i++) await delay(50);
@@ -86,7 +86,7 @@ test('repeated --host keeps the request-order array exactly; the notice is clien
// One destination stays flat and gets the additive client field.
const single = await f.call(['list', '--agent', 'claude', '--host', 'alpha', '--json']);
const flat = JSON.parse(single.stdout);
- assert.equal(Array.isArray(flat), false); assert.equal(flat.host, 'alpha'); assert.equal(flat.clientUpdate.latest, '0.3.6');
+ assert.equal(Array.isArray(flat), false); assert.equal(flat.host, 'alpha'); assert.equal(flat.clientUpdate.latest, '0.3.7');
await delay(500);
assert.deepEqual(f.requests, [], 'fresh cache: no refresh'); assert.equal(existsSync(f.receiver), false);
});
@@ -98,7 +98,7 @@ test('each invocation schedules at most one refresh regardless of host count; re
for (const item of JSON.parse(first.stdout)) assert.equal('clientUpdate' in item, false);
await f.settled(1);
assert.equal(f.requests.length, 1, 'one refresh for three hosts');
- assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.3.6');
+ assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.3.7');
f.seed(25 * 3600_000);
await f.call(f.args(['--output-format', 'text']));
await f.settled(2);
@@ -118,7 +118,7 @@ test('failure policy: per-host results (including failed ones) may carry the adv
const on = await f.call(['list', '--agent', 'claude', '--host', 'alpha', '--json'], down);
const offValue = JSON.parse(off.stdout), { clientUpdate, ...onValue } = JSON.parse(on.stdout);
assert.equal(offValue.ok, false); assert.equal(offValue.error, 'ssh_unreachable');
- assert.equal(on.code, off.code); assert.deepEqual(onValue, offValue); assert.equal(clientUpdate.latest, '0.3.6');
+ assert.equal(on.code, off.code); assert.deepEqual(onValue, offValue); assert.equal(clientUpdate.latest, '0.3.7');
// Mixed array: element shape and exit code unchanged; text gets one stderr line for the invocation.
const mixedOff = await f.call(f.args(['--json', '--no-update-notice']), down), mixedOn = await f.call(f.args(['--json']), down);
assert.equal(mixedOn.code, mixedOff.code); assert.deepEqual(JSON.parse(mixedOn.stdout), JSON.parse(mixedOff.stdout));
@@ -152,7 +152,7 @@ test('clientUpdate is client-local: a remote-supplied value is dropped', posix,
assert.equal(localRun.code, 0);
const { clientUpdate: own, ...local } = JSON.parse(localRun.stdout);
accepted(local, 'alpha');
- assert.equal(own.source, 'npm_registry_cache'); assert.equal(own.latest, '0.3.6'); assert.notEqual(own.command, 'fixture-only');
+ assert.equal(own.source, 'npm_registry_cache'); assert.equal(own.latest, '0.3.7'); assert.notEqual(own.command, 'fixture-only');
const text = await f.call(f.args(['--output-format', 'text', '--no-update-notice']), inject);
assert.equal(text.code, 0); assert.doesNotMatch(text.stdout, /fixture-only|Error:/); assert.equal(text.stderr, '');
assert.deepEqual(text.stdout.match(/^Host: .+$/gm), hosts.map(item => `Host: ${item}`));
diff --git a/test/release.test.ts b/test/release.test.ts
index fd6b3e0..12603df 100644
--- a/test/release.test.ts
+++ b/test/release.test.ts
@@ -49,13 +49,13 @@ test('release gate rejects moved main, absent/failed/pending CI and missing envi
assert.throws(() => validateGitHubGate(sha, main, runs, { name: 'npm', protection_rules: [{ type: 'required_reviewers', reviewers: [] }] }));
});
-test('0.3.5 stable staging requires verified 0.3.4 and unchanged preview/tag baselines', () => {
+test('0.3.6 stable staging requires verified 0.3.5 and unchanged preview/tag baselines', () => {
const previewDist = { integrity: 'sha512-h4SMvrQ/LWA9osd4EHIs9rSTqv1u+S3MXQAmn+yG/gZ9+7NwYMutq+Oa5K/0ggIq61Wfwdh11Cv+5YdEffiNMA==',
attestations: { url: 'https://registry.npmjs.org/-/npm/v1/attestations/session-peer@0.1.0-preview.1' } };
- const stableDist = { integrity: 'sha512-O9Uwpa0x1NZ8MNZCk0a1Z4hj7S+v0F0QeOJa+rKfbwcAYzBCSMwqmEWQWZQMWM36SbxVu3RowTSvpwPiKd/BDA==',
- attestations: { url: 'https://registry.npmjs.org/-/npm/v1/attestations/session-peer@0.3.4' } };
- const current = { name: pkg.name, versions: { '0.3.4': { dist: stableDist }, '0.1.0-preview.1': { dist: previewDist } },
- 'dist-tags': { latest: '0.3.4', preview: '0.1.0-preview.1' } };
+ const stableDist = { integrity: 'sha512-EJc2B5tcruzHXiHDd7tyJjv2DBLYE7IHJiPVHBzLY9fSVasEQRdwx+ZZmiyBF8WHpQ7+pI3SO2VXwsQTgOWP+Q==',
+ attestations: { url: 'https://registry.npmjs.org/-/npm/v1/attestations/session-peer@0.3.5' } };
+ const current = { name: pkg.name, versions: { '0.3.5': { dist: stableDist }, '0.1.0-preview.1': { dist: previewDist } },
+ 'dist-tags': { latest: '0.3.5', preview: '0.1.0-preview.1' } };
validateRegistryState(pkg, 'stable-stage', current);
assert.throws(() => validateRegistryState(pkg, 'stable-stage', null));
assert.throws(() => validateRegistryState(pkg, 'trusted-stage', current));
@@ -71,10 +71,10 @@ test('0.3.5 stable staging requires verified 0.3.4 and unchanged preview/tag bas
versions: { ...current.versions, '0.1.0-preview.1': { dist: { ...previewDist, attestations: undefined } } } }));
const preview = { ...pkg, version: '0.1.0-preview.2', publishConfig: { ...pkg.publishConfig, tag: 'preview' } };
assert.throws(() => validateRegistryState(preview, 'trusted-stage', { ...current, 'dist-tags': { latest: '0.1.0-preview.0' } }), /latest_points_to_prerelease/);
- assert.throws(() => validateRegistryState(pkg, 'stable-stage', { ...current, versions: { ...current.versions, '0.3.4': { dist: { ...stableDist, integrity: 'tampered' } } } }), /verified_stable_changed/);
- assert.throws(() => validateRegistryState(pkg, 'stable-stage', { ...current, versions: { ...current.versions, '0.3.4': { dist: { ...stableDist, attestations: undefined } } } }));
+ assert.throws(() => validateRegistryState(pkg, 'stable-stage', { ...current, versions: { ...current.versions, '0.3.5': { dist: { ...stableDist, integrity: 'tampered' } } } }), /verified_stable_changed/);
+ assert.throws(() => validateRegistryState(pkg, 'stable-stage', { ...current, versions: { ...current.versions, '0.3.5': { dist: { ...stableDist, attestations: undefined } } } }));
validateRegistryState(preview, 'trusted-stage', { ...current,
- 'dist-tags': { latest: '0.3.4', preview: '0.1.0-preview.1' } });
+ 'dist-tags': { latest: '0.3.5', preview: '0.1.0-preview.1' } });
});
test('release artifact binds version, allowlist, commit, SHA-256 and registry integrity', () => {
diff --git a/test/replies.test.ts b/test/replies.test.ts
index 30b3605..fad2292 100644
--- a/test/replies.test.ts
+++ b/test/replies.test.ts
@@ -141,7 +141,7 @@ if(args.at(-1)==='exit 0'){const mode=process.env.FAKE_PROBE;
if(mode==='timeout'){console.error('ssh: connect to host origin port 22: Operation timed out SECRET-SENTINEL');process.exit(255);}
console.error('SECRET-SENTINEL');process.exit(1);}
if((process.env.FAKE_DOWN||'').split(',').includes(dest)){console.error('Connection refused');process.exit(255);}
-if(args.at(-1).endsWith('--version')){console.log('session-peer 0.3.5 (typescript)');process.exit(0);}
+if(args.at(-1).endsWith('--version')){console.log('session-peer 0.3.6 (typescript)');process.exit(0);}
const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input,encoding:'utf8',env:{...process.env,SESSION_PEER_TAILSCALE:'off'}});
process.stdout.write(r.stdout);process.exit(r.status);`, { mode: 0o700 });
writeFileSync(join(path, 'tailscale'), `#!${process.execPath}
diff --git a/test/reply-agent-binding.test.ts b/test/reply-agent-binding.test.ts
index 8a9da81..3043fe0 100644
--- a/test/reply-agent-binding.test.ts
+++ b/test/reply-agent-binding.test.ts
@@ -45,7 +45,7 @@ function fixture(t: TestContext) {
const fs=require('node:fs'),{spawnSync}=require('node:child_process');
fs.appendFileSync(${JSON.stringify(sshLog)},'1');
if(process.argv.includes('-G')){console.log('user fixture-user');process.exit(0);}
-if(process.argv.at(-1).endsWith('--version')){console.log('session-peer 0.3.5 (typescript)');process.exit(0);}
+if(process.argv.at(-1).endsWith('--version')){console.log('session-peer 0.3.6 (typescript)');process.exit(0);}
const input=fs.readFileSync(0,'utf8');
const result=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input,encoding:'utf8',env:process.env});
process.stdout.write(result.stdout);process.exit(result.status);`, { mode: 0o700 });
diff --git a/test/skill-guidance.test.ts b/test/skill-guidance.test.ts
index fece198..edbcc27 100644
--- a/test/skill-guidance.test.ts
+++ b/test/skill-guidance.test.ts
@@ -38,9 +38,10 @@ test('published skill 0.2.1 complete bytes match the recorded release and finite
assert.deepEqual(validateSkillMetadata(text), expected);
assert.deepEqual(validateSkillMetadata(text, undefined), expected);
assert.deepEqual(validateSkillMetadata(text, '0.3.5'), expected);
+ assert.deepEqual(validateSkillMetadata(text, '0.3.6'), expected);
const path = fileURLToPath(url);
assert.deepEqual(inspectSkill(path), { ...expected, path });
- for (const runtime of ['0.3.2', '0.3.3', '0.3.4', '0.3.6', '0.4.0']) {
+ for (const runtime of ['0.3.2', '0.3.3', '0.3.4', '0.3.7', '0.4.0']) {
assert.deepEqual(validateSkillMetadata(text, runtime), {
status: 'incompatible', code: 'skill_contract_mismatch', verification: 'metadata_only'
});
diff --git a/test/skill-metadata.test.ts b/test/skill-metadata.test.ts
index 4140bde..83d7d4f 100644
--- a/test/skill-metadata.test.ts
+++ b/test/skill-metadata.test.ts
@@ -29,12 +29,12 @@ test('finite runtime/profile matrix preserves published 0.3.2 and 0.3.3 profiles
const legacy = fixtureText(), successor = fixtureText({ ...fields, version: '0.2.0', 'runtime-full-version': '0.3.2' });
assert.deepEqual(validateSkillMetadata(legacy, '0.3.2'), compatible);
assert.deepEqual(validateSkillMetadata(successor, '0.3.2'), incompatible);
- assert.equal(VERSION, '0.3.5');
+ assert.equal(VERSION, '0.3.6');
for (const text of [legacy, successor]) {
assert.deepEqual(validateSkillMetadata(text, '0.3.3'), compatible);
assert.deepEqual(validateSkillMetadata(text), compatible);
assert.deepEqual(validateSkillMetadata(text, undefined), validateSkillMetadata(text, VERSION));
- for (const runtime of ['0.0.9', '0.3.6', '0.4.0', '1.0.0']) assert.deepEqual(validateSkillMetadata(text, runtime), incompatible);
+ for (const runtime of ['0.0.9', '0.3.7', '0.4.0', '1.0.0']) assert.deepEqual(validateSkillMetadata(text, runtime), incompatible);
for (const runtime of [null, {}, 0, '', '0.3.3-preview.1', '00.3.3']) {
assert.deepEqual(validateSkillMetadata(text, runtime), missing);
}
@@ -43,7 +43,7 @@ test('finite runtime/profile matrix preserves published 0.3.2 and 0.3.3 profiles
test('0.3.4 patch row permits only the two exact published profiles', () => {
const successor = { ...fields, version: '0.2.0', 'runtime-full-version': '0.3.2' };
- assert.equal(VERSION, '0.3.5');
+ assert.equal(VERSION, '0.3.6');
for (const values of [fields, successor]) {
assert.deepEqual(validateSkillMetadata(fixtureText(values), '0.3.4'), compatible);
for (const key of Object.keys(values)) {
@@ -51,7 +51,7 @@ test('0.3.4 patch row permits only the two exact published profiles', () => {
}
assert.deepEqual(validateSkillMetadata(fixtureText(values, 'session-peer'), '0.3.4'), incompatible);
assert.deepEqual(validateSkillMetadata(fixtureText(values).replace('metadata:', 'metadata: []'), '0.3.4'), missing);
- for (const runtime of ['0.3.6', '0.4.0', '9.9.9']) assert.deepEqual(validateSkillMetadata(fixtureText(values), runtime), incompatible);
+ for (const runtime of ['0.3.7', '0.4.0', '9.9.9']) assert.deepEqual(validateSkillMetadata(fixtureText(values), runtime), incompatible);
for (const runtime of ['0.3.4-preview.1', '00.3.4']) assert.deepEqual(validateSkillMetadata(fixtureText(values), runtime), missing);
}
// Mixing legitimate fields from the two profiles must not create a third one.
@@ -59,19 +59,20 @@ test('0.3.4 patch row permits only the two exact published profiles', () => {
assert.deepEqual(validateSkillMetadata(fixtureText({ ...successor, 'runtime-full-version': '0.1.0' }), '0.3.4'), incompatible);
});
-test('0.3.5 release candidate accepts only the finite successor profile', t => {
+test('0.3.6 release candidate carries forward only the finite 0.3.5 profiles', t => {
const { file } = fixture(t);
const successor = { ...fields, version: '0.2.1', 'runtime-full-version': '0.3.2' };
const text = fixtureText(successor);
- assert.equal(VERSION, '0.3.5');
+ assert.equal(VERSION, '0.3.6');
for (const values of [fields, { ...fields, version: '0.2.0', 'runtime-full-version': '0.3.2' }, successor]) {
const profile = fixtureText(values);
assert.deepEqual(validateSkillMetadata(profile, '0.3.5'), compatible);
+ assert.deepEqual(validateSkillMetadata(profile, '0.3.6'), compatible);
assert.deepEqual(validateSkillMetadata(profile), compatible);
assert.deepEqual(validateSkillMetadata(profile, undefined), compatible);
writeFileSync(file, profile);
assert.deepEqual(inspectSkill(file), { path: file, ...compatible });
- for (const runtime of ['0.3.6', '0.4.0', '9.9.9']) {
+ for (const runtime of ['0.3.7', '0.4.0', '9.9.9']) {
assert.deepEqual(validateSkillMetadata(fixtureText(values), runtime), incompatible);
}
}
@@ -79,26 +80,28 @@ test('0.3.5 release candidate accepts only the finite successor profile', t => {
assert.deepEqual(validateSkillMetadata(text), compatible);
assert.deepEqual(validateSkillMetadata(text, undefined), compatible);
writeFileSync(file, text);
- // The candidate doctor uses compiled 0.3.5; explicit historical rows remain unchanged.
+ // The candidate doctor uses compiled 0.3.6; explicit historical rows remain unchanged.
assert.deepEqual(inspectSkill(file), { path: file, ...compatible });
- for (const runtime of [null, {}, 0, '', '0.3.5-preview.1', '00.3.5']) {
+ for (const runtime of [null, {}, 0, '', '0.3.5-preview.1', '00.3.5', '0.3.6-preview.1', '00.3.6']) {
assert.deepEqual(validateSkillMetadata(text, runtime), missing);
}
- for (const key of Object.keys(successor)) {
- assert.deepEqual(validateSkillMetadata(fixtureText({ ...successor, [key]: 'unsupported' }), '0.3.5'), incompatible, key);
- const omitted = { ...successor } as Record; delete omitted[key];
- assert.deepEqual(validateSkillMetadata(fixtureText(omitted), '0.3.5'), missing, key);
- const line = ` ${key}: "${successor[key as keyof typeof successor]}"`;
- for (const duplicate of [line, ` ${key}: "different"`]) {
- assert.deepEqual(validateSkillMetadata(text.replace(line, `${line}\n${duplicate}`), '0.3.5'), missing, key);
+ for (const runtime of ['0.3.5', '0.3.6']) {
+ for (const key of Object.keys(successor)) {
+ assert.deepEqual(validateSkillMetadata(fixtureText({ ...successor, [key]: 'unsupported' }), runtime), incompatible, key);
+ const omitted = { ...successor } as Record; delete omitted[key];
+ assert.deepEqual(validateSkillMetadata(fixtureText(omitted), runtime), missing, key);
+ const line = ` ${key}: "${successor[key as keyof typeof successor]}"`;
+ for (const duplicate of [line, ` ${key}: "different"`]) {
+ assert.deepEqual(validateSkillMetadata(text.replace(line, `${line}\n${duplicate}`), runtime), missing, key);
+ }
+ assert.deepEqual(validateSkillMetadata(text.replace(line, ` ${key}: []`), runtime), missing, key);
}
- assert.deepEqual(validateSkillMetadata(text.replace(line, ` ${key}: []`), '0.3.5'), missing, key);
+ assert.deepEqual(validateSkillMetadata(fixtureText(successor, 'session-peer'), runtime), incompatible);
+ assert.deepEqual(validateSkillMetadata(fixtureText({ ...successor, 'runtime-implementation': 'python' }), runtime), incompatible);
+ assert.deepEqual(validateSkillMetadata(fixtureText({ ...successor, 'runtime-full-version': '0.3.4' }), runtime), incompatible);
+ assert.deepEqual(validateSkillMetadata(fixtureText({ ...successor, version: '0.2.2' }), runtime), incompatible);
+ assert.deepEqual(validateSkillMetadata(text.replace('name: session-peer-ts', 'name: session-peer-ts\n extra'), runtime), missing);
}
- assert.deepEqual(validateSkillMetadata(fixtureText(successor, 'session-peer'), '0.3.5'), incompatible);
- assert.deepEqual(validateSkillMetadata(fixtureText({ ...successor, 'runtime-implementation': 'python' }), '0.3.5'), incompatible);
- assert.deepEqual(validateSkillMetadata(fixtureText({ ...successor, 'runtime-full-version': '0.3.4' }), '0.3.5'), incompatible);
- assert.deepEqual(validateSkillMetadata(fixtureText({ ...successor, version: '0.2.2' }), '0.3.5'), incompatible);
- assert.deepEqual(validateSkillMetadata(text.replace('name: session-peer-ts', 'name: session-peer-ts\n extra'), '0.3.5'), missing);
});
test('each unsupported well-formed scalar differs from missing, duplicated or malformed evidence', () => {
@@ -382,11 +385,12 @@ test('complete companion 0.2.1 candidate bytes match the exact reviewed fixture
assert.equal(createHash('sha256').update(bytes).digest('hex'),
'96d5a0d049f8a4d193703af3583b198a91079d44ae373de34e94a54e2296a4d6');
const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
- assert.equal(VERSION, '0.3.5');
+ assert.equal(VERSION, '0.3.6');
assert.deepEqual(validateSkillMetadata(text), compatible);
assert.deepEqual(validateSkillMetadata(text, undefined), compatible);
assert.deepEqual(validateSkillMetadata(text, '0.3.5'), compatible);
- for (const runtime of ['0.3.2', '0.3.3', '0.3.4', '0.3.6', '0.4.0']) {
+ assert.deepEqual(validateSkillMetadata(text, '0.3.6'), compatible);
+ for (const runtime of ['0.3.2', '0.3.3', '0.3.4', '0.3.7', '0.4.0']) {
assert.deepEqual(validateSkillMetadata(text, runtime), incompatible);
}
const { file } = fixture(t); writeFileSync(file, bytes);
diff --git a/test/ssh-hosts.test.ts b/test/ssh-hosts.test.ts
index d2081c3..232a0de 100644
--- a/test/ssh-hosts.test.ts
+++ b/test/ssh-hosts.test.ts
@@ -32,7 +32,7 @@ fs.appendFileSync(${JSON.stringify(log)},JSON.stringify({args,input})+'\\n');
const dest=args[args.indexOf('--')+1],list=k=>(process.env[k]||'').split(',');
if(args.includes('-G')){if(process.env.FAKE_G==='fail'||list('FAKE_G_FAIL').includes(dest))process.exit(255);console.log('hostname x\\nuser config-user\\nport 22');process.exit(0);}
if(list('FAKE_DOWN').includes(dest)){console.error('ssh: connect to host: Connection refused SECRET-SENTINEL');process.exit(255);}
-if(args.at(-1).endsWith('--version')){console.log('session-peer 0.3.5 (typescript)');process.exit(0);}
+if(args.at(-1).endsWith('--version')){console.log('session-peer 0.3.6 (typescript)');process.exit(0);}
if(list('FAKE_LOSS').includes(dest))process.exit(255);
const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input,encoding:'utf8',env:process.env});
if(list('FAKE_MALFORMED_TARGET').includes(dest)){const value=JSON.parse(r.stdout);value.target.agent={toString:null};r.stdout=JSON.stringify(value);}
@@ -339,7 +339,7 @@ const proxy=args.find(a=>a.startsWith('ProxyCommand='));
if(proxy){const cmd=proxy.slice(13).replace(/%(.)/g,(m,c)=>c==='%'?'%':c==='h'?name:c==='p'?port:c==='n'?dest:c==='r'?'config-user':m);
if(spawnSync('/bin/sh',['-c','exec '+cmd],{stdio:'inherit'}).status!==0)process.exit(255);}
fs.appendFileSync(${JSON.stringify(log)},JSON.stringify({hop:false,args,input})+'\\n');
-if(args.at(-1).endsWith('--version')){console.log('session-peer 0.3.5 (typescript)');process.exit(0);}
+if(args.at(-1).endsWith('--version')){console.log('session-peer 0.3.6 (typescript)');process.exit(0);}
const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input,encoding:'utf8',env:process.env});
process.stdout.write(r.stdout);process.exit(r.status);`, { mode: 0o700 });
const env = { PATH: bin + delimiter + f.env.PATH };
diff --git a/test/transport.test.ts b/test/transport.test.ts
index a048b64..0783d83 100644
--- a/test/transport.test.ts
+++ b/test/transport.test.ts
@@ -167,20 +167,20 @@ test('SSH preflight reports allowlisted causes without leaking stderr or sending
test('SSH request framing: no message in remote command; response loss never retries', async t => {
const { path, env } = setup(t), messages = await inbox(t, path);
const ssh = join(path, 'ssh');
- writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}const {spawnSync}=require('node:child_process'); const a=process.argv.slice(2);if(a.at(-1).endsWith('--version')){console.log('session-peer 0.3.5 (typescript)');process.exit(0);}if(a.at(-1).includes('SECRET-SENTINEL'))process.exit(99);const input=require('node:fs').readFileSync(0,'utf8');const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input,encoding:'utf8',env:process.env});process.stdout.write(r.stdout);process.exit(r.status);`, { mode: 0o700 });
+ writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}const {spawnSync}=require('node:child_process'); const a=process.argv.slice(2);if(a.at(-1).endsWith('--version')){console.log('session-peer 0.3.6 (typescript)');process.exit(0);}if(a.at(-1).includes('SECRET-SENTINEL'))process.exit(99);const input=require('node:fs').readFileSync(0,'utf8');const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input,encoding:'utf8',env:process.env});process.stdout.write(r.stdout);process.exit(r.status);`, { mode: 0o700 });
const result = await invoke(['send', '--host', 'fixture', '--to', String(process.pid), '--message', 'SECRET-SENTINEL', '--no-from'], { ...env, PATH: path + delimiter + env.PATH });
assert.equal(result.status, 'posted'); assert.equal(messages.length, 1);
// Valid JSON mixed with banner/trailing output is not a verified response.
for (const command of ['send', 'list']) for (const prefix of [true, false]) {
const response = JSON.stringify({ schemaVersion: 1, command, host: 'fixture', ok: true, status: 'posted', submitted: true, consumptionConfirmed: false });
const stdout = prefix ? 'untrusted banner\n' + response : response + '\ntrailing junk';
- writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}if(process.argv.at(-1).endsWith('--version'))console.log('session-peer 0.3.5 (typescript)');else console.log(${JSON.stringify(stdout)});`, { mode: 0o700 });
+ writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}if(process.argv.at(-1).endsWith('--version'))console.log('session-peer 0.3.6 (typescript)');else console.log(${JSON.stringify(stdout)});`, { mode: 0o700 });
const unverified = await invoke([command, '--host', 'fixture', ...(command === 'send' ? ['--to', 'fixture', '--message', 'not-provable'] : [])], { ...env, PATH: path + delimiter + env.PATH });
assert.equal(unverified.status, command === 'send' ? 'unknown' : 'refused');
assert.equal(unverified.submitted, command === 'send' ? null : false);
assert.equal(unverified.retryAllowed, false);
}
- writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}if(process.argv.at(-1).endsWith('--version'))console.log('session-peer 0.3.5 (typescript)');else process.exit(255);`, { mode: 0o700 });
+ writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}if(process.argv.at(-1).endsWith('--version'))console.log('session-peer 0.3.6 (typescript)');else process.exit(255);`, { mode: 0o700 });
const lost = await invoke(['send', '--host', 'fixture', '--to', 'fixture', '--message', 'lost'], { ...env, PATH: path + delimiter + env.PATH });
assert.equal(lost.status, 'unknown'); assert.equal(lost.retryAllowed, false); assert.equal(messages.length, 1);
});
@@ -329,7 +329,7 @@ test('a saved inactive home needs opt-in and still rejects relocated or unreadab
test('SSH preflight accepts a verified version despite noisy stderr and propagates remote exit codes', async t => {
const { path, env } = setup(t), messages = await inbox(t, path);
const ssh = join(path, 'ssh');
- writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}const {spawnSync}=require('node:child_process');if(process.argv.at(-1).endsWith('--version')){console.error('curl: (7) Failed to connect: Connection refused');console.log('session-peer 0.3.5 (typescript)');process.exit(0);}const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input:require('node:fs').readFileSync(0,'utf8'),encoding:'utf8',env:process.env});process.stdout.write(r.stdout);process.exit(r.status);`, { mode: 0o700 });
+ writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}const {spawnSync}=require('node:child_process');if(process.argv.at(-1).endsWith('--version')){console.error('curl: (7) Failed to connect: Connection refused');console.log('session-peer 0.3.6 (typescript)');process.exit(0);}const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input:require('node:fs').readFileSync(0,'utf8'),encoding:'utf8',env:process.env});process.stdout.write(r.stdout);process.exit(r.status);`, { mode: 0o700 });
const remoteEnv = { ...env, PATH: path + delimiter + env.PATH };
// #55 + #49/#51 over SSH: a flag-like body travels as --message= and posts literally.
const sent = await invoke(['send', '--host', 'fixture', '--to', String(process.pid), '--message=--dry-run', '--no-from'], remoteEnv);
diff --git a/test/updates.test.ts b/test/updates.test.ts
index b9466ee..9a692be 100644
--- a/test/updates.test.ts
+++ b/test/updates.test.ts
@@ -16,7 +16,7 @@ import { acquireLock, cacheDirectory, checkUpdate, compareVersions, manager, ref
const cli = resolve('dist/cli.js');
const SENTINEL = 'REGISTRY-BODY-SENTINEL';
type Reply = { status?: number; body?: string; wait?: number };
-async function fixture(t: TestContext, reply: Reply = { body: JSON.stringify({ latest: '0.3.6', preview: '0.4.0-preview.1' }) }) {
+async function fixture(t: TestContext, reply: Reply = { body: JSON.stringify({ latest: '0.3.7', preview: '0.4.0-preview.1' }) }) {
const root = mkdtempSync(join(process.env.TASK_TEMP ?? tmpdir(), 'codex-updates-'));
mkdirSync(join(root, '.claude/sessions'), { recursive: true });
const requests: string[] = [];
@@ -82,11 +82,11 @@ test('package-manager guidance requires a positively identified owner; uncertain
read: path => { if (!(path in files)) throw new Error('ENOENT'); return files[path]!; },
real: path => { if (path in links) return links[path]!; if (path in files) return path; throw new Error('ENOENT'); }
});
- const declared = JSON.stringify({ dependencies: { 'session-peer': '0.3.5' } });
+ const declared = JSON.stringify({ dependencies: { 'session-peer': '0.3.6' } });
const posixGlobal = (prefix: string) => probe({ [`${prefix}/lib/node_modules/session-peer/dist/cli.js`]: '' },
{ [`${prefix}/bin/session-peer`]: `${prefix}/lib/node_modules/session-peer/dist/cli.js` });
const windowsGlobal = (prefix: string) => probe({ [`${prefix}/session-peer.cmd`]: '@ECHO off\r\nnode "%dp0%\\node_modules\\session-peer\\dist\\cli.js" %*\r\n' });
- const cmd = { npm: 'npm install --global --ignore-scripts session-peer@0.3.6' };
+ const cmd = { npm: 'npm install --global --ignore-scripts session-peer@0.3.7' };
const cases: [string, NodeJS.Platform, Probe, string, string | null][] = [
['/usr/local/lib/node_modules/session-peer/dist/updates.js', 'linux', posixGlobal('/usr/local'), 'npm', cmd.npm],
['/home/u/.nvm/versions/node/v24.16.0/lib/node_modules/session-peer/dist/updates.js', 'darwin', posixGlobal('/home/u/.nvm/versions/node/v24.16.0'), 'npm', cmd.npm],
@@ -100,27 +100,27 @@ test('package-manager guidance requires a positively identified owner; uncertain
['/home/u/.nvm/versions/node/v24.16.0/lib/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'unknown', null],
['/home/u/.local/lib/node_modules/session-peer/dist/updates.js', 'linux',
probe({ '/home/u/.local/lib/node_modules/session-peer/dist/cli.js': '' }, { '/home/u/.local/bin/session-peer': '/home/u/.local/share/pipx/venvs/session-peer/bin/session-peer' }), 'unknown', null],
- ['/home/u/.volta/tools/image/packages/session-peer/lib/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'volta', 'volta install session-peer@0.3.6'],
- ['C:/Users/u/AppData/Local/Volta/tools/image/packages/session-peer/node_modules/session-peer/dist/updates.js', 'win32', probe({}), 'volta', 'volta install session-peer@0.3.6'],
+ ['/home/u/.volta/tools/image/packages/session-peer/lib/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'volta', 'volta install session-peer@0.3.7'],
+ ['C:/Users/u/AppData/Local/Volta/tools/image/packages/session-peer/node_modules/session-peer/dist/updates.js', 'win32', probe({}), 'volta', 'volta install session-peer@0.3.7'],
['C:\\work\\app\\node_modules\\session-peer\\dist\\updates.js', 'win32', probe({ 'C:/work/app/package.json': declared }), 'npm_project', null],
['/work/app/node_modules/session-peer/dist/updates.js', 'linux', probe({ '/work/app/package.json': declared }), 'npm_project', null],
['/work/app/node_modules/session-peer/dist/updates.js', 'linux', probe({ '/work/app/package.json': JSON.stringify({ dependencies: { other: '1' } }) }), 'unknown', null],
['/work/app/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'unknown', null],
- ['/home/u/.local/share/pnpm/global/5/node_modules/.pnpm/session-peer@0.3.5/node_modules/session-peer/dist/updates.js', 'linux',
- probe({ '/home/u/.local/share/pnpm/global/5/package.json': declared }), 'pnpm', 'pnpm add --global --ignore-scripts session-peer@0.3.6'],
- ['/work/app/node_modules/.pnpm/session-peer@0.3.5/node_modules/session-peer/dist/updates.js', 'linux', probe({ '/work/app/package.json': declared }), 'pnpm_project', null],
- ['/work/app/node_modules/.pnpm/session-peer@0.3.5/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'unknown', null],
- ['/home/u/.config/yarn/global/node_modules/session-peer/dist/updates.js', 'linux', probe({ '/home/u/.config/yarn/global/package.json': declared }), 'yarn', 'yarn global add --ignore-scripts session-peer@0.3.6'],
+ ['/home/u/.local/share/pnpm/global/5/node_modules/.pnpm/session-peer@0.3.6/node_modules/session-peer/dist/updates.js', 'linux',
+ probe({ '/home/u/.local/share/pnpm/global/5/package.json': declared }), 'pnpm', 'pnpm add --global --ignore-scripts session-peer@0.3.7'],
+ ['/work/app/node_modules/.pnpm/session-peer@0.3.6/node_modules/session-peer/dist/updates.js', 'linux', probe({ '/work/app/package.json': declared }), 'pnpm_project', null],
+ ['/work/app/node_modules/.pnpm/session-peer@0.3.6/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'unknown', null],
+ ['/home/u/.config/yarn/global/node_modules/session-peer/dist/updates.js', 'linux', probe({ '/home/u/.config/yarn/global/package.json': declared }), 'yarn', 'yarn global add --ignore-scripts session-peer@0.3.7'],
['/home/u/.config/yarn/global/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'unknown', null],
- ['/home/u/.bun/install/global/node_modules/session-peer/dist/updates.js', 'darwin', probe({ '/home/u/.bun/install/global/package.json': declared }), 'bun', 'bun add --global --ignore-scripts session-peer@0.3.6'],
- ['/home/u/.npm/_npx/abc123/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'npx', 'npx --yes --ignore-scripts session-peer@0.3.6 --version'],
+ ['/home/u/.bun/install/global/node_modules/session-peer/dist/updates.js', 'darwin', probe({ '/home/u/.bun/install/global/package.json': declared }), 'bun', 'bun add --global --ignore-scripts session-peer@0.3.7'],
+ ['/home/u/.npm/_npx/abc123/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'npx', 'npx --yes --ignore-scripts session-peer@0.3.7 --version'],
['/src/session-peer-ts/dist/updates.js', 'linux', probe({ '/src/session-peer-ts/package.json': JSON.stringify({ name: 'session-peer' }) }), 'source', null],
['/src/other/dist/updates.js', 'linux', probe({}), 'unknown', null],
['/opt/unrelated/node_modules/other/updates.js', 'linux', probe({}), 'unknown', null],
];
for (const [path, platform, files, owner, command] of cases) {
assert.equal(manager(path, platform, files), owner, `${platform} ${path}`);
- assert.equal(upgradeCommand(owner as Manager, '0.3.6'), command, path);
+ assert.equal(upgradeCommand(owner as Manager, '0.3.7'), command, path);
if (command === null) assert.ok(upgradeGuidance(owner as Manager).length > 0);
}
});
@@ -157,7 +157,7 @@ test('update --check reports npm dist-tags, writes a private atomic cache and le
assert.deepEqual(f.requests, ['/-/package/session-peer/dist-tags']);
const v = r.value;
assert.equal(v.command, 'update'); assert.equal(v.ok, true); assert.equal(v.package, 'session-peer');
- assert.equal(v.current, '0.3.5'); assert.equal(v.latest, '0.3.6'); assert.equal(v.channel, 'latest'); assert.equal(v.distTag, 'latest');
+ assert.equal(v.current, '0.3.6'); assert.equal(v.latest, '0.3.7'); assert.equal(v.channel, 'latest'); assert.equal(v.distTag, 'latest');
assert.equal(v.source, 'npm_registry'); assert.equal(v.status, 'update_available'); assert.equal(v.outdated, true); assert.equal(v.updated, false);
assert.equal(v.referenceVersion, '1.0.2'); assert.equal(v.cache, 'written');
// The test runs from a checkout; installed-path guidance is covered above and in package-smoke.
@@ -168,31 +168,31 @@ test('update --check reports npm dist-tags, writes a private atomic cache and le
assert.deepEqual([readFileSync(skill), readFileSync(python)], before);
const cached = JSON.parse(readFileSync(f.file, 'utf8'));
assert.deepEqual(Object.keys(cached).sort(), ['channel', 'checkedAt', 'latest', 'package', 'schemaVersion', 'source']);
- assert.equal(cached.latest, '0.3.6');
+ assert.equal(cached.latest, '0.3.7');
assert.deepEqual(readdirSync(f.cache), ['npm-update.json']);
if (process.platform !== 'win32') {
assert.equal(statSync(f.cache).mode & 0o777, 0o700); assert.equal(statSync(f.file).mode & 0o777, 0o600);
}
const text = await f.call(['update', '--check', '--output-format', 'text']);
- assert.equal(text.code, 0); assert.match(text.stdout, /0\.3\.5 \(typescript\): 0\.3\.6 available on npm dist-tag latest/);
+ assert.equal(text.code, 0); assert.match(text.stdout, /0\.3\.6 \(typescript\): 0\.3\.7 available on npm dist-tag latest/);
assert.match(text.stdout, /source checkout/); assert.equal(text.stderr, '');
- f.state.reply = { body: JSON.stringify({ latest: '0.3.5' }) };
+ f.state.reply = { body: JSON.stringify({ latest: '0.3.6' }) };
const current = await f.call(['update', '--check', '--json']);
assert.equal(current.value.status, 'up_to_date'); assert.equal(current.value.outdated, false); assert.equal(current.value.updateCommand, null);
});
test('preview channel accepts prereleases, compares separately and does not replace the stable cache', async t => {
const f = await fixture(t);
- f.seed('0.3.5');
+ f.seed('0.3.6');
const before = readFileSync(f.file, 'utf8');
const ahead = await f.call(['update', '--check', '--channel', 'preview', '--json']);
assert.equal(ahead.code, 0); assert.equal(ahead.value.latest, '0.4.0-preview.1'); assert.equal(ahead.value.channel, 'preview');
assert.equal(ahead.value.status, 'update_available'); assert.equal(ahead.value.outdated, true); assert.equal(ahead.value.cache, 'not_applicable');
- f.state.reply = { body: JSON.stringify({ latest: '0.3.5', preview: '0.1.0-preview.1' }) };
+ f.state.reply = { body: JSON.stringify({ latest: '0.3.6', preview: '0.1.0-preview.1' }) };
const older = await f.call(['update', '--check', '--channel=preview', '--json']);
assert.equal(older.value.status, 'ahead'); assert.equal(older.value.outdated, false); assert.equal(older.value.updateCommand, null);
assert.equal(readFileSync(f.file, 'utf8'), before);
- f.state.reply = { body: JSON.stringify({ latest: '0.3.5' }) };
+ f.state.reply = { body: JSON.stringify({ latest: '0.3.6' }) };
const missing = await f.call(['update', '--check', '--channel', 'preview', '--json']);
assert.equal(missing.code, 1); assert.equal(missing.value.error, 'dist_tag_missing');
});
@@ -205,7 +205,7 @@ test('invalid, failed and slow registry responses refuse without printing bodies
[{ body: JSON.stringify({ latest: 9, note: SENTINEL }) }, 'registry_response_invalid'],
[{ body: `[${JSON.stringify(SENTINEL)}]` }, 'registry_response_invalid'],
[{ body: `${SENTINEL} not json` }, 'registry_response_invalid'],
- [{ body: JSON.stringify({ latest: '0.3.6', padding: SENTINEL.repeat(4000) }) }, 'registry_response_invalid'],
+ [{ body: JSON.stringify({ latest: '0.3.7', padding: SENTINEL.repeat(4000) }) }, 'registry_response_invalid'],
[{ status: 500, body: SENTINEL }, 'registry_http_error'],
[{ status: 302, body: SENTINEL }, 'registry_unreachable'],
];
@@ -217,7 +217,7 @@ test('invalid, failed and slow registry responses refuse without printing bodies
const text = await f.call(['update', '--check', '--output-format=text']);
assert.match(text.stdout, new RegExp(`Error: ${error}`)); assert.equal(text.stdout.includes(SENTINEL), false);
}
- f.state.reply = { body: JSON.stringify({ latest: '0.3.6' }), wait: 8000 };
+ f.state.reply = { body: JSON.stringify({ latest: '0.3.7' }), wait: 8000 };
const started = Date.now();
const slow = await f.call(['update', '--check', '--json']);
assert.equal(slow.value.error, 'registry_timeout'); assert.ok(Date.now() - started < 7000, 'bounded timeout');
@@ -264,7 +264,7 @@ test('update without --check refuses with ownership guidance and no network; opt
test('cached notices are off by default, opt-in additive, stderr-only in text and opt-out wins', async t => {
const f = await fixture(t);
- f.seed('0.3.6');
+ f.seed('0.3.7');
const plain = await f.call(['list', '--json']);
assert.equal(plain.code, 0); assert.equal('clientUpdate' in plain.value, false); assert.equal(plain.stderr, '');
const on = { SESSION_PEER_UPDATE_NOTICE: '1' };
@@ -273,14 +273,14 @@ test('cached notices are off by default, opt-in additive, stderr-only in text an
const { clientUpdate, ...rest } = json.value;
assert.deepEqual(rest, plain.value);
assert.deepEqual(Object.keys(clientUpdate).sort(), ['channel', 'checkedAt', 'command', 'current', 'guidance', 'latest', 'managedBy', 'schemaVersion', 'source', 'status']);
- assert.equal(clientUpdate.status, 'available'); assert.equal(clientUpdate.current, '0.3.5'); assert.equal(clientUpdate.latest, '0.3.6');
+ assert.equal(clientUpdate.status, 'available'); assert.equal(clientUpdate.current, '0.3.6'); assert.equal(clientUpdate.latest, '0.3.7');
assert.equal(clientUpdate.source, 'npm_registry_cache'); assert.match(clientUpdate.checkedAt, /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ$/);
const textOff = await f.call(['list', '--output-format', 'text']);
const textOn = await f.call(['list', '--output-format', 'text'], { SESSION_PEER_UPDATE_NOTICE: 'yes' });
assert.equal(textOn.stdout, textOff.stdout); assert.equal(textOn.code, textOff.code);
- assert.match(textOn.stderr, /^Update available: session-peer 0\.3\.5 -> 0\.3\.6 \(npm dist-tag latest\)\. This is a source checkout; update it with git and rebuild\.\n$/);
+ assert.match(textOn.stderr, /^Update available: session-peer 0\.3\.6 -> 0\.3\.7 \(npm dist-tag latest\)\. This is a source checkout; update it with git and rebuild\.\n$/);
const doctor = await f.call(['doctor', '--json'], on);
- assert.equal(doctor.value.clientUpdate.latest, '0.3.6'); assert.equal(doctor.value.capabilities.updateCheck, true); assert.equal(doctor.value.capabilities.selfUpdate, false);
+ assert.equal(doctor.value.clientUpdate.latest, '0.3.7'); assert.equal(doctor.value.capabilities.updateCheck, true); assert.equal(doctor.value.capabilities.selfUpdate, false);
const refusal = await f.call(['send', '--to', 'codex:bad', '--json', 'hi'], on);
assert.equal(refusal.code, 2); assert.equal('clientUpdate' in refusal.value, false);
for (const [args, extra] of [[['list', '--json', '--no-update-notice'], on], [['list', '--json'], { ...on, SESSION_PEER_NO_UPDATE_NOTICE: 'TRUE' }],
@@ -288,7 +288,7 @@ test('cached notices are off by default, opt-in additive, stderr-only in text an
const r = await f.call(args, extra);
assert.equal(r.code, 0); assert.equal(r.stderr, ''); assert.equal(r.value?.clientUpdate, undefined);
}
- f.seed('0.3.5');
+ f.seed('0.3.6');
assert.equal('clientUpdate' in (await f.call(['list', '--json'], on)).value, false);
// Python's own update.json in the same directory is a different release stream.
writeFileSync(join(f.cache, 'update.json'), JSON.stringify({ schemaVersion: 1, latest: '9.9.9', checkedAt: Math.floor(Date.now() / 1000) }));
@@ -304,26 +304,26 @@ test('missing, expired and invalid caches refresh once in the background without
assert.deepEqual(first.value, baseline.value); assert.equal(first.code, baseline.code); assert.equal(first.stderr, '');
await f.settled(1);
assert.equal(f.requests.length, 1);
- assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.3.6');
+ assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.3.7');
if (process.platform !== 'win32') assert.equal(statSync(f.file).mode & 0o777, 0o600);
- assert.equal((await f.call(['list', '--json'], on)).value.clientUpdate.latest, '0.3.6');
+ assert.equal((await f.call(['list', '--json'], on)).value.clientUpdate.latest, '0.3.7');
assert.equal(f.requests.length, 1, 'fresh cache does not refresh');
- f.seed('0.3.6', 25 * 3600_000);
+ f.seed('0.3.7', 25 * 3600_000);
assert.equal('clientUpdate' in (await f.call(['list', '--json'], on)).value, false, 'expired cache is not shown');
await f.settled(2); assert.equal(f.requests.length, 2);
for (const content of ['not json', JSON.stringify({ schemaVersion: 1, package: 'session-peer', source: 'npm_registry', channel: 'latest', latest: '0.3.0-rc.1', checkedAt: Date.now() }),
- JSON.stringify({ schemaVersion: 1, package: 'session-peer', source: 'npm_registry', channel: 'latest', latest: '0.3.6', checkedAt: Date.now() + 3600_000 }), 'x'.repeat(5000)]) {
+ JSON.stringify({ schemaVersion: 1, package: 'session-peer', source: 'npm_registry', channel: 'latest', latest: '0.3.7', checkedAt: Date.now() + 3600_000 }), 'x'.repeat(5000)]) {
const count: number = f.requests.length;
writeFileSync(f.file, content);
const r = await f.call(['list', '--json'], on);
assert.equal(r.code, 0); assert.equal('clientUpdate' in r.value, false);
await f.settled(count + 1); assert.equal(f.requests.length, count + 1);
- assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.3.6');
+ assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.3.7');
}
});
test('concurrent invocations perform one bounded refresh; locks and failures back off', async t => {
- const f = await fixture(t, { body: JSON.stringify({ latest: '0.3.6' }), wait: 1000 });
+ const f = await fixture(t, { body: JSON.stringify({ latest: '0.3.7' }), wait: 1000 });
const on = { SESSION_PEER_UPDATE_NOTICE: '1' };
const results = await Promise.all(Array.from({ length: 8 }, () => f.call(['list', '--json'], on)));
for (const r of results) { assert.equal(r.code, 0); assert.equal('clientUpdate' in r.value, false); }
@@ -341,7 +341,7 @@ test('concurrent invocations perform one bounded refresh; locks and failures bac
await f.settled(2); assert.equal(f.requests.length, 2);
// Offline/failed refresh records a backoff without a version and never alters output.
f.state.reply = { status: 503, body: SENTINEL };
- f.seed('0.3.6', 25 * 3600_000);
+ f.seed('0.3.7', 25 * 3600_000);
const offline = await f.call(['list', '--json'], on);
assert.equal(offline.code, 0); assert.equal(offline.stdout.includes(SENTINEL), false);
await f.settled(3); assert.equal(f.requests.length, 3);
@@ -360,7 +360,7 @@ test('remote wire requests never read, refresh or report client notices', async
const on = { SESSION_PEER_UPDATE_NOTICE: '1' };
const wire = await f.call(['--stdio-request'], on, JSON.stringify({ schemaVersion: 1, args: ['list', '--json'] }));
assert.equal(wire.code, 0); assert.equal('clientUpdate' in wire.value, false);
- f.seed('0.3.6');
+ f.seed('0.3.7');
const seeded = await f.call(['--stdio-request'], on, JSON.stringify({ schemaVersion: 1, args: ['doctor', '--json'] }));
assert.equal('clientUpdate' in seeded.value, false);
await delay(1000);
@@ -371,7 +371,7 @@ test('remote wire requests never read, refresh or report client notices', async
test('over SSH the local client adds its own notice; the receiver never reads or refreshes', { skip: process.platform === 'win32' && 'POSIX fake ssh' }, async t => {
const f = await fixture(t);
- f.seed('0.3.6');
+ f.seed('0.3.7');
const bin = join(f.root, 'bin'), receiver = join(f.root, 'receiver-cache');
mkdirSync(bin);
writeFileSync(join(bin, 'ssh'), `#!${process.execPath}\nconst {spawnSync}=require('node:child_process');const flag=process.argv.at(-1).endsWith('--version')?'--version':'--stdio-request';` +
@@ -380,7 +380,7 @@ test('over SSH the local client adds its own notice; the receiver never reads or
const on = { SESSION_PEER_UPDATE_NOTICE: '1', PATH: `${bin}${delimiter}${process.env.PATH}` };
const json = await f.call(['list', '--host', 'audit', '--json'], on);
assert.equal(json.code, 0, json.stdout); assert.equal(json.value.host, 'audit');
- assert.equal(json.value.clientUpdate.latest, '0.3.6'); assert.equal(json.stderr, '');
+ assert.equal(json.value.clientUpdate.latest, '0.3.7'); assert.equal(json.stderr, '');
const text = await f.call(['list', '--host', 'audit', '--output-format', 'text'], on);
assert.equal(text.code, 0); assert.equal(text.stderr.match(/Update available/g)?.length, 1);
const off = await f.call(['list', '--host', 'audit', '--json', '--no-update-notice'], on);
@@ -430,9 +430,9 @@ test('explicit and background cache writes are serialized by the same lock and n
const locked = await f.call(['update', '--check', '--json']);
assert.equal(locked.code, 0); assert.equal(locked.value.latest, '0.3.9'); assert.equal(locked.value.cache, 'skipped_locked');
assert.equal(existsSync(f.file), false);
- f.state.reply = { body: JSON.stringify({ latest: '0.3.6' }) };
+ f.state.reply = { body: JSON.stringify({ latest: '0.3.7' }) };
await refreshCache({ ...f.env, SESSION_PEER_UPDATE_LOCK_TOKEN: background });
- assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.3.6'); assert.equal(existsSync(f.lock), false);
+ assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.3.7'); assert.equal(existsSync(f.lock), false);
f.state.reply = { body: JSON.stringify({ latest: '0.3.9' }) };
const written = await f.call(['update', '--check', '--json']);
assert.equal(written.value.cache, 'written'); assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.3.9');
@@ -440,7 +440,7 @@ test('explicit and background cache writes are serialized by the same lock and n
writeFileSync(f.file, JSON.stringify({ schemaVersion: 1, package: 'session-peer', source: 'npm_registry', channel: 'latest', latest: '0.3.9', checkedAt: Date.now() + 60_000 }));
const newer = readFileSync(f.file, 'utf8');
const late = acquireLock(env)!;
- f.state.reply = { body: JSON.stringify({ latest: '0.3.6' }) };
+ f.state.reply = { body: JSON.stringify({ latest: '0.3.7' }) };
await refreshCache({ ...f.env, SESSION_PEER_UPDATE_LOCK_TOKEN: late });
assert.equal(readFileSync(f.file, 'utf8'), newer); assert.equal(existsSync(f.lock), false);
const skipped = await f.call(['update', '--check', '--json']);
@@ -465,7 +465,7 @@ test('concurrent lock acquisition across processes yields one owner, and none fo
});
test('lock I/O failures: a failed token write removes only its own new lock; an unverifiable lock maps to cache failed', async t => {
- const f = await fixture(t, { body: JSON.stringify({ latest: '0.3.6' }) });
+ const f = await fixture(t, { body: JSON.stringify({ latest: '0.3.7' }) });
const env = { ...f.env };
t.after(() => { t.mock.restoreAll(); syncBuiltinESMExports(); });
const token = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
@@ -487,7 +487,7 @@ test('lock I/O failures: a failed token write removes only its own new lock; an
syncBuiltinESMExports();
const result = await checkUpdate('latest', env);
t.mock.restoreAll(); syncBuiltinESMExports();
- assert.equal(result.cache, 'failed'); assert.equal(result.latest, '0.3.6'); assert.equal(existsSync(f.file), false);
+ assert.equal(result.cache, 'failed'); assert.equal(result.latest, '0.3.7'); assert.equal(existsSync(f.file), false);
// Release failed too, so the lock is left for documented manual recovery.
assert.ok(existsSync(f.lock));
});