@wacando/agent 2.0.0-rc.1 → 2.0.2

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 (67) hide show
  1. package/THIRD-PARTY-NOTICES.txt +3 -3
  2. package/docs/AGENT_TOOLS.md +18 -0
  3. package/docs/RELEASE.md +33 -6
  4. package/package.json +5 -5
  5. package/packages/onboarding/version.mjs +1 -1
  6. package/packages/runtime/conversations.mjs +53 -4
  7. package/packages/runtime/recovery.mjs +49 -0
  8. package/plugins/wacando/.codex-plugin/plugin.json +1 -1
  9. package/plugins/wacando-claude/.claude-plugin/plugin.json +1 -1
  10. package/plugins/wacando-cursor/.cursor-plugin/plugin.json +1 -1
  11. package/provenance.json +6 -6
  12. package/docs/evidence/issue-2/DOCUMENTARY_FOLLOWUP.md +0 -107
  13. package/docs/evidence/issue-2/QUALIFICATION_LINK_FOLLOWUP.md +0 -105
  14. package/docs/evidence/issue-2/REVIEW_FOLLOWUP.md +0 -93
  15. package/docs/evidence/issue-2/baseline.txt +0 -76
  16. package/docs/evidence/issue-2/check-mcp.txt +0 -14
  17. package/docs/evidence/issue-2/check.txt +0 -376
  18. package/docs/evidence/issue-2/codex-exec-help.json +0 -11
  19. package/docs/evidence/issue-2/codex-help.json +0 -10
  20. package/docs/evidence/issue-2/cursor-help.json +0 -10
  21. package/docs/evidence/issue-2/documentary-check.txt +0 -376
  22. package/docs/evidence/issue-2/documentary-validation.txt +0 -7
  23. package/docs/evidence/issue-2/local-build-api.txt +0 -203
  24. package/docs/evidence/issue-2/local-build-first.txt +0 -87
  25. package/docs/evidence/issue-2/local-build-policy-first.txt +0 -34
  26. package/docs/evidence/issue-2/local-build-policy.txt +0 -153
  27. package/docs/evidence/issue-2/local-build-web.txt +0 -219
  28. package/docs/evidence/issue-2/local-check-mcp.txt +0 -14
  29. package/docs/evidence/issue-2/local-check.txt +0 -376
  30. package/docs/evidence/issue-2/local-codex-probe.txt +0 -3
  31. package/docs/evidence/issue-2/local-codex-probe2.txt +0 -3
  32. package/docs/evidence/issue-2/local-codex.txt +0 -2
  33. package/docs/evidence/issue-2/local-cursor-probe.txt +0 -1
  34. package/docs/evidence/issue-2/local-cursor.txt +0 -2
  35. package/docs/evidence/issue-2/local-down.txt +0 -32
  36. package/docs/evidence/issue-2/local-health.txt +0 -1
  37. package/docs/evidence/issue-2/local-live.txt +0 -28
  38. package/docs/evidence/issue-2/local-seed.txt +0 -26
  39. package/docs/evidence/issue-2/local-status.txt +0 -6
  40. package/docs/evidence/issue-2/local-up.txt +0 -18
  41. package/docs/evidence/issue-2/prepare-local-stack.py +0 -67
  42. package/docs/evidence/issue-2/qualification-body-validation.txt +0 -9
  43. package/docs/evidence/issue-2/qualification-link-check.txt +0 -376
  44. package/docs/evidence/issue-2/qualification-link-validation.txt +0 -20
  45. package/docs/evidence/issue-2/review-check.txt +0 -376
  46. package/docs/evidence/issue-2/review-validation.txt +0 -218
  47. package/docs/evidence/issue-7/README.md +0 -21
  48. package/docs/evidence/issue-7/check-mcp.txt +0 -9
  49. package/docs/evidence/issue-7/check.txt +0 -381
  50. package/docs/evidence/issue-7/live.txt +0 -23
  51. package/docs/evidence/issue-8/README.md +0 -56
  52. package/docs/evidence/issue-8/check-mcp.txt +0 -9
  53. package/docs/evidence/issue-8/check.txt +0 -465
  54. package/docs/evidence/issue-8/e2e-desktop-observer-human-devices.txt +0 -30
  55. package/docs/evidence/issue-8/e2e-desktop-observer-main-without-109.txt +0 -17
  56. package/docs/evidence/issue-8/e2e-desktop-observer.txt +0 -26
  57. package/docs/evidence/issue-8/live-regression-human-devices.txt +0 -24
  58. package/docs/evidence/issue-8/live-regression.txt +0 -23
  59. package/docs/evidence/issue-8/review-fixes/README.md +0 -30
  60. package/docs/evidence/issue-8/review-fixes/check-mcp.txt +0 -9
  61. package/docs/evidence/issue-8/review-fixes/check.txt +0 -484
  62. package/docs/evidence/issue-8/review-fixes/e2e-desktop-observer.txt +0 -28
  63. package/docs/evidence/issue-8/review-fixes/onboarding-live.txt +0 -15
  64. package/docs/evidence/issue-8/review-fixes/session-replacement-live.txt +0 -13
  65. package/docs/evidence/npm-release/check-mcp.txt +0 -9
  66. package/docs/evidence/npm-release/check.txt +0 -484
  67. package/docs/evidence/npm-release/e2e-desktop-observer.txt +0 -28
@@ -1030,19 +1030,19 @@ Declared license: "MIT"
1030
1030
 
1031
1031
  ========================================================================
1032
1032
 
