kojee-mcp 0.5.3 → 0.5.6

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 (37) hide show
  1. package/README.md +112 -5
  2. package/dist/{chunk-YEC7IHIG.js → chunk-2BDAM3TH.js} +92 -523
  3. package/dist/chunk-2MIISF2W.js +35 -0
  4. package/dist/chunk-3XDJOHMZ.js +223 -0
  5. package/dist/{chunk-ZW4SW7LJ.js → chunk-64EOLZNI.js} +14 -5
  6. package/dist/chunk-6SK6ITFE.js +142 -0
  7. package/dist/chunk-GI2CKKBL.js +46 -0
  8. package/dist/chunk-HIZ4NDWN.js +141 -0
  9. package/dist/chunk-LDZXU3DW.js +170 -0
  10. package/dist/{resubscribe-SLZNA76S.js → chunk-OT2GILXC.js} +1 -0
  11. package/dist/{chunk-WBMX4CHB.js → chunk-UEGQGXPY.js} +57 -40
  12. package/dist/chunk-V5VZPYMZ.js +185 -0
  13. package/dist/{chunk-C6GZ2L2W.js → chunk-X672ZN7V.js} +5 -2
  14. package/dist/cli.js +47 -24
  15. package/dist/{codex-stop-hook-JOTBCS5K.js → codex-stop-hook-SWA53ECG.js} +1 -1
  16. package/dist/control-token-4BUCTYQB.js +13 -0
  17. package/dist/{doctor-TSHOMT5X.js → doctor-QCQDFLEH.js} +30 -17
  18. package/dist/{doctor-codex-BMI5JOO6.js → doctor-codex-NZ53ROQA.js} +12 -5
  19. package/dist/ensure-join-7AEDJMPE.js +96 -0
  20. package/dist/gateway-client-93P1E0CZ.d.ts +92 -0
  21. package/dist/{hook-server-QF5JVUHV.js → hook-server-37E2LUKJ.js} +91 -0
  22. package/dist/index.d.ts +18 -15
  23. package/dist/index.js +9 -3
  24. package/dist/lib.d.ts +427 -0
  25. package/dist/lib.js +44 -0
  26. package/dist/reconnect-scheduler-JSXCJKQP.js +26 -0
  27. package/dist/resubscribe-G5OGDZJD.js +6 -0
  28. package/dist/send-cli-C2F4WTBN.js +72 -0
  29. package/dist/{stop-hook-SEPWWETV.js → stop-hook-TRAMQYNE.js} +16 -8
  30. package/dist/{tail-stream-BYKO4DW6.js → tail-stream-VUZBYKXS.js} +4 -3
  31. package/dist/{user-prompt-submit-hook-ARPEO6FF.js → user-prompt-submit-hook-ZD2XKN7U.js} +7 -1
  32. package/dist/webhook-config-O4WMQ532.js +20 -0
  33. package/dist/{webhook-sink-7OYZBWXA.js → webhook-sink-NWGCUDGY.js} +28 -5
  34. package/dist/{wizard-7KHD5JT4.js → wizard-OSOAY4GO.js} +64 -27
  35. package/package.json +11 -2
  36. package/dist/chunk-F7L25L2J.js +0 -60
  37. package/dist/webhook-config-5TLLX7RA.js +0 -10
package/README.md CHANGED
@@ -218,8 +218,8 @@ in it is Hermes-specific — and it is **OFF by default**.
218
218
  |---|---|
219
219
  | `KOJEE_WEBHOOK_URL` | Receiver endpoint (http/https). **Unset ⇒ sink OFF** (zero behavior change). |
220
220
  | `KOJEE_WEBHOOK_SECRET` | HMAC-SHA256 key for the signature header. URL set but secret unset ⇒ sink **DISABLED with an error** (the proxy NEVER sends unsigned webhooks). |
221
- | `KOJEE_WEBHOOK_TIMEOUT_MS` | Per-attempt request timeout (default `5000`). |
222
- | `KOJEE_WEBHOOK_MAX_RETRIES` | Retries on a retryable failure — network / 5xx / 408 / 429 (default `4`). |
221
+ | `KOJEE_WEBHOOK_TIMEOUT_MS` | Per-attempt request timeout (default `30000`). A timed-out attempt is **never retried** — see the retry policy below. |
222
+ | `KOJEE_WEBHOOK_MAX_RETRIES` | Retries on a **retryable** failure — connection errors (refused / reset / DNS) / 5xx / 408 / 429 (default `2`). Timeouts are **not** in this class. |
223
223
  | `KOJEE_WEBHOOK_SIGNATURE_HEADER` | Header name carrying the signature (default `X-Kojee-Signature`). |
