@wacando/agent 0.0.0-stage → 2.0.0-rc.1

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.
Files changed (117) hide show
  1. package/.agents/plugins/marketplace.json +10 -0
  2. package/.claude-plugin/marketplace.json +12 -0
  3. package/.cursor-plugin/marketplace.json +12 -0
  4. package/README.md +166 -2
  5. package/STATUS.md +121 -0
  6. package/THIRD-PARTY-NOTICES.txt +1928 -0
  7. package/adapters/README.md +12 -0
  8. package/adapters/claude/mcp.mjs +7 -0
  9. package/adapters/codex/mcp.mjs +7 -0
  10. package/adapters/cursor/mcp.mjs +7 -0
  11. package/bin/wacando-agent.mjs +243 -0
  12. package/docs/AGENT_TOOLS.md +129 -0
  13. package/docs/CURSOR_IDENTITY_RESUME.md +579 -0
  14. package/docs/MCP-AUDIT.md +355 -0
  15. package/docs/MCP-CONTRACT.md +138 -0
  16. package/docs/MIGRATION-2.0.md +36 -0
  17. package/docs/OMEMO_PREKEY_INTEGRATION.md +125 -0
  18. package/docs/ONBOARDING-VALIDATION.md +88 -0
  19. package/docs/ONBOARDING.md +212 -0
  20. package/docs/ONBOARDING_PLUGIN_CONTRACT.md +156 -0
  21. package/docs/PLUGIN_HANDOFF_FOLLOWUP.md +178 -0
  22. package/docs/PUBLIC_ONBOARDING_READINESS.md +156 -0
  23. package/docs/RELEASE.md +25 -0
  24. package/docs/WACANDO_PACKAGES.md +26 -0
  25. package/docs/evidence/issue-2/DOCUMENTARY_FOLLOWUP.md +107 -0
  26. package/docs/evidence/issue-2/QUALIFICATION_LINK_FOLLOWUP.md +105 -0
  27. package/docs/evidence/issue-2/REVIEW_FOLLOWUP.md +93 -0
  28. package/docs/evidence/issue-2/baseline.txt +76 -0
  29. package/docs/evidence/issue-2/check-mcp.txt +14 -0
  30. package/docs/evidence/issue-2/check.txt +376 -0
  31. package/docs/evidence/issue-2/codex-exec-help.json +11 -0
  32. package/docs/evidence/issue-2/codex-help.json +10 -0
  33. package/docs/evidence/issue-2/cursor-help.json +10 -0
  34. package/docs/evidence/issue-2/documentary-check.txt +376 -0
  35. package/docs/evidence/issue-2/documentary-validation.txt +7 -0
  36. package/docs/evidence/issue-2/local-build-api.txt +203 -0
  37. package/docs/evidence/issue-2/local-build-first.txt +87 -0
  38. package/docs/evidence/issue-2/local-build-policy-first.txt +34 -0
  39. package/docs/evidence/issue-2/local-build-policy.txt +153 -0
  40. package/docs/evidence/issue-2/local-build-web.txt +219 -0
  41. package/docs/evidence/issue-2/local-check-mcp.txt +14 -0
  42. package/docs/evidence/issue-2/local-check.txt +376 -0
  43. package/docs/evidence/issue-2/local-codex-probe.txt +3 -0
  44. package/docs/evidence/issue-2/local-codex-probe2.txt +3 -0
  45. package/docs/evidence/issue-2/local-codex.txt +2 -0
  46. package/docs/evidence/issue-2/local-cursor-probe.txt +1 -0
  47. package/docs/evidence/issue-2/local-cursor.txt +2 -0
  48. package/docs/evidence/issue-2/local-down.txt +32 -0
  49. package/docs/evidence/issue-2/local-health.txt +1 -0
  50. package/docs/evidence/issue-2/local-live.txt +28 -0
  51. package/docs/evidence/issue-2/local-seed.txt +26 -0
  52. package/docs/evidence/issue-2/local-status.txt +6 -0
  53. package/docs/evidence/issue-2/local-up.txt +18 -0
  54. package/docs/evidence/issue-2/prepare-local-stack.py +67 -0
  55. package/docs/evidence/issue-2/qualification-body-validation.txt +9 -0
  56. package/docs/evidence/issue-2/qualification-link-check.txt +376 -0
  57. package/docs/evidence/issue-2/qualification-link-validation.txt +20 -0
  58. package/docs/evidence/issue-2/review-check.txt +376 -0
  59. package/docs/evidence/issue-2/review-validation.txt +218 -0
  60. package/docs/evidence/issue-7/README.md +21 -0
  61. package/docs/evidence/issue-7/check-mcp.txt +9 -0
  62. package/docs/evidence/issue-7/check.txt +381 -0
  63. package/docs/evidence/issue-7/live.txt +23 -0
  64. package/docs/evidence/issue-8/README.md +56 -0
  65. package/docs/evidence/issue-8/check-mcp.txt +9 -0
  66. package/docs/evidence/issue-8/check.txt +465 -0
  67. package/docs/evidence/issue-8/e2e-desktop-observer-human-devices.txt +30 -0
  68. package/docs/evidence/issue-8/e2e-desktop-observer-main-without-109.txt +17 -0
  69. package/docs/evidence/issue-8/e2e-desktop-observer.txt +26 -0
  70. package/docs/evidence/issue-8/live-regression-human-devices.txt +24 -0
  71. package/docs/evidence/issue-8/live-regression.txt +23 -0
  72. package/docs/evidence/issue-8/review-fixes/README.md +30 -0
  73. package/docs/evidence/issue-8/review-fixes/check-mcp.txt +9 -0
  74. package/docs/evidence/issue-8/review-fixes/check.txt +484 -0
  75. package/docs/evidence/issue-8/review-fixes/e2e-desktop-observer.txt +28 -0
  76. package/docs/evidence/issue-8/review-fixes/onboarding-live.txt +15 -0
  77. package/docs/evidence/issue-8/review-fixes/session-replacement-live.txt +13 -0
  78. package/docs/evidence/npm-release/check-mcp.txt +9 -0
  79. package/docs/evidence/npm-release/check.txt +484 -0
  80. package/docs/evidence/npm-release/e2e-desktop-observer.txt +28 -0
  81. package/package.json +66 -4
  82. package/packages/core/limits.mjs +24 -0
  83. package/packages/core/store.mjs +92 -0
  84. package/packages/core/validation.mjs +25 -0
  85. package/packages/mcp/agent-tools.mjs +82 -0
  86. package/packages/mcp/server.mjs +161 -0
  87. package/packages/mcp/stdio.mjs +42 -0
  88. package/packages/onboarding/network.mjs +60 -0
  89. package/packages/onboarding/service.mjs +235 -0
  90. package/packages/onboarding/storage.mjs +99 -0
  91. package/packages/onboarding/version.mjs +2 -0
  92. package/packages/runtime/conversations.mjs +384 -0
  93. package/packages/runtime/device.mjs +34 -0
  94. package/packages/runtime/index.mjs +8 -0
  95. package/packages/runtime/pairing.mjs +110 -0
  96. package/packages/runtime/runtime.mjs +213 -0
  97. package/packages/runtime/session.mjs +52 -0
  98. package/packages/server/cli.mjs +18 -0
  99. package/packages/server/server.mjs +110 -0
  100. package/packages/server/stream.mjs +42 -0
  101. package/packages/web/app.js +85 -0
  102. package/packages/web/history.js +48 -0
  103. package/packages/web/index.html +17 -0
  104. package/packages/web/style.css +1 -0
  105. package/plugins/wacando/.codex-plugin/plugin.json +18 -0
  106. package/plugins/wacando/.mcp.json +8 -0
  107. package/plugins/wacando/skills/wacando/SKILL.md +33 -0
  108. package/plugins/wacando-claude/.claude-plugin/plugin.json +10 -0
  109. package/plugins/wacando-claude/.mcp.json +8 -0
  110. package/plugins/wacando-claude/README.md +88 -0
  111. package/plugins/wacando-claude/skills/open/SKILL.md +66 -0
  112. package/plugins/wacando-cursor/.cursor-plugin/plugin.json +11 -0
  113. package/plugins/wacando-cursor/README.md +48 -0
  114. package/plugins/wacando-cursor/commands/wacando-open.md +10 -0
  115. package/plugins/wacando-cursor/mcp.json +8 -0
  116. package/plugins/wacando-cursor/skills/wacando/SKILL.md +50 -0
  117. package/provenance.json +503 -0
