@matuto-ai/claude-code-broker-connector 0.4.0 → 0.6.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 +1 -1
- package/.claude-plugin/plugin.json +2 -10
- package/.mcp.json +1 -2
- package/README.md +84 -46
- package/bin/cli.mjs +9 -5
- package/bin/plugin-hook.mjs +2 -1
- package/bin/plugin-mcp.mjs +101 -25
- package/bin/plugin-monitor.mjs +2 -2
- package/docs/automatic-delivery.md +14 -3
- package/docs/candidate-verification.md +25 -0
- package/docs/installation-selection.md +52 -35
- package/docs/integration.md +15 -2
- package/docs/message-lifecycle.md +24 -0
- package/docs/publishing.md +2 -2
- package/docs/verification.md +30 -0
- package/package.json +1 -1
- package/scripts/audit-native.mjs +6 -5
- package/skills/connect/SKILL.md +34 -2
- package/skills/psst/SKILL.md +4 -4
- package/src/approval-attachment.mjs +31 -0
- package/src/approval-session.mjs +58 -0
- package/src/approval-support.mjs +6 -0
- package/src/cooperative-inbox.mjs +11 -7
- package/src/cooperative-stdio.mjs +16 -8
- package/src/installation-selection.mjs +11 -6
- package/src/message-format.mjs +26 -0
- package/src/plugin-state.mjs +22 -3
- package/src/preflight.mjs +83 -0
- package/src/reference-pin.mjs +6 -6
- package/src/setup-context.mjs +24 -0
- package/src/tool-scope.mjs +26 -0
- package/vendor/approval.mjs +181 -0
- package/vendor/psst/NOTICE.md +10 -9
- package/vendor/psst/reference/client.mjs +90 -15
- package/vendor/psst/reference/inbox.mjs +55 -10
- package/vendor/psst/reference/mcp-stdio.mjs +5 -1
- package/vendor/psst/reference/tools.mjs +38 -14
- package/vendor/psst/src/protocol.mjs +19 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "psst",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Session-bound Psst agent messaging with explicit receive and acknowledgment. Bounded opt-in native notices; no permission relay.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Bruno Orsolon"
|
|
@@ -11,17 +11,9 @@
|
|
|
11
11
|
"broker_url": {
|
|
12
12
|
"type": "string",
|
|
13
13
|
"title": "Psst broker URL",
|
|
14
|
-
"description": "HTTPS broker origin.
|
|
14
|
+
"description": "HTTPS broker origin. Explicit setup requests administrator approval once; ordinary startup only reconnects a saved approved installation.",
|
|
15
15
|
"required": false,
|
|
16
16
|
"default": ""
|
|
17
|
-
},
|
|
18
|
-
"pairing_secret": {
|
|
19
|
-
"type": "string",
|
|
20
|
-
"title": "Psst one-use pairing secret",
|
|
21
|
-
"description": "Enter only in this native private configuration field, never in chat. Used solely by explicit setup, not startup. Remove through native UI after successful setup if supported.",
|
|
22
|
-
"required": false,
|
|
23
|
-
"sensitive": true,
|
|
24
|
-
"default": ""
|
|
25
17
|
}
|
|
26
18
|
}
|
|
27
19
|
}
|
package/.mcp.json
CHANGED
package/README.md
CHANGED
|
@@ -1,49 +1,58 @@
|
|
|
1
1
|
# Psst for Claude Code
|
|
2
2
|
|
|
3
|
+
> **Unreleased 0.6.0 candidate.** The new commands/behavior below are not in the existing npm release. Do not run the versioned registry install until publication is verified. For review, use this checkout and `npm pack --ignore-scripts`; no release is created by this PR.
|
|
4
|
+
|
|
3
5
|
Native Claude Code plugin for discovery, send, receive, explicit read acknowledgment,
|
|
4
6
|
and delivery status through [Psst](https://github.com/matuto-ai/psst).
|
|
5
7
|
Install **inside the existing Linux sandbox where Claude already runs**. No host
|
|
6
8
|
launcher, new container, broker source checkout, API model key, or npm dependency
|
|
7
9
|
is needed. The plugin uses the existing Claude login and normal permissions.
|
|
8
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
|
+
|
|
9
18
|
## Install from GitHub
|
|
10
19
|
|
|
11
|
-
After release `v0.
|
|
20
|
+
After release `v0.6.0` is published, install the pinned release:
|
|
12
21
|
|
|
13
22
|
```sh
|
|
14
|
-
claude plugin marketplace add matuto-ai/claude-code-broker-connector#v0.
|
|
23
|
+
claude plugin marketplace add matuto-ai/claude-code-broker-connector#v0.6.0
|
|
15
24
|
claude plugin install psst@psst-connectors
|
|
16
25
|
```
|
|
17
26
|
|
|
18
27
|
Once released, pin the reviewed release tag instead of the branch. This package
|
|
19
28
|
is a private/custom marketplace, not a listing in Anthropic's official catalog.
|
|
20
29
|
Review the plugin prompt before installing. Requires **Linux, Node 24.19+ within
|
|
21
|
-
24.x on PATH**, and Claude Code with exec-form hooks (audited with 2.1.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
See [configuration, persistence and acceptance limits](docs/installation-selection.md).
|
|
30
|
+
24.x on PATH**, and Claude Code with exec-form hooks (audited with 2.1.310).
|
|
31
|
+
|
|
32
|
+
After installing, open Claude normally and configure just **broker_url** in the
|
|
33
|
+
native `/plugin` configuration dialog. No pairing-secret field, copied token or
|
|
34
|
+
per-session connection file is required. Close the panel and verify its reload
|
|
35
|
+
applied; if necessary run `/reload-plugins` inside Claude or start a fresh session.
|
|
36
|
+
Then explicitly run `/psst:connect` once. It requests access from an approval-capable
|
|
37
|
+
Psst broker. The broker owner logs into its `/admin` form and approves the exact
|
|
38
|
+
installation request. A URL alone is not authorization.
|
|
39
|
+
|
|
40
|
+
The connector generates and privately persists its candidate before contacting
|
|
41
|
+
the broker. While pending it cannot use messaging. Approval connects automatically;
|
|
42
|
+
future sessions reuse that installation authorization with independent client IDs
|
|
43
|
+
and inboxes. If Claude restarts while pending, it only checks the same durable
|
|
44
|
+
request: no new key or enrollment POST. Denial/expiry stop the attempt.
|
|
45
|
+
|
|
46
|
+
Explicit replacement uses `/psst:connect` with an explicit replacement request;
|
|
47
|
+
the old connection stays usable until the new approval attaches and selection is
|
|
48
|
+
safely committed. Cancel setup only stops this session's pending attachment; it
|
|
49
|
+
does not revoke shared access. Existing private bundles remain an advanced legacy
|
|
50
|
+
path, not the primary onboarding flow. See [persistence and limits](docs/installation-selection.md).
|
|
43
51
|
|
|
44
52
|
No environment-variable setup, extra agent or new container is required. The
|
|
45
|
-
plugin must be loaded before a genuine native SessionStart; its MCP tools
|
|
46
|
-
|
|
53
|
+
plugin must be loaded before a genuine native SessionStart; its MCP tools can be used while disconnected **only when the native host actually
|
|
54
|
+
loads the plugin MCP server**. Verify Psst appears in `/mcp`; hooks and skills
|
|
55
|
+
alone do not establish that. Existing sessions can reload the plugin, but a
|
|
47
56
|
reload does not fabricate native session binding. Existing private bundles work;
|
|
48
57
|
[private pairing files](docs/pairing.md) are optional when the broker supports them.
|
|
49
58
|
Legacy environment/default-file configuration remains supported; see
|
|
@@ -54,15 +63,17 @@ Legacy environment/default-file configuration remains supported; see
|
|
|
54
63
|
Ask Claude to use Psst for the peers and purpose you authorize, or invoke `/psst:psst`.
|
|
55
64
|
The native MCP tools are:
|
|
56
65
|
|
|
57
|
-
- `broker_setup`:
|
|
66
|
+
- `broker_setup`: explicitly request administrator approval using the configured URL; optional `replace: true` requires explicit replacement intent.
|
|
67
|
+
- `broker_setup_status`: read local status without a network or model polling loop.
|
|
68
|
+
- `broker_setup_cancel`: cancel this session’s pending setup, preserving existing access.
|
|
58
69
|
- `broker_connect`: activate this session using a private connection JSON file.
|
|
59
70
|
- `broker_disconnect`: stop transport without acknowledging unread messages.
|
|
60
71
|
- `broker_identity`: this exact session address.
|
|
61
72
|
- `broker_list_clients`: discover authorized peers; select the exact `client_id`.
|
|
62
|
-
- `broker_send`: send
|
|
63
|
-
- `broker_receive`: read the FIFO head
|
|
73
|
+
- `broker_send`: send to an exact recipient; technical retry keys stay private; inspect outgoing history after uncertainty.
|
|
74
|
+
- `broker_receive`: read the FIFO head with its private receipt kept inside the connector.
|
|
64
75
|
- `broker_acknowledge`: explicitly acknowledge that read using its `message_id`
|
|
65
|
-
|
|
76
|
+
(the connector resolves the previously exposed receipt internally), advancing to the next message.
|
|
66
77
|
- `broker_message_status`: broker delivery state.
|
|
67
78
|
- `broker_update_metadata`: descriptive name/project/model metadata.
|
|
68
79
|
|
|
@@ -71,6 +82,8 @@ A native plugin monitor starts automatically in every interactive Claude session
|
|
|
71
82
|
2.1.310+. Its bounded, body-free mailbox notices let Claude receive and explicitly
|
|
72
83
|
acknowledge messages without a user polling prompt. No terminal input is injected
|
|
73
84
|
and no permission is granted. Agent content remains untrusted AGENT data.
|
|
85
|
+
**Production requires automatic delivery; do not treat manual pull as a fallback.**
|
|
86
|
+
If the native monitor/MCP is absent, readiness is blocked.
|
|
74
87
|
**Prototype:** authenticated native wakeup is not yet certified;
|
|
75
88
|
[see delivery limits and evidence](docs/automatic-delivery.md).
|
|
76
89
|
|
|
@@ -100,27 +113,48 @@ After npm publication is verified, install the released package into a private
|
|
|
100
113
|
local tools directory (reuse your existing prefix when updating):
|
|
101
114
|
|
|
102
115
|
```sh
|
|
103
|
-
npm install --prefix "$HOME/.local/share/psst-claude" --ignore-scripts --no-audit --no-fund @matuto-ai/claude-code-broker-connector@0.
|
|
104
|
-
"$HOME/.local/share/psst-claude/node_modules/.bin/psst-claude"
|
|
116
|
+
npm install --prefix "$HOME/.local/share/psst-claude" --ignore-scripts --no-audit --no-fund @matuto-ai/claude-code-broker-connector@0.6.0
|
|
117
|
+
"$HOME/.local/share/psst-claude/node_modules/.bin/psst-claude" doctor
|
|
105
118
|
```
|
|
106
119
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
The read-only doctor needs no connection file. It checks Node **>=24.19 and <25**,
|
|
121
|
+
the bundled runtime, observed plugin-MCP suppression and the documented native
|
|
122
|
+
marketplace listing. It prints the exact native install or update step for this
|
|
123
|
+
package. It never writes registration, settings or credentials. Exit 2 means a
|
|
124
|
+
restriction/source conflict or unavailable inspection; exit 1 means an invalid
|
|
125
|
+
package/runtime/configuration. Exit 0 means only local checks passed, **not** native
|
|
126
|
+
readiness. If the launcher cannot list sources from `!`, use `/plugin` directly.
|
|
111
127
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
128
|
+
For a **fresh** local source, the printed native command is:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
/plugin install psst --marketplace <printed-package-root>
|
|
115
132
|
```
|
|
116
133
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
134
|
+
For an **existing source backed by this same npm directory**, keep the prefix
|
|
135
|
+
across upgrades, refresh `/plugin marketplace update psst-connectors`, then use
|
|
136
|
+
`/plugin manage` to update Psst at its existing scope. If `psst-connectors` points
|
|
137
|
+
elsewhere (including an existing managed local source), doctor stops: an npm
|
|
138
|
+
install in another prefix does not update it. Its administrator must update the
|
|
139
|
+
approved persistent source. Do not overwrite it, remove/re-add the marketplace,
|
|
140
|
+
create a duplicate or bypass restricted Git. Shared source/bootstrap upgrades
|
|
141
|
+
remain an administrator integration step, not an automatic connector migration.
|
|
142
|
+
|
|
143
|
+
After native installation/update, use a fresh session for genuine SessionStart.
|
|
144
|
+
Verify the exact version in `/plugin list` and actual Psst attachment in `/mcp`,
|
|
145
|
+
then `/plugin configure psst@psst-connectors` → broker URL → `/psst:connect`.
|
|
146
|
+
If MCP is missing, stop there: a standalone server smoke test is not attachment.
|
|
147
|
+
A reported `CLAUDE_CODE_SKIP_PLUGIN_MCP_SERVERS` restriction needs administrator
|
|
148
|
+
review at its approved source, not a flag override or a parallel MCP registration.
|
|
149
|
+
The doctor deliberately distinguishes an inherited flag from a setting observed
|
|
150
|
+
on disk; neither its absence nor readable settings prove effective host policy.
|
|
151
|
+
No repeated reload/restart loop is needed without new evidence.
|
|
152
|
+
|
|
153
|
+
The local npm path avoids Git distribution, but does not bypass native permission,
|
|
154
|
+
source ownership or sandbox policy. Real launcher compatibility and authenticated
|
|
155
|
+
native setup/arrival still need acceptance testing. No postinstall scripts run.
|
|
156
|
+
`doctor [absolute-config-path]` additionally validates an explicitly selected
|
|
157
|
+
legacy configuration without reading its credential file.
|
|
124
158
|
|
|
125
159
|
## Maintainer checks
|
|
126
160
|
|
|
@@ -154,3 +188,7 @@ of new sessions, and the limits of legacy external bundles. Each native session
|
|
|
154
188
|
still has its own client ID/inbox. The monitor uses the official always-start
|
|
155
189
|
trigger. Actual authenticated dialog-to-MCP handoff and native delivery acceptance
|
|
156
190
|
remain unverified; fixture tests are not a claim of live model acceptance.
|
|
191
|
+
|
|
192
|
+
Outgoing history, content, explicit resend and cancellation: [candidate contract](docs/message-lifecycle.md).
|
|
193
|
+
|
|
194
|
+
[Candidate verification and remaining gates](docs/candidate-verification.md).
|
package/bin/cli.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { supportedRuntime, pluginMcpDiagnostic, inspectNativeMarketplace } from '../src/preflight.mjs';
|
|
5
6
|
import { verifyReference } from '../src/reference-pin.mjs';
|
|
6
7
|
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
7
8
|
const [command, argument] = process.argv.slice(2);
|
|
@@ -9,10 +10,13 @@ try {
|
|
|
9
10
|
if (command === 'plugin-path') console.log(root);
|
|
10
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));
|
|
11
12
|
else if (command === 'doctor') {
|
|
12
|
-
if(
|
|
13
|
+
if(!supportedRuntime()) throw new Error('linux_node_24_19_through_24_x_required');
|
|
13
14
|
verifyReference(join(root,'vendor','psst'));
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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: inspectNativeMarketplace(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' || ['source_conflict','inspection_unavailable'].includes(report.installation.state)) 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. Follow its native /plugin install/update guidance; conflicts stop without changes. Then verify native /mcp before setup.');
|
|
17
21
|
else throw new Error('unknown_command');
|
|
18
22
|
} catch(error) { console.error(`psst-claude: ${/^[a-z0-9_]+$/.test(error.message)?error.message:'configuration_check_failed'}`);process.exitCode=1; }
|
package/bin/plugin-hook.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {processIdentity} from '../src/launch-state.mjs';
|
|
2
3
|
import { readPluginConfig, nativeStateConfig, pluginHook } from '../src/plugin-state.mjs';
|
|
3
4
|
try {
|
|
4
5
|
let size=0; const chunks=[];
|
|
5
6
|
for await (const chunk of process.stdin) { size+=chunk.length; if(size>256*1024) throw new Error('hook_too_large'); chunks.push(chunk); }
|
|
6
7
|
const event=JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
7
|
-
const result=pluginHook(nativeStateConfig(),event);
|
|
8
|
+
const result=pluginHook(nativeStateConfig(process.env,processIdentity(process.ppid),event.session_id),event);
|
|
8
9
|
// Compatibility for an already configured 0.2.1 host journal.
|
|
9
10
|
try { pluginHook(readPluginConfig(),event); } catch { /* Configuration is optional before connect. */ }
|
|
10
11
|
if(result) process.stdout.write(JSON.stringify(result)+'\n');
|
package/bin/plugin-mcp.mjs
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createToolScope } from '../src/tool-scope.mjs';
|
|
2
3
|
import { installationRoot, readSelection, selectedConfig, saveSelection, configuredBrokerUrl, enforceConfiguredBroker } from '../src/installation-selection.mjs';
|
|
3
|
-
import { connectPairingFile
|
|
4
|
+
import { connectPairingFile } from '../src/enrollment-support.mjs';
|
|
5
|
+
import {switchApproved} from '../src/approval-attachment.mjs';
|
|
6
|
+
import {ApprovalSession} from '../src/approval-session.mjs';
|
|
7
|
+
import {beginApproval,pollApproval,readApprovalState} from '../src/approval-support.mjs';
|
|
8
|
+
import {readSetupContext,saveSetupContext} from '../src/setup-context.mjs';
|
|
4
9
|
import { join, dirname } from 'node:path';
|
|
5
10
|
import { fileURLToPath } from 'node:url';
|
|
6
11
|
import { writeFileSync } from 'node:fs';
|
|
@@ -8,15 +13,15 @@ import { readPrivateJson, writePrivateJson, privateDirectory } from '../src/laun
|
|
|
8
13
|
import { verifyReference } from '../src/reference-pin.mjs';
|
|
9
14
|
import { readPluginConfig, nativeStateConfig, hostDirectory, readBinding, verifyBinding, bindingModel, journalName, refreshEnrollment } from '../src/plugin-state.mjs';
|
|
10
15
|
|
|
11
|
-
let server,runtime,inbox,timer,live=true,expected,directory,host,tools,connected=false,connecting=false,disabled=false,generation=0,autoAttempted=false;
|
|
12
|
-
let legacyConfig=null,connectedConfig=null,lastNotice=null,lastNoticeAt=0;
|
|
16
|
+
let toolScope,connectionAbort,recoveryPromise,server,runtime,inbox,timer,live=true,expected,directory,host,tools,connected=false,connecting=false,disabled=false,generation=0,autoAttempted=false;
|
|
17
|
+
let legacyConfig=null,connectedConfig=null,lastNotice=null,lastNoticeAt=0,approvalSession=null,setupContext=null,approvalTransition=null;
|
|
13
18
|
const failure=(code,effect='not_applied')=>({error:{code,retryable:true,effect}});
|
|
14
19
|
const bound=()=>live&&expected&&verifyBinding(directory,expected);
|
|
15
20
|
const active=()=>connected&&bound();
|
|
16
21
|
function status(){
|
|
17
22
|
if(!directory||!expected)return;
|
|
18
23
|
const head=connected&&inbox?inbox.head():null;
|
|
19
|
-
const value={host,sessionId:expected.sessionId,lifetime:expected.lifetime,connected:!!active(),pending_id:head?.state==='pending'?head.message_id:null};
|
|
24
|
+
const value={host,sessionId:expected.sessionId,lifetime:expected.lifetime,connected:!!active(),pending_id:head?.state==='pending'?head.message_id:null,setup_state:approvalSession?.status?.state??null,setup_connected:approvalSession?.status?.connected===true};
|
|
20
25
|
const key=JSON.stringify(value),now=Date.now();
|
|
21
26
|
if(key===lastNotice&&now-lastNoticeAt<2000)return;
|
|
22
27
|
writePrivateJson(join(directory,'notice.json'),{...value,updated_at:now});lastNotice=key;lastNoticeAt=now;
|
|
@@ -25,7 +30,7 @@ function monitorAttention(){
|
|
|
25
30
|
try{const s=readPrivateJson(join(directory,'monitor-state.json'));if(s.sessionId===expected.sessionId&&s.lifetime===expected.lifetime&&s.pending_id===(inbox?.head()?.message_id??null)&&Date.now()-s.updated_at>=0&&Date.now()-s.updated_at<5000)return {attention_required:s.attention_required===true,rate_limited:s.rate_limited===true,next_retry_at:s.next_retry_at};}catch{}
|
|
26
31
|
return {state:'not_observed'};
|
|
27
32
|
}
|
|
28
|
-
async function stop(closeSession){connected=false;status();await runtime?.stop({closeSession});inbox?.close();runtime=null;inbox=null;tools=null;}
|
|
33
|
+
async function stop(closeSession){connected=false;status();connectionAbort?.abort();await toolScope?.close();await recoveryPromise?.catch(()=>{});recoveryPromise=null;await runtime?.stop({closeSession});inbox?.close();runtime=null;inbox=null;tools=null;}
|
|
29
34
|
try {
|
|
30
35
|
const root=dirname(dirname(fileURLToPath(import.meta.url)));verifyReference(join(root,'vendor','psst'));
|
|
31
36
|
const [{Inbox,newSessionIdentity},{BrokerClient},{AdapterRuntime},{createTools},{createCooperativeInbox},{serveStdio}]=await Promise.all([
|
|
@@ -40,11 +45,12 @@ try {
|
|
|
40
45
|
try{const b=readBinding(candidate);if(b.host.pid===host.pid&&verifyBinding(candidate,b)){directory=candidate;expected=b;return true;}}catch{}
|
|
41
46
|
}return false;
|
|
42
47
|
}
|
|
43
|
-
async function connect(config){
|
|
44
|
-
config=
|
|
48
|
+
async function connect(config,{allowSavedOrigin=false,validIntent=()=>true}={}){
|
|
49
|
+
config=refreshEnrollment(config);if(!allowSavedOrigin)config=enforceConfiguredBroker(config);
|
|
45
50
|
if(connecting)return failure('connection_busy');
|
|
46
51
|
if(!confirm())return failure('session_binding_unavailable');
|
|
47
52
|
if(connected)return connectedConfig.brokerUrl===config.brokerUrl&&connectedConfig.tokenFile===config.tokenFile&&connectedConfig.enrollment?.credentialId===config.enrollment?.credentialId?tools.broker_identity():failure('connection_conflict');
|
|
53
|
+
if(runtime&&!connected)await stop(false);
|
|
48
54
|
connecting=true;disabled=false;const epoch=generation;
|
|
49
55
|
try{
|
|
50
56
|
const journal=privateDirectory(join(directory,journalName(expected.lifetime,config)));
|
|
@@ -55,24 +61,29 @@ try {
|
|
|
55
61
|
const identityFile=join(journal,'identity.json');
|
|
56
62
|
try{writeFileSync(identityFile,JSON.stringify(newSessionIdentity('claude-code'))+'\n',{mode:0o600,flag:'wx'});}catch(e){if(e.code!=='EEXIST')throw e;}
|
|
57
63
|
const identity=readPrivateJson(identityFile);inbox=new Inbox({directory:journal,identity});
|
|
64
|
+
connectionAbort=new AbortController();toolScope=createToolScope();
|
|
58
65
|
const client=new BrokerClient({url:config.brokerUrl,tokenFile:config.tokenFile,caFile:config.caFile,insecureDevelopment:config.insecureDevelopment===true,inbox});
|
|
59
|
-
runtime=new AdapterRuntime({client,inbox,verifySession:bound,isAlive:bound,onDiagnostic:({code})=>process.stderr.write(`psst_${code}\n`)});
|
|
66
|
+
runtime=new AdapterRuntime({client,inbox,verifySession:bound,isAlive:bound,onDiagnostic:({code})=>{if(['unauthenticated','forbidden','session_closed'].includes(code)){connected=false;status();}process.stderr.write(`psst_${code}\n`);}});
|
|
60
67
|
tools={...createTools({client,inbox,observeMetadata:()=>({model:bindingModel(readBinding(directory))})}),...createCooperativeInbox({inbox,isActive:active}),
|
|
61
|
-
broker_identity:async()=>({credential_id:config.enrollment?.credentialId??null,credential_expires_at:config.enrollment?.expiresAt??null,monitor_attention:monitorAttention(),client_id:identity.client_id,client_type:'claude-code',native_session_id:expected.sessionId,local_mode:'
|
|
68
|
+
broker_identity:async()=>({credential_id:config.enrollment?.credentialId??null,credential_expires_at:config.enrollment?.expiresAt??null,monitor_attention:monitorAttention(),client_id:identity.client_id,client_type:'claude-code',native_session_id:expected.sessionId,mode:'automatic_required',local_mode:'native_monitor_delivery',readiness:monitorAttention().state==='not_observed'?'blocked_monitor_not_observed':'native_wakeup_acceptance_unverified',automatic_wakeup:'bounded_monitor_when_interactive',native_acceptance_recorded:false})};
|
|
62
69
|
await runtime.start({name:config.name,project:config.project??null,model:bindingModel(expected)});
|
|
63
|
-
if(!bound()||generation!==epoch||disabled)throw Error('session_changed');
|
|
70
|
+
if(!bound()||generation!==epoch||disabled||!validIntent())throw Error('session_changed');
|
|
71
|
+
recoveryPromise=client.recoverOutgoing({limit:10,signal:AbortSignal.any([connectionAbort.signal,AbortSignal.timeout(5000)])});
|
|
72
|
+
await recoveryPromise.catch(()=>{});
|
|
73
|
+
if(!bound()||generation!==epoch||disabled||!validIntent())throw Error('session_changed');connected=true;connectedConfig=config;disabled=false;writePrivateJson(join(directory,'activation.json'),{sessionId:expected.sessionId,lifetime:expected.lifetime,disabled:false,config});status();return tools.broker_identity();
|
|
64
74
|
}catch{await stop(!bound());return failure(generation!==epoch?'connection_cancelled':'connection_failed','unknown');}finally{connecting=false;}
|
|
65
75
|
}
|
|
66
|
-
const names=['broker_list_clients','broker_send','broker_update_metadata','broker_message_status','broker_receive','broker_acknowledge','broker_identity'];
|
|
67
|
-
const guarded=Object.fromEntries(names.map(name=>[name,async args=>{
|
|
76
|
+
const names=['broker_list_clients','broker_send','broker_update_metadata','broker_message_status','broker_receive','broker_acknowledge','broker_identity','broker_sent_history','broker_message_content','broker_resend','broker_cancel'];
|
|
77
|
+
const guarded=Object.fromEntries(names.map(name=>[name,async (args,context)=>{
|
|
68
78
|
if(!active())return failure(expected?'not_connected':'session_binding_unavailable');
|
|
69
|
-
const result=await tools[name](args);status();return result;
|
|
79
|
+
return toolScope.run(async signal=>{const result=await tools[name](args,{...context,signal});status();return result;},context?.signal);
|
|
70
80
|
}]));
|
|
71
81
|
let pairing=false;
|
|
72
|
-
const explicitConnect=async({connection_file}
|
|
82
|
+
const explicitConnect=async({connection_file})=>{
|
|
83
|
+
approvalSession?.cancel();
|
|
73
84
|
// Explicit user intent supersedes deferred startup resume, including failed attempts.
|
|
74
85
|
autoAttempted=true;
|
|
75
|
-
if(pairing||connecting)return failure('connection_busy');
|
|
86
|
+
if(pairing||connecting||approvalTransition)return failure('connection_busy');
|
|
76
87
|
if(!confirm())return failure('session_binding_unavailable');
|
|
77
88
|
try{
|
|
78
89
|
let selectionRevision=null,selectionReadable=true;const selectionEpoch=generation;
|
|
@@ -81,13 +92,12 @@ try {
|
|
|
81
92
|
if(!selectionReadable)selection={saved:false,reason:'installation_selection_invalid_or_unreadable'};
|
|
82
93
|
else try{selection=saveSelection(file,selectionRevision,installationRoot(),connectedConfig);}catch{selection={saved:false,reason:'installation_selection_unavailable'};}
|
|
83
94
|
return {...result,installation_selection:selection};};
|
|
84
|
-
const input=
|
|
85
|
-
if(
|
|
95
|
+
const input=readPrivateJson(connection_file);
|
|
96
|
+
if(input?.format==='psst-pairing-v1'){
|
|
86
97
|
if(connected)return failure('disconnect_before_pairing');
|
|
87
98
|
pairing=true;const epoch=generation;
|
|
88
99
|
const options={directory:join(installationRoot(),'enrollment'),clientType:'claude-code',brokerUrl:configuredBrokerUrl()??undefined};
|
|
89
|
-
|
|
90
|
-
const result=nativeSetup?await connectPairingSecret({...options,pairingSecret:process.env.PSST_PLUGIN_PAIRING_SECRET}):await connectPairingFile({...options,pairingFile:connection_file});
|
|
100
|
+
const result=await connectPairingFile({...options,pairingFile:connection_file});
|
|
91
101
|
if(epoch!==generation||!bound())return failure('connection_cancelled','unknown');
|
|
92
102
|
return remember(await connect(readPluginConfig(result.connectionFile)),result.connectionFile);
|
|
93
103
|
}
|
|
@@ -96,10 +106,67 @@ try {
|
|
|
96
106
|
finally{pairing=false;}
|
|
97
107
|
};
|
|
98
108
|
guarded.broker_connect=args=>explicitConnect(args);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
const setupOptions=brokerUrl=>({brokerUrl,directory:installationRoot(),clientType:'claude-code',label:'Claude Code installation'});
|
|
110
|
+
const updateSetup=state=>{
|
|
111
|
+
if(!setupContext||!state.requestId)return;
|
|
112
|
+
try{
|
|
113
|
+
const latest=readSetupContext(),nextState=state.connected?'applied':state.state;
|
|
114
|
+
if(latest?.broker_url===setupContext.broker_url&&latest.selection_revision===setupContext.selection_revision&&latest.request_id===state.requestId){
|
|
115
|
+
setupContext=latest;if(latest.state===nextState||latest.state==='applied')return status();
|
|
116
|
+
}
|
|
117
|
+
setupContext=saveSetupContext({...setupContext,request_id:state.requestId,state:nextState},setupContext.revision);
|
|
118
|
+
}
|
|
119
|
+
catch{/* A competing explicit choice owns selection; never overwrite it. */}
|
|
120
|
+
status();
|
|
121
|
+
};
|
|
122
|
+
approvalSession=new ApprovalSession({beginApproval,pollApproval,readApprovalState,isCurrent:()=>bound()&&!disabled,
|
|
123
|
+
onState:updateSetup,onApproved:async(result,current)=>{
|
|
124
|
+
const context=readSetupContext();
|
|
125
|
+
if(!current()||!context||context.broker_url!==setupContext?.broker_url||context.selection_revision!==setupContext?.selection_revision||(context.revision!==setupContext?.revision&&context.request_id!==result.requestId)||(context.request_id&&context.request_id!==result.requestId))return {connected:false,error:'setup_context_changed'};
|
|
126
|
+
const selection=readSelection();if((selection?.revision??null)!==context.selection_revision&&selection?.connection_file!==result.connectionFile)return {connected:false,error:'installation_selection_changed'};
|
|
127
|
+
const config=readPluginConfig(result.connectionFile),previous=connected?connectedConfig:null;
|
|
128
|
+
if(new URL(config.brokerUrl).origin!==context.broker_url)return {connected:false,error:'configured_broker_mismatch'};
|
|
129
|
+
const transaction={phase:'stopping'};approvalTransition=transaction;
|
|
130
|
+
const restoreAllowed=()=>approvalTransition===transaction&&bound()&&!disabled;
|
|
131
|
+
return switchApproved({previous,stop:()=>stop(false),
|
|
132
|
+
connect:(old,{restore,isCurrent})=>connect(restore?old:config,{allowSavedOrigin:restore,validIntent:isCurrent}),
|
|
133
|
+
select:()=>current()?saveSelection(result.connectionFile,context.selection_revision,installationRoot(),connectedConfig):{saved:false,reason:'connection_cancelled'},
|
|
134
|
+
isCurrent:()=>current()&&approvalTransition===transaction,canRestore:restoreAllowed,
|
|
135
|
+
onPhase:phase=>{
|
|
136
|
+
if(approvalTransition!==transaction)return;
|
|
137
|
+
transaction.phase=phase;
|
|
138
|
+
if(phase==='finished'){
|
|
139
|
+
if(!connected&&expected)writePrivateJson(join(directory,'activation.json'),{sessionId:expected.sessionId,lifetime:expected.lifetime,disabled:true});
|
|
140
|
+
approvalTransition=null;
|
|
141
|
+
}
|
|
142
|
+
}});
|
|
143
|
+
}});
|
|
144
|
+
guarded.broker_setup=async({replace=false}={})=>{
|
|
145
|
+
autoAttempted=true;if(pairing||connecting||approvalSession.busy||approvalTransition)return failure('connection_busy');
|
|
146
|
+
if(!confirm())return failure('session_binding_unavailable');
|
|
147
|
+
try{
|
|
148
|
+
const brokerUrl=configuredBrokerUrl();if(!brokerUrl)return failure('native_setup_configuration_required');
|
|
149
|
+
const old=readSetupContext();
|
|
150
|
+
if(old&&old.broker_url===brokerUrl&&['requested','pending','approved'].includes(old.state)&&!replace){setupContext=old;}
|
|
151
|
+
else setupContext=saveSetupContext({broker_url:brokerUrl,selection_revision:readSelection()?.revision??null,request_id:null,state:'requested'},old?.revision??null);
|
|
152
|
+
disabled=false;writePrivateJson(join(directory,'setup-activation.json'),{sessionId:expected.sessionId,lifetime:expected.lifetime,cancelled:false});
|
|
153
|
+
return await approvalSession.start(setupOptions(brokerUrl),{replace});
|
|
154
|
+
}catch(error){
|
|
155
|
+
// A failed POST may have durable local candidate state. Later startup may GET
|
|
156
|
+
// it, but never invent a new request or retry POST automatically.
|
|
157
|
+
return {...failure(error.retryable?'approval_temporarily_unavailable':'approval_setup_blocked',error.effect??'not_applied'),...(Number.isFinite(error.retryAfterMs)?{retry_after_ms:Math.max(1000,Math.min(300000,error.retryAfterMs))}:{})};
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const deliveryReadiness=()=>({mode:'automatic_required',readiness:monitorAttention().state==='not_observed'?'blocked_monitor_not_observed':'native_wakeup_acceptance_unverified',manual_receive:'diagnostic_test_only'});
|
|
161
|
+
guarded.broker_setup_status=async()=>{
|
|
162
|
+
if(approvalSession.status.state!=='not_started')return {...approvalSession.status,...deliveryReadiness()};
|
|
163
|
+
try{const context=readSetupContext();if(!context)return {state:active()?'connected':'setup_required',connected:!!active(),...deliveryReadiness()};
|
|
164
|
+
const saved=readApprovalState(setupOptions(context.broker_url));return saved?{state:saved.state,requestId:saved.requestId,requestExpiresAt:saved.requestExpiresAt,connected:!!active(),...deliveryReadiness()}:{state:'setup_required',connected:!!active(),...deliveryReadiness()};
|
|
165
|
+
}catch{return {state:'blocked',error:'invalid_saved_setup',connected:!!active(),...deliveryReadiness()};}
|
|
166
|
+
};
|
|
167
|
+
guarded.broker_setup_cancel=async()=>{approvalSession.cancel();if(approvalTransition&&['stopping','attaching','selecting'].includes(approvalTransition.phase))generation++;if(expected)writePrivateJson(join(directory,'setup-activation.json'),{sessionId:expected.sessionId,lifetime:expected.lifetime,cancelled:true});return {state:'cancelled',existing_connection_retained:!!active()};};
|
|
168
|
+
guarded.broker_disconnect=async()=>{approvalSession.cancel();autoAttempted=true;generation++;disabled=true;connected=false;if(expected)writePrivateJson(join(directory,'activation.json'),{sessionId:expected.sessionId,lifetime:expected.lifetime,disabled:true});status();if(connecting)void runtime?.stop({closeSession:false}).catch(()=>{});else await stop(false);return{connected:false,pending_connection_cancelled:connecting||pairing,pending_retained:true,native_acceptance_recorded:false};};
|
|
169
|
+
server=serveStdio({tools:guarded,onClose:async()=>{const closeSession=!bound();approvalSession?.cancel();generation++;live=false;await stop(closeSession);},onDiagnostic:()=>{}});
|
|
103
170
|
// Stay available while disconnected. Never retry failed enrollment/auth indefinitely.
|
|
104
171
|
timer=setInterval(()=>{void(async()=>{
|
|
105
172
|
if(expected&&!bound()){live=false;await server.close();return;}
|
|
@@ -107,13 +174,22 @@ try {
|
|
|
107
174
|
if(expected&&!autoAttempted&&!disabled){
|
|
108
175
|
autoAttempted=true;let config=legacyConfig;
|
|
109
176
|
try{const a=readPrivateJson(join(directory,'activation.json'));if(a.sessionId===expected.sessionId&&a.lifetime===expected.lifetime){disabled=a.disabled===true;config=a.config??null;}}catch{}
|
|
177
|
+
let resumeOptions=null;
|
|
178
|
+
try{
|
|
179
|
+
const saved=readSetupContext(),cancel=(()=>{try{return readPrivateJson(join(directory,'setup-activation.json'));}catch{return null;}})();
|
|
180
|
+
if(saved&&['requested','pending','approved'].includes(saved.state)&&!(cancel?.sessionId===expected.sessionId&&cancel?.lifetime===expected.lifetime&&cancel?.cancelled)&&!disabled){
|
|
181
|
+
const local=readApprovalState(setupOptions(saved.broker_url));
|
|
182
|
+
if(local&&(!saved.request_id||saved.request_id===local.requestId)){setupContext=saved;resumeOptions=setupOptions(saved.broker_url);}
|
|
183
|
+
}
|
|
184
|
+
}catch{process.stderr.write('psst_setup_resume_unavailable: existing request state is invalid; no new request created.\n');}
|
|
110
185
|
if(config&&!disabled){
|
|
111
|
-
try{const result=await connect(config);if(result?.error)process.stderr.write('psst_autoconnect_unavailable: saved installation could not attach; use explicit connect after correcting configuration.\n');}
|
|
186
|
+
try{const result=await connect(config,{allowSavedOrigin:!!resumeOptions&&setupContext.selection_revision===(readSelection()?.revision??null)});if(result?.error)process.stderr.write('psst_autoconnect_unavailable: saved installation could not attach; use explicit connect after correcting configuration.\n');}
|
|
112
187
|
catch{process.stderr.write('psst_autoconnect_unavailable: saved installation or configured broker is invalid; use explicit connect after correcting configuration.\n');}
|
|
113
188
|
}
|
|
189
|
+
if(resumeOptions&&!disabled)await approvalSession.resume(resumeOptions);
|
|
114
190
|
}
|
|
115
191
|
if(active())status();
|
|
116
192
|
})().catch(()=>{live=false;void server.close();});},250);
|
|
117
193
|
await server.done;
|
|
118
194
|
}catch{process.stderr.write('psst_mcp_unavailable: native session/configuration check failed; no message was acknowledged.\n');process.exitCode=1;}
|
|
119
|
-
finally{if(timer)clearInterval(timer);live=false;await server?.close();await runtime?.stop({closeSession:true});inbox?.close();}
|
|
195
|
+
finally{approvalSession?.cancel();if(timer)clearInterval(timer);live=false;await server?.close();await runtime?.stop({closeSession:true});inbox?.close();}
|
package/bin/plugin-monitor.mjs
CHANGED
|
@@ -11,7 +11,7 @@ try {
|
|
|
11
11
|
if(!Number.isSafeInteger(pid)||pid<1||!sessionId)throw Error('native_monitor_context_missing');
|
|
12
12
|
const host=processIdentity(pid),candidates=[];
|
|
13
13
|
try{candidates.push(hostDirectory(readPluginConfig(),host).directory);}catch{}
|
|
14
|
-
candidates.push(hostDirectory(nativeStateConfig(),host).directory);
|
|
14
|
+
candidates.push(hostDirectory(nativeStateConfig(process.env,host,sessionId),host).directory);
|
|
15
15
|
let expected,directory;
|
|
16
16
|
// Native always-start monitors may precede the SessionStart hook. Wait only for
|
|
17
17
|
// this exact native owner/session; never create a binding or infer another one.
|
|
@@ -25,7 +25,7 @@ try {
|
|
|
25
25
|
try{
|
|
26
26
|
const status=readPrivateJson(join(directory,'notice.json'));
|
|
27
27
|
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)){
|
|
28
|
-
process.stdout.write('Psst: authorized collaboration mailbox has pending AGENT data. Call broker_receive; after reading explicitly broker_acknowledge its exact receipt. No human permission or action completion is implied.'+(budget.snapshot.attention_required?' Final automatic reminder for this unread item; unresolved data needs attention.':'')+'\n');
|
|
28
|
+
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');
|
|
29
29
|
}
|
|
30
30
|
writePrivateJson(join(directory,'monitor-state.json'),{sessionId,lifetime:expected.lifetime,pending_id:status.pending_id,...budget.snapshot,updated_at:Date.now()});
|
|
31
31
|
}catch{/* Disconnected/not yet configured: silent, no broker access. */}
|
|
@@ -37,7 +37,7 @@ or task-success signal. Native permissions remain in force.
|
|
|
37
37
|
|
|
38
38
|
## Limits and failure behavior
|
|
39
39
|
|
|
40
|
-
- Interactive Claude Code 2.1.310+ only. Headless/SDK has
|
|
40
|
+
- Interactive Claude Code 2.1.310+ only. Headless/SDK has diagnostic-only read tools but no
|
|
41
41
|
plugin monitor. There is no hidden Channels flag or permission-relay capability.
|
|
42
42
|
- Monitors run at plugin-hook trust level, **outside Claude's internal shell-tool
|
|
43
43
|
sandbox**, but inside the existing Docker/container boundary. Respect workspace
|
|
@@ -46,7 +46,7 @@ or task-success signal. Native permissions remain in force.
|
|
|
46
46
|
after 120 seconds. At most 30 notices in a rolling ten-minute window, which refills; new messages
|
|
47
47
|
are not globally silenced forever. A head still unread after its third notice
|
|
48
48
|
enters explicit attention-required state (visible through broker_identity);
|
|
49
|
-
it remains durable for
|
|
49
|
+
it remains durable for an explicitly requested diagnostic read, with no idle model polling loop.
|
|
50
50
|
- Native output batching/rate limits can suppress notices. Writing stdout is not
|
|
51
51
|
proof of native transcript storage, model reading, or action completion. Only
|
|
52
52
|
an explicit matching `broker_acknowledge` advances the local FIFO.
|
|
@@ -54,7 +54,7 @@ or task-success signal. Native permissions remain in force.
|
|
|
54
54
|
pending messages. The monitor remains an inert local watcher for explicit
|
|
55
55
|
reconnect because Claude arms this monitor once per session. Reconnecting does
|
|
56
56
|
not reset the three-reminder budget for the same unread head: it still needs
|
|
57
|
-
|
|
57
|
+
explicit diagnostic receive/acknowledgment; a new head gets its own budget. A native binding
|
|
58
58
|
change/SessionEnd terminates it. Broker presence may remain until lease expiry.
|
|
59
59
|
- Failed connect is reported without raw configuration/token errors and is not
|
|
60
60
|
retried forever. The user can explicitly retry the same private file.
|
|
@@ -82,3 +82,14 @@ and official docs establish the supported path, not an authenticated success cla
|
|
|
82
82
|
References:
|
|
83
83
|
- https://code.claude.com/docs/en/plugins/components#monitors
|
|
84
84
|
- https://code.claude.com/docs/en/plugins/manifest-reference#monitors
|
|
85
|
+
|
|
86
|
+
## Production requirement (2026-11-01)
|
|
87
|
+
|
|
88
|
+
Automatic receive is required for production. Headless/manual cooperative receive
|
|
89
|
+
is retained only for explicit diagnostics/tests and must not be reported as a
|
|
90
|
+
working production fallback. Missing native MCP or monitor capability is a blocked
|
|
91
|
+
state; preserve pending messages without auto-acknowledging or bypassing policy.
|
|
92
|
+
Receive now renders a compact AI-agent header and escaped message JSON. Wire
|
|
93
|
+
origin/authority validation and normal native permissions are unchanged. Explicit
|
|
94
|
+
read ACK needs only the exact message ID; its already exposed same-inbox receipt
|
|
95
|
+
is resolved internally, with supplied legacy tokens still validated strictly.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Unreleased candidate verification — 2026-11-01
|
|
2
|
+
|
|
3
|
+
Related issue: #11; not closed by this partial delivery.
|
|
4
|
+
|
|
5
|
+
## Checked locally
|
|
6
|
+
|
|
7
|
+
Syntax + 70/70 unit tests passed; integration 22 passed and one unavailable container gate skipped. Unit aggregate includes package validation.
|
|
8
|
+
|
|
9
|
+
Synthetic native parent + real TLS/broker/monitor notices, same-host MCP restart and 30 independent native hosts.
|
|
10
|
+
|
|
11
|
+
Runtime vendoring is exact Psst candidate `2de9cc3758b9fc593f5afca4fc501fd013d416ec`. Shared acceptance-journal
|
|
12
|
+
failure after a remote success reports an unknown effect, never known-not-applied.
|
|
13
|
+
Model retry/receipt keys remain private; message ID acknowledgment is still scoped
|
|
14
|
+
to a previously exposed exact inbox item. Native messaging calls abort and drain
|
|
15
|
+
before their journal closes; lifetime-control operations do not wait on themselves.
|
|
16
|
+
|
|
17
|
+
## Not established
|
|
18
|
+
|
|
19
|
+
Managed plugin-MCP suppression and authenticated native idle/busy/monitor acceptance remain external gates. Preflight is diagnostic, not a supported policy bypass or complete durable source-upgrade solution.
|
|
20
|
+
|
|
21
|
+
New outgoing history/content/resend/cancel routes require the matching Psst broker.
|
|
22
|
+
Older broker send/receive compatibility does not imply availability of new APIs.
|
|
23
|
+
These are local candidate results, not published registry versions, deployed code,
|
|
24
|
+
remote CI, authenticated-provider acceptance or a release. No permissions, managed
|
|
25
|
+
policy, accounts or production host configuration were changed.
|