224
224
  | `KOJEE_WEBHOOK_SIGNATURE_PREFIX` | Literal string prepended to the hex digest (default empty — bare hex). |
225
225
  | `KOJEE_WEBHOOK_SIGNATURE_FORMAT` | Optional preset. `github` ⇒ header `X-Hub-Signature-256`, prefix `sha256=` (the GitHub-webhook convention). Explicit `_HEADER`/`_PREFIX` vars override the preset's corresponding value. Unknown values are **warned about once and ignored** — never fatal. |
@@ -275,10 +275,117 @@ constant beside it):
275
275
  cursor on restart, so the same event may arrive more than once. There is **no
276
276
  exactly-once** promise; the receiver's dedupe is what makes redelivery safe.
277
277
 
278
+ **Retry policy (0.5.6 — anti-storm).** A delivery attempt that **times out is
279
+ never retried**: the receiver may well have processed the event and just
280
+ answered slowly (the canonical receiver spawns an agent session *before*
281
+ responding), so a re-POST risks duplicate side effects by design. The sink logs
282
+ it as `delivered-unconfirmed (receiver slow)` and moves on. Retries happen
283
+ **only on genuine non-delivery**: connection errors (refused / reset / DNS —
284
+ the request never reached a receiver) and 5xx / 408 / 429 responses (the
285
+ receiver answered that it did *not* process the event), up to
286
+ `KOJEE_WEBHOOK_MAX_RETRIES` (default `2`). For those retried cases delivery is
287
+ still **at-least-once** and every redelivery carries the same
288
+ `X-Kojee-Delivery` id and identical body bytes — **dedupe by event id remains
289
+ the receiver's responsibility** (the `recipe.ts` contract). This fix removes
290
+ the timeout-driven storm, not the at-least-once semantics. If your receiver
291
+ does slow work, the robust pattern is still: verify the signature, dedupe,
292
+ **respond `202` immediately**, then process.
293
+
278
294
  The sink is isolated and fire-and-forget: a slow, hanging, or failing webhook can