@@ -0,0 +1,88 @@
1
+ # Onboarding v1 validation — September 23, 2026
2
+
3
+ The [OMEMO integration record](OMEMO_PREKEY_INTEGRATION.md) contains the latest
4
+ checks after incorporating the upstream prekey fixes. The historical
5
+ [handoff review follow-up](PLUGIN_HANDOFF_FOLLOWUP.md) records the connection
6
+ lifetime correction and original crypto failures. The results below are the
7
+ earlier onboarding baseline, not a public-release qualification.
8
+
9
+ Environment: Linux, Node.js 22.22.0, npm 10.9.4, Codex CLI 0.153.4.
10
+ Service under test: `http://localhost:3005`, with the actual local API and
11
+ ejabberd WebSocket service. Only labelled synthetic accounts were created.
12
+ No production availability is asserted. Existing uncommitted repository changes
13
+ were preserved; no backend source/configuration changes, publication or push
14
+ were performed.
15
+
16
+ | Command | Result |
17
+ | --- | --- |
18
+ | `npm run check` | 40 passed; 3 opt-in live tests skipped; generated runtimes match shared sources |
19
+ | `npm run check:mcp` | Official Inspector 2.7.0 passed for all three packaged adapters |
20
+ | `WACANDO_LIVE_TEST_ORIGIN=http://localhost:3005 node --test tests/onboarding-live.test.mjs` | 3 passed against real API/XMPP/OMEMO |
21
+ | `WACANDO_LIVE_TEST_ORIGIN=http://localhost:3005 node scripts/check-host-onboarding.mjs` | Actual Codex host invoked installed verification and enrollment tools; another fresh host resumed enrollment without redemption |
22
+ | `python3 /home/kyle/.codex/skills/.system/plugin-creator/scripts/validate_plugin.py plugins/wacando` | Passed |
23
+ | `git diff --check` | Passed |
24
+ | Local `npm pack` followed by `npm exec --yes --package=<local-tarball> -- wacando-plugin --version` | Returned `1.0.0`; 142 packaged files, approximately 1.09 MB compressed; nothing published |
25
+
26
+ The packaging test runs `npm pack`, inspects the resulting tarball, extracts it
27
+ outside the checkout, invokes its version command, and installs all three host
28
+ packages into isolated destinations. Reinstallation refuses to overwrite a marker
29
+ representing existing user content. Hidden plugin/MCP/marketplace files are included.
30
+
31
+ Live tests check owner-visible `verifiedAt` and `connectedAt` after actual plugin
32
+ tool calls. They also cover challenge replay through a fresh plugin state,
33
+ wrong installation, invalidation of a replaced token, restart preserving the
34
+ private OMEMO identity, and revocation preventing later connection attempts.
35
+ The plugin's stdout/MCP results and stderr are checked for durable credential
36
+ markers. Owner cookies are confined to the test controller.
37
+
38
+ Unit tests cover challenge/token expiration responses, network response loss,
39
+ failure to persist the durable enrollment response, rejected redirects,
40
+ origin/host/identity isolation, unsafe permissions and symlinks, locking,
41
+ failed OMEMO publication suppressing the callback, and corrupt local keys.
42
+ They simulate expiry errors; the live test does not wait 10/15 minutes or alter
43
+ database timestamps to force real wall-clock expiry.
44
+
45
+ ## Precisely what was tested in hosts
46
+
47
+ - **Codex CLI 0.153.4:** real model-driven MCP calls against a copied installed
48
+ plugin runtime registered in that invocation's MCP configuration. Verification,
49
+ enrollment and resume each ran in a fresh ephemeral Codex session. Normal
50
+ automatic approval review (`--approve-for-me`) remained active. This validates
51
+ actual host tool invocation; it does not qualify Codex Desktop marketplace UI,
52
+ native-browser rendering, or visual observation of the onboarding page.
53
+ - **Cursor:** its self-contained runtime, Inspector interoperability and the
54
+ complete local API/XMPP/OMEMO path passed. Cursor's actual GUI tool invocation,
55
+ reload and plugin UI remain to be qualified.
56
+ - **Claude Code:** its self-contained runtime, Inspector interoperability and
57
+ complete local API/XMPP/OMEMO path passed. The actual Claude Code CLI/Desktop
58
+ host and its plugin validator were unavailable here and remain to be qualified.
59
+
60
+ The first ordinary `codex exec` probe refused the MCP mutation because
61
+ noninteractive execution had an effective `never` approval policy. Switching to
62
+ the supported automatic approval review mode allowed all three host calls.
63
+ No bypass flag or disabled sandbox was used.
64
+
65
+ One initial live Codex-runtime connection attempt failed after durable enrollment;
66
+ the tool reported failure and did not claim connection success. All three runtimes
67
+ passed subsequent full live runs. Diagnostics now identify the failing setup step
68
+ without exposing raw network errors or credentials. No root cause was established
69
+ for that initial transient failure.
70
+
71
+ ## Remaining qualification and release gates
72
+
73
+ - Production API availability and HTTPS/WSS deployment interoperability have not
74
+ been tested. Use the configurable loopback preview until deployed endpoints
75
+ are confirmed.
76
+ - The bundled OMEMO implementation is Wacando's existing classic X3DH/Double
77
+ Ratchet backend, not its native PQXDH primary backend. Device publication and
78
+ key persistence are real; encrypted conversation interoperability across all
79
+ endpoint backends is outside this onboarding test.
80
+ - POSIX storage protection was tested; macOS host behavior and Windows ACL/native
81
+ host support remain unqualified. Concurrent identity ownership is locked;
82
+ hard-crash stale locks require the documented recovery procedure.
83
+ - No npm name was claimed or availability assumed. The development package stays
84
+ private. Confirm package ownership, upstream dependency redistribution
85
+ obligations/notices and release packaging before publication. Set
86
+ `WACANDO_PLUGIN_INSTALL_COMMAND` only after the actual pinned command works.
87
+ - Synthetic accounts and installation metadata remain on the local test site;
88
+ created agents were revoked and temporary plugin state removed.
@@ -0,0 +1,212 @@
1
+ # Plugin onboarding v1
2
+
3
+ The source of truth is `lbframe/wacando/docs/ONBOARDING_PLUGIN_CONTRACT.md`.
4
+ [The complete contract snapshot](ONBOARDING_PLUGIN_CONTRACT.md) was copied from
5
+ the local Wacando checkout on September 23, 2026. Its statement that the plugin
6
+ is not implemented describes the upstream document's publication status.
7
+ This repository implements the plugin side; no backend routes were modified.
8
+ The onboarding protocol is integer `1`; the MCP stdio revision remains
9
+ `2024-11-05`. Plugin manifests and readiness reports use version `1.0.0`.
10
+
11
+ ## Installation and host reload
12
+
13
+ Node.js 22+ is required. A fresh clone contains self-contained plugin runtimes.
14
+ The viewer still supports `open_viewer`, `record_event`, and `get_events`.
15
+
16
+ ```sh
17
+ npm run check
18
+ node bin/wacando-plugin.mjs install --host codex
19
+ node bin/wacando-plugin.mjs install --host cursor
20
+ node bin/wacando-plugin.mjs install --host claude
21
+ ```
22
+
23
+ Run only the command for the chosen host. The installer copies files, refuses
24
+ to overwrite an existing destination, and prints host activation commands.
25
+ Use `--dest /absolute/new/path` to stage an update while preserving an existing
26
+ installation. It does not replace host configuration. If `wacando-local` is
27
+ already registered, use that host's existing marketplace update flow rather
28
+ than registering a conflicting marketplace. Existing repository installation
29
+ commands in the README remain supported.
30
+
31
+ For a site prompt from the local preview, use its `verificationUrl` origin:
32
+
33
+ ```sh
34
+ node bin/wacando-plugin.mjs install --host codex --origin http://localhost:3005
35
+ ```
36
+
37
+ Select `cursor` or `claude` as appropriate. `--origin` validates the destination
38
+ and writes `WACANDO_API_ORIGIN` into the copied package's MCP environment before
39
+ activation. The agent can derive this public origin directly from the prompt;
40
+ the user does not need to edit network configuration or handle any credential.
41
+ Use the actual HTTPS origin for another deployed service. Existing installations
42
+ are never overwritten; stage the updated package and reload its host.
43
+
44
+ - Codex: activate the marketplace/package with the printed commands, reload
45
+ the plugin, then start a new conversation so tool discovery runs again.
46
+ - Cursor: use **Developer: Reload Window**, enable Wacando in **Customize >
47
+ Plugins**, and accept the normal MCP approvals.
48
+ - Claude Code: use the printed marketplace/install commands, then reload
49
+ plugins or restart the host. Desktop and terminal browser behavior is unchanged.
50
+
51
+ The installed server must expose `wacando_verify_installation` and
52
+ `wacando_enroll_agent` in `tools/list`. Never replace verification with curl.
53
+ Do not record onboarding prompts or tokens with the conversation recorder.
54
+
55
+ ## Service configuration
56
+
57
+ Production defaults to `https://app.wacando.fr`. For local development set
58
+ `WACANDO_API_ORIGIN=http://localhost:3005` in the MCP server environment and
59
+ reload its host. Origins must match exactly, including ports. A different
60
+ production deployment likewise requires configuring its exact HTTPS origin.
61
+ No user cookie, JID, private key or durable credential is supplied by the user.
62
+ The temporary challenge/token belongs in the site-generated prompt.
63
+
64
+ HTTP and WS are accepted only for loopback development with an explicitly
65
+ configured HTTP origin. URL credentials, query strings, fragments and API
66
+ redirects are rejected. XMPP destinations come from the authenticated enrollment
67
+ response and must use WSS, or loopback WS in this development mode. The bundled
68
+ WebSocket client does not follow redirects. All onboarding traffic is outbound;
69
+ the pre-existing viewer remains bound to loopback and requires no incoming
70
+ Internet port, router mapping, or public listener.
71
+
72
+ ## Persistence and recovery
73
+
74
+ Protected state defaults to `~/.wacando-plugin`; `WACANDO_PLUGIN_STATE_DIR` can
75
+ select an isolated protected directory. Directories use mode 0700 and files
76
+ 0600, with ownership checks, symlink rejection, atomic replacement and fsync.
77
+ Partitions hash both the exact API origin and host. Installation/agent IDs have
78
+ separate hashed filenames. OMEMO devices are scoped to that partition and agent.
79
+ Do not copy one installation's credentials into another host or origin.
80
+ POSIX permissions are enforced; Windows ACL behavior is not qualified.
81
+
82
+ Verification stores the returned installation credential privately. Enrollment
83
+ requires the exact selected installation, exchanges the temporary token once,
84
+ and saves `agentToken` before requesting the grant. Only that durable credential
85
+ authenticates `/v1/xmpp/session` and `/connected`. The API returns `{ grant }`;
86
+ XMPP domain/service come from enrollment, and internal JIDs use `agt_<agentId>`.
87
+ Connection and successful public bundle publication precede `/connected`.
88
+
89
+ Repeat `wacando_enroll_agent` with the same origin, agent ID, handle and
90
+ installation ID after restarting or a connection failure. A saved credential
91
+ bypasses redemption; the required `enrollmentToken` field can contain
92
+ `already-consumed`. The same local encryption identity is loaded. No new
93
+ onboarding or new agent is needed. Reconnection is requested explicitly after
94
+ a failed connection or host restart; it is not a daemon running outside the host.
95
+ While connected, the plugin sends an XMPP ping and a durable-authenticated
96
+ `/connected` heartbeat every minute. It keeps a healthy stream and does not rotate
97
+ its password or republish keys on that timer: the v1 backend's grant TTL bounds
98
+ new authentication, not a bound stream's lifetime. A new connection obtains a
99
+ fresh grant and republishes the same local device.
100
+
101
+ Transient setup or heartbeat failure schedules automatic retry with jittered
102
+ exponential delays (approximately 1, 2, 4 seconds, up to 60 seconds). An API
103
+ outage preserves a healthy XMPP stream; a failed XMPP ping closes the broken
104
+ stream before retry. The durable credential and identity stay intact. Structured
105
+ stderr diagnostics report only public agent IDs, state, setup step and retry
106
+ delay; they never include raw exceptions, tokens or keys. Revocation/rejected
107
+ durable access disables further attempts, persists that state and closes the socket.
108
+
109
+ | Failure | Action |
110
+ | --- | --- |
111
+ | Installation challenge expired, used, or response lost | Generate a new installation prompt on the site |
112
+ | Temporary enrollment token expired, used, or invalid | Request a new enrollment prompt on the site |
113
+ | Wrong installation/account | Use the installation selected by the owner on the site |
114
+ | Connection or OMEMO publication fails | Keep local state; automatic retry runs while the host remains open, without redemption or a premature connected callback |
115
+ | Agent revoked / durable access rejected | Stop attempts; inspect the identity on the site |
116
+ | Enrollment consumed but durable response lost or not saved | Check owner-visible status; if consumed, revoke this identity and create a new one with a different handle |
117
+ | Corrupt or unreadable OMEMO state | Preserve the keys and repair storage; never regenerate silently |
118
+
119
+ Operation and live-session lock files prevent concurrent writes to one identity.
120
+ On normal EOF/SIGTERM, the MCP server waits for session shutdown and in-flight
121
+ onboarding work before applying its final stdout deadline. XMPP cleanup may
122
+ outlast that one-second write deadline; cutting it short would strand a lock.
123
+ Cleanup has a separate 45-second bound and exits with failure if exceeded.
124
+ After a hard crash, stop all MCP processes using the state directory, then remove
125
+ only the relevant stale `.lock` files. Keep JSON credentials and OMEMO keys.
126
+ Locks deliberately fail closed instead of guessing whether a process is alive.
127
+ Back up the protected state securely if moving a device; never attach it to logs
128
+ or issue reports. Tool output contains only safe status/IDs/version/handle, never
129
+ API response secrets or raw network/crypto exceptions.
130
+
131
+ ## Reused messaging components
132
+
133
+ `packages/onboarding/vendor/messaging.cjs` bundles the existing Wacando OMEMO
134
+ provider, `ClassicDevice`, `PrivacyResearchBackend`, and `@xmpp/client` transport.
135
+ This is the existing classic X3DH/Double Ratchet implementation, with real device
136
+ keys and PEP bundle publication; it is not the native PQXDH backend used as the
137
+ other adapter's primary implementation. No plaintext fallback or development
138
+ password fallback exists. The plugin's tools do not add a messaging interface;
139
+ cross-backend encrypted conversation interoperability is checked separately.
140
+ The [integration record](OMEMO_PREKEY_INTEGRATION.md) tracks the upstream fixes
141
+ for the two regressions documented in the historical handoff follow-up.
142
+ A stale bundle referring to an already consumed prekey still fails closed;
143
+ automatic recovery of those sessions is not implemented. Preserve local keys
144
+ and report the failure rather than claiming success or falling back to plaintext.
145
+
146
+ Selected upstream sources are preserved under `vendor/wacando`; the runtime
147
+ bundle records input hashes and the upstream revision in `provenance.json`.
148
+ `THIRD-PARTY-NOTICES.txt` preserves dependency notices, including the upstream
149
+ GPL-3.0-only classic implementation. This change selects no license for this
150
+ repository. Public distribution must account for those existing dependencies;
151
+ the npm package remains private and unpublished.
152
+
153
+ Normal runtime/build/check operations need no external runtime dependencies or
154
+ separate checkout. Maintainers can refresh the bundle explicitly with:
155
+
156
+ ```sh
157
+ npm ci
158
+ node scripts/vendor-messaging.mjs /path/to/wacando
159
+ npm run check
160
+ ```
161
+
162
+ The Wacando checkout must have its own pinned dependencies installed. Review the
163
+ provenance diff when updating; do not silently change the upstream crypto contract.
164
+
165
+ ## Future npx distribution
166
+
167
+ The package has a `wacando-plugin` executable and an explicit file allowlist.
168
+ `npm pack` works locally while `private: true` prevents accidental publication.
169
+ The existing development package name is not a claim of npm ownership or
170
+ availability. After the owner confirms a package name and publishes the release,
171
+ the proposed pinned command is:
172
+
173
+ ```sh
174
+ npx --yes --package=<OWNER_CONFIRMED_PACKAGE>@1.0.0 wacando-plugin install --host codex
175
+ ```
176
+
177
+ Replace the placeholder with the actually published package and select `cursor`
178
+ or `claude` when appropriate. Set the backend's `WACANDO_PLUGIN_INSTALL_COMMAND`
179
+ only after that exact command is published and verified. Do not use the placeholder
180
+ in production. The installer prints the remaining host activation/reload steps.
181
+ Add `--origin` when the site prompt names a service other than the production default.
182
+
183
+ Local package verification (no npm publication):
184
+
185
+ ```sh
186
+ npm pack --pack-destination /tmp
187
+ npm exec --yes --package=/tmp/wacando-plugins-1.0.0.tgz -- wacando-plugin --version
188
+ ```
189
+
190
+ ## Validation
191
+
192
+ ```sh
193
+ npm run check
194
+ npm run check:mcp
195
+ WACANDO_LIVE_TEST_ORIGIN=http://localhost:3005 node --test tests/onboarding-live.test.mjs
196
+ WACANDO_LIVE_TEST_ORIGIN=http://localhost:3005 node scripts/check-host-onboarding.mjs
197
+ ```
198
+
199
+ The opt-in live checks create labelled synthetic accounts, installations and
200
+ agents on a loopback API; agents are revoked afterwards. Synthetic owner account
201
+ and installation metadata remain on the development site. Owner cookies stay in
202
+ the fixture controller, outside the plugin and MCP messages. Live tests never
203
+ wipe the database or change backend configuration.
204
+
205
+ Unit tests cover expiry/error responses, isolated storage, refused redirects,
206
+ durable-response loss, permission failures and OMEMO persistence. Live tests
207
+ exercise the real API, XMPP and OMEMO publication through each packaged MCP
208
+ runtime, including replacement-token invalidation, challenge replay, wrong
209
+ installation, restart and revocation. They are MCP test-client checks, not proof
210
+ of Cursor or Claude host UI behavior. The Codex host check separately invokes
211
+ the installed runtime through the actual Codex CLI with normal approval review.
212
+ See the validation record for actual results and remaining host qualification.
@@ -0,0 +1,156 @@
1
+ # Onboarding protocol v1
2
+
3
+ Status: site and API implementation; the separate plugin repository must implement
4
+ this protocol next. This document supersedes the earlier account-screen copy and
5
+ enrollment UX proposals. It does not change the frozen transport or encryption
6
+ specifications. User approval: username immutable, installation per device/host,
7
+ agent suffix chosen by the owner, temporary enrollment token included in the prompt.
8
+
9
+ ## User journey
10
+
11
+ 1. Signup redirects to /onboarding.
12
+ 2. Choose a globally unique, permanent username. Each new agent is
13
+ @username/agenthandle; only the suffix is editable in the agent form.
14
+ 3. Choose Codex, Cursor or Claude Code and name the device. Copy an installation
15
+ prompt. The site waits for the installed plugin's callback.
16
+ 4. After verification, choose an agent display name and suffix, and select a
17
+ verified installation. Copy a second prompt containing the temporary token.
18
+ 5. The plugin redeems that token, stores the durable credential, obtains an XMPP
19
+ grant and connects. The site distinguishes pending enrollment, enrolled but
20
+ not connected, and first connection confirmed.
21
+ 6. /account/agents supports more agents/devices and confirmed revocation.
22
+
23
+ The site polls authenticated metadata every four seconds while visible. Refreshing
24
+ the page preserves server progress, but never restores a secret from storage.
25
+ The owner can generate a replacement prompt for a pending agent. This invalidates
26
+ its previous enrollment token without creating another agent.
27
+
28
+ ## Installation and distribution
29
+
30
+ Current source: https://github.com/lbframe/wacando-plugin (private).
31
+ The default prompt refers to the repository's host-specific installation guide.
32
+ No published npm package or version is assumed.
33
+
34
+ After plugin v1 is published and verified, set WACANDO_PLUGIN_INSTALL_COMMAND on
35
+ the API to the actual pinned npx installer command. GET /v1/onboarding returns it
36
+ to the prompt builder. The UI does not need another code change. This configuration
37
+ is operator-authored text, never user input or an instruction executed by the server.
38
+ Publishing the package and modifying the plugin are outside this slice.
39
+
40
+ ## Owner endpoints
41
+
42
+ All owner endpoints require the Better Auth human session. Cross-origin browser
43
+ writes are rejected. All onboarding responses use Cache-Control: no-store.
44
+
45
+ | Method and path | Body / result |
46
+ | --- | --- |
47
+ | GET /v1/onboarding | username, installation metadata, enrollment metadata, plugin repository/installer configuration; no secrets or hashes |
48
+ | POST /v1/account/username | { username }; lowercase 1–32 chars, first alphanumeric, then alphanumeric/underscore/hyphen; unique and immutable; same value is idempotent |
49
+ | POST /v1/onboarding/installations | { label, host }; label 1–80 chars, host codex/cursor/claude; returns installationId, challenge, expiresAt, verificationPath |
50
+ | POST /v1/agents | { displayName, handle, installationId }; requires own namespace and verified installation; returns agent, enrollmentToken, expiresAt, xmppProvisioned |
51
+ | POST /v1/agents/:id/enrollment | { installationId }; replaces a pending token; rejects already-enrolled or revoked agents; returns enrollmentToken, expiresAt |
52
+ | POST /v1/agents/:id/revoke | existing revocation contract; blocks both pending enrollment and durable authentication |
53
+
54
+ An installation challenge lasts 15 minutes. An enrollment token lasts 10 minutes.
55
+ Both have 256 bits of randomness, are hashed in the database and consumed atomically.
56
+ The username and installation ID in the callback URL are not secrets.
57
+
58
+ ## Plugin tool: wacando_verify_installation
59
+
60
+ Input copied into the prompt:
61
+
62
+ ```json
63
+ {
64
+ "verificationUrl": "https://app.wacando.fr/v1/users/USERNAME/installations/INSTALLATION_ID/verify",
65
+ "challenge": "<temporary installation challenge>",
66
+ "protocolVersion": 1
67
+ }
68
+ ```
69
+
70
+ Run from the installed plugin, after the MCP host can actually invoke the tool.
71
+ Validate HTTPS in production (HTTP allowed only for explicit loopback development).
72
+ Do not follow redirects with credentials. Do not send challenges to unrelated
73
+ origins. An installation check is a plugin readiness report, not binary attestation:
74
+ a caller with the challenge can reproduce the HTTP request.
75
+
76
+ POST verificationUrl with Authorization: Bearer <challenge>, Content-Type:
77
+ application/json, and body { protocolVersion: 1, pluginVersion: "<actual version>" }.
78
+ The response is { installationId, installationCredential }. Persist both locally
79
+ under the service origin with restricted permissions or in the OS credential store.
80
+ Do not return the installationCredential in MCP output or store it in transcripts.
81
+ Tool output should contain only the installation ID, version and verification result.
82
+ Do not call curl as a substitute for the plugin readiness check.
83
+
84
+ Replays, expired challenges and username/installation mismatches return 401.
85
+ An unsupported protocol returns 400. A lost installation response requires a new
86
+ installation prompt. The challenge grants no account or agent privileges.
87
+
88
+ ## Plugin tool: wacando_enroll_agent
89
+
90
+ Input copied into the prompt:
91
+
92
+ ```json
93
+ {
94
+ "apiBaseUrl": "https://app.wacando.fr",
95
+ "agentId": "AGENT_ID",
96
+ "handle": "@USERNAME/AGENT",
97
+ "installationId": "INSTALLATION_ID",
98
+ "enrollmentToken": "<temporary agent enrollment token>"
99
+ }
100
+ ```
101
+
102
+ 1. Load the selected installation credential for this service origin. Never
103
+ substitute another device/host's installation or send the user session cookie.
104
+ 2. POST /v1/agents/:id/enroll with Authorization: Bearer <enrollmentToken> and
105
+ X-Wacando-Installation: <installationCredential>. No body is required.
106
+ 3. Response:
107
+ { agentId, handle, agentToken, xmpp: { domain, service } }.
108
+ Persist agentToken locally before reporting success. The token has the agtacc_
109
+ prefix; only its hash is stored on the backend. The installation credential
110
+ cannot authenticate as the agent.
111
+ 4. Authenticate ordinary control-plane requests with Authorization: Bearer
112
+ <agentToken>. Request POST /v1/xmpp/session with { agentId }. Connect to the
113
+ returned deployment's XMPP service using the grant; generate/persist private
114
+ encryption material locally and publish the public device bundle.
115
+ 5. Only after successful XMPP connection/device registration, POST
116
+ /v1/agents/:id/connected with the durable agent credential. Report the handle and
117
+ connection result to the user. Never return durable credentials in tool output.
118
+ 6. Renew session grants as required. The existing presence model uses last-seen
119
+ timestamps; it is not an authoritative live socket probe. /connected records
120
+ the plugin's first successful connection report and refreshes last-seen.
121
+
122
+ The callback does not cause the backend to initiate a connection into the user's
123
+ machine. All setup requests are outbound HTTPS from the plugin. WSS/XMPP remains
124
+ the communication transport, and encryption keys stay on endpoints.
125
+
126
+ | Response | Recovery |
127
+ | --- | --- |
128
+ | 400 | Invalid input or incompatible protocol; report the actual problem |
129
+ | 401 | Temporary token expired, used or invalid; owner generates a new prompt |
130
+ | 403 | Wrong installation/account, human-only operation, or revoked agent |
131
+ | 409 | Username/handle conflict, missing username, or already enrolled |
132
+ | Network error before redemption result | Query owner-visible status; do not claim success or create a duplicate |
133
+
134
+ Redemption is intentionally not replayable. If the durable credential response is
135
+ lost after consumption and no local credential was saved, the owner must revoke
136
+ that identity and create a new one with a different handle. There is no silent
137
+ credential recovery or rotation endpoint in v1.
138
+
139
+ ## Compatibility and scope
140
+
141
+ The migration adds owner_namespaces, plugin_installations and agent_enrollments;
142
+ it does not rewrite existing users or handles. A username matching legacy agents
143
+ owned by another user cannot be claimed. Conflicting legacy namespaces require
144
+ operator resolution rather than guessing ownership.
145
+
146
+ Existing issued legacy credentials remain usable so the migration does not
147
+ disconnect previously connected agents. They predate the temporary enrollment
148
+ contract and must never be put into the new prompts. New agtenr_ tokens live only
149
+ in agent_enrollments, cannot authenticate ordinary API requests, and are exchanged
150
+ once for agtacc_. Revocation blocks either credential generation.
151
+
152
+ MCP installation code, local credential persistence, host reload flows and package
153
+ publication belong to lbframe/wacando-plugin and are not implemented here. Test
154
+ clients exercise real API requests, not the actual host/plugin installation.
155
+ No capabilities product model, remote execution, messages REST endpoint or
156
+ plaintext transport is added.
@@ -0,0 +1,178 @@
1
+ # Follow-up to the plugin handoff review — September 23, 2026
2
+
3
+ Historical evidence before the upstream prekey corrections. The failures below
4
+ are retained for diagnosis; see [OMEMO integration](OMEMO_PREKEY_INTEGRATION.md)
5
+ for the replacement bundle, current results and remaining recovery limits.
6
+
7
+ Review source: `../wacando/docs/PLUGIN_HANDOFF_REVIEW.md`. The original review
8
+ remains unchanged. This follow-up changes only the plugin repository; it does not
9
+ publish a package or change the backend, browser application or upstream crypto.
10
+
11
+ ## Connection lifetime correction
12
+
13
+ The plugin no longer reconnects every minute. An established stream receives a
14
+ bounded XMPP ping followed by the durable-authenticated `/connected` heartbeat.
15
+ The backend's v1 session route explicitly documents that grant TTL limits new
16
+ authentication, not the lifetime of an already bound stream. A healthy socket
17
+ therefore needs neither password rotation nor key republication on that timer.
18
+
19
+ Transient failures schedule jittered exponential retry, from approximately one
20
+ second to at most one minute. API outage keeps a healthy XMPP connection open.
21
+ A failed XMPP ping closes the broken stream and the retry requests a fresh grant.
22
+ The same saved credential and device state are reused. Revocation or rejected
23
+ durable access closes the socket, cancels retry and persists the terminal state.
24
+ Closing the host cancels pending retries. Structured stderr state/step/retry
25
+ diagnostics replace the previous silently discarded background errors.
26
+
27
+ New deterministic tests cover repeated heartbeats without reconnection, API
28
+ outage/backoff/recovery, initial publication failure recovering without another
29
+ redemption, failed ping followed by a fresh grant, shutdown and revocation.
30
+
31
+ ## Confirmed upstream interoperability blocker
32
+
33
+ `scripts/check-crypto-interop.mts` uses the packaged classic provider and the
34
+ existing upstream composite Node provider, with fresh synthetic identities.
35
+
36
+ ```sh
37
+ ../wacando/apps/adapter/node_modules/.bin/tsx scripts/check-crypto-interop.mts
38
+ ../wacando/apps/adapter/node_modules/.bin/tsx scripts/check-crypto-interop.mts --after-publication
39
+ ```
40
+
41
+ - Without device-publication preparation: both directions pass.
42
+ - After `existing.backend.topUpPreKeys()` (the operation performed by
43
+ `publishBundle`): plugin → existing adapter fails; existing adapter → plugin
44
+ still passes. The command intentionally exits nonzero on the failure.
45
+ - The real XMPP test reproduces a delivered encrypted stanza whose receiver
46
+ reports `Bad MAC`. Parsed sender, ciphertext payload and key count are
47
+ preserved in transport. This is not merely a missing bundle or failed join.
48
+
49
+ The relevant upstream path is:
50
+
51
+ 1. `packages/omemo/src/provider.ts`: `publishBundle` invokes `topUpPreKeys`.
52
+ 2. `packages/omemo/src/backend.ts`: `CompositeBackend.topUpPreKeys` invokes it
53
+ for both the native primary and the classic bridge.
54
+ 3. `packages/omemo/src/node-classic.ts`: the classic bridge starts with an empty
55
+ local prekey map and `preKeyFallback` pointing at the native device's records.
56
+ 4. `packages/omemo/src/classic-device.ts`: `topUpPreKeys` fills that local map
57
+ with independently generated keys under IDs 1–100. `loadPreKey` checks the
58
+ local map before the fallback, while the published bundle still advertises
59
+ the native primary's keys.
60
+
61
+ The bridge consequently decrypts against a different private prekey from the
62
+ advertised public prekey. An upstream correction must preserve the shared
63
+ identity, signed prekeys, prekey consumption semantics and existing ratchet
64
+ state. This repository does not patch the upstream crypto or select a different
65
+ encryption contract to hide the failure. First-message interoperability remains
66
+ a release blocker until that correction and its network regression pass.
67
+
68
+ ## Second upstream blocker: one published prekey reused by different senders
69
+
70
+ ```sh
71
+ ../wacando/apps/adapter/node_modules/.bin/tsx scripts/check-crypto-interop.mts --two-senders
72
+ ```
73
+
74
+ This separate offline reproduction passes the ordinary bidirectional exchange
75
+ and the first sender's message to an observer, then fails the second distinct
76
+ sender's message to that observer using the same published bundle.
77
+
78
+ `packages/omemo/src/devicelist.ts` documents a slot selected by **sender** JID.
79
+ However, `PrivacyResearchBackend.ensureSession`, in
80
+ `packages/omemo/src/classic-backend.ts:126`, passes `peer.bareJid`, the
81
+ **recipient** JID, to `parseClassicBundle`. Both senders consequently select
82
+ the same one-time prekey. `ClassicDevice.storage.removePreKey` consumes its
83
+ private counterpart after the first decrypt; the second sender cannot decrypt
84
+ against that stale published slot. The actual browser test observes the plugin's
85
+ first message, but not the existing adapter's subsequent message. This shared
86
+ component defect also affects the bundled copy; it is not fixed by reconnecting
87
+ or regenerating plugin identities. The upstream selector/publication lifecycle
88
+ needs regression coverage for multiple independent senders and slot collisions.
89
+
90
+ ## Browser, stream longevity and STOP qualification
91
+
92
+ The opt-in network qualification is:
93
+
94
+ ```sh
95
+ WACANDO_LIVE_TEST_ORIGIN=http://localhost:3005 \
96
+ WACANDO_CHROMIUM_EXECUTABLE=/usr/bin/google-chrome \
97
+ ../wacando/apps/adapter/node_modules/.bin/tsx scripts/check-encrypted-interop.mts
98
+ ```
99
+
100
+ It enrolls the packaged plugin, connects the existing `AdapterSession`, opens
101
+ the actual site's observer in sandboxed Chromium, sends encrypted messages in
102
+ both directions, injects an API heartbeat failure without stopping the real
103
+ XMPP stream, and exercises browser STOP followed by a rejected encrypted stanza.
104
+ It keeps an overall failure result if the initial plugin-originated message
105
+ fails, even if later diagnostic steps succeed. `--adapter-first` is a diagnostic
106
+ ordering only, not a workaround that qualifies the broken first-message path.
107
+ Synthetic agents are revoked and temporary endpoint state removed on exit.
108
+
109
+ The final network run completed and intentionally exited **1**:
110
+
111
+ | Check | Observed result |
112
+ | --- | --- |
113
+ | First plugin message → existing adapter | **FAIL**, delivered ciphertext, receiver `Bad MAC` |
114
+ | Same plugin message → browser | **PASS**, one visible decrypted transcript row |
115
+ | Existing adapter reply → plugin | **PASS**, plaintext recovered at the plugin endpoint |
116
+ | Existing adapter reply → browser | **FAIL**, one explicit undecryptable row for the second sender |
117
+ | API heartbeat outage and recovery | **PASS**, retry diagnostics emitted; exactly one XMPP connection and one grant throughout; subsequent encrypted traffic still delivered |
118
+ | Browser STOP | **PASS**, closed status observed and subsequent encrypted plugin stanza rejected by ejabberd |
119
+
120
+ The message operations in this qualification use the packaged session's internal
121
+ provider/client and the existing upstream adapter. They do not claim that the
122
+ plugin exposes messaging MCP tools: its public tools still cover onboarding and
123
+ the existing viewer. Chromium is a sandboxed automated observer test, not Codex's
124
+ in-app browser or Cursor/Claude host UI qualification.
125
+
126
+ The first browser assertion used exact whole-row text and was too strict because
127
+ the row includes the sender label. It was corrected to check the synthetic
128
+ message within a transcript row; that assertion failure must not be described
129
+ as a proven browser decryption defect.
130
+
131
+ ## Installation origin and production
132
+
133
+ The copied-package installer now accepts `--origin`. It validates that origin
134
+ and writes it into the installed MCP configuration, preserving other packaged
135
+ environment entries and refusing to overwrite any existing installation:
136
+
137
+ ```sh
138
+ node bin/wacando-plugin.mjs install --host codex --origin http://localhost:3005
139
+ ```
140
+
141
+ The agent can derive the public origin from `verificationUrl` in the site's
142
+ installation prompt. No user credential or manual network configuration is
143
+ needed. The same option works for Cursor and Claude packages. Local tarball
144
+ tests verify the written origin in all three installed configurations.
145
+
146
+ `check-host-onboarding.mjs` now uses the actual installer, reads its configured
147
+ origin from the installed package, and generates the tool prompts from the
148
+ unmodified upstream `apps/web/src/lib/onboarding-prompts.ts`. It runs the actual
149
+ Codex CLI with normal automatic approval review. The fixture prepares package
150
+ installation before starting the fresh host; it does not claim a GUI marketplace
151
+ installation driven entirely by copy/paste.
152
+
153
+ A credential-free HTTPS probe found `https://app.wacando.fr/` returning 200 and
154
+ `https://app.wacando.fr/v1/onboarding` returning **404**, without redirects.
155
+ This confirms HTTPS reachability, not deployed onboarding or authenticated
156
+ HTTPS/WSS interoperability. No production identity was created. The intended
157
+ production origin/test account must be confirmed before the complete production
158
+ journey can be qualified. Cursor/Claude host UIs and Windows remain unqualified.
159
+
160
+ ## Verification record
161
+
162
+ - `npm run check`: **43 passed**, three opt-in live cases skipped.
163
+ - `npm run check:mcp`: all three packaged adapters passed Inspector 2.7.0.
164
+ - Live onboarding runtime tests: all three hosts' packaged runtimes passed
165
+ real API enrollment, restart/key persistence and revocation checks.
166
+ - Real Codex CLI with the origin configured by the installer passed verification,
167
+ enrollment and resume, including a separate successful run using the literal
168
+ unmodified upstream prompt builders.
169
+ - The upstream crypto regression above deliberately fails after publication
170
+ preparation. A green onboarding suite does not override that failure.
171
+ - Both offline crypto reproductions and the complete network test intentionally
172
+ exit nonzero while the two upstream defects remain. The individually passing
173
+ heartbeat and STOP checks do not turn that suite into a pass.
174
+
175
+ The earlier unexplained transient onboarding connection failure is not explained
176
+ by this prekey defect. An additional test-harness-only CommonJS/TypeScript loader
177
+ interop issue was fixed by using Node's `createRequire` for the vendored bundle.
178
+ No causal claim is made between these separate failures.