diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a8df84..ae65d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,11 @@ include the full version, for example `## 0.7.0-beta.1`. ### Fixed +- Restore compatibility with OpenCode 2.0.6: update the pinned SDKs, discover + services through `/api/info`, refresh owner activity with `session.update`, + and interrupt sessions with `resume: false`. Replace the removed headless + activation command with location-scoped `plugin.list` and document validation. + - Honor formal approvals for the exact verified PR commit even when publication or description recovery finishes later. Retain review revocation, authorization, and merge-readiness checks; keep timestamp gating for unbound merge comments. diff --git a/README.md b/README.md index bfb9f15..c4bd1de 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ titles, and can merge after an authorized approval. The TUI is optional. ## Requirements -- OpenCode **2** with a working model; tested with `0.0.0-beta-19398`. +- OpenCode **2.0.6** with a working model; client/plugin SDK pinned to `2.0.6`. - Node.js 22+, npm, and Git on macOS/Linux. - GitHub authentication (`gh auth login` and `gh auth setup-git`, or a token in the service environment) and permission to comment, push, and create PRs. @@ -113,13 +113,19 @@ Only the authenticated GitHub user is allowed by default; add colleagues to Run once for **each configured primary checkout**, with its absolute path: ```bash -opencode2 api v2.plugin.awaitActivation --param 'location[directory]=/absolute/path/to/your-project' +opencode2 api plugin.list --param 'location[directory]=/absolute/path/to/your-project' ``` This starts the shared service if needed and loads the project's plugins. The -command exits; the bot keeps running without a TUI or extra monitoring process. +response lists the loaded plugins; confirm `automation` has `state.status` equal +to `active`. The command exits; the bot keeps running without a TUI or extra monitoring process. **Repeat it after every service restart.** +OpenCode 2.0.6 uses operation names without the `v2.` prefix and no longer exposes +`plugin.awaitActivation`. Upgrade the automation package together with OpenCode; +older beta SDKs cannot discover its service correctly. See +[upgrade compatibility](docs/installation.md#opencode-206-compatibility). + For automatic startup after a machine reboot, put one invocation per project in your operating system's startup mechanism, under the same user, after networking is available. Use absolute executable/repository paths (`command -v opencode2` diff --git a/docs/advanced.md b/docs/advanced.md index a10e539..a2f6940 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -4,7 +4,7 @@ The [README](../README.md) covers the combined package and setup wizard. Use `examples/advanced.opencode.jsonc` for custom schedules, multiple repositories, or separately loaded scheduler and dispatcher components. -Both components use only the OpenCode **2** SDK, pinned to `0.0.0-beta-19398`: +Both components use only the OpenCode **2** SDK, pinned to `2.0.6`: - `automation.scheduler`: invokes configured RPC methods on an interval. - `automation.github`: owns issue discovery, comments, isolated work, verification, diff --git a/docs/architecture.md b/docs/architecture.md index 6e82828..11c723c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -72,8 +72,9 @@ In the shared background service, each scheduler/dispatcher component renews one empty maintenance session in the owner location at startup and every ten minutes. OpenCode counts durable session events as activity; polling plugin APIs alone does not prevent its hourly inactivity eviction. No model is prompted by keepalive. -The service PID must match the plugin process before any maintenance session is -created. Shutdown releases local SDK waits independently of adapter cancellation, +The service is discovered through `/api/info`; `server.info` must report the plugin +process PID before any maintenance session is created. `session.update` refreshes +its title without prompting a model. Shutdown releases local SDK waits independently of adapter cancellation, settles state writes, bounds RPC disposal, and releases locks. A healthy worktree session continues and the replacement owner reconciles its saved identity. diff --git a/docs/bot-workflow.md b/docs/bot-workflow.md index 5ef862d..9fd8a8e 100644 --- a/docs/bot-workflow.md +++ b/docs/bot-workflow.md @@ -16,7 +16,8 @@ happen inside `running`; they are not additional persisted phases. ```mermaid flowchart TD - Load[Load combined automation plugin] --> Owner{Primary Git checkout root?} + Activate[Open TUI or call plugin.list for the owner location] --> Load[Load combined automation plugin] + Load --> Owner{Primary Git checkout root?} Owner -->|No or outside Git| Inactive[Plugin stays inactive] Owner -->|Yes| Config[Use nonempty plugin options or read .opencode/automation.json] Config -->|No project config| Inactive @@ -49,8 +50,9 @@ flowchart TD Merge --> Worker RPC -.-> Keepalive[Each component touches the same empty owner session every ten minutes] State -.-> Keepalive - Keepalive --> PID{Registered service PID matches this process?} - PID -->|Yes| Touch[Create or reuse maintenance session, then emit rename event] + Keepalive --> Discover[Discover service through /api/info] + Discover --> PID{server.info PID matches this process?} + PID -->|Yes| Touch[Create or reuse maintenance session, then session.update its title] PID -->|No| Skip[Skip keepalive] Stop[Owner reload or shutdown] --> Cleanup[Stop timers, save stopped inventory snapshots, settle writes, dispose RPC, release locks] Cleanup --> Preserve[Preserve durable queue and healthy worktree execution] @@ -90,7 +92,9 @@ flowchart TD - Each component renews one empty owner maintenance session at startup and every ten minutes. Durable session events refresh OpenCode's inactivity timer; listing plugins does not. No model is prompted. A PID check prevents touching a different - service. Requests do not overlap and have a 15-second deadline. Pausing issue + service, using `server.info` after discovery through `/api/info` in SDK 2.0.6. + The title update uses `session.update`. Requests do not overlap and have a + 15-second deadline. Pausing issue scans does not pause keepalive. Standalone servers without a matching registered service skip it. - State is schema-validated and saved through a temporary file, file sync, and @@ -309,13 +313,13 @@ sequenceDiagram alt Operator closes task U->>D: Confirm Stop and close task in bot menu D->>D: Persist closing and reject new checkpoints and prompts - D->>S: Interrupt known task sessions and wait for idleness + D->>S: Interrupt known task sessions with resume false and wait for idleness D->>D: Drain in-flight worker and persist closed Note over D,G: Preserve work and history, no GitHub closure request else Operator cancels the round U->>D: Confirm Cancel current round D->>D: Persist cancelling and reject new execution checkpoints - D->>S: Interrupt saved sessions and wait for idleness + D->>S: Interrupt saved sessions with resume false and wait for idleness D->>D: Drain worker and question posts, archive round, enter watching Note over D,G: Keep PR tracking and future feedback, preserve cancelled worktree else Owner is disposed @@ -408,7 +412,7 @@ flowchart TD ID --> Session[Get saved helper or create only on explicit not-found] Session --> Prompt[Send deterministic attachment prompt, hooks disable all tools] Prompt --> Wait[Wait with session deadline] - Wait -->|Timeout| Interrupt[Attempt helper interruption and return error] + Wait -->|Timeout| Interrupt[Interrupt helper with resume false and return error] Wait -->|Other failure| Error Wait -->|Completed| Result{Succeeded outcome and non-error final assistant with finish stop?} Result -->|No| Error @@ -724,7 +728,7 @@ flowchart TD Close[bot menu: Stop and close task] --> Flight{Publication or merge already in flight?} Flight -->|Yes| RejectClose[Reject closure, wait and try again] Flight -->|No| SaveClose[Persist closing before interruption] - SaveClose --> Interrupt[Interrupt known sessions, missing sessions count as stopped] + SaveClose --> Interrupt[Interrupt with resume false, missing sessions count as stopped] Interrupt --> Drain[Wait for current worker and pending question posts] Drain --> Again[Interrupt again to cover in-flight session creation] Again --> Closed[Persist closed, preserve history and all local work] @@ -735,7 +739,7 @@ flowchart TD CancelRound[Cancel current round] --> Publish{Publication or merge in flight?} Publish -->|Yes| RejectClose Publish -->|No| SaveCancel[Persist cancelling, block prompts, hooks and checkpoints] - SaveCancel --> DrainCancel[Interrupt sessions, drain worker and questions, interrupt again] + SaveCancel --> DrainCancel[Interrupt with resume false, drain worker and questions, interrupt again] DrainCancel -->|Success| Watch[Archive round, clear live errors, enter watching] DrainCancel -->|Failure| RetryCancel[Keep cancelling and error, retry after 30 seconds or owner restart] RetryCancel --> DrainCancel diff --git a/docs/installation.md b/docs/installation.md index 5e70ea5..efc1859 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -44,7 +44,7 @@ hook; remove the OpenCode loaders first as described in the README. ## Prerequisites -- OpenCode **2**, with a working model. Tested SDK version: `0.0.0-beta-19398`. +- OpenCode **2.0.6**, with a working model. Client/plugin SDK version: `2.0.6`. - Node.js 22+, npm, Git, and Bash on macOS/Linux. - GitHub authentication and permission to comment, push, create PRs, and merge. - A target repository with issues enabled and at least one pushed commit. @@ -113,6 +113,29 @@ entrypoints in an isolated Node installation without initializing a renderer. ## Testing and migration +### OpenCode 2.0.6 compatibility + +This branch pins `@opencode/client`, `@opencode/plugin`, and the development theme +package to `2.0.6`. Update the automation package when upgrading OpenCode from the +older beta build. An old package can still appear active and scan GitHub while +its management CLI and owner keepalive fail to discover the newer service. + +Service discovery now probes `/api/info`. Owner keepalive verifies the service +PID with `client.server.info()` and updates the same empty maintenance session +through `session.update`. Session interruption uses `resume: false` to stop work +without automatically resuming it. The package does not claim compatibility +with the earlier beta API. + +For headless startup, use `opencode2 api plugin.list --param +'location[directory]=/absolute/path/to/project'` on one line. Confirm that +`automation` is `active` in the response and repeat for each owner after a +service restart. The old `v2.plugin.awaitActivation` operation is unavailable. +`opencode2 api session.active` lists active execution before a planned restart. +After upgrading, verify `opencode2-automation status` inside each owner checkout, +fresh dispatcher/scheduler timestamps, and `/bot` in a reopened TUI. + +### State preservation + Use a separate test repository when testing on another machine. Independent machines do not share queue ownership and can duplicate work on the same issues. This installation procedure does not migrate sessions, queues, or worktrees. diff --git a/package-lock.json b/package-lock.json index 3baa36f..054a610 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "0.6.5", "hasInstallScript": true, "dependencies": { - "@opencode/client": "0.0.0-beta-19398", - "@opencode/plugin": "0.0.0-beta-19398", + "@opencode/client": "2.0.6", + "@opencode/plugin": "2.0.6", "proper-lockfile": "^4.1.2", "zod": "^4.1.0" }, @@ -19,7 +19,7 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", - "@opencode/theme": "0.0.0-beta-19398", + "@opencode/theme": "2.0.6", "@opentui/core": "0.5.10", "@opentui/solid": "0.5.10", "@types/node": "^22.0.0", @@ -205,9 +205,9 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.977.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.9.tgz", - "integrity": "sha512-reqPFEQrZxDZpeGj4PFMepBeR5LGYHRqq/L0motTzgFkCRBA4rFdaVXDSLYyGHhxVz7sT2PDnPN9CluGSfgyJA==", + "version": "3.978.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.978.0.tgz", + "integrity": "sha512-2yX9LUmxPklVjSGTb8dfnWRJSiFQ3TeH2nn7G1mdKHTfnabzF0+gfrS8rYfLWmZrQ8A3mEcxMJjRc51dL5KWaA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.974.5", @@ -224,12 +224,12 @@ } }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.972.69", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.69.tgz", - "integrity": "sha512-vpsh9VWmQVC/nsdzf72F2yUMBOFT1aq+hoLseYV03zjVXVHkjqSNJskFRKPFxc3MRFrHNbSSmOwsbYE15u9ecw==", + "version": "3.972.70", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.70.tgz", + "integrity": "sha512-KlU89w6Hmb4oZB5zFz/MNIhPOBQGVE7KrDr3BTPCwC4W+q566YH8tGNsAML781LKATtqmNCGFry8XvsJ2XPusg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -240,12 +240,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.70", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.70.tgz", - "integrity": "sha512-H404B7dJl2mCrBqahDEYsanB0xhdDp6tXnXcTUnXmmpy2Q3J0Ho0bUajZ2jr/RdwzCyS59Gi8xXIFwPLGBl6Uw==", + "version": "3.972.71", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.71.tgz", + "integrity": "sha512-JN+JHruYZw3GUZB8YGAlDk4wTDPOEAEEdEzj5nS0xodWR4smzHsN7PnK2j6IeOsDIj2aqua5DSbhXl9Gtf90FQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", + "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -256,12 +256,12 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.72", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.72.tgz", - "integrity": "sha512-X98zYOrVOeuosCX+6ktf29FC2N2GHPLia7qv6mzPzTc+RPAuHWCDS++Z6JK7eGYqb/v6uaW7bAXaOvDBfol+0w==", + "version": "3.972.73", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.73.tgz", + "integrity": "sha512-uyYYnJOnlis8uQzaYGPd7N1JoioCoNpXgnkXYixsWJXHXgXyYi8WXJSDfofxJeWfQIGWLe2Nwyq60Uc7MZdVOg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", + "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/fetch-http-handler": "^5.7.2", @@ -274,19 +274,19 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.973.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.15.tgz", - "integrity": "sha512-Rykg6s5ceBuynMOGWgoowO4N+27JfnqXAnVaSunZl0hOO1XodSrxGNz6sCEbnmS0lAfQZDKyb3fbr46gSuv6Sg==", + "version": "3.973.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.16.tgz", + "integrity": "sha512-i++ly+0Uxa+u3ebSSyr0S/3CFhFJDxCXT3+Zj+mW2bXenEx5bKGCdTIKFu39SgXBNhWDjex/8cXUx9MUTMCrTw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/credential-provider-env": "^3.972.70", - "@aws-sdk/credential-provider-http": "^3.972.72", - "@aws-sdk/credential-provider-login": "^3.972.77", - "@aws-sdk/credential-provider-process": "^3.972.70", - "@aws-sdk/credential-provider-sso": "^3.973.14", - "@aws-sdk/credential-provider-web-identity": "^3.972.76", - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/credential-provider-env": "^3.972.71", + "@aws-sdk/credential-provider-http": "^3.972.73", + "@aws-sdk/credential-provider-login": "^3.972.78", + "@aws-sdk/credential-provider-process": "^3.972.71", + "@aws-sdk/credential-provider-sso": "^3.973.15", + "@aws-sdk/credential-provider-web-identity": "^3.972.77", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/credential-provider-imds": "^4.4.16", @@ -298,13 +298,13 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.77", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.77.tgz", - "integrity": "sha512-Jb59xfEISoN5mmbnA+HYqdtrSX3CgCtJoof+V5D8/TgUI56W63GEEd5Y58WijU3Ou6+WEgaLD1feVzaRXV5IDQ==", + "version": "3.972.78", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.78.tgz", + "integrity": "sha512-eUtswnXu0+Ii9ieRK+0L7aPFV3Z/dnW2VntJzjBP9xs8s+8p5nBNuymIXtXwZ+5r5+XJP3e32nMkuZ/r0HozEA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -315,17 +315,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.82", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.82.tgz", - "integrity": "sha512-znDkEOGXB8W3kG1LJUKP3foBZY/9qLM0eil/DxWXSp37XsdsRLQHE/d/OaCGGVgKpA6znR38h/+INk8do1FjiA==", + "version": "3.972.83", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.83.tgz", + "integrity": "sha512-jdso7ejzfRnatxMUZK4S/U6KbaDPCvfIV4XL+IQAPFDBt5rj5Fq595euqlK8Le4lNCMFR9oUpt+1l0aMgaayOQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.70", - "@aws-sdk/credential-provider-http": "^3.972.72", - "@aws-sdk/credential-provider-ini": "^3.973.15", - "@aws-sdk/credential-provider-process": "^3.972.70", - "@aws-sdk/credential-provider-sso": "^3.973.14", - "@aws-sdk/credential-provider-web-identity": "^3.972.76", + "@aws-sdk/credential-provider-env": "^3.972.71", + "@aws-sdk/credential-provider-http": "^3.972.73", + "@aws-sdk/credential-provider-ini": "^3.973.16", + "@aws-sdk/credential-provider-process": "^3.972.71", + "@aws-sdk/credential-provider-sso": "^3.973.15", + "@aws-sdk/credential-provider-web-identity": "^3.972.77", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/credential-provider-imds": "^4.4.16", @@ -337,12 +337,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.70", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.70.tgz", - "integrity": "sha512-2ry03fGRJr4sV3jI+ocjj5JqALnFD6ymM5KiNCDZMvq8bX2GSbE0vji4aM43TVCl2nXqqLRZaUxdq/KeWRAY4Q==", + "version": "3.972.71", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.71.tgz", + "integrity": "sha512-lYmXJa4gvq4xN1lrT5NiP5vIYYKcGWAdj8y+8o6dlcateB5eF3Dn8DtmjjHKfMBrTPAMr2pebIiX/UOj8c1/UA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", + "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -353,14 +353,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.973.14", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.14.tgz", - "integrity": "sha512-jkhg/8ocAAoc0RFyLMhCw+/zZh7gystQgd4F4hznNa8P4Cc501PQmxd+jGLiMHodPJ+7Zv/3znM62gZojyasmA==", + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.15.tgz", + "integrity": "sha512-6Jhcf4v0pSFdjk1EW2kvzuEBKD+UZ2uNcHUIglKKLndD20YhvkL2kdmDOV5/j4mYuWWwe/a1FQ1aomU86/Cg5Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", - "@aws-sdk/token-providers": "3.1116.0", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", + "@aws-sdk/token-providers": "3.1129.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -371,13 +371,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.76", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.76.tgz", - "integrity": "sha512-d3AGyVu759PGr35mEB2s22xxlNEA5rpdxtSPJthfPFJvoQ8dt357iVPECqWfUxXp1toJAvKmbtcIYVGigaGsCA==", + "version": "3.972.77", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.77.tgz", + "integrity": "sha512-uylIQSUWpfLuH2LovxEEfwzJGM/SabLOfLMg6YXu/E8jJEKUdpdILCVCQCdFvHyu/7dLJOHPMfrSwduxO56NkQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -416,12 +416,12 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.44", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.44.tgz", - "integrity": "sha512-NhEgryjlBF9w38ZXqGymQV28IhkYa1mKhlbYnqIis57AYwWGVYfUPgg/qC2rLRqOUfblxx++irvju10kVTa8Vw==", + "version": "3.997.45", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.45.tgz", + "integrity": "sha512-mooq9Q+jLa18VoM7HouczmslZU60iiB0aKc/Ztnq/luIL1ud0z4DnYprLR/ZO1gp331S9tJctM1HZr7u6YKBXQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", + "@aws-sdk/core": "^3.978.0", "@aws-sdk/signature-v4-multi-region": "^3.996.46", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", @@ -450,13 +450,13 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1116.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1116.0.tgz", - "integrity": "sha512-ygIivKqh8aHzNkucOCXHyIBgBpLPfrSI0mCqXF+vLBsPTUKqj0VSqAY0GFPe7lQl4HntjOcQ+KSyS7oUV2C54Q==", + "version": "3.1129.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1129.0.tgz", + "integrity": "sha512-Sbl3rpzQdsG4ZK2zh0JWUYyZPKKorJlVOddA2T0DVbKJFrsW8J6wgnslxxUH04+WaBMr4A1HzJZvZX0xUvkniA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -2165,13 +2165,13 @@ } }, "node_modules/@opencode/ai": { - "version": "0.0.0-beta-19398", - "resolved": "https://registry.npmjs.org/@opencode/ai/-/ai-0.0.0-beta-19398.tgz", - "integrity": "sha512-plBGJR3vrmNLNf1sJTyr64x7Ku1WRnpSZLKj9zfp2vslXpnwbPSI/95Mw0xTyqg+HSCc+TA7IyGIWJR0tev6Ig==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/ai/-/ai-2.0.6.tgz", + "integrity": "sha512-zbIvozv+drSK9gAfg2Vx3+nkMNfXAxZGAYmXKVd465EBGQv+v3D3pCJfGoj4lCQRku4vnGcu5DQf4RVhAaFX+g==", "license": "MIT", "dependencies": { "@aws-sdk/credential-providers": "3.1057.0", - "@opencode/schema": "0.0.0-beta-19398", + "@opencode/schema": "2.0.6", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", @@ -2180,13 +2180,13 @@ } }, "node_modules/@opencode/client": { - "version": "0.0.0-beta-19398", - "resolved": "https://registry.npmjs.org/@opencode/client/-/client-0.0.0-beta-19398.tgz", - "integrity": "sha512-t0o0OznIVTf2//6/qOeahUPQ1a25MYOQLp338Zf952/36lB3lM4G9HN60tKslSelwUHHBn8ZFCDj5VF6DbZrlA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/client/-/client-2.0.6.tgz", + "integrity": "sha512-yS8Ztw5KJzoY94GTiEmaX+2OqJYVVdc2Pyj8joNvmFywMfNzhS5aqqX+LpprlZdiDHKBLN9GhMU775TRPLol0A==", "license": "MIT", "dependencies": { - "@opencode/protocol": "0.0.0-beta-19398", - "@opencode/schema": "0.0.0-beta-19398" + "@opencode/protocol": "2.0.6", + "@opencode/schema": "2.0.6" }, "peerDependencies": { "effect": "4.0.0-rc.112", @@ -2202,23 +2202,23 @@ } }, "node_modules/@opencode/plugin": { - "version": "0.0.0-beta-19398", - "resolved": "https://registry.npmjs.org/@opencode/plugin/-/plugin-0.0.0-beta-19398.tgz", - "integrity": "sha512-0UGjsGgGP9rKq700nnhOyqmmINnsn9shlx5PZ9MpLhZoJv1EFEfJLzom5paLzoKuJF3jGLMi7kvdFdo1fc/dvg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/plugin/-/plugin-2.0.6.tgz", + "integrity": "sha512-suf6q6vC6w8QnmvEC2IJrAKFeLuZlSGNsDtcah5C3gWXyZTEsOaIe9/P0+NEOXcsexH1nnUidR6MOSWdl5JuHw==", "license": "MIT", "dependencies": { "@ai-sdk/provider": "3.0.8", - "@opencode/ai": "0.0.0-beta-19398", - "@opencode/client": "0.0.0-beta-19398", - "@opencode/protocol": "0.0.0-beta-19398", - "@opencode/schema": "0.0.0-beta-19398", - "@opencode/util": "0.0.0-beta-19398", + "@opencode/ai": "2.0.6", + "@opencode/client": "2.0.6", + "@opencode/protocol": "2.0.6", + "@opencode/schema": "2.0.6", + "@opencode/util": "2.0.6", "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112", "zod": "4.1.8" }, "peerDependencies": { - "@opencode/theme": "0.0.0-beta-19398", + "@opencode/theme": "2.0.6", "@opentui/core": ">=0.5.10", "@opentui/solid": ">=0.5.10", "solid-js": ">=1.9.0" @@ -2248,19 +2248,19 @@ } }, "node_modules/@opencode/protocol": { - "version": "0.0.0-beta-19398", - "resolved": "https://registry.npmjs.org/@opencode/protocol/-/protocol-0.0.0-beta-19398.tgz", - "integrity": "sha512-sRgB+vA8gXMrQvG5/xZZJ6fqMDNMlvyYSPz+98iDmDiydHrrA5fj9fzFgpGjRx+lAkM0wv8z5/1jjOhldzwgRA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/protocol/-/protocol-2.0.6.tgz", + "integrity": "sha512-31pt2SC2/urvitx8q8A0i0IwewwAu70/knkSfY9NFhZyLfggfNrPMUvlzMV0rwbBgubXbyRFRshU6B2tX/6czQ==", "license": "MIT", "dependencies": { - "@opencode/schema": "0.0.0-beta-19398", + "@opencode/schema": "2.0.6", "effect": "4.0.0-rc.112" } }, "node_modules/@opencode/schema": { - "version": "0.0.0-beta-19398", - "resolved": "https://registry.npmjs.org/@opencode/schema/-/schema-0.0.0-beta-19398.tgz", - "integrity": "sha512-An+yCwSpRwDHGyr9hhznNCZZYU+t0ocYWeq5RBecOBCEfHF9t/6OMU7BQFR2/YSQLh6yUV2LuSO9RxPd+pEBcQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/schema/-/schema-2.0.6.tgz", + "integrity": "sha512-oJC1Uc1ZhqQ5fBUhRza5tFc4Y8UKy0bWxpOv4f9qm6VCs16xgNGPi5Dts4bM29KbtfARr3W926tHvk7BxzUf9A==", "license": "MIT", "dependencies": { "@standard-schema/spec": "1.1.0", @@ -2268,9 +2268,9 @@ } }, "node_modules/@opencode/theme": { - "version": "0.0.0-beta-19398", - "resolved": "https://registry.npmjs.org/@opencode/theme/-/theme-0.0.0-beta-19398.tgz", - "integrity": "sha512-sBu295Bfs1Bfkm6h/6VRnuJl+0KMOSSugzpOP5EY/jb+y3KR5h1l3gShIoUo7ze3eM5wx3golQWEuZStIYrcWA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/theme/-/theme-2.0.6.tgz", + "integrity": "sha512-JivpW9TQ2Wqiu19MTgtf7cdrlxkXVkJBJhmlpmNQMu0zOqGsm3+Z49dI6NcBlmLYlOgu9ZDCFvx58S3pwNza6w==", "devOptional": true, "license": "MIT", "dependencies": { @@ -2279,9 +2279,9 @@ } }, "node_modules/@opencode/util": { - "version": "0.0.0-beta-19398", - "resolved": "https://registry.npmjs.org/@opencode/util/-/util-0.0.0-beta-19398.tgz", - "integrity": "sha512-4xsvfogeFhSwQMv7e4fZ7c9BPfLBzXd3NTowqBLWlTJ9pkku6ffPitB7NanrbvTVu7A6Hq8RLqPJloyCHdyz4g==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/util/-/util-2.0.6.tgz", + "integrity": "sha512-E7snU0RQZMynpDgT7/v9QZVGOX1dviZ1TZzVkspNrLJ/ME5SH5zgto91yLp8DgvbNQNiLAijwXJanD33kYh9eQ==", "license": "MIT", "dependencies": { "@effect/opentelemetry": "4.0.0-rc.112", @@ -3096,12 +3096,12 @@ } }, "node_modules/@smithy/core": { - "version": "3.33.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.33.3.tgz", - "integrity": "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg==", + "version": "3.34.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.34.1.tgz", + "integrity": "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.17.2", + "@smithy/types": "^4.18.0", "tslib": "^2.6.2" }, "engines": { @@ -5049,9 +5049,9 @@ } }, "node_modules/ip-address": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", - "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz", + "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", "license": "MIT", "engines": { "node": ">= 12" @@ -5836,9 +5836,9 @@ } }, "node_modules/p-map": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.7.tgz", - "integrity": "sha512-VaWRu2i4FJNRtiRWCuuQRgfQ1B7a6+gMSrO+3j0EQi/k0ULfS9kosRxGoiqwzIjZTDI02tGfk5mXXltLg6QtfQ==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.8.tgz", + "integrity": "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw==", "license": "MIT", "engines": { "node": ">=18" @@ -6305,9 +6305,9 @@ "license": "MIT" }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -6557,9 +6557,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "version": "3.0.24", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.24.tgz", + "integrity": "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==", "license": "CC0-1.0" }, "node_modules/ssri": { diff --git a/package.json b/package.json index 1719be5..82eb700 100644 --- a/package.json +++ b/package.json @@ -39,14 +39,14 @@ "test:tui": "bun --conditions=browser test/fixtures/sidebar-render.ts" }, "dependencies": { - "@opencode/client": "0.0.0-beta-19398", - "@opencode/plugin": "0.0.0-beta-19398", + "@opencode/client": "2.0.6", + "@opencode/plugin": "2.0.6", "proper-lockfile": "^4.1.2", "zod": "^4.1.0" }, "devDependencies": { "@eslint/js": "^10.0.1", - "@opencode/theme": "0.0.0-beta-19398", + "@opencode/theme": "2.0.6", "@opentui/core": "0.5.10", "@opentui/solid": "0.5.10", "@types/node": "^22.0.0", diff --git a/src/executor.ts b/src/executor.ts index 52e9768..8bff076 100644 --- a/src/executor.ts +++ b/src/executor.ts @@ -288,7 +288,7 @@ export class OpenCodeExecutor implements Executor { const results = await Promise.allSettled(ids.map(async sessionID => { const request = { signal: AbortSignal.timeout(15_000) }; try { - await this.ctx.session.interrupt({ sessionID, continue: false }, request); + await this.ctx.session.interrupt({ sessionID, resume: false }, request); if (related) await this.ctx.session.wait({ sessionID }, request); } catch (error) { if (!isNotFound(error)) throw error; } })); diff --git a/src/lifecycle.ts b/src/lifecycle.ts index e433079..2bde2e4 100644 --- a/src/lifecycle.ts +++ b/src/lifecycle.ts @@ -13,10 +13,10 @@ export async function cleanup(...steps: (() => void | Promise)[]) { } export interface OwnerClient { - health: { get(options: { signal: AbortSignal }): Promise<{ pid: number }> }; + server: { info(options: { signal: AbortSignal }): Promise<{ pid: number }> }; session: { create(input: { id: string; title: string; location: { directory: string }; metadata: Record }, options: { signal: AbortSignal }): Promise<{ id: string; location: { directory: string }; metadata?: Record }>; - rename(input: { sessionID: string; title: string }, options: { signal: AbortSignal }): Promise; + update(input: { sessionID: string; title: string }, options: { signal: AbortSignal }): Promise; }; } @@ -28,7 +28,7 @@ export async function touchOwner(directory: string, signal: AbortSignal, connect if (!client) return false; // A standalone server must never activate a second owner in a different // background service. The public request must return to this exact process. - if ((await client.health.get({ signal })).pid !== process.pid) return false; + if ((await client.server.info({ signal })).pid !== process.pid) return false; // OpenCode's inactivity sweep observes durable session events, NOT HTTP // requests or plugin RPC. Reuse one empty maintenance session; never prompt // a model, touch a user's session, or create a new session on every tick. @@ -36,8 +36,8 @@ export async function touchOwner(directory: string, signal: AbortSignal, connect const title = "Automation owner keepalive"; const session = await client.session.create({ id, title, location: { directory }, metadata: { automation: "owner-keepalive" } }, { signal }); if (session.location.directory !== directory || session.metadata?.automation !== "owner-keepalive") throw new Error("Automation keepalive session identity mismatch"); - // Rename emits Session.Renamed even when the title is unchanged. - await client.session.rename({ sessionID: session.id, title }, { signal }); + // Updating the title emits durable owner activity without prompting a model. + await client.session.update({ sessionID: session.id, title }, { signal }); return true; } diff --git a/src/runtime.ts b/src/runtime.ts index 9b29ccd..5eeaac9 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -120,7 +120,7 @@ export async function setupRuntime(ctx: Plugin.Context, options: GithubOptions) if (!answer || answer.error || answer.finish !== "stop") throw new Error("Media helper returned no completed answer"); return { content: `Media helper ${id}:\n${JSON.stringify(answer).slice(0, 24000)}` }; } catch (error) { - if (sessionRequest.signal.aborted) await ctx.session.interrupt({ sessionID: id, continue: false }, { signal: AbortSignal.timeout(15000) }).catch(() => {}); + if (sessionRequest.signal.aborted) await ctx.session.interrupt({ sessionID: id, resume: false }, { signal: AbortSignal.timeout(15000) }).catch(() => {}); throw error; } }, diff --git a/test/executor.test.ts b/test/executor.test.ts index 844d06b..534b704 100644 --- a/test/executor.test.ts +++ b/test/executor.test.ts @@ -324,7 +324,7 @@ test("a real wait deadline records a recoverable session stop and interrupts onc test("task closure interrupts all saved sessions, tolerates missing ones and waits for idleness", async () => { const interrupted: string[] = [], waited: string[] = []; const ctx = { session: { - interrupt: async ({ sessionID }: { sessionID: string }) => { interrupted.push(sessionID); if (sessionID === "gone") throw { _tag: "Session.NotFoundError" }; }, + interrupt: async (input: { sessionID: string; resume?: boolean }) => { assert.deepEqual(input, { sessionID: input.sessionID, resume: false }); interrupted.push(input.sessionID); if (input.sessionID === "gone") throw { _tag: "Session.NotFoundError" }; }, wait: async ({ sessionID }: { sessionID: string }) => { waited.push(sessionID); }, } } as unknown as Plugin.Context; const t = { ...task(), sessionID: "main", sessionIDs: ["main", "previous", "gone"], helpers: [{ id: "media", parentID: "main", capability: "vision" as const }] }; diff --git a/test/lifecycle.test.ts b/test/lifecycle.test.ts index cb745fb..45d0e26 100644 --- a/test/lifecycle.test.ts +++ b/test/lifecycle.test.ts @@ -59,10 +59,10 @@ test("owner heartbeat only touches the matching service process and owner direct const calls: string[] = []; let pid = process.pid + 1; const client: OwnerClient = { - health: { get: async () => ({ pid }) }, + server: { info: async () => ({ pid }) }, session: { create: async input => { calls.push(input.location.directory); return input; }, - rename: async () => { calls.push("activity"); }, + update: async () => { calls.push("activity"); }, }, }; const signal = new AbortController().signal; @@ -78,11 +78,11 @@ test("keepalive emits durable owner activity, reuses one session, and never invo let session: Awaited> | undefined; let created = 0, renamed = 0, clock = 0, expiresAt = 60; const client: OwnerClient = { - health: { get: async () => ({ pid: process.pid }) }, + server: { info: async () => ({ pid: process.pid }) }, session: { create: async input => { if (!session) { session = input; created++; } assert.equal(input.id, session.id); return session; }, // OpenCode LocationActivity refreshes only on durable SessionEvent events. - rename: async ({ sessionID }) => { assert.equal(sessionID, session!.id); expiresAt = clock + 60; renamed++; }, + update: async ({ sessionID }) => { assert.equal(sessionID, session!.id); expiresAt = clock + 60; renamed++; }, }, }; for (clock = 0; clock <= 180; clock += 10) { diff --git a/test/opencode-compatibility.test.ts b/test/opencode-compatibility.test.ts new file mode 100644 index 0000000..f11560f --- /dev/null +++ b/test/opencode-compatibility.test.ts @@ -0,0 +1,62 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createServer } from "node:http"; +import { mkdtemp, writeFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { OpenCode } from "@opencode/client"; +import { Service } from "@opencode/client/service"; +import { touchOwner } from "../src/lifecycle.js"; +import { GithubRpc } from "../src/rpc.js"; + +test("OpenCode 2.0.6 discovery, owner keepalive, RPC and interruption use the current HTTP contract", async () => { + const directory = await mkdtemp(join(tmpdir(), "oc2-compatibility-")); + const calls: { method: string; path: string; query: string; body: Record }[] = []; + let pid = process.pid; + const server = createServer(async (request, response) => { + const url = new URL(request.url!, "http://localhost"); + const chunks: Buffer[] = []; + for await (const chunk of request) chunks.push(Buffer.from(chunk)); + const body = chunks.length ? JSON.parse(Buffer.concat(chunks).toString()) : {}; + calls.push({ method: request.method!, path: url.pathname, query: url.search, body }); + response.setHeader("content-type", "application/json"); + if (request.headers.authorization !== "Basic " + Buffer.from("opencode:fixture-password").toString("base64")) { + response.writeHead(401).end("{}"); return; + } + if (url.pathname === "/api/info") { + response.end(JSON.stringify({ version: "2.0.6", pid, urls: [], paths: { tmp: directory } })); return; + } + if (url.pathname === "/api/session" && request.method === "POST") { response.end(JSON.stringify({ data: body })); return; } + if (url.pathname.startsWith("/api/session/") && request.method === "PATCH") { response.writeHead(204).end(); return; } + if (url.pathname.endsWith("/interrupt")) { response.end(JSON.stringify({ interrupted: true })); return; } + if (url.pathname === "/api/rpc/automation.github/status") { response.end(JSON.stringify({ output: [] })); return; } + response.writeHead(404).end("{}"); + }); + try { + await new Promise((resolve, reject) => { server.once("error", reject); server.listen(0, "127.0.0.1", resolve); }); + const address = server.address(); assert.ok(address && typeof address !== "string"); + const file = join(directory, "service.json"); + await writeFile(file, JSON.stringify({ version: "2.0.6", pid, url: `http://127.0.0.1:${address.port}`, password: "fixture-password" })); + const endpoint = await Service.discover({ file }); assert.ok(endpoint); + const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) }); + assert.equal(await touchOwner("/owner", new AbortController().signal, async () => client), true); + const created = calls.find(c => c.method === "POST" && c.path === "/api/session")!; + assert.deepEqual(created.body.location, { directory: "/owner" }); + assert.deepEqual(created.body.metadata, { automation: "owner-keepalive" }); + assert.ok(calls.some(c => c.method === "PATCH" && c.path === `/api/session/${created.body.id}` && c.body.title === "Automation owner keepalive")); + assert.deepEqual(await client.rpc(GithubRpc).status({}, { location: { directory: "/owner" } }), []); + const rpc = calls.find(c => c.path === "/api/rpc/automation.github/status")!; + assert.deepEqual(rpc.body, { input: {} }); + assert.equal(new URLSearchParams(rpc.query).get("location[directory]"), "/owner"); + await client.session.interrupt({ sessionID: "ses_fixture", resume: false }); + assert.equal(new URLSearchParams(calls.at(-1)!.query).get("resume"), "false"); + assert.deepEqual(calls.at(-1)?.body, {}); + assert.ok(calls.every(c => !c.path.includes("health") && !c.path.includes("prompt"))); + pid++; + assert.equal(await Service.discover({ file }), undefined, "A registration for another PID must not be trusted"); + } finally { + server.closeAllConnections(); + await new Promise(resolve => server.close(() => resolve())); + await rm(directory, { recursive: true, force: true }); + } +});