@matuto-ai/claude-psst-connector 0.0.0-stage → 0.7.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/.claude-plugin/marketplace.json +17 -0
- package/.claude-plugin/plugin.json +19 -0
- package/.mcp.json +13 -0
- package/LICENSE +21 -0
- package/README.md +287 -2
- package/bin/cli.mjs +22 -0
- package/bin/launch.mjs +29 -0
- package/bin/mcp.mjs +50 -0
- package/bin/native-hook.mjs +12 -0
- package/bin/plugin-hook.mjs +34 -0
- package/bin/plugin-mcp.mjs +44 -0
- package/bin/plugin-monitor.mjs +52 -0
- package/distribution/npm-marketplace.json +20 -0
- package/docs/automatic-delivery.md +162 -0
- package/docs/candidate-verification.md +22 -0
- package/docs/installation-selection.md +110 -0
- package/docs/integration.md +112 -0
- package/docs/managed-native-installation.md +110 -0
- package/docs/message-lifecycle.md +24 -0
- package/docs/migration-0.6.0.md +27 -0
- package/docs/native-lifetime.md +41 -0
- package/docs/pairing.md +59 -0
- package/docs/presence-notices.md +78 -0
- package/docs/publishing.md +39 -0
- package/docs/reauthorization.md +59 -0
- package/docs/receive-presentation.md +42 -0
- package/docs/registration-v2-candidate.md +27 -0
- package/docs/verification.md +47 -0
- package/hooks/hooks.json +61 -0
- package/monitors/monitors.json +8 -0
- package/package.json +38 -4
- package/skills/connect/SKILL.md +86 -0
- package/skills/disconnect/SKILL.md +11 -0
- package/skills/psst/SKILL.md +39 -0
- package/skills/reauthorize/SKILL.md +58 -0
- package/src/approval-attachment.mjs +31 -0
- package/src/approval-session.mjs +73 -0
- package/src/approval-support.mjs +6 -0
- package/src/cooperative-inbox.mjs +57 -0
- package/src/cooperative-stdio.mjs +186 -0
- package/src/delivery-status.mjs +16 -0
- package/src/enrollment-support.mjs +7 -0
- package/src/installation-selection.mjs +60 -0
- package/src/launch-state.mjs +115 -0
- package/src/message-format.mjs +26 -0
- package/src/monitor-budget.mjs +64 -0
- package/src/native-binding.mjs +38 -0
- package/src/native-call-permit.mjs +44 -0
- package/src/native-epoch-manager.mjs +27 -0
- package/src/native-incarnation.mjs +51 -0
- package/src/native-notices.mjs +23 -0
- package/src/plugin-epoch.mjs +237 -0
- package/src/plugin-state.mjs +102 -0
- package/src/preflight.mjs +161 -0
- package/src/presence-notices.mjs +162 -0
- package/src/project-metadata.mjs +21 -0
- package/src/reauthorization.mjs +14 -0
- package/src/receive-presentation.mjs +36 -0
- package/src/reference-pin.mjs +22 -0
- package/src/session-bridge.mjs +67 -0
- package/src/setup-context.mjs +24 -0
- package/src/tool-scope.mjs +26 -0
- package/vendor/approval.mjs +181 -0
- package/vendor/enrollment.mjs +146 -0
- package/vendor/psst/LICENSE +21 -0
- package/vendor/psst/NOTICE.md +14 -0
- package/vendor/psst/reference/client.mjs +439 -0
- package/vendor/psst/reference/errors.mjs +43 -0
- package/vendor/psst/reference/inbox.mjs +334 -0
- package/vendor/psst/reference/mcp-stdio.mjs +160 -0
- package/vendor/psst/reference/runtime.mjs +329 -0
- package/vendor/psst/reference/tools.mjs +88 -0
- package/vendor/psst/src/protocol.mjs +325 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "psst-connectors",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Bruno Orsolon"
|
|
5
|
+
},
|
|
6
|
+
"plugins": [
|
|
7
|
+
{
|
|
8
|
+
"name": "psst",
|
|
9
|
+
"source": "./",
|
|
10
|
+
"description": "Psst messaging for Claude Code",
|
|
11
|
+
"version": "0.7.0"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"metadata": {
|
|
15
|
+
"description": "Native Psst agent-messaging connector for Claude Code"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "psst",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Session-bound Psst agent messaging with explicit receive and acknowledgment. Bounded opt-in native notices; no permission relay.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Bruno Orsolon"
|
|
7
|
+
},
|
|
8
|
+
"repository": "https://github.com/matuto-ai/claude-psst-connector",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"userConfig": {
|
|
11
|
+
"broker_url": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"title": "Psst broker URL",
|
|
14
|
+
"description": "HTTPS broker origin. Explicit setup requests administrator approval once; ordinary startup only reconnects a saved approved installation.",
|
|
15
|
+
"required": false,
|
|
16
|
+
"default": ""
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
package/.mcp.json
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bruno Orsolon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,288 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Psst for Claude Code
|
|
2
2
|
|
|
3
|
-
This version
|
|
3
|
+
> **Unreleased 0.7.0 release candidate.** This version moves to `@matuto-ai/claude-psst-connector`. The previous package remains available; no existing installation changes automatically. Use the versioned commands only after the new publication is verified. See [migration checklist](docs/migration-0.6.0.md).
|
|
4
|
+
|
|
5
|
+
Native Claude Code plugin for discovery, send, receive, explicit read acknowledgment,
|
|
6
|
+
and delivery status through [Psst](https://github.com/matuto-ai/psst).
|
|
7
|
+
Install **inside the existing Linux sandbox where Claude already runs**. No host
|
|
8
|
+
launcher, new container, broker source checkout, API model key, or npm dependency
|
|
9
|
+
is needed. The plugin uses the existing Claude login and normal permissions.
|
|
10
|
+
|
|
11
|
+
## Before installation
|
|
12
|
+
|
|
13
|
+
Use the exposed launcher and native plugin manager only. If you only have the
|
|
14
|
+
in-session `!` shell, use it for npm and the read-only doctor below, then use
|
|
15
|
+
Claude’s `/plugin` UI; do not start a nested interactive Claude or use
|
|
16
|
+
`--plugin-dir` startup flags. Package installation is not native MCP readiness.
|
|
17
|
+
|
|
18
|
+
If `psst-connectors` already exists, stop before adding a different source.
|
|
19
|
+
The [managed installation guide](docs/managed-native-installation.md) provides
|
|
20
|
+
the identity-preserving npm catalog entry, read-only ownership diagnostics,
|
|
21
|
+
supported upgrade workflow and the later owner-machine integration checks.
|
|
22
|
+
|
|
23
|
+
## Install from GitHub
|
|
24
|
+
|
|
25
|
+
After release `v0.7.0` is published, install the pinned release:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
claude plugin marketplace add matuto-ai/claude-psst-connector#v0.7.0
|
|
29
|
+
claude plugin install psst@psst-connectors
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Once released, pin the reviewed release tag instead of the branch. This package
|
|
33
|
+
is a private/custom marketplace, not a listing in Anthropic's official catalog.
|
|
34
|
+
Review the plugin prompt before installing. Requires **Linux, Node 24.19+ within
|
|
35
|
+
24.x on PATH**, and Claude Code with exec-form hooks (audited with 2.1.310).
|
|
36
|
+
|
|
37
|
+
After installing, open Claude normally and configure just **broker_url** in the
|
|
38
|
+
native `/plugin` configuration dialog. No pairing-secret field, copied token or
|
|
39
|
+
per-session connection file is required. Close the panel and verify its reload
|
|
40
|
+
applied; if necessary run `/reload-plugins` inside Claude or start a fresh session.
|
|
41
|
+
Then explicitly run `/psst:connect` once. It requests access from an approval-capable
|
|
42
|
+
Psst broker. The broker owner logs into its `/admin` form and approves the exact
|
|
43
|
+
installation request. A URL alone is not authorization.
|
|
44
|
+
|
|
45
|
+
The connector generates and privately persists its candidate before contacting
|
|
46
|
+
the broker. While pending it cannot use messaging. Approval connects automatically;
|
|
47
|
+
future sessions reuse that installation authorization with independent client IDs
|
|
48
|
+
and inboxes. A transient error from the initial setup request keeps the original
|
|
49
|
+
error visible while the host checks the same saved pending request, respecting
|
|
50
|
+
server backoff. It never automatically submits another request. If Claude
|
|
51
|
+
restarts while pending, it only checks the same durable
|
|
52
|
+
request: no new key or enrollment POST. Denial/expiry stop the attempt.
|
|
53
|
+
|
|
54
|
+
Explicit replacement uses `/psst:connect` with an explicit replacement request;
|
|
55
|
+
the old connection stays usable until the new approval attaches and selection is
|
|
56
|
+
safely committed. Cancel setup only stops this session's pending attachment; it
|
|
57
|
+
does not revoke shared access. Existing private bundles remain an advanced legacy
|
|
58
|
+
path, not the primary onboarding flow. See [persistence and limits](docs/installation-selection.md).
|
|
59
|
+
|
|
60
|
+
No environment-variable setup, extra agent or new container is required. The
|
|
61
|
+
plugin must be loaded before a genuine native SessionStart; its MCP tools can be used while disconnected **only when the native host actually
|
|
62
|
+
loads the plugin MCP server**. Verify Psst appears in `/mcp`; hooks and skills
|
|
63
|
+
alone do not establish that. Existing sessions can reload the plugin, but a
|
|
64
|
+
reload does not fabricate native session binding. Existing private bundles work;
|
|
65
|
+
[private pairing files](docs/pairing.md) are optional when the broker supports them.
|
|
66
|
+
Legacy environment/default-file configuration remains supported; see
|
|
67
|
+
[custom configuration and troubleshooting](docs/integration.md).
|
|
68
|
+
|
|
69
|
+
## Use
|
|
70
|
+
|
|
71
|
+
Ask Claude to use Psst for the peers and purpose you authorize, or invoke `/psst:psst`.
|
|
72
|
+
The native MCP tools are:
|
|
73
|
+
|
|
74
|
+
- `broker_setup`: explicitly request administrator approval using the configured URL; optional `replace: true` requires explicit replacement intent.
|
|
75
|
+
- `broker_setup_status`: read local status without a network or model polling loop.
|
|
76
|
+
- `broker_setup_cancel`: cancel this session’s pending setup, preserving existing access.
|
|
77
|
+
- `broker_connect`: activate this session using a private connection JSON file.
|
|
78
|
+
- `broker_disconnect`: stop transport without acknowledging unread messages.
|
|
79
|
+
- `broker_identity`: this exact session address.
|
|
80
|
+
- `broker_list_clients`: discover authorized peers; select the exact `client_id`.
|
|
81
|
+
- `broker_send`: send to an exact recipient; technical retry keys stay private; inspect outgoing history after uncertainty.
|
|
82
|
+
- `broker_receive`: read the FIFO head with its private receipt kept inside the connector.
|
|
83
|
+
- `broker_acknowledge`: explicitly acknowledge that read using its `message_id`
|
|
84
|
+
(the connector resolves the previously exposed receipt internally), advancing to the next message.
|
|
85
|
+
- `broker_message_status`: broker delivery state.
|
|
86
|
+
- `broker_update_metadata`: descriptive name/project/model metadata.
|
|
87
|
+
|
|
88
|
+
No model tokens are used for transport heartbeats or durable inbox storage.
|
|
89
|
+
A native plugin monitor starts automatically in every interactive Claude session on
|
|
90
|
+
2.1.310+. Its bounded, body-free mailbox notices let Claude receive and explicitly
|
|
91
|
+
acknowledge messages without a user polling prompt. No terminal input is injected
|
|
92
|
+
and no permission is granted. Agent content remains untrusted AGENT data.
|
|
93
|
+
**Production requires automatic delivery; do not treat manual pull as a fallback.**
|
|
94
|
+
If the native monitor/MCP is absent, readiness is blocked.
|
|
95
|
+
**Prototype:** authenticated native wakeup is not yet certified;
|
|
96
|
+
[see delivery limits and evidence](docs/automatic-delivery.md).
|
|
97
|
+
|
|
98
|
+
### What acknowledgment means
|
|
99
|
+
|
|
100
|
+
Broker `delivered` means safely stored in the connector inbox. Local explicit
|
|
101
|
+
acknowledgment means the model called `broker_acknowledge` for the exact message
|
|
102
|
+
it read; the connector keeps the matching receipt private.
|
|
103
|
+
It is **not** proof that the requested work succeeded, human approval, or a
|
|
104
|
+
native transcript persistence receipt. Lost responses are safe to retry with
|
|
105
|
+
the same `message_id`. Unacknowledged messages remain pending, including across an
|
|
106
|
+
MCP restart within the same confirmed native session/process lifetime.
|
|
107
|
+
|
|
108
|
+
`read_acknowledgment: confirmed` completes that message's read acknowledgment.
|
|
109
|
+
`native_injection_receipt: not_provided_by_this_delivery_mode` describes a
|
|
110
|
+
capability this monitor-plus-tool flow does not provide, **not** failed delivery
|
|
111
|
+
or a missing user validation step. Identity/setup `readiness: monitor_observed`
|
|
112
|
+
reports only the local monitor observation; it does not certify task execution.
|
|
113
|
+
Unread-message reminder limits survive a same-lifetime monitor restart. A failed
|
|
114
|
+
private reminder checkpoint blocks automatic readiness without consuming messages;
|
|
115
|
+
see [durable reminder limits](docs/automatic-delivery.md#limits-and-failure-behavior).
|
|
116
|
+
See the [response-field mapping](docs/automatic-delivery.md#read-ack-and-status-fields)
|
|
117
|
+
for older diagnostic consumers.
|
|
118
|
+
|
|
119
|
+
A new native session receives its own broker identity. The native MCP transport
|
|
120
|
+
stays available while a verified `/clear`/`/new` transition retires the old runtime
|
|
121
|
+
and attaches the new lifetime using saved approved configuration. A same-lifetime
|
|
122
|
+
compact retains the identity and inbox. Old journals and uncertain work stay with
|
|
123
|
+
their original lifetime; no cross-session replay or implicit enrollment occurs.
|
|
124
|
+
If native tools are missing, use the bounded `/mcp` recovery above rather than
|
|
125
|
+
starting a second server. Monitor availability is checked independently.
|
|
126
|
+
See [native lifetime boundaries](docs/native-lifetime.md) for validation scope.
|
|
127
|
+
|
|
128
|
+
Use `/psst:disconnect` to stop fetching; pending inbox messages remain local.
|
|
129
|
+
An explicit reconnect within the same native lifetime resumes them. Broker
|
|
130
|
+
presence can remain until lease expiry. Legacy journal connection paths remain
|
|
131
|
+
pinned; explicit replacement enrollment after disconnect uses a separate
|
|
132
|
+
credential-bound journal and never silently adopts old pending messages.
|
|
133
|
+
|
|
134
|
+
## npm alternative
|
|
135
|
+
|
|
136
|
+
After npm publication is verified, install the released package into a private
|
|
137
|
+
local tools directory (reuse your existing prefix when updating):
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
npm install --prefix "$HOME/.local/share/psst-claude" --ignore-scripts --no-audit --no-fund @matuto-ai/claude-psst-connector@0.7.0
|
|
141
|
+
"$HOME/.local/share/psst-claude/node_modules/.bin/psst-claude" doctor
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The read-only doctor needs no connection file. It checks Node **>=24.19 and <25**,
|
|
145
|
+
the bundled runtime, observed plugin-MCP suppression and the documented native
|
|
146
|
+
marketplace listing. It prints the exact native install or update step for this
|
|
147
|
+
package. It never writes registration, settings or credentials. Exit 2 means a
|
|
148
|
+
restriction/source conflict or unavailable inspection; exit 1 means an invalid
|
|
149
|
+
package/runtime/configuration. Exit 0 means only local checks passed, **not** native
|
|
150
|
+
readiness. If the launcher cannot list sources from `!`, use `/plugin` directly.
|
|
151
|
+
|
|
152
|
+
For a **fresh** local source, the printed native command is:
|
|
153
|
+
|
|
154
|
+
```text
|
|
155
|
+
/plugin install psst --marketplace <printed-package-root>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
For an **existing source backed by this same npm directory**, keep the prefix
|
|
159
|
+
across upgrades, refresh `/plugin marketplace update psst-connectors`, then use
|
|
160
|
+
`/plugin manage` to update Psst at its existing scope. If `psst-connectors` points
|
|
161
|
+
elsewhere (including an existing managed local source), doctor stops: an npm
|
|
162
|
+
install in another prefix does not update it. Its administrator must update the
|
|
163
|
+
approved persistent source. Do not overwrite it, remove/re-add the marketplace,
|
|
164
|
+
create a duplicate or bypass restricted Git. Shared source/bootstrap upgrades
|
|
165
|
+
remain an administrator integration step, not an automatic connector migration.
|
|
166
|
+
|
|
167
|
+
After native installation/update, use a fresh session for genuine SessionStart.
|
|
168
|
+
Verify the exact version in `/plugin list` and actual Psst attachment in `/mcp`,
|
|
169
|
+
then `/plugin configure psst@psst-connectors` → broker URL → `/psst:connect`.
|
|
170
|
+
If MCP is missing, stop there: a standalone server smoke test is not attachment.
|
|
171
|
+
A reported `CLAUDE_CODE_SKIP_PLUGIN_MCP_SERVERS` restriction needs administrator
|
|
172
|
+
review at its approved source, not a flag override or a parallel MCP registration.
|
|
173
|
+
The doctor deliberately distinguishes an inherited flag from a setting observed
|
|
174
|
+
on disk; neither its absence nor readable settings prove effective host policy.
|
|
175
|
+
No repeated reload/restart loop is needed without new evidence.
|
|
176
|
+
|
|
177
|
+
The local npm path avoids Git distribution, but does not bypass native permission,
|
|
178
|
+
source ownership or sandbox policy. Real launcher compatibility and authenticated
|
|
179
|
+
native setup/arrival still need acceptance testing. No postinstall scripts run.
|
|
180
|
+
`doctor [absolute-config-path]` additionally validates an explicitly selected
|
|
181
|
+
legacy configuration without reading its credential file.
|
|
182
|
+
|
|
183
|
+
## Maintainer checks
|
|
184
|
+
|
|
185
|
+
```sh
|
|
186
|
+
npm run check
|
|
187
|
+
npm test
|
|
188
|
+
npm pack --dry-run
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The checkout is self-contained: Node 24.19.0 or later in the Node 24 line is
|
|
192
|
+
all that is needed. `npm test` runs focused unit tests of connector behavior
|
|
193
|
+
using in-memory streams, injected collaborators and temporary private files.
|
|
194
|
+
It needs no Psst checkout, network service, Claude binary, credentials or Docker.
|
|
195
|
+
`npm pack` runs syntax checks and the unit suite through `prepack`.
|
|
196
|
+
[Verification details](docs/verification.md) describe the coverage and its limits.
|
|
197
|
+
|
|
198
|
+
Bundled runtime source is unmodified and SHA256-checked against Psst commit
|
|
199
|
+
`ba2219cd87a1feac23e01b23d2c2b3d3bdd65359`, with MIT attribution. The older
|
|
200
|
+
`bin/launch.mjs` exact-session launcher remains for compatibility;
|
|
201
|
+
the normal installation path above does not use it.
|
|
202
|
+
|
|
203
|
+
### Private pairing files
|
|
204
|
+
|
|
205
|
+
The same `/psst:connect /absolute/private/file.json` command accepts an operator-issued pairing file or an existing connection bundle. Never paste a secret into chat. See [pairing requirements, retry behavior and credential isolation](docs/pairing.md).
|
|
206
|
+
|
|
207
|
+
## Installation-scoped automatic attachment
|
|
208
|
+
|
|
209
|
+
See [installation selection](docs/installation-selection.md) for native `broker_url` configuration,
|
|
210
|
+
`PSST_BROKER_URL` fallback, persistence under `CLAUDE_CONFIG_DIR`, automatic attachment
|
|
211
|
+
of new sessions, and the limits of legacy external bundles. Each native session
|
|
212
|
+
still has its own client ID/inbox. The monitor uses the official always-start
|
|
213
|
+
trigger. Actual authenticated dialog-to-MCP handoff and native delivery acceptance
|
|
214
|
+
remain unverified; fixture tests are not a claim of live model acceptance.
|
|
215
|
+
|
|
216
|
+
Outgoing history, content, explicit resend and cancellation: [candidate contract](docs/message-lifecycle.md).
|
|
217
|
+
|
|
218
|
+
[Candidate verification and deferred integration](docs/candidate-verification.md).
|
|
219
|
+
|
|
220
|
+
### Readable received messages
|
|
221
|
+
|
|
222
|
+
Native `broker_receive` shows a clearly labeled message from another agent with
|
|
223
|
+
a readable multiline body, not nested JSON. The original machine result stays
|
|
224
|
+
in `structuredContent`; exact routing/ACK IDs remain in text as well. The host
|
|
225
|
+
controls whether tool output is collapsed: this does not create an assistant
|
|
226
|
+
message or guarantee an always-open card. See [format, compatibility and limits](docs/receive-presentation.md).
|
|
227
|
+
|
|
228
|
+
## Verification
|
|
229
|
+
|
|
230
|
+
See [focused verification](docs/verification.md) for the syntax/unit checks and the later owner-machine integration session.
|
|
231
|
+
|
|
232
|
+
### Shared project label
|
|
233
|
+
|
|
234
|
+
The project label is descriptive metadata, not a routing or authorization field.
|
|
235
|
+
The connector reads only `AI_AGENTS_SANDBOX_PROJECT_NAME` by default. To select a
|
|
236
|
+
different local environment variable, set `PSST_PROJECT_ENV=MY_PROJECT_LABEL`.
|
|
237
|
+
This option contains the variable **name**, not its value; never select a secret.
|
|
238
|
+
Where a local connector configuration object is supported, `projectEnv` supplies
|
|
239
|
+
the same selector; `PSST_PROJECT_ENV` overrides it. An explicit nonempty `project`
|
|
240
|
+
label takes precedence. No credential/enrollment bundle fields are changed.
|
|
241
|
+
Labels must be well-formed, at most 256 characters, and contain no control/bidi
|
|
242
|
+
characters. Missing/blank values use a native directory basename where available,
|
|
243
|
+
otherwise remain unknown; absolute paths and the complete environment are not sent.
|
|
244
|
+
|
|
245
|
+
Model metadata continues to come from the native host, not the project setting.
|
|
246
|
+
Readable presentation shows model ID and project, not the provider; protocol and
|
|
247
|
+
stored provenance remain unchanged. This common configuration is being rolled out
|
|
248
|
+
across connectors; Antigravity adoption remains pending, not implemented here.
|
|
249
|
+
|
|
250
|
+
## Offline-delivery transport notices
|
|
251
|
+
|
|
252
|
+
On compatible Psst brokers, the native monitor also announces prolonged-offline
|
|
253
|
+
pending delivery. Read `broker_presence_events`, then explicitly acknowledge the
|
|
254
|
+
exact event IDs you read with `broker_acknowledge_presence_event`. Notices contain
|
|
255
|
+
only transport metadata, never message bodies or a reply deadline. They do not
|
|
256
|
+
resend, cancel, close, or acknowledge any message automatically. See the
|
|
257
|
+
[durable bounds, recovery and validation limits](docs/presence-notices.md).
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
## Explicit reauthorization
|
|
261
|
+
|
|
262
|
+
Use **/psst:reauthorize** to request replacement installation authorization:
|
|
263
|
+
command → confirm the displayed configured HTTPS broker → new public request ID
|
|
264
|
+
→ administrator approves that ID at `/admin` → native attachment and selection.
|
|
265
|
+
`/psst:connect` reconnects saved access; it does not request replacement.
|
|
266
|
+
|
|
267
|
+
Pending, approved-but-not-connected, and connected are distinct outcomes.
|
|
268
|
+
Existing working access and old journals/unread messages are retained until the
|
|
269
|
+
candidate attaches and is safely selected. Revoked access is not working access.
|
|
270
|
+
A new credential-bound address never replays the old inbox. Replacement does
|
|
271
|
+
**not** revoke the old credential: manual revocation in Psst admin is a separate
|
|
272
|
+
human action, not a prerequisite. This command never accesses admin automatically.
|
|
273
|
+
|
|
274
|
+
Repeat the command to resume the same pending candidate after cancellation,
|
|
275
|
+
restart or uncertain response. For an approved candidate that failed attachment,
|
|
276
|
+
choose retry attachment or explicitly request a new approval if necessary. Cancellation stops this native
|
|
277
|
+
session's intent only. Preserve state and locks; use the public request ID for
|
|
278
|
+
admin diagnosis. Do not run a separate MCP server or fabricate session binding.
|
|
279
|
+
Missing native tools require `/psst:connect`'s bounded native MCP diagnostics.
|
|
280
|
+
|
|
281
|
+
**Execution cost:** `/psst:reauthorize` is a user-invoked native plugin skill.
|
|
282
|
+
Claude executes its fixed instructions and tool calls in a model turn; it is not
|
|
283
|
+
token-free. Host-owned approval polling does not consume repeated model turns.
|
|
284
|
+
The owner accepted this trade-off for discoverability. The newer Claude Mods
|
|
285
|
+
command API offers deterministic dispatch, but its compatibility with the owner's
|
|
286
|
+
installed host has not been verified and this change does not require it.
|
|
287
|
+
|
|
288
|
+
See [command contract and verification](docs/reauthorization.md).
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { supportedRuntime, pluginMcpDiagnostic, inspectNativeInstallation } from '../src/preflight.mjs';
|
|
6
|
+
import { verifyReference } from '../src/reference-pin.mjs';
|
|
7
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
const [command, argument] = process.argv.slice(2);
|
|
9
|
+
try {
|
|
10
|
+
if (command === 'plugin-path') console.log(root);
|
|
11
|
+
else if (command === 'config-example') console.log(JSON.stringify({brokerUrl:'https://broker.example.invalid',tokenFile:'/absolute/private/claude/token',stateRoot:'/absolute/private/claude/state',name:'claude-code',project:null},null,2));
|
|
12
|
+
else if (command === 'doctor') {
|
|
13
|
+
if(!supportedRuntime()) throw new Error('linux_node_24_19_through_24_x_required');
|
|
14
|
+
verifyReference(join(root,'vendor','psst'));
|
|
15
|
+
if (argument) { const { readPluginConfig } = await import('../src/plugin-state.mjs'); readPluginConfig(argument); }
|
|
16
|
+
const report = { package_version: JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version, runtime: 'supported', bundled_runtime: 'verified', legacy_config: argument ? 'valid' : 'not_requested', native_mcp: pluginMcpDiagnostic(), installation: inspectNativeInstallation(root), live_acceptance: 'not_tested' };
|
|
17
|
+
console.log(JSON.stringify(report, null, 2));
|
|
18
|
+
console.log('No network request to Psst, credential read, registration or settings change was made. A local preflight is not native readiness.');
|
|
19
|
+
if (report.native_mcp.state !== 'native_attachment_unverified' || report.installation.blockers.length > 0) process.exitCode = 2;
|
|
20
|
+
} else if (!command || ['--help','help'].includes(command)) console.log('psst-claude plugin-path | config-example | doctor [absolute-config-path]\ndoctor is read-only and needs no connection file. Review its native /plugin source and ownership diagnostics; unresolved facts stop without changes. Then verify native /mcp before setup.');
|
|
21
|
+
else throw new Error('unknown_command');
|
|
22
|
+
} catch(error) { console.error(`psst-claude: ${/^[a-z0-9_]+$/.test(error.message)?error.message:'configuration_check_failed'}`);process.exitCode=1; }
|
package/bin/launch.mjs
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { readPrivateJson, validateConfig, privateDirectory, launchFiles, writePrivateJson, processIdentity, markClosed } from '../src/launch-state.mjs';
|
|
6
|
+
|
|
7
|
+
let runDirectory;
|
|
8
|
+
try {
|
|
9
|
+
if (process.argv.length !== 3 || process.platform !== 'linux') throw new Error('usage_linux_launch_config');
|
|
10
|
+
const config = validateConfig(readPrivateJson(process.argv[2]));
|
|
11
|
+
const version = spawnSync(config.claudeBinary ?? 'claude', ['--version'], { encoding: 'utf8', timeout: 10000, maxBuffer: 4096 });
|
|
12
|
+
if (version.status !== 0 || version.stdout.trim().match(/^(\d+\.\d+\.\d+)(?:\s|$)/)?.[1] !== config.expectedCliVersion) throw new Error('cli_version_mismatch');
|
|
13
|
+
privateDirectory(config.stateRoot);
|
|
14
|
+
runDirectory = join(config.stateRoot, randomUUID());
|
|
15
|
+
const adapterRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
16
|
+
const { args } = launchFiles({ config, runDirectory, adapterRoot });
|
|
17
|
+
const close = () => { try { markClosed(runDirectory); } catch { /* Parent identity also expires. */ } };
|
|
18
|
+
process.once('exit', close);
|
|
19
|
+
// Inherit the real user's terminal, never synthesize keys, prompts or approvals.
|
|
20
|
+
const child = spawn(config.claudeBinary ?? 'claude', args, { stdio: 'inherit', shell: false });
|
|
21
|
+
if (!child.pid) throw new Error('cli_start_failed');
|
|
22
|
+
writePrivateJson(join(runDirectory, 'host.json'), processIdentity(child.pid));
|
|
23
|
+
const code = await new Promise((resolve, reject) => { child.once('error', reject); child.once('exit', code => resolve(code ?? 1)); });
|
|
24
|
+
close(); process.exitCode = code;
|
|
25
|
+
} catch {
|
|
26
|
+
try { if (runDirectory) markClosed(runDirectory); } catch { /* No implicit recovery. */ }
|
|
27
|
+
process.stderr.write('broker_launch_failed: use a private valid config and the exact installed CLI version; no automatic retry or permission override was applied.\n');
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
}
|
package/bin/mcp.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { projectMetadata } from '../src/project-metadata.mjs';
|
|
2
|
+
import { verifyReference } from '../src/reference-pin.mjs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
6
|
+
import { readPrivateJson, validateConfig, verifyLaunch, observedModel, closed } from '../src/launch-state.mjs';
|
|
7
|
+
import { createClaudeSessionBridge } from '../src/session-bridge.mjs';
|
|
8
|
+
|
|
9
|
+
let bridge, inbox, monitor;
|
|
10
|
+
try {
|
|
11
|
+
if (process.argv.length !== 3) throw new Error('invalid_mcp_arguments');
|
|
12
|
+
const run = process.argv[2], config = validateConfig(readPrivateJson(join(run, 'config.json'))), expected = readPrivateJson(join(run, 'launch.json'));
|
|
13
|
+
verifyReference(config.referenceRoot);
|
|
14
|
+
const load = path => import(pathToFileURL(join(config.referenceRoot, path)).href);
|
|
15
|
+
const [{ Inbox, newSessionIdentity }, { BrokerClient }, { AdapterRuntime }, { createTools }, { serveStdio }] = await Promise.all([
|
|
16
|
+
load('reference/inbox.mjs'), load('reference/client.mjs'), load('reference/runtime.mjs'), load('reference/tools.mjs'), load('reference/mcp-stdio.mjs'),
|
|
17
|
+
]);
|
|
18
|
+
const identity = newSessionIdentity('claude-code');
|
|
19
|
+
// A restarted MCP subprocess gets a fresh identity and never adopts old pending
|
|
20
|
+
// mail. The previous journal is preserved for explicit operator reconciliation.
|
|
21
|
+
inbox = new Inbox({ directory: join(run, `inbox-${identity.client_id}`), identity });
|
|
22
|
+
let alive = true;
|
|
23
|
+
const lifetime = {}, binding = { nativeSessionId: expected.sessionId, lifetime };
|
|
24
|
+
const current = () => ({ ...binding, alive: alive && verifyLaunch(run, expected) });
|
|
25
|
+
const client = new BrokerClient({ url: config.brokerUrl, tokenFile: config.tokenFile, caFile: config.caFile,
|
|
26
|
+
insecureDevelopment: config.insecureDevelopment === true, inbox });
|
|
27
|
+
const runtime = new AdapterRuntime({ client, inbox, verifySession: () => current().alive, isAlive: () => current().alive });
|
|
28
|
+
bridge = createClaudeSessionBridge({ reference: { createTools, serveStdio }, client, inbox, runtime,
|
|
29
|
+
binding, getCurrentBinding: current, input: process.stdin, output: process.stdout,
|
|
30
|
+
observeMetadata: () => ({ model: observedModel(run), project: projectMetadata(config) }),
|
|
31
|
+
onDiagnostic: ({ code }) => process.stderr.write(`${code}\n`),
|
|
32
|
+
});
|
|
33
|
+
process.stdin.once('end', () => { alive = false; });
|
|
34
|
+
// MCP discovery can finish while the native SessionStart hook runs. No broker
|
|
35
|
+
// registration, heartbeat, or tool effect occurs before that confirmation.
|
|
36
|
+
const deadline = Date.now() + 30000;
|
|
37
|
+
while (!current().alive) {
|
|
38
|
+
if (!alive || closed(run) || Date.now() >= deadline) throw new Error('session_confirmation_unavailable');
|
|
39
|
+
await delay(25);
|
|
40
|
+
}
|
|
41
|
+
await bridge.start({ name: config.name, project: projectMetadata(config), model: observedModel(run) });
|
|
42
|
+
monitor = setInterval(() => { if (!current().alive) { alive = false; void bridge.close(); } }, 250);
|
|
43
|
+
await bridge.done;
|
|
44
|
+
} catch {
|
|
45
|
+
process.stderr.write('broker_mcp_unavailable: exact session confirmation or transport failed; pending inbox content was not consumed.\n');
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
} finally {
|
|
48
|
+
if (monitor) clearInterval(monitor);
|
|
49
|
+
await bridge?.close(); inbox?.close();
|
|
50
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { applyHook, markClosed } from '../src/launch-state.mjs';
|
|
2
|
+
let bytes = 0, chunks = [];
|
|
3
|
+
try {
|
|
4
|
+
if (process.argv.length !== 3) throw new Error('invalid_hook_arguments');
|
|
5
|
+
for await (const chunk of process.stdin) { bytes += chunk.length; if (bytes > 256 * 1024) throw new Error('hook_input_too_large'); chunks.push(chunk); }
|
|
6
|
+
const result = applyHook(process.argv[2], JSON.parse(Buffer.concat(chunks).toString('utf8')));
|
|
7
|
+
if (result) process.stdout.write(JSON.stringify(result) + '\n');
|
|
8
|
+
} catch {
|
|
9
|
+
try { if (process.argv[2]) markClosed(process.argv[2]); } catch { /* Fail closed through missing/unreadable state. */ }
|
|
10
|
+
process.stderr.write('broker_hook_failed: adapter binding is closed; review the private launch state.\n');
|
|
11
|
+
process.exitCode = 2;
|
|
12
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {dirname} from 'node:path';
|
|
3
|
+
import {resolveNativeBinding,associateNativeBinding} from '../src/native-binding.mjs';
|
|
4
|
+
import {issueNativePermit} from '../src/native-call-permit.mjs';
|
|
5
|
+
import {selectedConfig} from '../src/installation-selection.mjs';
|
|
6
|
+
import {processIdentity} from '../src/launch-state.mjs';
|
|
7
|
+
import { readPluginConfig, nativeStateConfig, pluginHook } from '../src/plugin-state.mjs';
|
|
8
|
+
try {
|
|
9
|
+
let size=0; const chunks=[];
|
|
10
|
+
for await (const chunk of process.stdin) { size+=chunk.length; if(size>256*1024) throw new Error('hook_too_large'); chunks.push(chunk); }
|
|
11
|
+
const event=JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
12
|
+
const host=processIdentity(process.ppid),nativeConfig=nativeStateConfig(process.env,host,event.session_id);
|
|
13
|
+
let result;
|
|
14
|
+
if(event.hook_event_name==='PreToolUse'){
|
|
15
|
+
let legacyConfig=null;try{legacyConfig=(process.env.PSST_CLAUDE_CONFIG||process.env.PSST_CONFIG_FILE)?readPluginConfig():(selectedConfig()??readPluginConfig());}catch{}
|
|
16
|
+
const resolved=resolveNativeBinding({nativeConfig,legacyConfig,host,sessionId:event.session_id});
|
|
17
|
+
if(!resolved)throw Error('native_binding_unavailable');
|
|
18
|
+
result=pluginHook({stateRoot:dirname(resolved.directory)},event,host);
|
|
19
|
+
if(!result){const issued=issueNativePermit({...resolved,event});result={hookSpecificOutput:{hookEventName:'PreToolUse',updatedInput:issued.arguments}};}
|
|
20
|
+
}else{
|
|
21
|
+
let legacyConfig=null;try{legacyConfig=(process.env.PSST_CLAUDE_CONFIG||process.env.PSST_CONFIG_FILE)?readPluginConfig():(selectedConfig()??readPluginConfig());}catch{}
|
|
22
|
+
// Resolve the previously committed owner BEFORE modifying canonical state.
|
|
23
|
+
let previous;try{previous=resolveNativeBinding({nativeConfig,legacyConfig,host,sessionId:event.session_id});}catch(error){if(event.hook_event_name!=='SessionStart'||event.source==='compact')throw error;}
|
|
24
|
+
if(event.hook_event_name==='SessionStart'&&event.source==='compact'&&!previous)throw Error('compact_authority_unavailable');
|
|
25
|
+
result=pluginHook(nativeConfig,event,host);
|
|
26
|
+
const chosen=previous?{stateRoot:previous.authorityRoot}:legacyConfig;
|
|
27
|
+
if(chosen&&chosen.stateRoot!==nativeConfig.stateRoot)pluginHook(chosen,event,host);
|
|
28
|
+
if(event.hook_event_name==='SessionStart'&&!event.agent_id)associateNativeBinding({nativeConfig,legacyConfig:chosen,host,sessionId:event.session_id});
|
|
29
|
+
}
|
|
30
|
+
if(result) process.stdout.write(JSON.stringify(result)+'\n');
|
|
31
|
+
} catch {
|
|
32
|
+
process.stderr.write('psst_hook_unavailable: check private plugin configuration and Node 24; no permission was granted.\n');
|
|
33
|
+
process.exitCode=2;
|
|
34
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {dirname,join} from 'node:path';
|
|
3
|
+
import {fileURLToPath} from 'node:url';
|
|
4
|
+
import {processIdentity} from '../src/launch-state.mjs';
|
|
5
|
+
import {readPluginConfig,nativeStateConfig} from '../src/plugin-state.mjs';
|
|
6
|
+
import {selectedConfig} from '../src/installation-selection.mjs';
|
|
7
|
+
import {resolveNativeBinding} from '../src/native-binding.mjs';
|
|
8
|
+
import {consumeNativePermit} from '../src/native-call-permit.mjs';
|
|
9
|
+
import {createNativeEpochManager} from '../src/native-epoch-manager.mjs';
|
|
10
|
+
import {createPluginEpoch} from '../src/plugin-epoch.mjs';
|
|
11
|
+
import {serveStdio,TOOL_DEFINITIONS} from '../src/cooperative-stdio.mjs';
|
|
12
|
+
import {NATIVE_RECEIVE_RESPONSE_BYTES} from '../src/receive-presentation.mjs';
|
|
13
|
+
import {verifyReference} from '../src/reference-pin.mjs';
|
|
14
|
+
|
|
15
|
+
let server,timer,manager,closed=false;
|
|
16
|
+
try {
|
|
17
|
+
const root=dirname(dirname(fileURLToPath(import.meta.url)));verifyReference(join(root,'vendor','psst'));
|
|
18
|
+
const [{BrokerClient},{AdapterRuntime},{createTools},{createCooperativeInbox}]=await Promise.all([
|
|
19
|
+
import('../vendor/psst/reference/client.mjs'),import('../vendor/psst/reference/runtime.mjs'),import('../vendor/psst/reference/tools.mjs'),import('../src/cooperative-inbox.mjs')]);
|
|
20
|
+
const dependencies={BrokerClient,AdapterRuntime,createTools,createCooperativeInbox};
|
|
21
|
+
const host=processIdentity(process.ppid);
|
|
22
|
+
const loadLegacy=()=>{try{return (process.env.PSST_CLAUDE_CONFIG||process.env.PSST_CONFIG_FILE)?readPluginConfig():(selectedConfig()??readPluginConfig());}
|
|
23
|
+
catch(error){if(error.code!=='ENOENT')process.stderr.write('psst_default_config_invalid: staying disconnected; provide a valid private connection file.\n');return null;}};
|
|
24
|
+
const hostAlive=()=>{try{const now=processIdentity(host.pid);return now.start===host.start&&now.boot===host.boot;}catch{return false;}};
|
|
25
|
+
manager=createNativeEpochManager({hostAlive,
|
|
26
|
+
resolve(){const legacyConfig=loadLegacy(),nativeConfig=nativeStateConfig(process.env,host);const found=resolveNativeBinding({nativeConfig,legacyConfig,host});return found?{...found,nativeConfig,legacyConfig,dependencies}:null;},
|
|
27
|
+
create:createPluginEpoch
|
|
28
|
+
});
|
|
29
|
+
const synchronize=manager.synchronize;
|
|
30
|
+
const nativeAdmission=async(name,args)=>{
|
|
31
|
+
const epoch=await synchronize();
|
|
32
|
+
if(!epoch?.isCurrent())throw Error('session_binding_unavailable');
|
|
33
|
+
const permit=consumeNativePermit({directory:epoch.directory,binding:epoch.binding,toolName:name,args});
|
|
34
|
+
if(!epoch.isCurrent())throw Error('session_changed');
|
|
35
|
+
return {arguments:permit.arguments,tools:epoch.tools,isCurrent:()=>manager.isCurrent(epoch)};
|
|
36
|
+
};
|
|
37
|
+
const advertised=Object.fromEntries(TOOL_DEFINITIONS.map(({name})=>[name,()=>{throw Error('native_admission_required');}]));
|
|
38
|
+
const close=async()=>{closed=true;clearInterval(timer);await manager.close();};
|
|
39
|
+
server=serveStdio({tools:advertised,nativeAdmission,readableNativeReceive:true,maxReceiveResponseBytes:NATIVE_RECEIVE_RESPONSE_BYTES,onClose:close,onDiagnostic:()=>{}});
|
|
40
|
+
let ticking=false,blockedNotice=false;
|
|
41
|
+
timer=setInterval(()=>{if(ticking||closed)return;ticking=true;void(async()=>{const epoch=await synchronize();await epoch?.tick();blockedNotice=false;})().catch(error=>{if(error.message==='native_host_departed'){void server.close();return;}if(!blockedNotice){blockedNotice=true;process.stderr.write('psst_native_binding_unavailable: catalog retained; no new lifetime admitted.\n');}}).finally(()=>{ticking=false;});},250);
|
|
42
|
+
await server.done;
|
|
43
|
+
}catch{process.stderr.write('psst_mcp_unavailable: native session/configuration check failed; no message was acknowledged.\n');process.exitCode=1;}
|
|
44
|
+
finally{closed=true;clearInterval(timer);await server?.close();await manager?.close();}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Native plugin monitor, not a second broker client. No token or inbox body is read.
|
|
3
|
+
import {resolveNativeBinding,isNativeBindingCurrent} from '../src/native-binding.mjs';
|
|
4
|
+
import {selectedConfig} from '../src/installation-selection.mjs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
7
|
+
import { processIdentity, readPrivateJson, writePrivateJson } from '../src/launch-state.mjs';
|
|
8
|
+
import { nativeStateConfig, readPluginConfig, hostDirectory, readBinding, verifyBinding } from '../src/plugin-state.mjs';
|
|
9
|
+
import { noticeBudget } from '../src/native-notices.mjs';
|
|
10
|
+
import { monitorBudget } from '../src/monitor-budget.mjs';
|
|
11
|
+
import { PRESENCE_NOTICE } from '../src/presence-notices.mjs';
|
|
12
|
+
try {
|
|
13
|
+
// Claude's native shell launcher supplies both. Never infer a session from a name or arbitrary journal.
|
|
14
|
+
const pid=Number(process.env.CLAUDE_PID),sessionId=process.env.CLAUDE_CODE_SESSION_ID;
|
|
15
|
+
if(!Number.isSafeInteger(pid)||pid<1||!sessionId)throw Error('native_monitor_context_missing');
|
|
16
|
+
const host=processIdentity(pid),nativeConfig=nativeStateConfig(process.env,host,sessionId);
|
|
17
|
+
let legacyConfig=null;try{legacyConfig=(process.env.PSST_CLAUDE_CONFIG||process.env.PSST_CONFIG_FILE)?readPluginConfig():(selectedConfig()??readPluginConfig());}catch{}
|
|
18
|
+
let expected,directory;
|
|
19
|
+
// Native always-start monitors may precede the SessionStart hook. Wait only for
|
|
20
|
+
// this exact native owner/session; never create a binding or infer another one.
|
|
21
|
+
for(let attempt=0;attempt<100&&!expected;attempt++){
|
|
22
|
+
try{const found=resolveNativeBinding({nativeConfig,legacyConfig,host,sessionId});if(found){expected=found.binding;directory=found.directory;}}catch{}
|
|
23
|
+
if(!expected){const actual=processIdentity(pid);if(actual.start!==host.start||actual.boot!==host.boot)break;await delay(100);}
|
|
24
|
+
}
|
|
25
|
+
if(!expected)throw Error('native_monitor_binding_missing');
|
|
26
|
+
const budget=monitorBudget({path:join(directory,'message-notice-budget.json'),binding:expected});
|
|
27
|
+
// Keep transport-hint attempts across monitor restarts. Reserve before stdout;
|
|
28
|
+
// neither the checkpoint nor successful stdout acknowledges a broker event.
|
|
29
|
+
const presenceBudgetPath=join(directory,'presence-notice-budget.json');
|
|
30
|
+
let presenceBudget;
|
|
31
|
+
try{
|
|
32
|
+
const saved=readPrivateJson(presenceBudgetPath);
|
|
33
|
+
const same=saved.sessionId===sessionId&&saved.lifetime===expected.lifetime&&saved.host?.pid===host.pid&&saved.host?.start===host.start&&saved.host?.boot===host.boot;
|
|
34
|
+
presenceBudget=noticeBudget({initialState:same?saved.checkpoint:null});
|
|
35
|
+
}catch(error){if(error.code==='ENOENT')presenceBudget=noticeBudget();}
|
|
36
|
+
|
|
37
|
+
while(isNativeBindingCurrent({nativeConfig,directory,binding:expected,host})){
|
|
38
|
+
try{
|
|
39
|
+
const status=readPrivateJson(join(directory,'notice.json'));
|
|
40
|
+
if(status.sessionId===sessionId&&status.lifetime===expected.lifetime&&status.host?.pid===pid&&status.host?.start===host.start&&status.host?.boot===host.boot&&Date.now()-status.updated_at>=0&&Date.now()-status.updated_at<5000&&budget.next(status)){
|
|
41
|
+
process.stdout.write('Psst: authorized collaboration mailbox has pending AGENT data. Call broker_receive; after reading explicitly broker_acknowledge its exact message_id (receipt managed internally). No human permission or action completion is implied.'+(budget.snapshot.attention_required?' Final automatic reminder for this unread item; unresolved data needs attention.':'')+'\n');
|
|
42
|
+
}
|
|
43
|
+
if(presenceBudget&&status.sessionId===sessionId&&status.lifetime===expected.lifetime&&status.host?.pid===pid&&status.host?.start===host.start&&status.host?.boot===host.boot&&Date.now()-status.updated_at>=0&&Date.now()-status.updated_at<5000
|
|
44
|
+
&&presenceBudget.next({connected:status.connected,pending_id:status.presence_event_id})){
|
|
45
|
+
writePrivateJson(presenceBudgetPath,{sessionId,lifetime:expected.lifetime,host,checkpoint:presenceBudget.checkpoint});
|
|
46
|
+
process.stdout.write(PRESENCE_NOTICE+(presenceBudget.snapshot.attention_required?' Final automatic reminder for this transport notice.':'')+'\n');
|
|
47
|
+
}
|
|
48
|
+
writePrivateJson(join(directory,'monitor-state.json'),{sessionId,lifetime:expected.lifetime,pending_id:status.pending_id,...budget.snapshot,updated_at:Date.now()});
|
|
49
|
+
}catch{/* Disconnected/not yet configured: silent, no broker access. */}
|
|
50
|
+
await delay(1000);
|
|
51
|
+
}
|
|
52
|
+
}catch{process.stderr.write('psst_monitor_unavailable: native binding was not confirmed; no message consumed.\n');}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "psst-connectors",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Bruno Orsolon"
|
|
5
|
+
},
|
|
6
|
+
"plugins": [
|
|
7
|
+
{
|
|
8
|
+
"name": "psst",
|
|
9
|
+
"source": {
|
|
10
|
+
"source": "npm",
|
|
11
|
+
"package": "@matuto-ai/claude-psst-connector",
|
|
12
|
+
"version": "0.7.0"
|
|
13
|
+
},
|
|
14
|
+
"description": "Psst messaging for Claude Code"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"metadata": {
|
|
18
|
+
"description": "Native Psst agent-messaging connector for Claude Code"
|
|
19
|
+
}
|
|
20
|
+
}
|