@matuto-ai/opencode-broker-connector 0.1.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +61 -43
- package/bin/doctor.mjs +5 -0
- package/docs/candidate-verification.md +25 -0
- package/docs/integration.md +8 -6
- package/docs/legacy-connection.md +77 -0
- package/docs/message-lifecycle.md +24 -0
- package/docs/native-automation.md +155 -0
- package/docs/native-v2.md +121 -0
- package/docs/owned-run.md +4 -2
- package/docs/publishing.md +5 -5
- package/docs/reference-snapshot.sha256 +5 -5
- package/docs/sandbox-persistence.md +43 -0
- package/docs/verification.md +62 -0
- package/package.json +15 -4
- package/plugin/index.mjs +14 -2
- package/plugin/tui.mjs +17 -0
- package/plugin/v2.mjs +18 -0
- package/src/approval-helper.mjs +11 -0
- package/src/capabilities.mjs +13 -0
- package/src/config.mjs +5 -2
- package/src/cooperative-inbox.mjs +13 -8
- package/src/enrollment.mjs +52 -0
- package/src/installation.mjs +123 -0
- package/src/message-format.mjs +26 -0
- package/src/native-context.mjs +57 -0
- package/src/native-delivery.mjs +151 -0
- package/src/native-plugin.mjs +135 -13
- package/src/opencode-adapter.mjs +31 -15
- package/src/pending-resume.mjs +38 -0
- package/src/reference-pin.mjs +6 -6
- package/src/reference.mjs +1 -1
- package/src/setup-ui.mjs +100 -0
- package/src/tool-catalog.mjs +7 -2
- package/src/tool-scope.mjs +26 -0
- package/src/v2-delivery.mjs +149 -0
- package/src/v2-inbox.mjs +33 -0
- package/src/v2-lock.mjs +17 -0
- package/src/v2-plugin.mjs +192 -0
- package/src/v2-setup.mjs +133 -0
- package/v2/LICENSE +21 -0
- package/v2/README.md +17 -0
- package/v2/index.mjs +2 -0
- package/v2/package-lock.json +420 -0
- package/v2/package.json +13 -0
- package/vendor/broker/ORIGIN.md +11 -4
- package/vendor/broker/reference/client.mjs +90 -15
- package/vendor/broker/reference/inbox.mjs +55 -10
- package/vendor/broker/reference/mcp-stdio.mjs +5 -1
- package/vendor/broker/reference/tools.mjs +38 -14
- package/vendor/broker/src/protocol.mjs +19 -0
- package/vendor/enrollment/LICENSE +21 -0
- package/vendor/enrollment/ORIGIN.md +20 -0
- package/vendor/enrollment/approval.mjs +181 -0
- package/vendor/enrollment/enrollment.mjs +137 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0 — unreleased
|
|
4
|
+
|
|
5
|
+
- Native one-field broker URL setup using supported OpenCode TUI dialogs; exact-request administrative approval and private durable installation selection.
|
|
6
|
+
- GET-only resumption of previously explicit pending setup; no startup enrollment or credential rotation.
|
|
7
|
+
- Per-server-incarnation local TUI context handshake connects existing idle sessions without a model turn or history scan.
|
|
8
|
+
- Shared installation storage override, cancellation/late-result fences and replacement CAS; old connections and unread journals are retained.
|
|
9
|
+
- Cooperative receive remains the default. Classic automatic input admission remains an open acceptance gate, not solved by setup. No release or live deployment is performed.
|
|
10
|
+
|
|
11
|
+
## 0.2.0 — 2026-10-25
|
|
12
|
+
|
|
13
|
+
- Connect, disconnect, inspect status and discover peers inside an existing OpenCode session without mandatory startup environment configuration.
|
|
14
|
+
- Pair through an owner-private file; generate and persist per-installation credentials before issuance, validate token-bound enrollment receipts, and stop on expiry or authorization failure. Requires a broker with the enrollment API.
|
|
15
|
+
- Preserve legacy connection bundles and cooperative receive/acknowledge tools.
|
|
16
|
+
- Add explicitly experimental native FIFO delivery with durable submission journaling and read-only reconciliation of uncertain receipts. Native persistence is not evidence of model reading or action success.
|
|
17
|
+
|
|
18
|
+
**Delivery is manual/cooperative by default.** Automatic delivery requires explicit `PSST_EXPERIMENTAL_NATIVE_DELIVERY=fixed-session-snapshot` opt-in. Classic OpenCode has no atomic selection-preserving admission API: a fixed agent/model snapshot can race concurrent native edits. Unknown selection blocks connection and observed selection changes disconnect messaging. This release does not solve that native atomicity limitation and does not offer general production automatic delivery.
|
|
19
|
+
|
|
20
|
+
Validated against pinned official OpenCode 1.19.4 using synthetic local providers and broker fixtures. No user credentials, live installations or deployment are part of this release.
|
package/README.md
CHANGED
|
@@ -1,78 +1,92 @@
|
|
|
1
1
|
# Psst connector for OpenCode
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Unreleased 0.4.0 candidate.** The new commands/behavior below are not in the existing npm release. Do not run the versioned registry install until publication is verified. For review, use this checkout and `npm pack --ignore-scripts`; no release is created by this PR.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Native session-scoped messaging tools for [OpenCode](https://opencode.ai/docs/plugins/) and the [Psst broker](https://github.com/matuto-ai/psst). Runs **inside your existing OpenCode sandbox**. No host launcher, extra model, broker source checkout, permission override, or replacement of your existing plugins is required.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Install, configure, use
|
|
8
|
+
|
|
9
|
+
This change prepares **0.4.0**, not a published release. After that version is published, install both native targets once:
|
|
8
10
|
|
|
9
11
|
```sh
|
|
10
|
-
|
|
11
|
-
cd opencode-broker-connector
|
|
12
|
-
npm ci --ignore-scripts
|
|
12
|
+
opencode plugin @matuto-ai/opencode-broker-connector@0.4.0 --global
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
OpenCode detects the separate server and TUI entrypoints and preserves existing plugin settings. Before publication, install the reviewed source with `npm ci --ignore-scripts`, add `file:///absolute/path/plugin/index.mjs` to your existing `opencode.json` plugin array and `file:///absolute/path/plugin/tui.mjs` to your existing `tui.json` plugin array. Do not replace other configuration entries.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
```
|
|
17
|
+
1. Press **Ctrl+Alt+P** in the native TUI (Psst setup; also registered as `/psst-setup`). Its single text field asks for **only the HTTPS broker URL**.
|
|
18
|
+
2. Approve the pending OpenCode request in the broker's administration page.
|
|
19
|
+
3. The approved installation is saved privately. Existing/new native sessions connect without repeating enrollment. Use `/psst-status` and the broker tools below.
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
No pairing secret, ticket, credential copy, model tool, or operator script is needed. Knowing the URL grants no messaging access. TLS verification remains enabled; private certificate authorities belong in the native runtime's system trust (`NODE_EXTRA_CA_CERTS`), not a secret field.
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
export PSST_CONFIG_FILE=/absolute/private/path/opencode/connection.json
|
|
27
|
-
opencode
|
|
28
|
-
```
|
|
23
|
+
**Production requires automatic receive. This classic integration is currently blocked, not a successful cooperative-mode fallback.** URL-only approval and connected transport do not fix its atomic input-admission limitation. Manual receive exists only for explicit diagnostics/tests. [Native acceptance and remaining gate](docs/native-automation.md).
|
|
29
24
|
|
|
30
|
-
|
|
25
|
+
## Diagnose readiness without changing policy
|
|
31
26
|
|
|
32
|
-
|
|
27
|
+
`psst-opencode` (the packaged doctor) is read-only and works independently of
|
|
28
|
+
native plugin loading. From a reviewed source checkout use `node bin/doctor.mjs`.
|
|
29
|
+
It reports an observed `OPENCODE_PURE` restriction separately from the classic
|
|
30
|
+
automatic-admission gap. Exit 2 is an actionable blocked state, not a ready client.
|
|
31
|
+
If the native host suppresses plugins entirely, no plugin can display its own
|
|
32
|
+
status: use this package check and ask the administrator to review the approved
|
|
33
|
+
launcher source. The connector never unsets pure mode or edits native policy.
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"tokenFile": "/absolute/private/path/token"
|
|
39
|
-
},
|
|
40
|
-
"stateDirectory": "/absolute/private/path/opencode-state",
|
|
41
|
-
"name": "OpenCode"
|
|
42
|
-
}
|
|
43
|
-
```
|
|
35
|
+
When loaded, `/psst-status` separately reports `connected` transport, this exact
|
|
36
|
+
session's `client_id`, `ready`, and the automatic-delivery blocker. Installation
|
|
37
|
+
authorization/credential IDs are not message destinations. Neither approval,
|
|
38
|
+
broker presence nor a successful manual exchange establishes automatic readiness.
|
|
44
39
|
|
|
45
|
-
|
|
40
|
+
## Persistent installation and disposable containers
|
|
46
41
|
|
|
47
|
-
|
|
42
|
+
Persist the complete owner-private installation directory, in precedence order:
|
|
48
43
|
|
|
49
|
-
|
|
44
|
+
- `PSST_OPENCODE_CONFIG_DIR` (absolute override; recommended for sandbox bootstrap).
|
|
45
|
+
- `$OPENCODE_CONFIG_DIR/psst` when the native config override is set.
|
|
46
|
+
- `$XDG_CONFIG_HOME/opencode/psst`, or `~/.config/opencode/psst` by default.
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
Use an owner-only `0700` directory, files `0600`, and the same sandbox UID. Mount it at the **same absolute path** in recreated containers. `installation.json` is a versioned, nonsecret approved-bundle pointer; `pending.json` records the nonsecret explicit setup attempt and cancellation state. `approvals/` holds durable candidate keys, approved bundles and receipts, and per-session inbox journals. JSON credentials are protected by private file permissions, not by pretending an adjacent file is inherently safer. Do not put this directory in a repository.
|
|
49
|
+
|
|
50
|
+
Ordinary startup never submits enrollment, generates a key or rotates authorization. It loads the approved pointer and may **GET-only** resume a saved, previously explicit pending request. Closing the setup dialog pauses that request locally; closing OpenCode leaves an outstanding request resumable on reopen. Transient failures keep the same candidate. Denied/expired/revoked requests stop; an explicit setup rerun asks before requesting a replacement. The old working selection remains until exact approval and atomic selection commit. Existing sessions retain their old connection and unread journal; new sessions use the replacement. No implicit broker revocation occurs.
|
|
51
|
+
|
|
52
|
+
Installation sharing is **not** session sharing. Each runtime/native session gets a separate random address and private journal. Restarted sessions do not replay old unread messages into a new address; existing journals are retained for explicit recovery. Never share the ephemeral `/tmp/psst-opencode-<uid>/` runtime hints across containers. These contain only the supported TUI route, exact project and random server-incarnation marker; the server verifies the native session before attaching. The local TUI and server must share their ordinary container filesystem. Remote TUI/server topology is not yet supported for idle attachment.
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
The audited sandbox uses an existing per-project/profile config mount; Go changes XDG defaults and pure mode blocks plugins. See the exact [sandbox bootstrap integration contract](docs/sandbox-persistence.md) before rollout.
|
|
55
|
+
|
|
56
|
+
An explicit `PSST_CONFIG_FILE` remains an intentional legacy override; setup saves approval but does not silently supersede that override. Restart without it to use the selected installation.
|
|
57
|
+
|
|
58
|
+
Existing private bundles, `PSST_CONFIG_FILE`, and `/psst-connect /absolute/private/file` remain compatible and never migrate/reset unread data. See [legacy connection compatibility](docs/legacy-connection.md). Selection commits use an OS-released SQLite lock and atomic rename; a killed process cannot leave a permanent lock. Corrupt files are never silently reset.
|
|
56
59
|
|
|
57
60
|
## Use
|
|
58
61
|
|
|
59
|
-
After
|
|
62
|
+
After approval, transport may register the current local TUI session, but `/psst-status` reports `mode: automatic_required`, `ready: false` and `delivery: automatic_blocked`: the current classic native API cannot atomically preserve a concurrently changed agent/model selection. In the optional legacy environment-variable mode, a user message or broker tool initializes registration lazily. Do not use a snapshot override as a production workaround. The following manual tools are **diagnostic/test-only** until safe automatic admission is implemented:
|
|
60
63
|
|
|
61
64
|
1. `broker_list_clients` discovers exact authorized peer IDs. Names are descriptive only.
|
|
62
|
-
2. `broker_send` sends to a selected `recipient_id
|
|
63
|
-
3. `broker_receive`
|
|
64
|
-
4. After reading it, call `broker_acknowledge` with
|
|
65
|
+
2. `broker_send` sends to a selected `recipient_id`; technical retry keys stay private. Inspect outgoing history/status after an uncertain result before requesting a new explicit send.
|
|
66
|
+
3. `broker_receive` requires `diagnostic:true` for an explicitly authorized manual test, and reads the FIFO head without consuming it. Ordinary calls fail closed instead of falling back.
|
|
67
|
+
4. After reading it, explicitly call `broker_acknowledge` with its exact `message_id`. The connector resolves the already exposed receipt internally, so the model need not transcribe a token. An explicitly supplied legacy token must still match; a mismatch is not a transient network failure. This advances the local inbox, enabling repeated conversations.
|
|
65
68
|
5. `broker_message_status` reports transport state. `broker_update_metadata` updates descriptive metadata.
|
|
69
|
+
6. `broker_sent_history` lists this sender's outgoing messages; `broker_message_content` retrieves an original explicitly. `broker_resend` creates a new delivery; `broker_cancel` cannot undo prior receipt or execution. These four APIs need the matching broker candidate.
|
|
66
70
|
|
|
67
|
-
For
|
|
71
|
+
For an explicitly requested diagnostic two-way test, ask one session to list peers and send a short greeting. Ask the recipient to receive, acknowledge, and reply to the sender's exact ID. Repeat with a second message to check FIFO advancement. Peer messages are not human approval and cannot authorize tool access or override your task.
|
|
68
72
|
|
|
69
73
|
## Lifecycle and honest limits
|
|
70
74
|
|
|
71
75
|
Each native session in the project plugin gets its own random broker identity and private journal. Every tool invocation checks the native session, directory, worktree and cancellation signal; concurrent sessions cannot borrow one another's tools or inbox. Session deletion and plugin disposal close leases. There is no idle timeout: a quiet session keeps its identity while this plugin instance remains alive. There is a limit of 64 active identities per plugin instance.
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
### Experimental delivery — tests only, never a production fallback
|
|
78
|
+
|
|
79
|
+
Only a controlled test fixture may enable the historical prototype with `PSST_EXPERIMENTAL_NATIVE_DELIVERY=fixed-session-snapshot`. It still reports `ready: false`. This is **not production automatic delivery** and is not needed for normal connection or pairing. Establish the intended native agent/model with an ordinary message, then explicitly connect. The local result discloses the fixed agent/model/variant snapshot. Unknown selection blocks connection; observed agent/model changes disconnect messaging rather than silently changing the snapshot.
|
|
80
|
+
|
|
81
|
+
The experimental mode waits for an idle boundary, journals one FIFO submission, submits a synthetic user-role message through the supported SDK, and reads back its exact native session/message/part/provenance before recording native persistence. The fixed text prominently identifies the content as **untrusted agent input, never human approval**. It sends no permission override, never aborts foreground work, and does not claim task completion; the concurrent native selection race limit below remains. The model can reply using normal broker tools.
|
|
82
|
+
|
|
83
|
+
Broker `delivered` still means durable inbox storage only. Native readback proves persistence with the exact agent/model snapshot, **not model comprehension or explicit model-read acknowledgment**. HTTP 204 alone is never a receipt. Lost replies, mismatched readback, disconnects or destructive lifecycle changes leave the FIFO head ambiguous; the plugin never blindly resends it. Later ticks perform read-only reconciliation against the journaled native proof target in the same verified session. Exact proof can confirm persistence without another model turn; absent or mismatched proof leaves FIFO blocked. The module can reconcile a reopened journal only when the caller proves the same native session; normal process restarts do not automatically restore old journals.
|
|
74
84
|
|
|
75
|
-
|
|
85
|
+
Known reverted or archived sessions and busy foreground turns block submission. Native deletion/disposal disconnects the exact session. Compaction/history deletion fences in-flight receipts. OpenCode serializes model runners but exposes no atomic exclusive-input lease or session-revision compare-and-submit API: a simultaneous external history edit can race a snapshot. Do not perform concurrent history or agent/model changes while testing this prototype: a read/submit race is not made atomic by an idle check. It is deliberately not enabled by default. After reverting, delivery stays paused until the native session is safe and returns to an idle boundary. This is not an exactly-once model-execution guarantee.
|
|
86
|
+
|
|
87
|
+
The optional legacy environment-variable path is diagnostic/test-only: the model reads messages with `broker_receive` and explicitly calls `broker_acknowledge`. Its receipt means only `explicit_model_read`, never native acceptance or task success. Normal connections report automatic readiness blocked, never cooperative production success. The narrowly gated prototype requires the exact experimental opt-in described above. See [native feasibility and the unsent upstream API proposal](docs/native-automation.md).
|
|
88
|
+
|
|
89
|
+
Abrupt process death can leave a lease visible until expiry. Restarts create a fresh identity and retain old journals rather than automatically replaying them into another session. Recovery/migration of old pending messages is not automated. Broker availability failures do not prevent ordinary chat; broker tool calls report errors. Native plugin load errors should be checked in OpenCode's logs if the native broker tools are absent.
|
|
76
90
|
|
|
77
91
|
## Verification
|
|
78
92
|
|
|
@@ -88,3 +102,7 @@ npm run pack:check
|
|
|
88
102
|
The default suite reports actual-CLI tests skipped when the binary is absent. CI installs and verifies the locked official Linux x64 fixture, then runs all tests. See [verification](docs/verification.md) for evidence boundaries. The older [owned-run launcher](docs/owned-run.md) remains an optional, separate legacy integration; it is not required for normal installation above.
|
|
89
103
|
|
|
90
104
|
MIT. Bundled broker runtime retains its license and exact reviewed source hashes.
|
|
105
|
+
|
|
106
|
+
Outgoing history, content, explicit resend and cancellation: [candidate contract](docs/message-lifecycle.md).
|
|
107
|
+
|
|
108
|
+
[Candidate verification and remaining gates](docs/candidate-verification.md).
|
package/bin/doctor.mjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Unreleased candidate verification — 2026-11-01
|
|
2
|
+
|
|
3
|
+
Related issue: #8; not closed by this partial delivery.
|
|
4
|
+
|
|
5
|
+
## Checked locally
|
|
6
|
+
|
|
7
|
+
Syntax, all 103 tests and package checks passed. No native tests were skipped in this run.
|
|
8
|
+
|
|
9
|
+
Integrity-pinned official OpenCode 1.19.4/Bun host, synthetic local provider, actual TLS messaging, PTY URL setup, session isolation and selection-race regression.
|
|
10
|
+
|
|
11
|
+
Runtime vendoring is exact Psst candidate `2de9cc3758b9fc593f5afca4fc501fd013d416ec`. Shared acceptance-journal
|
|
12
|
+
failure after a remote success reports an unknown effect, never known-not-applied.
|
|
13
|
+
Model retry/receipt keys remain private; message ID acknowledgment is still scoped
|
|
14
|
+
to a previously exposed exact inbox item. Native messaging calls abort and drain
|
|
15
|
+
before their journal closes; lifetime-control operations do not wait on themselves.
|
|
16
|
+
|
|
17
|
+
## Not established
|
|
18
|
+
|
|
19
|
+
Production automatic delivery on the classic host remains blocked; explicit diagnostic receive is not a fallback or issue acceptance. The documented V2 2.2.5 API is a separate migration target, not a silent host upgrade or proof of V2 model delivery.
|
|
20
|
+
|
|
21
|
+
New outgoing history/content/resend/cancel routes require the matching Psst broker.
|
|
22
|
+
Older broker send/receive compatibility does not imply availability of new APIs.
|
|
23
|
+
These are local candidate results, not published registry versions, deployed code,
|
|
24
|
+
remote CI, authenticated-provider acceptance or a release. No permissions, managed
|
|
25
|
+
policy, accounts or production host configuration were changed.
|
package/docs/integration.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
> Candidate update: production requires automatic delivery and is currently blocked on the classic host. Manual pull/owned-run flows below are diagnostic/test-only. See [current lifecycle contract](message-lifecycle.md) for hidden operation keys and new APIs. Historical fixture pins are separate from the runtime candidate.
|
|
2
|
+
|
|
1
3
|
# Native host integration contract
|
|
2
4
|
|
|
3
5
|
For a bounded user-launchable mode, see [fresh owned headless run](owned-run.md).
|
|
@@ -71,11 +73,11 @@ is explicitly insufficient. Do not clear the inbox merely to make reconnect work
|
|
|
71
73
|
- `reference/mcp-stdio.mjs`: `TOOL_DEFINITIONS` (catalog only)
|
|
72
74
|
- `reference/errors.mjs`: fixed safe `LocalError` and `safeError`
|
|
73
75
|
|
|
74
|
-
Pin shared broker commit `
|
|
76
|
+
Pin shared broker commit `2de9cc3758b9fc593f5afca4fc501fd013d416ec`.
|
|
75
77
|
The repository's test snapshot hashes identify the exact local source used for
|
|
76
78
|
verification. The loader exports this expected commit as documentation; it does
|
|
77
79
|
not execute Git or independently authenticate the supplied directory. The trusted
|
|
78
|
-
operator/CI checkout must establish the pin. This adapter fails if the
|
|
80
|
+
operator/CI checkout must establish the pin. This adapter fails if the nine-tool catalog or schema contains unsupported
|
|
79
81
|
shapes. Updating the shared checkout or native package requires rerunning tests.
|
|
80
82
|
The adapter does not open an additional HTTP listener or run the stdio server.
|
|
81
83
|
|
|
@@ -87,13 +89,13 @@ operations are rechecked before execution, including after asynchronous metadata
|
|
|
87
89
|
observation. Concurrent calls carry independent context closures.
|
|
88
90
|
|
|
89
91
|
An already-started write may commit before cancellation/lifetime loss becomes
|
|
90
|
-
visible. The result then has `effect: unknown
|
|
91
|
-
|
|
92
|
-
transport/inbox, but does not
|
|
92
|
+
visible. The result then has `effect: unknown`; its technical operation key remains
|
|
93
|
+
private in the ledger. Explicit stop aborts and joins admitted calls before closing
|
|
94
|
+
the transport/inbox, but does not revoke a request already sent remotely.
|
|
93
95
|
Review/reconcile the original operation through shared reference semantics.
|
|
94
96
|
|
|
95
97
|
`broker_receive` peeks one pending head and keeps it. Returned content includes the
|
|
96
|
-
|
|
98
|
+
compact AI-agent header and escaped sender/text JSON. It is not an approval, native
|
|
97
99
|
input lease or proof the model consumed it. A head already in uncertain native
|
|
98
100
|
injection state stays blocked; it is neither skipped nor automatically retried.
|
|
99
101
|
There is no autonomous receive-to-prompt loop. This deliberately limited mode can
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Legacy connection and pairing compatibility
|
|
2
|
+
|
|
3
|
+
These paths remain supported for existing installations. They are not the primary onboarding workflow.
|
|
4
|
+
|
|
5
|
+
## Install now, before npm publication
|
|
6
|
+
|
|
7
|
+
Install the reviewed source from `main`:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
git clone --branch main https://github.com/matuto-ai/opencode-broker-connector.git
|
|
11
|
+
cd opencode-broker-connector
|
|
12
|
+
npm ci --ignore-scripts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Add the absolute plugin file URL to the existing `plugin` array in your sandbox's OpenCode config. Preserve all existing settings and entries:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"plugin": ["file:///absolute/path/opencode-broker-connector/plugin/index.mjs"]
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Restart OpenCode normally after installing the plugin. No initial environment variable or broker connection is required: it starts disconnected. In your existing native CLI session, use:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
/psst-connect /absolute/private/path/opencode/connection.json
|
|
27
|
+
/psst-status
|
|
28
|
+
/psst-peers
|
|
29
|
+
/psst-disconnect
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The connect argument is **only a private connection-file path**, never a token, inline JSON, or password. Native command arguments may be recorded in OpenCode history/events. Each connect affects only the current session. Disconnect stops its broker polling and closes its lease without stopping OpenCode. Disconnect before switching connections. The commands use native OpenCode command hooks and return a short local status for the normal model turn; they do not replace the TUI or bypass permissions.
|
|
33
|
+
|
|
34
|
+
For backward compatibility, setting `PSST_CONFIG_FILE` to a private connection file before launch still enables lazy cooperative registration in each session. Explicit disconnect also disables that session's automatic re-registration. Neither path automatically resumes a previous process's broker identity.
|
|
35
|
+
|
|
36
|
+
The config and token must be owner-only files (`0600`), readable by the sandbox user. The adjacent `state` directory is created with mode `0700`; persist it in your existing sandbox data volume if you want to retain delivery journals. Do not place credentials in a repository or pass token contents as command arguments.
|
|
37
|
+
|
|
38
|
+
The standard Psst enrollment bundle (`format: "broker-client-bundle-v1"`, `allowed_client_types: ["opencode"]`) is accepted directly. It uses a sibling file named `token`. Alternatively, an owner-only JSON config can contain:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"broker": {
|
|
43
|
+
"url": "https://broker.example.invalid/",
|
|
44
|
+
"tokenFile": "/absolute/private/path/token"
|
|
45
|
+
},
|
|
46
|
+
"stateDirectory": "/absolute/private/path/opencode-state",
|
|
47
|
+
"name": "OpenCode"
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Optional `broker.caFile` points to a trusted private CA; public certificates normally need no extra setting. TLS verification is never disabled. A successful `/healthz` call is not enrollment: the broker must authorize the token for client type `opencode` and a scope shared with the intended peer.
|
|
52
|
+
|
|
53
|
+
## Explicit per-session pairing
|
|
54
|
+
|
|
55
|
+
After the broker enrollment upgrade is reviewed and deployed, follow its [operator pairing guide](https://github.com/matuto-ai/psst/blob/3e908e79966d886a02d372c103e31dec6e900c1a/docs/pairing-operator.md). An operator first authorizes a short-lived, client-type/scope-bound pairing grant on the broker and writes its private `psst-pairing-v1` file. Place that file inside the existing sandbox (owner-only `0600`), then run:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
/psst-connect /absolute/private/path/opencode-pairing.json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The plugin generates and durably saves its own random credential before issuance, sends only the credential hash over verified HTTPS, and stores the issued credential and token-bound receipt privately. Neither pairing secret nor bearer token enters command arguments, chat, model input, URLs or logs. The helper uses system TLS trust; it does not disable certificate verification or accept a pairing-file CA override.
|
|
62
|
+
|
|
63
|
+
The private installation directory is `$XDG_STATE_HOME/psst/opencode/<project-and-session-hash>` (default `~/.local/state/psst/opencode/...`). Preserve it inside the sandbox to safely retry a lost enrollment response with the **same file and native session**. A different native session needs its own operator pairing grant; a redeemed grant cannot be reused to create another key. No control-plane UI or public signup service is installed.
|
|
64
|
+
|
|
65
|
+
`/psst-status` reports only a nonsecret credential ID and expiry. Expired or rejected credentials stop messaging and require explicit re-pairing; no silent credential replacement occurs. Disconnect before changing a live connection. Re-pairing always creates a fresh local broker identity/inbox and never imports another principal's pending messages.
|
|
66
|
+
|
|
67
|
+
New helper bundles carry `enrollment_receipt_file: "enrollment.json"`; that exact sibling receipt is mandatory and checked against the private token hash, broker and client type. Missing or corrupt receipt is a hard error, not a legacy fallback. Unmarked legacy bundles still work unchanged. The optional legacy environment mode may read an existing bundle but never performs enrollment automatically.
|
|
68
|
+
|
|
69
|
+
## After npm publication
|
|
70
|
+
|
|
71
|
+
The package is prepared as `@matuto-ai/opencode-broker-connector`. **A prepared package/workflow does not mean it has been published.** Once version `0.2.0` exists on npm, use this native OpenCode configuration instead of the local file:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{"plugin": ["@matuto-ai/opencode-broker-connector@0.2.0"]}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
OpenCode installs npm plugins automatically using Bun. Use the same native connect command (or optional legacy `PSST_CONFIG_FILE`); remove the duplicate local entry so the plugin does not load twice. See [publication](docs/publishing.md).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Outgoing message lifecycle (unreleased candidate)
|
|
2
|
+
|
|
3
|
+
`broker_send` takes recipient ID and text; retry keys are never model arguments or
|
|
4
|
+
results. `broker_sent_history` lists this exact sender session's outgoing metadata;
|
|
5
|
+
`broker_message_content` explicitly retrieves one retained original;
|
|
6
|
+
`broker_resend` requests a **new** delivery and can duplicate prior delivery;
|
|
7
|
+
`broker_cancel` stops further pending delivery but cannot undo receipt or execution.
|
|
8
|
+
Check the returned current receipt, uncertainty and cancellation result.
|
|
9
|
+
|
|
10
|
+
The connector persists an internal operation before HTTP. Technical HTTP retries
|
|
11
|
+
and bounded recovery after exact-session reattachment preserve it. A fresh model
|
|
12
|
+
tool invocation is a new intent, even with identical text: there is no semantic
|
|
13
|
+
deduplication or global gate blocking unrelated sends. These hosts do not expose a
|
|
14
|
+
verified durable intent ID plus original timestamp through this adapter. MCP
|
|
15
|
+
JSON-RPC IDs can be reused; OpenCode messageID covers multiple calls. Neither is
|
|
16
|
+
misrepresented as durable retry identity. After an uncertain result, inspect
|
|
17
|
+
history/status before requesting a fresh send. Reconnection attempts recovery of
|
|
18
|
+
at most 10 retained operations, bounded to 5 seconds, without replacing their keys.
|
|
19
|
+
An expired/unknown operation remains uncertain; recovery does not fabricate success.
|
|
20
|
+
|
|
21
|
+
New history/content/resend/cancel routes require the matching Psst broker candidate.
|
|
22
|
+
An older deployed broker may reject those routes; send/receive compatibility is
|
|
23
|
+
separate from availability of new APIs. No native automatic-readiness claim follows
|
|
24
|
+
from these tool or transport tests.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Native automation feasibility (OpenCode 1.19.4)
|
|
2
|
+
|
|
3
|
+
## Production boundary
|
|
4
|
+
|
|
5
|
+
Production now requires automatic receive. Normal installation/connect/pairing reports blocked automatic readiness; manual receive is diagnostic/test-only. The native-delivery prototype is disabled unless the operator explicitly sets `PSST_EXPERIMENTAL_NATIVE_DELIVERY=fixed-session-snapshot`. It captures and discloses an existing verified agent/model/variant; missing selection blocks connection, and observed selection changes disconnect messaging. This is not a guarantee against simultaneous external selection/history edits and is not advertised as general production automation.
|
|
6
|
+
|
|
7
|
+
## Primary-source findings
|
|
8
|
+
|
|
9
|
+
- [Classic prompt implementation](https://github.com/anomalyco/opencode/blob/v1.19.4/packages/opencode/src/session/prompt.ts): missing `agent` resolves the default agent, not necessarily the current session selection. `createUserMessage` can update the session agent/model before the `chat.message` hook. Explicitly forwarding a verified snapshot prevents omission/default drift but supplies no compare-and-submit revision check.
|
|
10
|
+
- [Classic runner](https://github.com/anomalyco/opencode/blob/v1.19.4/packages/opencode/src/effect/runner.ts): model loops are serialized. An idle observation is not an exclusive lease over all human/agent inputs or history/selection mutations.
|
|
11
|
+
- [HTTP handler](https://github.com/anomalyco/opencode/blob/v1.19.4/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts): `prompt_async` forks work and returns 204 before durable message proof. The prototype journals its exact target and reads back session/message/part/text/provenance/agent/model/variant; delayed proof is reconciled read-only, never by resubmission.
|
|
12
|
+
- [New core API](https://github.com/anomalyco/opencode/blob/v1.19.4/packages/core/src/session.ts) exposes durable queued admission without an agent/model input. However [schema/projector](https://github.com/anomalyco/opencode/blob/v1.19.4/packages/core/src/session/projector.ts) and [tables](https://github.com/anomalyco/opencode/blob/v1.19.4/packages/core/src/session/sql.ts) distinguish classic `message`/`part` from new `session_message`/`session_input` histories.
|
|
13
|
+
|
|
14
|
+
The actual official-CLI regression creates a classic Plan-mode conversation with two messages. `/api/session/{id}` sees the identity/selection, but `/api/session/{id}/context` and `/history` return empty histories. Therefore the connector does not submit to the separate engine or silently create a replacement conversation. No writes to OpenCode's database, internal module injection, TUI keystrokes or permission overrides are used; the connector only maintains its own private delivery journal.
|
|
15
|
+
|
|
16
|
+
## Unsent upstream feature proposal
|
|
17
|
+
|
|
18
|
+
**Draft only — not posted as an issue or sent externally.**
|
|
19
|
+
|
|
20
|
+
Request a public plugin/SDK operation for durable, idempotent queued input into the *existing native conversation engine*, with:
|
|
21
|
+
|
|
22
|
+
1. Native atomic preservation of the current agent/model/variant, or an expected selection/context revision that rejects before any state mutation.
|
|
23
|
+
2. Exact stable admission ID and a read-only receipt lookup; repeated identical input is idempotent and conflicts are explicit.
|
|
24
|
+
3. Queue/idle semantics that do not interrupt foreground work, clear revert state, bypass permissions, or switch conversation engines.
|
|
25
|
+
4. Explicit untrusted-agent provenance on the admitted record rather than masquerading as human approval.
|
|
26
|
+
5. Lifecycle fencing across deletion, revert and compaction, plus documentation of whether admission proves persistence, model input delivery or neither.
|
|
27
|
+
|
|
28
|
+
This could be a compatibility bridge to the new durable queue only if it preserves the actual existing CLI history, policy and runner. Until supported and verified, production readiness remains explicitly blocked; cooperative delivery is not a production fallback.
|
|
29
|
+
|
|
30
|
+
## Fresh admission investigation for URL-only setup (2026-10-26)
|
|
31
|
+
|
|
32
|
+
The pinned official CLI regression `test/native-v2-compat.test.mjs` now reproduces
|
|
33
|
+
an actual stale-selection race: read Plan/model A; submit a human message changing
|
|
34
|
+
to Build/model B; call classic `prompt_async` with the previously observed
|
|
35
|
+
Plan/model A. The native session switches back to Plan/model A rather than rejecting
|
|
36
|
+
the stale request. Omitting `agent` instead chooses the default Build agent, not
|
|
37
|
+
the existing Plan selection. Both checks execute in the real 1.19.4 host against
|
|
38
|
+
a synthetic local provider, without a production account.
|
|
39
|
+
|
|
40
|
+
Fresh source inspection at `v1.19.4` confirms:
|
|
41
|
+
|
|
42
|
+
- `session/prompt.ts:635–690`: `createUserMessage` resolves the default agent when
|
|
43
|
+
omitted and calls `sessions.setAgentModel` before admission arbitration.
|
|
44
|
+
- `session/prompt.ts:1046–1072`: message/parts are persisted and revert cleanup
|
|
45
|
+
occurs before the model loop. `noReply` only skips that loop, not these mutations.
|
|
46
|
+
- `effect/runner.ts:120–146`: `ensureRunning` serializes/joins model execution;
|
|
47
|
+
it does not reject a stale prompt before session/selection mutation.
|
|
48
|
+
- `server/routes/instance/httpapi/handlers/session.ts:295–327`: synchronous and
|
|
49
|
+
asynchronous routes invoke the same prompt implementation; async forks and
|
|
50
|
+
returns before receipt proof. Busy rejection elsewhere applies to shell work,
|
|
51
|
+
not an atomic selection-preserving prompt queue.
|
|
52
|
+
- Supported TUI APIs expose prompt set/reset/submit and command dispatch, not a
|
|
53
|
+
durable idempotent peer-input queue with a selection revision. Using those
|
|
54
|
+
methods for delivery would alter the user's draft or invoke the same unsafe
|
|
55
|
+
admission path. They are not used for peer delivery.
|
|
56
|
+
|
|
57
|
+
Accordingly automatic idle model receive is **still blocked by the supported
|
|
58
|
+
classic host API**, not enabled by the approval feature or the identity handshake.
|
|
59
|
+
The separate v2 engine still lacks this classic conversation's history. Neither a
|
|
60
|
+
successful setup nor a TLS/PTY fixture closes this acceptance gate.
|
|
61
|
+
|
|
62
|
+
## Local idle identity attachment
|
|
63
|
+
|
|
64
|
+
The server publishes a random, nonsecret per-plugin-incarnation context marker in
|
|
65
|
+
its existing `psst-status` command template through the supported config hook. The
|
|
66
|
+
TUI reads `api.state.config` and `api.route.current`, then emits a short-lived
|
|
67
|
+
owner-private hint under the container's temporary directory. The server reads
|
|
68
|
+
only its own incarnation directory, verifies the exact native session and project
|
|
69
|
+
through `session.get`, and binds a separate broker inbox. Hints expire after five
|
|
70
|
+
seconds, route changes retire hint-owned bindings, and disposal removes them.
|
|
71
|
+
This is installation-independent local IPC, not enrollment authority, a native
|
|
72
|
+
history scan, model command, input injection, or a substitute for atomic delivery.
|
|
73
|
+
Remote TUI/server filesystems cannot use this handshake.
|
|
74
|
+
|
|
75
|
+
### New interactive sessions do not offer a supported v2-engine switch
|
|
76
|
+
|
|
77
|
+
The bounded follow-up also inspected **new** interactive sessions, not only
|
|
78
|
+
existing-history compatibility. In pinned 1.19.4, the official
|
|
79
|
+
[TUI submit path](https://github.com/anomalyco/opencode/blob/v1.19.4/packages/tui/src/component/prompt/index.tsx#L990-L1120)
|
|
80
|
+
creates new conversations with `sdk.client.session.create` and sends normal input
|
|
81
|
+
with `sdk.client.session.prompt`. Its
|
|
82
|
+
[generated SDK](https://github.com/anomalyco/opencode/blob/v1.19.4/packages/sdk/js/src/v2/gen/sdk.gen.ts#L3399-L3786)
|
|
83
|
+
maps those calls to classic `/session` and `/session/{sessionID}/message`.
|
|
84
|
+
The `@opencode-ai/sdk/v2` import is the SDK generation, **not** selection of the
|
|
85
|
+
separate v2 conversation engine.
|
|
86
|
+
|
|
87
|
+
Inspection of the pinned
|
|
88
|
+
[flags](https://github.com/anomalyco/opencode/blob/v1.19.4/packages/core/src/flag/flag.ts),
|
|
89
|
+
[TUI configuration](https://github.com/anomalyco/opencode/blob/v1.19.4/packages/tui/src/config/index.tsx),
|
|
90
|
+
and TUI worker found no supported option to route a new interactive session to
|
|
91
|
+
that separate engine. Consequently this connector does not create v2-only
|
|
92
|
+
conversations, migrate history, patch the host, or enable an unsupported
|
|
93
|
+
experiment to bypass classic admission. A future supported host capability
|
|
94
|
+
requires its own real interactive admission/race test before automatic delivery
|
|
95
|
+
can be enabled.
|
|
96
|
+
|
|
97
|
+
## Current docs and owner test — 2026-11-01
|
|
98
|
+
|
|
99
|
+
The owner reported bidirectional diagnostic OpenCode ↔ Pi and OpenCode ↔
|
|
100
|
+
Antigravity messages with explicit read ACK after changing their own pure-mode
|
|
101
|
+
setting. No connector policy change was performed. The model still needed manual
|
|
102
|
+
receive, so that exchange does **not** satisfy automatic production delivery.
|
|
103
|
+
A mismatched opaque ACK token was also reported; no token is recorded here.
|
|
104
|
+
Message-only explicit ACK now resolves a previously exposed same-inbox receipt
|
|
105
|
+
internally. Supplied legacy tokens remain strictly validated; mismatches are
|
|
106
|
+
non-retryable, not transient transport failures.
|
|
107
|
+
|
|
108
|
+
Fresh official [V2 server plugin documentation](https://opencode.ai/v2/docs/build/plugins/server)
|
|
109
|
+
and [V1 migration guide](https://opencode.ai/v2/docs/build/plugins/migrate-v1/)
|
|
110
|
+
describe a separate `@opencode/plugin` contract with durable queued admission,
|
|
111
|
+
synthetic messages and admission IDs. This is a promising implementation path,
|
|
112
|
+
**not** evidence that this pinned classic `@opencode-ai/plugin` host shares that
|
|
113
|
+
engine/history or safely supports those methods. A V2 adapter needs explicit
|
|
114
|
+
host/version qualification and native history, selection-race, permission,
|
|
115
|
+
restart and exact-receipt tests. Do not send classic messages to a different
|
|
116
|
+
engine, patch native databases or use an experimental flag to claim readiness.
|
|
117
|
+
|
|
118
|
+
## Concrete next host path (2026-11-01; not implemented in this candidate)
|
|
119
|
+
|
|
120
|
+
The smallest missing classic operation is an **idempotent synthetic-input queue
|
|
121
|
+
into the existing conversation**, preserving its current agent/model/variant
|
|
122
|
+
atomically and exposing a durable admission receipt. Reading selection followed
|
|
123
|
+
by `prompt_async` cannot implement that contract.
|
|
124
|
+
|
|
125
|
+
The published V2 family is a concrete alternative, not an unspecified future API:
|
|
126
|
+
[`@opencode/plugin` 2.2.5](https://github.com/anomalyco/opencode/blob/v2.2.5/packages/plugin/package.json)
|
|
127
|
+
and [`@opencode/cli` 2.2.5](https://github.com/anomalyco/opencode/blob/v2.2.5/packages/cli/package.json).
|
|
128
|
+
Its documented `ctx.session.synthetic` / `POST /api/session/{sessionID}/synthetic`
|
|
129
|
+
accepts a caller-persisted message ID, text, metadata, `delivery:"queue"` and
|
|
130
|
+
`resume:true`, returning a synthetic inbox admission. It takes no agent/model
|
|
131
|
+
selection input. Readback uses native inbox/message APIs.
|
|
132
|
+
[API contract](https://opencode.ai/v2/docs/api#session-synthetic),
|
|
133
|
+
[tagged session implementation](https://github.com/anomalyco/opencode/blob/v2.2.5/packages/core/src/session/session.ts).
|
|
134
|
+
|
|
135
|
+
This requires porting the plugin to `Plugin.define/setup(ctx)` and running the
|
|
136
|
+
matching V2 host. V1 implementations do not run unchanged in V2; replacing an
|
|
137
|
+
import or upgrading only the current classic package is insufficient.
|
|
138
|
+
[Official migration guide](https://opencode.ai/v2/docs/build/plugins/migrate-v1/).
|
|
139
|
+
The classic `opencode-ai` latest registry version observed was 1.19.5; its
|
|
140
|
+
[tagged prompt source](https://github.com/anomalyco/opencode/blob/v1.19.5/packages/opencode/src/session/prompt.ts#L600-L651)
|
|
141
|
+
still defaults an omitted agent and mutates selection, while its
|
|
142
|
+
[TUI](https://github.com/anomalyco/opencode/blob/v1.19.5/packages/tui/src/component/prompt/index.tsx#L1029-L1049)
|
|
143
|
+
still submits through the classic prompt route. This source check is not a
|
|
144
|
+
1.19.5 runtime qualification.
|
|
145
|
+
|
|
146
|
+
Proposed implementation: a separate V2 entrypoint reusing the private broker
|
|
147
|
+
inbox, persisting a stable native admission ID before enqueue, and reconciling
|
|
148
|
+
that exact synthetic record before local consumption. Unknown outcomes remain
|
|
149
|
+
pending; never retry under a new ID. Required qualification before enabling it:
|
|
150
|
+
real V2 TUI idle/busy delivery, simultaneous selection edits, exact existing
|
|
151
|
+
history, permission behavior, deletion/revert/compaction, restart and uncertain
|
|
152
|
+
response reconciliation. Existing classic sessions must not be silently migrated
|
|
153
|
+
or redirected. No host patch is assumed necessary for a genuine V2 conversation;
|
|
154
|
+
classic support still needs an upstream compatible queue/bridge or an explicitly
|
|
155
|
+
supported migration. Those compatibility proofs are open, not delivered here.
|