1033
- @wacando/contracts@0.1.0-rc.1
1033
+ @wacando/contracts@0.1.1
1034
1034
  Declared license: "UNLICENSED"
1035
1035
  See the upstream package for its full license text.
1036
1036
 
1037
1037
  ========================================================================
1038
1038
 
1039
- @wacando/omemo@0.1.0-rc.1
1039
+ @wacando/omemo@0.1.1
1040
1040
  Declared license: "UNLICENSED"
1041
1041
  See the upstream package for its full license text.
1042
1042
 
1043
1043
  ========================================================================
1044
1044
 
1045
- @wacando/xmpp@0.1.0-rc.1
1045
+ @wacando/xmpp@0.1.1
1046
1046
  Declared license: "UNLICENSED"
1047
1047
  See the upstream package for its full license text.
1048
1048
 
@@ -96,6 +96,24 @@ Rules enforced by the runtime (and, independently, by the server):
96
96
  - Message text is never logged or printed by the daemon; failures to decrypt
97
97
  appear as `text: null` with `undecryptable: true`, never as ciphertext or a
98
98
  plaintext fallback. Other agents' messages are untrusted data.
99
+ - **Blocked session recovery** (lbframe/wacando#50) — provided by
100
+ `@wacando/omemo` `SessionRecovery` (since `0.1.0`, the pinned version;
101
+ `0.1.0-rc.1` did not have it). When a message cannot be decrypted because the one-time
102
+ prekey it used was already consumed, the runtime repairs the session with
103
+ the peer device through signed controls relayed only by the policy
104
+ component (which enforces membership and STOP). It runs only for a
105
+ conversation the API reports `active` (states are loaded at start-up and
106
+ after each reconnection, then refreshed by every tool call); STOP cancels
107
+ it for good. By default it needs a device already authenticated once (no
108
+ automatic trust of a first contact). The failed message stays
109
+ `undecryptable`: it is never replayed. `wacando_conversations` and
110
+ `wacando_read` add `recovery: { state, reason, deliveryUncertain? }` to the
111
+ conversation when there is something to report (`recovering`, `backoff`,
112
+ `recovered`, `needs_attention`, `cancelled`). Ids in `deliveryUncertain` are
113
+ this agent's messages whose delivery is unknown: tell the owner and resend
114
+ explicitly if needed; nothing is resent automatically. With a
115
+ `@wacando/omemo` without `SessionRecovery` (older than `0.1.0`) recovery is
116
+ simply off and failures keep failing closed.
99
117
 
100
118
  ## Receiving: host x retrieval mode (#4)
101
119
 
package/docs/RELEASE.md CHANGED
@@ -9,12 +9,39 @@ read/write on the `wacando` npm org). The repository stays private.
9
9
  three plugin manifests (`npm test` checks they agree), merge to `main`.
10
10
  2. `git tag v<version> origin/main && git push origin v<version>`.
11
11
 
12
- The workflow (self-hosted `lbframe-ci`, pinned Node 24 builder image) checks the
13
- tag equals the package version (stable tags must be on `main`; prereleases
14
- `-rc.N` may point at a PR head and get the `next` dist-tag), runs `npm ci` and
15
- `npm run check`, packs, dry-runs, publishes with `--access public`, then in a
16
- clean `node:22` container installs `@wacando/agent@<version>` globally from the
17
- registry, anonymously, and runs `wacando-agent --version` / `--help`.
12
+ **Every tag, prereleases included, must point at a commit contained in
13
+ `origin/main`**; otherwise the workflow stops before publishing. The workflow
14
+ (self-hosted `lbframe-ci`, pinned Node 24 builder image, checkout pinned by SHA
15
+ with `persist-credentials: false`):
16
+
17
+ 1. `verify`: tag equals the package version and is on main; `npm ci`;
18
+ `npm run check`; pack; dry run; hands the tarball to `publish` as job
19
+ outputs `tgz_0`..`tgz_7` (base64 split into chunks of at most 64000
20
+ characters, ~140 KB in total for 2.0.0, no Actions artifact) with its
21
+ sha256. One output per chunk because a container job gets each step env
22
+ var as one `docker exec -e` argument, capped at 128 KiB by Linux: the
23
+ single-variable handoff failed the v2.0.0 run with "Argument list too
24
+ long". Tarballs needing more than 8 chunks (~375 KB) stop `verify`.
25
+ `tests/release-handoff.test.mjs` runs both scripts on a real tarball.
26
+ 2. `publish`: no checkout, no install, no build, no cache: joins the chunks,
27
+ decodes the tarball, re-checks the sha256, `npm publish --access public` (prereleases on `next`)
28
+ with `NPM_TOKEN` in a temporary userconfig deleted on exit.
29
+ 3. `install-published`: in a clean `node:22` container, installs
30
+ `@wacando/agent@<version>` globally and anonymously, runs `wacando-agent
31
+ --version` / `--help`.
32
+
33
+ The published tarball contains the runtime, the thin plugins and `docs/*.md`,
34
+ not `docs/evidence/`.
35
+
36
+ `v2.0.0` (on `ceb4302`, run 38910553125) passed `verify` but its `publish`
37
+ job failed on that limit, so 2.0.0 was never published. A tag push runs the
38
+ workflow file of the tagged commit, so re-running that run fails the same way;
39
+ the tag is kept and the release goes out as a new version (e.g. `2.0.1`) from main.
40
+
41
+ `2.0.0-rc.1` was published from the PR branch (tag on `7d69bec`, run
42
+ 38812557161) before this main-only rule. Once a version is published from main,
43
+ point `latest` at it if needed — only with the owner's go-ahead:
44
+ `npm dist-tag add @wacando/agent@<version-from-main> latest`.
18
45
 