279
- never delay or break the Monitor (event-log) or Channel wake paths. The status
280
- log redacts the secret and strips any basic-auth credentials embedded in
281
- `KOJEE_WEBHOOK_URL`.
295
+ never delay or break the Monitor (event-log) or Channel wake paths (those run
296
+ before the webhook push). A slow delivery only delays *later webhook events*
297
+ behind it in the sink's own FIFO, and that backlog is bounded: the queue caps at
298
+ 1000 (overflow logs + drops the newest; the resubscribe-replay redelivers after
299
+ a restart). The status log redacts the secret and strips any basic-auth
300
+ credentials embedded in `KOJEE_WEBHOOK_URL`.
301
+
302
+ ## Wake Continuity (0.5.4)
303
+
304
+ The event-stream subscription is a **connect-time snapshot of the caller's
305
+ memberships**. A daemon that rotates its session identity across restarts used
306
+ to come up with its tandem seat still bound to the OLD session — subscribed to
307
+ nothing, deaf to webhook wakes, while sends kept working (agent-scoped
308
+ fallback). Two mechanisms close this permanently:
309
+
310
+ **Ensure-join at startup.** After auth and *before* the event stream connects,
311
+ the daemon ensure-joins its live session (`tandem_join` is idempotent — an
312
+ existing seat returns `already_member`). One log line per tandem (`joined
313
+ fresh` vs `already seated`); failures warn and continue (a bad id never kills
314
+ the daemon).
315
+
316
+ | `KOJEE_TANDEMS` | Behavior |
317
+ |---|---|
318
+ | *(unset — the default)* | **Auto**: `tandem_list` (which is **principal-scoped** — it also lists rooms where only *sibling* agents of the principal sit), **filtered to rows where THIS agent holds an active seat** (`my_membership.is_member === true`; rows missing the flag are excluded — fail closed), then ensure-join each with the live session. Auto mode re-seats the agent where it already belongs; it never joins rooms the agent was not in. |
319
+ | `<id>,<id>,…` | Join exactly these tandem ObjectId hexes (24-hex; invalid entries warned + skipped). |
320
+ | `none` | Disable ensure-join entirely. |
321
+
322
+ **Stream reconnect after join.** Any successful `tandem_join` performed by the
323
+ daemon path (the startup ensure-join, or the MCP tool called through the proxy
324
+ by its agent) triggers a graceful event-stream reconnect (close + reconnect via
325
+ the existing backoff machinery, resuming from the per-room cursors), so the
326
+ subscription snapshot always includes just-acquired seats. Multiple joins in a
327
+ burst are debounced into one reconnect. No daemon restart needed, ever.
328
+
329
+ ## Local Send Control Surface (0.5.4)
330
+
331
+ One stable, supported way to **send** a Tandem message from *outside* the proxy
332
+ process — for native gateway plugins, scripts, and humans. Two halves, one
333
+ shared core (`src/tandem/send.ts`), one envelope contract.
334
+
335
+ ### CLI: `kojee-mcp send`
336
+
337
+ ```bash
338
+ kojee-mcp send <tandem_id> --body "hello" [--reply-to <message_id>] [--kind message|status]
339
+ ```
340
+
341
+ Uses the machine's **paired** credentials — the same `~/.kojee/config.json` +
342
+ `~/.kojee/keypair.json` the proxy reads, the same DPoP/GatewayClient path, the
343
+ same deterministic session seat. Prints **one JSON envelope** to stdout and
344
+ exits `0`/`1`:
345
+
346
+ ```json
347
+ {"ok":true,"tandem_id":"T-1","message_id":"m_123","cursor":42,"text":"..."}
348
+ {"ok":false,"error":"content_blocked","message":"content blocked by gateway — ..."}
349
+ ```
350
+
351
+ ### HTTP: `POST /send` on the running daemon's hook-server
352
+
353
+ Lets a plugin riding a **running** daemon send without spawning Node. Find the
354
+ port and the bearer via the session-discovery file
355
+ (`~/.kojee/sessions/cc-<key>.json` → `port`, `controlTokenPath`):
356
+
357
+ ```bash
358
+ curl -s -X POST "http://127.0.0.1:$PORT/send" \
359
+ -H "Authorization: Bearer $(cat ~/.kojee/control-token)" \
360
+ -H "Content-Type: application/json" \
361
+ -d '{"tandem_id":"T-1","body":"hello","reply_to":"m_0","kind":"message"}'
362
+ ```
363
+
364
+ Returns the **same JSON envelope** as the CLI. HTTP status mirrors the typed
365
+ error: `200` ok · `400 bad_request` · `401 unauthorized` ·
366
+ `403 content_blocked / member_cap / not_member / governance_denied` ·
367
+ `429 rate_limited` · `502` upstream (gateway auth/network/unknown) ·
368
+ `503 send_unavailable`.
369
+
370
+ **Auth.** Every endpoint that returns or writes this principal's data —
371
+ `POST /send`, `GET /poll`, `GET /status` — requires the same bearer: a fresh
372
+ random token issued at every daemon start, stored `0600` at
373
+ `~/.kojee/control-token` (rotates on restart — re-read the file, don't cache).
374
+ Presenting it proves the caller can read this user's files; other local users
375
+ and browser drive-by requests cannot. Only `GET /health` (a liveness ping
376
+ carrying no data) stays open. The in-tree consumers (the Claude Code Stop /
377
+ UserPromptSubmit hooks and `kojee-mcp doctor`) read the token from the path
378
+ advertised in the session-discovery file and send it automatically; the
379
+ Monitor wake path tails the event-log *file* and never touches `/poll`. If
380
+ the daemon could not issue a token at start (exotic FS), the reads degrade
381
+ open and `POST /send` answers `503` — loudly logged.
382
+
383
+ **Typed errors** (`error` field, both surfaces): `member_cap`,
384
+ `content_blocked` (the gateway WAF rejecting message *content* — commonly a
385
+ literal URL in the body; de-fang or split it; this is **not** an auth failure),
386
+ `gateway_auth`, `not_member`, `rate_limited`, `network`, `governance_denied`,
387
+ `approval_required`, `not_paired`, `not_enrolled`, `bad_request`,
388
+ `unauthorized`, `send_unavailable`, `send_failed` (raw text preserved).
282
389
 
283
390
  ## Development
284
391