@matuto-ai/opencode-broker-connector 0.1.1 → 0.3.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 +34 -38
- package/docs/legacy-connection.md +77 -0
- package/docs/native-automation.md +95 -0
- package/docs/publishing.md +5 -5
- package/docs/sandbox-persistence.md +34 -0
- package/docs/verification.md +60 -0
- package/package.json +9 -3
- package/plugin/index.mjs +14 -2
- package/plugin/tui.mjs +14 -0
- package/src/approval-helper.mjs +11 -0
- package/src/config.mjs +5 -2
- package/src/enrollment.mjs +52 -0
- package/src/installation.mjs +122 -0
- package/src/native-context.mjs +57 -0
- package/src/native-delivery.mjs +151 -0
- package/src/native-plugin.mjs +131 -11
- package/src/pending-resume.mjs +38 -0
- package/src/setup-ui.mjs +100 -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,62 +1,48 @@
|
|
|
1
1
|
# Psst connector for OpenCode
|
|
2
2
|
|
|
3
|
-
Native
|
|
3
|
+
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.
|
|
4
4
|
|
|
5
|
-
## Install
|
|
5
|
+
## Install, configure, use
|
|
6
6
|
|
|
7
|
-
After
|
|
7
|
+
This change prepares **0.3.0**, not a published release. After that version is published, install both native targets once:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
|
|
11
|
-
cd opencode-broker-connector
|
|
12
|
-
npm ci --ignore-scripts
|
|
10
|
+
opencode plugin @matuto-ai/opencode-broker-connector@0.3.0 --global
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
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
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
```
|
|
15
|
+
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**.
|
|
16
|
+
2. Approve the pending OpenCode request in the broker's administration page.
|
|
17
|
+
3. The approved installation is saved privately. Existing/new native sessions connect without repeating enrollment. Use `/psst-status` and the broker tools below.
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
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
20
|
|
|
25
|
-
|
|
26
|
-
export PSST_CONFIG_FILE=/absolute/private/path/opencode/connection.json
|
|
27
|
-
opencode
|
|
28
|
-
```
|
|
21
|
+
**Receiving remains cooperative, not automatic model wake-up.** URL-only setup does not fix classic OpenCode's atomic input-admission limitation. [Native acceptance and remaining gate](docs/native-automation.md).
|
|
29
22
|
|
|
30
|
-
|
|
23
|
+
## Persistent installation and disposable containers
|
|
31
24
|
|
|
32
|
-
|
|
25
|
+
Persist the complete owner-private installation directory, in precedence order:
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"url": "https://broker.example.invalid/",
|
|
38
|
-
"tokenFile": "/absolute/private/path/token"
|
|
39
|
-
},
|
|
40
|
-
"stateDirectory": "/absolute/private/path/opencode-state",
|
|
41
|
-
"name": "OpenCode"
|
|
42
|
-
}
|
|
43
|
-
```
|
|
27
|
+
- `PSST_OPENCODE_CONFIG_DIR` (absolute override; recommended for sandbox bootstrap).
|
|
28
|
+
- `$OPENCODE_CONFIG_DIR/psst` when the native config override is set.
|
|
29
|
+
- `$XDG_CONFIG_HOME/opencode/psst`, or `~/.config/opencode/psst` by default.
|
|
44
30
|
|
|
45
|
-
|
|
31
|
+
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.
|
|
46
32
|
|
|
47
|
-
|
|
33
|
+
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.
|
|
48
34
|
|
|
49
|
-
|
|
35
|
+
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.
|
|
50
36
|
|
|
51
|
-
|
|
52
|
-
{"plugin": ["@matuto-ai/opencode-broker-connector@0.1.1"]}
|
|
53
|
-
```
|
|
37
|
+
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.
|
|
54
38
|
|
|
55
|
-
|
|
39
|
+
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.
|
|
40
|
+
|
|
41
|
+
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
42
|
|
|
57
43
|
## Use
|
|
58
44
|
|
|
59
|
-
After
|
|
45
|
+
After approval, the current local TUI session is registered in cooperative mode. General automatic delivery remains off: 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. No separate wrapper is needed:
|
|
60
46
|
|
|
61
47
|
1. `broker_list_clients` discovers exact authorized peer IDs. Names are descriptive only.
|
|
62
48
|
2. `broker_send` sends to a selected `recipient_id`. Preserve its `send_key` when retrying an uncertain send.
|
|
@@ -70,7 +56,17 @@ For a simple two-way test, ask one session to list peers and send a short greeti
|
|
|
70
56
|
|
|
71
57
|
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
58
|
|
|
73
|
-
|
|
59
|
+
### Experimental delivery — off by default
|
|
60
|
+
|
|
61
|
+
A controlled prototype can be explicitly enabled before launching OpenCode with `PSST_EXPERIMENTAL_NATIVE_DELIVERY=fixed-session-snapshot`. This is **not production general 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.
|
|
62
|
+
|
|
63
|
+
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.
|
|
64
|
+
|
|
65
|
+
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.
|
|
66
|
+
|
|
67
|
+
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.
|
|
68
|
+
|
|
69
|
+
The optional legacy environment-variable mode remains cooperative: 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 explicit connections are cooperative too. 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).
|
|
74
70
|
|
|
75
71
|
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 six tools are absent.
|
|
76
72
|
|
|
@@ -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,95 @@
|
|
|
1
|
+
# Native automation feasibility (OpenCode 1.19.4)
|
|
2
|
+
|
|
3
|
+
## Production boundary
|
|
4
|
+
|
|
5
|
+
Normal install/connect/pairing is cooperative. 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, default cooperative delivery is the honest production behavior.
|
|
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.
|
package/docs/publishing.md
CHANGED
|
@@ -5,21 +5,21 @@ Package name: `@matuto-ai/opencode-broker-connector`. The npm workflow follows t
|
|
|
5
5
|
1. Review and merge the feature PR.
|
|
6
6
|
2. Bootstrap the package under an npm account authorized for this package if it does not exist yet. Account creation, credentials and first publication are operator actions, not performed by the connector.
|
|
7
7
|
3. Configure npm's GitHub Actions trusted publisher for owner `matuto-ai`, repository `opencode-broker-connector`, workflow `npm-publish.yml`. If the repository moves, update package metadata and trusted-publisher settings together.
|
|
8
|
-
4. Create a GitHub release whose tag exactly matches `v` + package.json version (next `v0.
|
|
8
|
+
4. Create a GitHub release whose tag exactly matches `v` + package.json version (next `v0.3.0`).
|
|
9
9
|
5. The action runs syntax, tests including pinned actual CLI, package inspection, then `npm publish --access public --provenance` using OIDC. No long-lived npm token is stored in the workflow.
|
|
10
10
|
6. Confirm npm has the exact version before recommending npm installation. Failed publishing is not repaired by silently changing a version or recreating a tag.
|
|
11
11
|
|
|
12
12
|
`id-token: write` is limited to the release workflow. This repository does not configure a trusted publisher, generate credentials, publish, merge or deploy automatically on ordinary pushes/PRs. Those events run read-only CI checks only.
|
|
13
13
|
|
|
14
|
-
Before npm publication, the local-file installation in README works directly from `main` after the
|
|
14
|
+
Before npm publication, the local-file installation in README works directly from `main` after the release PR is reviewed and merged. No unpublished dependency is required.
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Minor release
|
|
17
17
|
|
|
18
|
-
This PR prepares `0.
|
|
18
|
+
This PR prepares `0.3.0`; the intended GitHub release tag is `v0.3.0`.
|
|
19
19
|
After review and merge, the maintainer can publish that release from the merged
|
|
20
20
|
commit to exercise npm Trusted Publishing. This code change does not itself
|
|
21
21
|
create a tag, release, or npm publication. Verify the workflow succeeds and the
|
|
22
22
|
scoped npm version exists before recommending it for installation.
|
|
23
23
|
|
|
24
|
-
Existing `0.1.
|
|
24
|
+
Existing `0.1.x` npm packages and old GitHub tags remain untouched. Do not reuse
|
|
25
25
|
or move an old release tag; the new tag must exactly match the package version.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Sandbox bootstrap contract
|
|
2
|
+
|
|
3
|
+
Historical source inspected: `ai-agents-sandbox` snapshot
|
|
4
|
+
`a7f63e8b98d37c030df6ca095d2e75960111e37c`,
|
|
5
|
+
`agents/opencode-native/config.sh:get_project_mounts/get_project_env`.
|
|
6
|
+
This is design evidence, **not verification of current live mounts**.
|
|
7
|
+
|
|
8
|
+
The existing launcher maps `<project>/opencode-zen/{config,data,cache,state}`
|
|
9
|
+
(or the separate `opencode-go` profile) to:
|
|
10
|
+
|
|
11
|
+
| Leaf | Container path |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| config | `/home/node/.config/opencode` |
|
|
14
|
+
| data | `/home/node/.local/share/opencode` |
|
|
15
|
+
| cache | `/home/node/.cache/opencode` |
|
|
16
|
+
| state | `/home/node/.local/state/opencode` |
|
|
17
|
+
|
|
18
|
+
Use `PSST_OPENCODE_CONFIG_DIR=/home/node/.config/opencode/psst` in that bootstrap
|
|
19
|
+
so the whole installation uses the **existing config mount**, including Go's
|
|
20
|
+
managed `XDG_CONFIG_HOME=/etc` override. The `psst` subdirectory must be owned by
|
|
21
|
+
the sandbox user, mode 0700; config/key files are 0600. Recreated containers must
|
|
22
|
+
mount it at the same absolute path. Different project/profile mounts intentionally
|
|
23
|
+
remain separate authorizations unless bootstrap explicitly shares this private
|
|
24
|
+
installation directory. Sharing it never means sharing native session IDs/inboxes.
|
|
25
|
+
Do not mount the ephemeral `/tmp/psst-opencode-<uid>` directory across containers.
|
|
26
|
+
|
|
27
|
+
The archived launcher also sets `OPENCODE_PURE=1` and disables project config.
|
|
28
|
+
Official 1.19.4 explicitly skips external TUI plugins in pure mode. Therefore
|
|
29
|
+
that historical bootstrap needs an intentional native plugin allowlist/install
|
|
30
|
+
integration before this connector can load; merely saving files cannot override
|
|
31
|
+
that policy. Preserve managed model/provider, permission and credential policy.
|
|
32
|
+
This connector does not remove pure mode or edit the sandbox launcher itself.
|
|
33
|
+
The current owner environment remains an acceptance gate until its effective
|
|
34
|
+
bootstrap is inspected and the intended plugin installation is permitted.
|
package/docs/verification.md
CHANGED
|
@@ -110,3 +110,63 @@ arbitration; per-CLI Podman containers or two-host LAN operation.
|
|
|
110
110
|
`broker_receive` remains a non-consuming FIFO **peek**. A native tool return or
|
|
111
111
|
model read does not advance the inbox. See [owned-run instructions](owned-run.md)
|
|
112
112
|
for operator requirements and failure/unknown-outcome behavior.
|
|
113
|
+
|
|
114
|
+
## Explicit connection regression coverage
|
|
115
|
+
|
|
116
|
+
The packed production plugin is loaded by official OpenCode 1.19.4 with no `PSST_CONFIG_FILE`. A native session command connects using a private file, discovery sees the exact new session, and native disconnect prevents subsequent message injection while the host remains available. The fixture uses a deterministic local model provider and a trusted loopback TLS broker; no paid provider, user credential, desktop/TUI automation, or production installation is exercised. Unit tests cover disconnected startup, session-isolated status, disconnect/no re-registration, and deleted-session fencing. Automatic model delivery and pairing issuance are separate changes, not claims of this connection-only step.
|
|
117
|
+
|
|
118
|
+
## Experimental native delivery (explicit opt-in only)
|
|
119
|
+
|
|
120
|
+
The second actual-host regression loads the npm-packed production plugin into official OpenCode 1.19.4/Bun, connects with no startup broker environment, sends two FIFO messages through verified TLS, with the fixed-session-snapshot experimental opt-in, preserves Plan mode and the selected model, and observes both exact synthetic native messages and the second payload in the deterministic local model's input. It never calls `broker_receive`, never accesses paid providers, and verifies disconnect prevents later injection. This proves real native integration, not a desktop/TUI end-to-end installation or production-broker test. Unit regressions cover missing/wrong readback, single in-flight FIFO, busy/reverted/human boundaries, compaction during submit, abort during receipt lookup, lost submit responses, delayed readback reconciliation and same-session journal reopen without another native submission. Native source basis: `packages/opencode/src/session/prompt.ts`, `effect/runner.ts`, and `server/routes/instance/httpapi/handlers/session.ts` at official tag v1.19.4.
|
|
121
|
+
|
|
122
|
+
## Scoped pairing boundary
|
|
123
|
+
|
|
124
|
+
A further actual OpenCode smoke loads the packed plugin, passes only a private pairing-file path to the native command, and exercises the unmodified shared enrollment helper over system-trusted HTTPS. A synthetic endpoint implementing the frozen broker contract records a hash-only credential and authorizes it in the real transport fixture; automatic peer messages then reach the deterministic local model. This checks native/helper/credential integration, not the production broker's operator issuance implementation (covered by upstream broker tests). Unit tests additionally cover marked bundle receipt deletion/type/origin/token mismatches, private permissions, per-native-session attempt separation, expiry shutdown, revoked authorization shutdown and no late connect after disconnect.
|
|
125
|
+
|
|
126
|
+
The default connector remains cooperative. Additional pinned-host coverage proves that the official v2 session identity is visible while its context/history remain empty for a classic CLI conversation with real messages. The connector does not mix these engines. Unknown agent/model state blocks the experiment and observed changes disconnect it. No test establishes atomic admission against simultaneous external native selection edits.
|
|
127
|
+
|
|
128
|
+
## URL-only approval and persistent native configuration (0.3.0 candidate)
|
|
129
|
+
|
|
130
|
+
The new gates use official **OpenCode 1.19.4**, not a mocked host API:
|
|
131
|
+
|
|
132
|
+
- `native-tui.test.mjs`: loads the production TUI entrypoint and sends real PTY
|
|
133
|
+
keyboard input through the supported Ctrl+Alt+P setup binding. Invalid URLs
|
|
134
|
+
remain unselected; the single-field URL form durably saves its request before
|
|
135
|
+
attempted HTTPS transport. This is a disposable test terminal, not a user's TUI.
|
|
136
|
+
- `native-tui-approval.test.mjs`: loads the production TUI and server entrypoints;
|
|
137
|
+
a transparent fixture creates/navigates to an empty existing classic session
|
|
138
|
+
using the supported SDK. Real keyboard setup requests actual broker enrollment;
|
|
139
|
+
authenticated admin form/CSRF approval commits config; the private native-context
|
|
140
|
+
hint connects that already-idle session. Its native message history remains
|
|
141
|
+
empty, proving setup/attachment did not manufacture a model turn.
|
|
142
|
+
- `native-approval-integration.test.mjs`: real broker approval, 30 distinct native
|
|
143
|
+
sessions in the packed production plugin, process restart with unchanged
|
|
144
|
+
installation authorization and new session addresses, then real admin revocation.
|
|
145
|
+
- `native-installation.test.mjs`: preserves legacy installation compatibility
|
|
146
|
+
across the same native 30-session/restart path.
|
|
147
|
+
- `installation.test.mjs`, `setup-ui.test.mjs`, and `native-context.test.mjs` cover
|
|
148
|
+
selection CAS, cancellation/late results, exact request binding, replacement,
|
|
149
|
+
transient backoff, GET-only startup resume, terminal denial/revocation/expiry,
|
|
150
|
+
corrupt/private files, distinct container runtime roots, stale incarnation/owner
|
|
151
|
+
hints and route changes. These unit fixtures are not real container orchestration.
|
|
152
|
+
|
|
153
|
+
CI checks out the immutable broker dependency specified in `.github/workflows/ci.yml`
|
|
154
|
+
and supplies `PSST_APPROVAL_BROKER_DIR`; without it the real-broker native gates
|
|
155
|
+
explicitly skip. Locally point that variable at that exact reviewed broker tree.
|
|
156
|
+
No provider credentials, live broker, real user installation or release is used.
|
|
157
|
+
The helper hash/source anchor is recorded in `vendor/enrollment/ORIGIN.md`.
|
|
158
|
+
|
|
159
|
+
**Not established by these gates:** paid/authenticated model provider behavior,
|
|
160
|
+
classic atomic automatic model receive (the fresh native regression demonstrates
|
|
161
|
+
why it is unsafe), or unchanged historical sandbox pure-mode policy permitting
|
|
162
|
+
plugins. Native process recreation is not itself Docker/Podman mount evidence;
|
|
163
|
+
real orchestration gates are reported separately rather than inferred from unit
|
|
164
|
+
fixtures. See `docs/sandbox-persistence.md` for the exact audited bootstrap boundary.
|
|
165
|
+
|
|
166
|
+
The separate `native-containers` CI matrix runs the approved 30-session/revocation
|
|
167
|
+
native test with each official OpenCode server inside a fresh Docker or rootless
|
|
168
|
+
Podman container, using the pinned Node image and integrity-checked official CLI.
|
|
169
|
+
On recreation the test uses a new HOME/data/cache/state; only the private selected
|
|
170
|
+
installation plus read-only installed code/bootstrap fixtures remain available.
|
|
171
|
+
The runtime `/tmp` hints are not mounted. A submitted workflow is not a pass:
|
|
172
|
+
the exact PR head's Docker and Podman jobs are the authoritative execution evidence.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matuto-ai/opencode-broker-connector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24.19.0 <25"
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
},
|
|
17
17
|
"description": "Session-scoped Psst messaging plugin for OpenCode",
|
|
18
18
|
"main": "./plugin/index.mjs",
|
|
19
|
-
"exports":
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./plugin/index.mjs",
|
|
21
|
+
"./server": "./plugin/index.mjs",
|
|
22
|
+
"./tui": "./plugin/tui.mjs"
|
|
23
|
+
},
|
|
20
24
|
"files": [
|
|
21
25
|
"plugin",
|
|
22
26
|
"src",
|
|
@@ -27,7 +31,9 @@
|
|
|
27
31
|
"docs",
|
|
28
32
|
"LICENSE",
|
|
29
33
|
"README.md",
|
|
30
|
-
"vendor/broker/ORIGIN.md"
|
|
34
|
+
"vendor/broker/ORIGIN.md",
|
|
35
|
+
"vendor/enrollment",
|
|
36
|
+
"CHANGELOG.md"
|
|
31
37
|
],
|
|
32
38
|
"repository": {
|
|
33
39
|
"type": "git",
|
package/plugin/index.mjs
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { tool } from '@opencode-ai/plugin';
|
|
2
|
-
import { readConfig } from '../src/config.mjs';
|
|
3
2
|
import { loadBrokerReference } from '../src/reference.mjs';
|
|
4
3
|
import { createNativePlugin } from '../src/native-plugin.mjs';
|
|
4
|
+
import { createConnectionLoader, readExistingConnection } from '../src/enrollment.mjs';
|
|
5
|
+
import { readInstallation } from '../src/installation.mjs';
|
|
6
|
+
import { startPendingResume } from '../src/pending-resume.mjs';
|
|
7
|
+
import { createContextReceiver } from '../src/native-context.mjs';
|
|
5
8
|
export default async function PsstPlugin(native) {
|
|
6
|
-
|
|
9
|
+
const reference = await loadBrokerReference();
|
|
10
|
+
const config = process.env.PSST_CONFIG_FILE ? await readExistingConnection(process.env.PSST_CONFIG_FILE) : undefined;
|
|
11
|
+
const contextReceiver = createContextReceiver({ directory: native.directory });
|
|
12
|
+
const resume = startPendingResume();
|
|
13
|
+
try {
|
|
14
|
+
return await createNativePlugin({ native, tool, reference, config, contextReceiver,
|
|
15
|
+
connectFile: createConnectionLoader({ directory: native.directory }),
|
|
16
|
+
loadInstallation: readInstallation, onDispose: resume.stop,
|
|
17
|
+
experimentalNativeDelivery: process.env.PSST_EXPERIMENTAL_NATIVE_DELIVERY === 'fixed-session-snapshot' });
|
|
18
|
+
} catch (error) { resume.stop(); contextReceiver.close(); throw error; }
|
|
7
19
|
}
|
package/plugin/tui.mjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { installSetupUI } from '../src/setup-ui.mjs';
|
|
2
|
+
import { readSelection, readPending } from '../src/installation.mjs';
|
|
3
|
+
import { startContextPublisher } from '../src/native-context.mjs';
|
|
4
|
+
export default { id: 'matuto.psst', tui: async api => {
|
|
5
|
+
startContextPublisher(api);
|
|
6
|
+
await installSetupUI(api);
|
|
7
|
+
try {
|
|
8
|
+
if (!process.env.PSST_CONFIG_FILE && !readSelection()) {
|
|
9
|
+
const pending = readPending();
|
|
10
|
+
api.ui.toast({ title: 'Psst', variant: 'info', duration: 6000,
|
|
11
|
+
message: pending && !pending.paused ? 'Approval pending. Your saved request resumes without new enrollment.' : 'Setup needed: press Ctrl+Alt+P and enter the broker URL.' });
|
|
12
|
+
}
|
|
13
|
+
} catch { api.ui.toast({ title: 'Psst', variant: 'warning', message: 'Private configuration is invalid; no enrollment or credential reset was attempted.', duration: 6000 }); }
|
|
14
|
+
} };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { lstatSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
export const APPROVAL_SHA256 = 'a60c3a0ccd7deb50556586cd67841bb89a300ea3c7f1af3a91d437f856b7a2d5';
|
|
5
|
+
export async function loadApprovalHelper() {
|
|
6
|
+
const path = fileURLToPath(new URL('../vendor/enrollment/approval.mjs', import.meta.url));
|
|
7
|
+
const stat = lstatSync(path);
|
|
8
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 65536
|
|
9
|
+
|| createHash('sha256').update(readFileSync(path)).digest('hex') !== APPROVAL_SHA256) throw new Error('approval_snapshot_mismatch');
|
|
10
|
+
return import('../vendor/enrollment/approval.mjs');
|
|
11
|
+
}
|
package/src/config.mjs
CHANGED
|
@@ -4,19 +4,21 @@ const fail = () => { throw new Error('psst_configuration_invalid'); };
|
|
|
4
4
|
const plain = value => value && typeof value === 'object' && !Array.isArray(value);
|
|
5
5
|
export function readConfig(path) {
|
|
6
6
|
if (typeof path !== 'string' || !isAbsolute(path)) fail();
|
|
7
|
-
let fd;
|
|
7
|
+
let fd, marked = false;
|
|
8
8
|
try {
|
|
9
9
|
fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
10
10
|
const stat = fstatSync(fd);
|
|
11
11
|
if (!stat.isFile() || stat.mode & 0o077 || stat.size > 16384 || process.getuid && stat.uid !== process.getuid()) fail();
|
|
12
12
|
let value = JSON.parse(readFileSync(fd, 'utf8'));
|
|
13
13
|
if (value?.format === 'broker-client-bundle-v1') {
|
|
14
|
-
const allowed = ['format', 'protocol_version', 'broker_url', 'token_file', 'file_paths_relative_to', 'authorization_header', 'authorization_scheme', 'insecure_development', 'allowed_client_types', 'scope', 'ca_file'];
|
|
14
|
+
const allowed = ['format', 'protocol_version', 'broker_url', 'token_file', 'file_paths_relative_to', 'authorization_header', 'authorization_scheme', 'insecure_development', 'allowed_client_types', 'scope', 'ca_file', 'enrollment_receipt_file'];
|
|
15
15
|
if (Object.keys(value).some(key => !allowed.includes(key)) || value.protocol_version !== '1'
|
|
16
16
|
|| value.file_paths_relative_to !== 'connection_file_directory' || value.authorization_header !== 'Authorization'
|
|
17
17
|
|| value.authorization_scheme !== 'Bearer' || value.insecure_development !== false
|
|
18
18
|
|| !Array.isArray(value.allowed_client_types) || value.allowed_client_types.length !== 1 || value.allowed_client_types[0] !== 'opencode'
|
|
19
|
+
|| value.enrollment_receipt_file !== undefined && value.enrollment_receipt_file !== 'enrollment.json'
|
|
19
20
|
|| value.token_file !== 'token' || typeof value.scope !== 'string') fail();
|
|
21
|
+
marked = value.enrollment_receipt_file === 'enrollment.json';
|
|
20
22
|
const base = dirname(realpathSync(path));
|
|
21
23
|
value = { broker: { url: value.broker_url, tokenFile: resolve(base, value.token_file),
|
|
22
24
|
...(value.ca_file ? { caFile: resolve(base, value.ca_file) } : {}) }, stateDirectory: resolve(base, 'state') };
|
|
@@ -29,6 +31,7 @@ export function readConfig(path) {
|
|
|
29
31
|
|| value.name !== undefined && (typeof value.name !== 'string' || !value.name.trim() || value.name.length > 100 || /[\x00-\x1f\x7f]/.test(value.name))) fail();
|
|
30
32
|
const url = new URL(value.broker.url);
|
|
31
33
|
if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash || url.pathname !== '/') fail();
|
|
34
|
+
if (marked) Object.defineProperty(value, 'enrollmentReceiptRequired', { value: true });
|
|
32
35
|
return value;
|
|
33
36
|
} catch { fail(); }
|
|
34
37
|
finally { if (fd !== undefined) closeSync(fd); }
|