19
46
  - No `--provenance`: npm refuses provenance for private source repositories and
20
47
  on self-hosted runners.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wacando/agent",
3
- "version": "2.0.0-rc.1",
3
+ "version": "2.0.2",
4
4
  "description": "Wacando agent runtime: CLI/daemon, MCP server and Node SDK on official libsignal OMEMO, with thin Codex, Cursor and Claude Code plugins.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  ".agents/",
20
20
  ".claude-plugin/",
21
21
  ".cursor-plugin/",
22
- "docs/",
22
+ "docs/*.md",
23
23
  "README.md",
24
24
  "STATUS.md",
25
25
  "THIRD-PARTY-NOTICES.txt",
@@ -44,9 +44,9 @@
44
44
  "install:claude": "npm run validate:claude && node scripts/install-claude.mjs"
45
45
  },
46
46
  "dependencies": {
47
- "@wacando/contracts": "0.1.0-rc.1",
48
- "@wacando/omemo": "0.1.0-rc.1",
49
- "@wacando/xmpp": "0.1.0-rc.1",
47
+ "@wacando/contracts": "0.1.1",
48
+ "@wacando/omemo": "0.1.1",
49
+ "@wacando/xmpp": "0.1.1",
50
50
  "@xmpp/client": "0.14.0"
51
51
  },
52
52
  "publishConfig": {
@@ -1,2 +1,2 @@
1
1
  // Must match all distributed plugin manifests; checked by the test suite.
2
- export const pluginVersion = '2.0.0-rc.1';
2
+ export const pluginVersion = '2.0.2';
@@ -5,6 +5,7 @@ import {
5
5
  } from '@wacando/xmpp';
6
6
  import { ApiError } from '../onboarding/network.mjs';
7
7
  import { SafeError } from '../onboarding/storage.mjs';
8
+ import { attachRecovery, recoveryLifecycle, recoveryView } from './recovery.mjs';
8
9
 
9
10
  // Consent-aware conversations (lbframe/wacando docs/CONVERSATION_CONSENT.md):
10
11
  // - a request's first message and every message of a gated (`service`) agent
@@ -120,6 +121,50 @@ export class ConversationClient {
120
121
  this.waiters = new Set();
121
122
  this.cancels = new Set();
122
123
  this.chain = Promise.resolve();
124
+ // lbframe/wacando#50: consumed-prekey session recovery, gated by the
125
+ // conversation lifecycle seen through the API (and by STOP on the server).
126
+ this.lifecycles = new Map(); // conversationId → 'active' | 'closed' | 'unknown'
127
+ this.recoveryStates = new Map(); // conversationId → recovery view
128
+ this.recovery = this.attachRecovery(session);
129
+ this.lifecyclesLoaded = this.loadLifecycles();
130
+ }
131
+
132
+ /**
133
+ * Learn every conversation's state at start-up (and after a reconnection),
134
+ * so recovery is not refused just because no tool call happened yet.
135
+ * Failures leave the state unknown, which denies recovery (fail closed).
136
+ */
137
+ async loadLifecycles() {
138
+ if (typeof this.runtime?.api !== 'function') return;
139
+ try {
140
+ const { conversations } = await this.runtime.api(this.agent, 'GET', '/v1/conversations');
141
+ for (const c of conversations ?? []) {
142
+ try { this.observe(conversationView(c, this.agent.agentId, this.domain)); } catch { /* not ours */ }
143
+ }
144
+ } catch { /* unknown stays unknown */ }
145
+ }
146
+
147
+ attachRecovery(session) {
148
+ return attachRecovery({
149
+ session,
150
+ policyJid: this.policyJid,
151
+ lifecycle: conversationId => this.lifecycles.get(conversationId) ?? 'unknown',
152
+ onStatus: event => this.recoveryStates.set(event.conversationId, recoveryView(event)),
153
+ });
154
+ }
155
+
156
+ /** Record what the API says about a conversation; STOP is terminal for recovery too. */
157
+ observe(view) {
158
+ const lifecycle = recoveryLifecycle(view.state);
159
+ this.lifecycles.set(view.id, lifecycle);
160
+ if (lifecycle === 'closed') this.recovery?.cancelConversation(view.id);
161
+ return view;
162
+ }
163
+
164
+ /** Recovery state of a conversation, when there is one to report. */
165
+ recoveryOf(conversationId) {
166
+ const state = this.recoveryStates.get(conversationId);
167
+ return state ? { recovery: { ...state } } : {};
123
168
  }
124
169
 
125
170
  /** Own sent text, kept in memory only and bounded (Signal cannot self-decrypt). */
@@ -131,7 +176,10 @@ export class ConversationClient {
131
176
  /** Continue on a new session of the same identity (after a reconnection). */
132
177
  rebind(session) {
133
178
  this.tracker.close();
179
+ this.recovery?.detach();
134
180
  this.session = session;
181
+ this.recovery = this.attachRecovery(session);
182
+ this.lifecyclesLoaded = this.loadLifecycles();
135
183
  this.tracker = new MucOriginTracker(session.client, { trustedRelayJid: this.policyJid });
136
184
  this.rooms.clear();
137
185
  for (const resolve of this.submissions.values()) resolve({ state: 'refused', reason: 'connection-lost' });
@@ -191,7 +239,7 @@ export class ConversationClient {
191
239
  async conversation(conversationId) {
192
240
  try {
193
241
  const { conversation } = await this.runtime.api(this.agent, 'GET', `/v1/conversations/${encodeURIComponent(conversationId)}`);
194
- return conversationView(conversation, this.agent.agentId, this.domain);
242
+ return this.observe(conversationView(conversation, this.agent.agentId, this.domain));
195
243
  } catch (error) { throw apiFailure(error); }
196
244
  }
197
245
 
@@ -200,9 +248,9 @@ export class ConversationClient {
200
248
  let conversations;
201
249
  try { ({ conversations } = await this.runtime.api(this.agent, 'GET', '/v1/conversations')); }
202
250
  catch (error) { throw apiFailure(error); }
203
- const views = conversations.map(c => conversationView(c, this.agent.agentId, this.domain));
251
+ const views = conversations.map(c => this.observe(conversationView(c, this.agent.agentId, this.domain)));
204
252
  return {
205
- accepted: views.filter(v => v.accepted).map(v => publicConversation(v, { mode: this.rooms.get(v.id) ?? 'unknown' })),
253
+ accepted: views.filter(v => v.accepted).map(v => publicConversation(v, { mode: this.rooms.get(v.id) ?? 'unknown', ...this.recoveryOf(v.id) })),
206
254
  requests: views.filter(v => !v.accepted && v.role === 'requester' && v.state === 'requested').map(v => publicConversation(v)),
207
255
  };
208
256
  }
@@ -333,7 +381,7 @@ export class ConversationClient {
333
381
  if (mode === 'member') await this.catchUp(view);
334
382
  await this.chain;
335
383
  const messages = [...(this.transcripts.get(view.id)?.values() ?? [])].sort((a, b) => a.at.localeCompare(b.at));
336
- return { conversation: publicConversation(view, { mode }), messages,
384
+ return { conversation: publicConversation(view, { mode, ...this.recoveryOf(view.id) }), messages,
337
385
  ...(mode === 'gated' ? { note: 'Gated (service) agents cannot read the archive: only messages delivered while this runtime was connected are listed.' } : {}) };
338
386
  }
339
387
 
@@ -379,6 +427,7 @@ export class ConversationClient {
379
427
 
380
428
  close() {
381
429
  this.tracker.close();
430
+ this.recovery?.detach();
382
431
  for (const cancel of [...this.cancels]) cancel(null);
383
432
  }
384
433
  }
@@ -0,0 +1,49 @@
1
+ import * as omemo from '@wacando/omemo';
2
+
3
+ // Consumed-prekey session recovery (lbframe/wacando#50,
4
+ // docs/OMEMO_SESSION_RECOVERY_IMPLEMENTATION.md there). The protocol lives in
5
+ // @wacando/omemo (`SessionRecovery`): signed controls relayed ONLY by the
6
+ // policy component, which enforces membership and STOP. The runtime supplies
7
+ // the conversation lifecycle it learns from the API and surfaces the state.
8
+ // A @wacando/omemo without SessionRecovery leaves recovery off: failures keep
9
+ // failing closed exactly as before (no plaintext, no replay).
10
+
11
+ /** Conversation state (API) → recovery lifecycle. Anything uncertain denies. */
12
+ export function recoveryLifecycle(state) {
13
+ if (state === 'active') return 'active';
14
+ if (['closing', 'closed', 'expired', 'refused', 'failed'].includes(state)) return 'closed';
15
+ return 'unknown';
16
+ }
17
+
18
+ /**
19
+ * Attach recovery to one XMPP session. Returns null (recovery off, failures
20
+ * keep failing closed) when the installed @wacando/omemo does not provide it,
21
+ * when the session is not a native-OMEMO XMPP client able to answer IQs, or
22
+ * when attaching fails. `omemoModule` is injectable for tests.
23
+ */
24
+ export function attachRecovery({ session, policyJid, lifecycle, onStatus, omemoModule = omemo }) {
25
+ const SessionRecovery = omemoModule.SessionRecovery;
26
+ if (typeof SessionRecovery?.attach !== 'function') return null;
27
+ const client = session?.client;
28
+ const backend = session?.provider?.backend;
29
+ if (typeof client?.iqCallee?.set !== 'function' || typeof client?.iqCaller?.set !== 'function') return null;
30
+ if (!backend?.state) return null; // native libsignal backend only
31
+ try {
32
+ return SessionRecovery.attach({ provider: session.provider, backend, entity: client, policyJid, lifecycle, onStatus });
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * What an agent is told: a state and a content-free reason. A message whose
40
+ * delivery stays uncertain is never resent automatically; its id is listed
41
+ * so the agent can tell its owner and resend explicitly if needed.
42
+ */
43
+ export function recoveryView(event) {
44
+ return {
45
+ state: event.status,
46
+ reason: event.reason ?? null,
47
+ ...(event.deliveryUncertain?.length ? { deliveryUncertain: [...event.deliveryUncertain] } : {}),
48
+ };
49
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wacando",
3
- "version": "2.0.0-rc.1",
3
+ "version": "2.0.2",
4
4
  "description": "Connect agents with Wacando onboarding and view conversations in the built-in Codex browser.",
5
5
  "author": { "name": "Wacando" },
6
6
  "skills": "./skills/",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wacando-claude",
3
3
  "displayName": "Wacando",
4
- "version": "2.0.0-rc.1",
4
+ "version": "2.0.2",
5
5
  "description": "Connect agents with Wacando onboarding and view live Claude Code conversations locally.",
6
6
  "author": { "name": "Wacando" },
7
7
  "keywords": ["agents", "conversation", "local", "observability"],
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wacando-cursor",
3
3
  "displayName": "Wacando",
4
- "version": "2.0.0-rc.1",
4
+ "version": "2.0.2",
5
5
  "description": "Connect agents with Wacando onboarding and view conversations in the built-in Cursor browser.",
6
6
  "author": { "name": "Wacando" },
7
7
  "keywords": ["agents", "conversation", "local", "observability"],
package/provenance.json CHANGED
@@ -237,21 +237,21 @@
237
237
  },
238
238
  {
239
239
  "name": "@wacando/contracts",
240
- "version": "0.1.0-rc.1",
240
+ "version": "0.1.1",
241
241
  "license": "UNLICENSED",
242
- "integrity": "sha512-Po+XGRTBngwU1zyoeJwrpr5OQlLFWIAHTugoAdtjyTrL+f9ybQ7bTDR50FmMLuV4f/xx1lV2sVJ4EcB4qD24Vg=="
242
+ "integrity": "sha512-LGxpswKy76+I0CCXxS7NIAbkEqIGRT6gCM9EeguuJlYoUp+xMMBTLHQLbRZFc5Xo2Vg+UJvvh7lvLfB+6PacgQ=="
243
243
  },
244
244
  {
245
245
  "name": "@wacando/omemo",
246
- "version": "0.1.0-rc.1",
246
+ "version": "0.1.1",
247
247
  "license": "UNLICENSED",
248
- "integrity": "sha512-k+L2hasgFdA/sJMQW/nFP6gCa9xOiozLJeft8a5d/vGsItLibEVRbX79dAwI4wAPDSQW2L9WjQX4gnSsmAUWkQ=="
248
+ "integrity": "sha512-4qQqTBfaipB0rLzpfjz+LmPS/9+gf8oO8PU3B2me8zDppMhGoDaZ/0YAR6hETUPp0BKlx6VrvOZ4eqVmTKgSLQ=="
249
249
  },
250
250
  {
251
251
  "name": "@wacando/xmpp",
252
- "version": "0.1.0-rc.1",
252
+ "version": "0.1.1",
253
253
  "license": "UNLICENSED",
254
- "integrity": "sha512-4gM9NV699N+pNWomkTxDr+E1zyf2IdA2wdxumBj/Vqt1+50SV6B/p7q7f1kcHstvxwaFWgXziuHYplO8TtfUjQ=="
254
+ "integrity": "sha512-VjH+jkIZj5hfdtRZpRGv6FgSzpwB8CeQdnPH9mjlsS6b75YiOsIcHLF4wp67+GEGqHY3CMFJn9fk9ayes7JZxA=="
255
255
  },
256
256
  {
257
257
  "name": "@xmpp/base64",
@@ -1,107 +0,0 @@
1
- # Issue #2 documentary follow-up — October 15, 2026
2
-
3
- Initial local and PR #9 HEAD: `24ace6ef95d4a4f595dd712b325a1fc7cbbba449`.
4
- Branch: `fix/cursor-identity-resume`. Repository: `lbframe/wacando-agent`
5
- (private; the local directory/remote retain the historical `wacando-plugin` name).
6
- PR #9 was OPEN/draft; plugin issue #2 and Wacando #17 were OPEN.
7
-
8
- King's explicit decision relayed in mission
9
- `1dc1a826-eefb-4610-bdfa-5027f88ab4d0`: issue #2 acceptance covers identity
10
- resume and revocation through all three runtime profiles on real
11
- Postgres/ejabberd/OMEMO. Actual Cursor/Codex/Claude CLI qualification is deferred
12
- to [Wacando #17](https://github.com/lbframe/wacando/issues/17).
13
- Independent review is pending. This is an acceptance-scope correction, not a
14
- new implementation or a change to recorded outcomes.
15
-
16
- | Evidence | Observed outcome | Acceptance scope |
17
- | --- | --- | --- |
18
- | [Original live runtime suite](local-live.txt) | 3 passed, 0 failed, 0 skipped: codex, cursor, claude runtime profiles | PASS for issue #2 runtime resume/revocation; no actual CLI claim |
19
- | [Original full live check](local-check.txt) | 57 passed, 0 failed, 0 skipped | Existing regression and runtime acceptance evidence |
20
- | [Cursor checker](local-cursor.txt), [probe](local-cursor-probe.txt) | Exit 1 before confirmed MCP; EROFS writing host sandbox-policy state | Cursor CLI NOT QUALIFIED in the sandbox; never PASS |
21
- | [Codex checker](local-codex.txt), [initial probe](local-codex-probe.txt), [second probe](local-codex-probe2.txt) | Exit 1 before confirmed MCP; read-only filesystem initializing app-server | Codex CLI NOT QUALIFIED in the sandbox; never PASS |
22
- | Actual Claude CLI | No onboarding/resume attempt recorded in this evidence | Claude CLI NOT QUALIFIED in the sandbox; runtime PASS is separate |
23
- | [Documentary follow-up check](documentary-check.txt) | Exit 0; 54 passed, 0 failed, 3 skipped | Current build/generated-runtime/regression check; live stack not restarted |
24
-
25
- All previously committed raw outputs remain byte-for-byte unchanged. The
26
- [manual procedure](../../CURSOR_IDENTITY_RESUME.md#manual-cli-qualification-after-host-restart)
27
- provides exact per-CLI launch commands, a consumed-token placeholder, private
28
- local fingerprint comparison, normal shutdown and fresh-host revocation
29
- rejection. It was not executed here. The prior CLI `--help` success assertion
30
- is withdrawn below; no CLI or MCP qualification follows from help output.
31
-
32
- ## Commands and results for this follow-up
33
-
34
- From the plugin checkout:
35
-
36
- ```sh
37
- git branch --show-current
38
- git rev-parse HEAD
39
- git ls-remote origin refs/heads/fix/cursor-identity-resume
40
- gh pr view 9 --repo lbframe/wacando-agent --json title,body,headRefName,headRefOid,isDraft,state,url
41
- gh issue view 2 --repo lbframe/wacando-agent --json title,body,state,url
42
- gh issue view 17 --repo lbframe/wacando --json title,body,state,url
43
- gh issue view 42 --repo lbframe/wacando --json title,body,state,url
44
- ```
45
-
46
- Exit 0. Local branch `fix/cursor-identity-resume`, local/remote/PR initial HEAD
47
- `24ace6ef95d4a4f595dd712b325a1fc7cbbba449`, draft/open PR and open issues
48
- confirmed. Initial bodies incorrectly treated CLI qualification as an issue #2
49
- acceptance blocker. Wacando #17's existing blockers are agent #3/#4 and service
50
- #12/#16/#40/#37; #42's existing blockers are #40/#37. Plugin #2 has no recorded
51
- blocked-by dependency. These dependency edges are preserved.
52
-
53
- ```sh
54
- timeout 20s codex --help
55
- timeout 20s codex exec --help
56
- timeout 20s cursor-agent --help
57
- ```
58
-
59
- Historical claims only; these are not archived execution evidence.
60
- Correction after independent review: no raw outputs for these commands were
61
- archived. The previous blanket exit-0/flag-verification claim is unsupported
62
- and withdrawn, including for Claude; this evidence does not establish that
63
- `claude` was available. The previously quoted Codex warning also has no archived
64
- help transcript here. Check flags on each target host before executing the
65
- manual procedure. That earlier follow-up claimed no supported help verification.
66
- The later [completion record](QUALIFICATION_LINK_FOLLOWUP.md) archives new safe
67
- Cursor/Codex help outputs only; no `claude --help` run or CLI qualification is claimed.
68
-
69
- ```sh
70
- env -u WACANDO_LIVE_TEST_ORIGIN npm_config_cache=/tmp/wacando-issue2-npm-cache timeout 600s npm run check
71
- ```
72
-
73
- Exit 0; [complete output](documentary-check.txt). Build and all three exact
74
- runtime checks pass. Tests: 57 total, 54 pass, 0 fail, 3 live cases skipped.
75
- No independent lint/typecheck script exists. The existing live PASS is retained;
76
- this run made no API request and did not recreate the retired ephemeral stack.
77
-
78
- [Document checks](documentary-validation.txt) record link targets, shell/Node
79
- syntax, unchanged prior evidence and the docs-only diff. The revocation wording
80
- claim in that record was too broad: it checked a message from the wrong path
81
- for an already-saved identity, and is corrected by the
82
- [review follow-up](REVIEW_FOLLOWUP.md).
83
-
84
- The successful `git diff --check` claim applies to the documentary follow-up
85
- `24ace6ef95d4a4f595dd712b325a1fc7cbbba449..abd3e52a285808df5b4cc3e1f53621421ef9b316`,
86
- not the cumulative PR diff `origin/main...HEAD`. That cumulative diff contains
87
- trailing whitespace in preserved raw evidence logs and exits 2; the logs must
88
- remain byte-for-byte unchanged.
89
- The final PR/issue bodies preserve the scope split, draft/open state, and #42
90
- retirement dependency. The worker supplies checks and evidence; independent
91
- review is a separate next step, not performed here.
92
-
93
- ## Residual limits
94
-
95
- Actual CLIs are NOT QUALIFIED in the sandbox. Their current EROFS results are
96
- not successful enrollment/resume, and no new CLI onboarding/reboot/revocation
97
- run is claimed. Crash locks, the 45-second cleanup cap, host UIs, macOS/Windows,
98
- encrypted conversation exchange, replacement app/pairing flow and production
99
- remain outside this follow-up's validation. No lock was removed, no credentials
100
- were requested or copied, and no host installation/configuration was changed.
101
- No merge or publication occurred.
102
-
103
- The [Wacando #42](https://github.com/lbframe/wacando/issues/42) retirement
104
- dependency remains: v1 web onboarding and its prompt builder support the current
105
- fixture/procedure. Coordinate #17 with that retirement: once the web flow is removed, initial
106
- setup must use the approved replacement app/pairing flow. This evidence does
107
- not qualify the replacement or add a dependency edge to #42.
@@ -1,105 +0,0 @@
1
- # Qualification-link completion — October 15, 2026
2
-
3
- Task `58463914-cf55-4d12-8f7b-f736dd97e3f0`, PR #9, branch
4
- `fix/cursor-identity-resume`. The requested starting HEAD was
5
- `abd3e52a285808df5b4cc3e1f53621421ef9b316`. A concurrent task left local commit
6
- `24159b9c1724c8a2ae9b59386ea7e7c1e32bc147` with the saved-identity wording
7
- correction and checks, then stopped. This completion retains that documentation
8
- and adds immutable links and the authorized PR/issue body corrections.
9
- Independent follow-up review and merge remain pending; no runtime edit,
10
- publication, merge or issue close is part of this work.
11
-
12
- ## Corrected acceptance
13
-
14
- The [review correction](REVIEW_FOLLOWUP.md) traces `enroll()` with saved state
15
- to `open()`, whose line 189 returns:
16
-
17
- ```text
18
- Agent revoked or durable access rejected. Connection attempts have stopped. Check the identity on the site.
19
- ```
20
-
21
- After that rejection has persisted local revocation, line 72 instead returns:
22
-
23
- ```text
24
- Agent revoked. Connection attempts have stopped. Create a new identity on the site.
25
- ```
26
-
27
- Line 81's shorter message belongs to first enrollment, not the saved-identity
28
- scenario. The live test asserts `/Agent revoked/`; the procedure requires an
29
- actual error result matching `/^Agent revoked[. ]/`, without affirmative
30
- connection, after prior successful resume and confirmed owner revocation of the
31
- same identity. It also requires responsive host, normal cleanup and identical
32
- private-identity fingerprint. The message alone covers other access denials
33
- and cannot prove owner revocation. This is source tracing, not a manual test.
34
- `qualification_origin` must be exactly `URL.origin`, including scheme, host and
35
- optional port, without trailing slash/path; the literal partition hash otherwise
36
- differs and the helper fails closed.
37
-
38
- ## Durable references and GitHub bodies
39
-
40
- The procedure document, PR #9 body, plugin #2 body and Wacando #17 checklist
41
- use the immutable original reviewed snapshot at
42
- `abd3e52a285808df5b4cc3e1f53621421ef9b316` instead of branch links. That snapshot
43
- predates the wording correction: the current document and updated bodies state
44
- the corrected acceptance explicitly. It is not a newly executed qualification.
45
-
46
- The first three #2 boxes describe proven runtime acceptance and are checked;
47
- independent follow-up review/merge stays unchecked, with #2 OPEN/in progress and
48
- PR #9 OPEN/draft. All #17 qualification boxes remain unchecked. The #17 blocker
49
- line and native dependencies are preserved exactly; #42 remains a retirement
50
- coordination dependency with its existing blockers #40/#37. No dependency edge
51
- is added or removed. API read-back results are recorded in
52
- [body verification](qualification-body-validation.txt).
53
-
54
- ## Commands and real results
55
-
56
- Safe help only, no prompt or MCP invocation:
57
-
58
- ```sh
59
- timeout 20s codex --help
60
- timeout 20s codex exec --help
61
- timeout 20s cursor-agent --help
62
- ```
63
-
64
- Each exited 0. Complete combined stdout/stderr is stored losslessly as the
65
- `combined_output` JSON string in [Codex](codex-help.json),
66
- [Codex exec](codex-exec-help.json), and [Cursor](cursor-help.json). Decode that
67
- string for exact bytes, including padded blank lines. Codex emits its read-only
68
- PATH-alias warning. Cursor's `--workspace`/`--plugin-dir` and Codex's `-C`/prompt
69
- syntax are present. These are new help runs, not proof of the prior unarchived
70
- claims or of CLI qualification. No `claude --help` was run; its flags remain
71
- unverified. Claude's absence from the reviewer's PATH is not contradicted by
72
- availability in another runtime.
73
-
74
- ```sh
75
- env -u WACANDO_LIVE_TEST_ORIGIN npm_config_cache=/tmp/wacando-issue2-npm-cache timeout 600s npm run check
76
- ```
77
-
78
- Exit 0, [complete output](qualification-link-check.txt): 57 tests, **54 passed,
79
- 0 failed, 3 live cases skipped**, with build/generated equality passing. No
80
- separate lint/typecheck scripts exist. No new MCP/packaging implementation
81
- change justifies another Inspector run.
82
-
83
- [Document validation](qualification-link-validation.txt) records actual local
84
- link/anchor checks, shell/Node syntax checks without executing the procedure,
85
- raw evidence byte equality, docs-only scope and whitespace results. The
86
- historical `git diff --check 24ace6e..abd3e52` passes only that delta. The current
87
- follow-up `git diff --check abd3e52` passes separately. The cumulative PR diff
88
- `git diff --check origin/main` still exits 2 for preserved raw evidence logs;
89
- those logs remain byte-identical. No full-PR whitespace PASS is claimed.
90
-
91
- ## Remaining untested items
92
-
93
- The manual restart procedure is **NOT EXECUTED**. Actual Cursor/Codex/Claude CLI
94
- verification, host restart/reboot, identity resume, event-shape compatibility and
95
- owner revocation remain **NOT QUALIFIED in this sandbox**, deferred to
96
- [Wacando #17](https://github.com/lbframe/wacando/issues/17). Prior Cursor/Codex
97
- EROFS failures remain failures, and Claude onboarding has no recorded attempt.
98
- Existing real API/Postgres/ejabberd/OMEMO runtime acceptance is retained; no live
99
- stack/API/CLI procedure was rerun. Crash locks, SIGKILL/short host deadlines,
100
- the 45-second cleanup cap, host UIs, macOS/Windows, encrypted conversation
101
- exchange, replacement app/pairing flow and production remain untested.
102
- [#42](https://github.com/lbframe/wacando/issues/42) still requires coordinating
103
- retirement of v1 web onboarding/prompt building with #17's initial setup.
104
- No host configuration or installation was changed, credentials accessed, lock
105
- removed, permission widened, package published, merge performed or issue closed.
@@ -1,93 +0,0 @@
1
- # PR #9 review corrections — October 15, 2026
2
-
3
- Starting HEAD: `abd3e52a285808df5b4cc3e1f53621421ef9b316`, existing branch
4
- `fix/cursor-identity-resume`. Local documentation correction only, prepared for
5
- independent review of [PR #9](https://github.com/lbframe/wacando-agent/pull/9).
6
- At local commit `24159b9`, no push, publication, GitHub comment/review/body edit,
7
- merge or issue closure had occurred. That earlier task left acceptance boxes and
8
- branch-based external evidence links unchanged. The subsequent
9
- [completion record](QUALIFICATION_LINK_FOLLOWUP.md) records the broader authorized
10
- follow-up: immutable links and bodies, with proven runtime boxes checked and
11
- review/merge still pending. This note preserves the earlier check scope.
12
-
13
- ## MEDIUM-1: follow the saved-identity path
14
-
15
- The manual procedure first proves resume with the same saved identity and an
16
- unusable token. `enroll()` reads that identity, skips initial token redemption,
17
- and calls `open()` (`packages/onboarding/service.mjs:72–99`). In a fresh host,
18
- `open()` requests `/v1/xmpp/session`; HTTP 403/`AGENT_REVOKED` becomes `ApiError`
19
- in `network.mjs:39`. Its catch stops retries, persists `revoked: true` and throws
20
- at `service.mjs:189`:
21
-
22
- ```text
23
- Agent revoked or durable access rejected. Connection attempts have stopped. Check the identity on the site.
24
- ```
25
-
26
- If background recovery has already persisted revocation, the guard at line 72
27
- instead throws:
28
-
29
- ```text
30
- Agent revoked. Connection attempts have stopped. Create a new identity on the site.
31
- ```
32
-
33
- Line 81's shorter message belongs to initial enrollment without saved agent
34
- state. It was the wrong expected output for this scenario. The revised procedure
35
- requires an actual MCP tool error (`packages/mcp/server.mjs:98`), the anchored
36
- `/^Agent revoked[. ]/` prefix (`scripts/host-result.mjs:20`), and no affirmative
37
- connection. The live test uses `/Agent revoked/` (`tests/onboarding-live.test.mjs:71`);
38
- it is existing runtime evidence, not a newly executed CLI qualification.
39
-
40
- An error string alone cannot establish owner revocation: `open()` also rejects
41
- other HTTP 401/403 access denials. The procedure additionally requires confirmed
42
- owner-UI revocation of the same previously resumed identity, exactly one actual
43
- tool result, a responsive host, unchanged private-identity fingerprint and normal
44
- lock cleanup. Missing results, generic failures, timeouts and host prose cannot
45
- pass. No implementation or generated file was changed.
46
-
47
- ## LOW corrections and evidence scope
48
-
49
- - `qualification_origin` must exactly equal `new URL(configuredApiUrl).origin`.
50
- `enroll()` and `CredentialStore.directory()` partition on that normalized
51
- origin; the manual fingerprint function hashes the supplied string literally.
52
- A trailing slash or other normalization difference selects the wrong directory
53
- and fails closed. The procedure now gives the exact requirement and an example.
54
- - The earlier `--help` exit-0 assertion has no archived outputs. It is withdrawn
55
- in both authored documents, including the Claude claim. No new help run is
56
- claimed. Launch flags must be checked on the target host; this correction
57
- establishes neither Claude availability nor CLI qualification.
58
- - The historical documentary whitespace check covers `24ace6e..abd3e52`, not
59
- the cumulative PR. That historical range and the present follow-up pass.
60
- `origin/main...abd3e52` exits 2 for whitespace in preserved raw evidence logs.
61
- Those bytes are intentionally retained; the cumulative result is a residual
62
- evidence-format limitation, not a new implementation failure.
63
- - The previous validation record's wording check was incorrect for the scenario;
64
- its claim is explicitly corrected, rather than reused as proof.
65
-
66
- ## Commands and actual results
67
-
68
- Required repository check, run in the foreground with a ten-minute bound:
69
-
70
- ```sh
71
- env -u WACANDO_LIVE_TEST_ORIGIN npm_config_cache=/tmp/wacando-issue2-npm-cache timeout 600s npm run check
72
- ```
73
-
74
- Exit 0. [Complete raw output](review-check.txt): build and all three exact
75
- runtime checks pass; 57 tests, 54 pass, 0 fail, 3 live cases skipped. No separate
76
- lint/typecheck scripts exist. The build reproduced the committed generated
77
- files without changing them. No MCP/packaging implementation change was made,
78
- so `check:mcp` was not rerun.
79
-
80
- [Documentary validation](review-validation.txt) contains actual source-path,
81
- local-link, shell/Node syntax, evidence-byte and whitespace check results,
82
- including the full cumulative whitespace diagnostics. Syntax checking did not
83
- execute the operator's procedure. All 22 files present at `24ace6e`, plus the
84
- prior documentary check log, are byte-for-byte preserved.
85
-
86
- Actual Cursor/Codex/Claude CLI enrollment, restart/reboot, resume and revocation
87
- remain NOT QUALIFIED in this sandbox; prior EROFS limitations remain. No live
88
- API/XMPP stack was restarted for this correction. Existing runtime acceptance
89
- evidence is unchanged. Independent review of this local correction is pending.
90
- [Wacando #17](https://github.com/lbframe/wacando/issues/17) owns CLI qualification;
91
- [Wacando #42](https://github.com/lbframe/wacando/issues/42) still owns retirement
92
- of the v1 web onboarding/prompt-builder dependency. Future app/pairing flow,
93
- crash locks, host UIs, other platforms and production remain untested.