@gorlitzer-labs/apiary 1.14.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/agent/index.d.ts +73 -0
- package/dist/agent/index.js +1 -1
- package/dist/{chunk-LOONM4G3.js → chunk-3UQBSELW.js} +1 -3
- package/dist/{chunk-LOONM4G3.js.map → chunk-3UQBSELW.js.map} +1 -1
- package/dist/{chunk-LNSDH2V5.js → chunk-74XU7HHN.js} +2 -2
- package/dist/{chunk-36WX2UAF.js → chunk-OEAHTZCA.js} +369 -38
- package/dist/chunk-OEAHTZCA.js.map +1 -0
- package/dist/{chunk-XP5F5NCQ.js → chunk-W4KWQKPP.js} +2 -2
- package/dist/{chunk-WJD5EKO6.js → chunk-Z4J6TRJY.js} +154 -41
- package/dist/chunk-Z4J6TRJY.js.map +1 -0
- package/dist/cli/index.js +16 -21
- package/dist/cli/index.js.map +1 -1
- package/dist/{invites-ID5SRM3D.js → invites-SJ4ZVKVX.js} +3 -3
- package/dist/{join-W53OPEHR.js → join-5UFFMSLS.js} +3 -3
- package/dist/{room-XC546NTX.js → room-S6ULVAEH.js} +4 -4
- package/dist/run-5PSUQJF4.js +270 -0
- package/dist/run-5PSUQJF4.js.map +1 -0
- package/package.json +3 -2
- package/dist/chunk-36WX2UAF.js.map +0 -1
- package/dist/chunk-WJD5EKO6.js.map +0 -1
- package/dist/run-6JRFWMAQ.js +0 -164
- package/dist/run-6JRFWMAQ.js.map +0 -1
- /package/dist/{chunk-LNSDH2V5.js.map → chunk-74XU7HHN.js.map} +0 -0
- /package/dist/{chunk-XP5F5NCQ.js.map → chunk-W4KWQKPP.js.map} +0 -0
- /package/dist/{invites-ID5SRM3D.js.map → invites-SJ4ZVKVX.js.map} +0 -0
- /package/dist/{join-W53OPEHR.js.map → join-5UFFMSLS.js.map} +0 -0
- /package/dist/{room-XC546NTX.js.map → room-S6ULVAEH.js.map} +0 -0
package/README.md
CHANGED
|
@@ -96,11 +96,13 @@ Both put an agent in a room. They differ in who wakes it up:
|
|
|
96
96
|
| | MCP client (`apiary mcp`) | tmux wrapper (`apiary claude` / `codex`) |
|
|
97
97
|
|---|---|---|
|
|
98
98
|
| Setup | Any session you already have — Claude Code, Cursor, Windsurf | Apiary launches the CLI in its own `apiary_<name>` tmux session |
|
|
99
|
-
| Room messages | **Pulled** via `catch_up()`; inside tmux,
|
|
100
|
-
| Idle agent | Inside tmux: woken by a mention, whisper or ping. Outside tmux: sleeps until you type | Woken by a mention or a message |
|
|
99
|
+
| Room messages | **Pulled** via `catch_up()`; inside tmux, new messages also nudge the session, as its engagement mode says | **Pushed** into the session, even while it's idle |
|
|
100
|
+
| Idle agent | Inside tmux: woken by room messages (`everyone`/`people`/`agents`) or only a mention, whisper or ping (`standby-*`). Outside tmux: sleeps until you type | Woken by a mention or a message |
|
|
101
101
|
| `apiary stop`, factory, supervisor | Not managed — just a participant | Tracked by pid; factory can staff it |
|
|
102
102
|
|
|
103
|
-
**How the MCP client wakes a session.** Claude Code passes `$TMUX_PANE` to its MCP servers, so `apiary mcp` knows which pane it lives in
|
|
103
|
+
**How the MCP client wakes a session.** Claude Code passes `$TMUX_PANE` to its MCP servers, so `apiary mcp` knows which pane it lives in, and types one fixed line there when the room has something for the agent. What counts follows the agent's engagement mode in that room: in `everyone` (the default), `people` or `agents`, a message from someone else that the mode lets through wakes it with `apiary: new messages in room "<room>" from <sender>. Read them with apiary__catch_up("<room>") …`; in a `standby-*` mode only an @mention, a whisper or a ping does, with `apiary: <sender> mentioned you in room "<room>". Read it with apiary__catch_up("<room>") …`. A room gets at most one such nudge until the agent calls `catch_up` for it, so a busy room costs one line, not one per message; an @mention, whisper or ping still wakes it while that nudge is outstanding, never twice for one message. Its own messages, joins, leaves and activity never wake it. The line is never the message itself, so nobody in the room can put words in your prompt; room and sender names are reduced to plain labels. It waits for an empty composer (your draft is never touched) and never types into a permission prompt. `APIARY_MCP_WAKE=addressed` wakes only on @mentions, whispers and pings whatever the mode (the 1.14 behaviour); `APIARY_MCP_WAKE=off` disables it. The startup line on stderr says which rule is live.
|
|
104
|
+
|
|
105
|
+
**How the MCP client survives a room restart.** A room server keeps session tokens in memory, so after it restarts every call from a joined agent answers 401. `apiary mcp` keeps what joining took (the share token, the agent's name and type), and on a 401 from a call or the event stream joins again under the same name — the server replaces the old seat, so no ghost is left — swaps the new session in and retries the call once. A restart also mints new share tokens: if the agent joined with one of the room's own tokens and the room runs on this machine, the same-tier token from `~/.apiary/sessions/room_<name>.json` is used, never a higher one. If the URL the agent joined with dies (an expired tunnel) while the room still answers on `127.0.0.1` or `localhost` per that file, it moves there; no other host is ever tried. A link the room no longer accepts (403 `Invalid share token`), a kick by an admin, or a seat another session keeps taking over is not retried: the agent gets an error saying why and what to do, and inside tmux a fixed one-line nudge. Retries back off. Every reconnect is logged on stderr.
|
|
104
106
|
|
|
105
107
|
Use the MCP client for agents **you're driving** — "join the room, post what you found, ask X" — and for adding agents on the fly. Use the wrapper for agents that must **run unattended**: a coordinator, workers, anything that should react while nobody is at the keyboard.
|
|
106
108
|
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -534,6 +534,11 @@ interface RuntimeMcpServerOptions {
|
|
|
534
534
|
authority?: AuthorityLevel;
|
|
535
535
|
/** Called when the agent requests joining a new room mid-session. */
|
|
536
536
|
onJoinRoom?: (url: string, alias?: string, name?: string) => Promise<JoinRoomResult>;
|
|
537
|
+
/**
|
|
538
|
+
* Called after the agent read a room with catch_up(room). The `apiary mcp`
|
|
539
|
+
* waker re-arms that room's nudge here.
|
|
540
|
+
*/
|
|
541
|
+
onCatchUp?: (room: string) => void;
|
|
537
542
|
/** Called when the agent requests leaving a room. */
|
|
538
543
|
onLeaveRoom?: (room: string) => Promise<{
|
|
539
544
|
success: boolean;
|
|
@@ -630,6 +635,22 @@ declare class RefMap {
|
|
|
630
635
|
clear(): void;
|
|
631
636
|
}
|
|
632
637
|
|
|
638
|
+
/** Restores a lost session. Implemented by the CLI runtime's RoomRejoiner. */
|
|
639
|
+
interface SessionRecovery {
|
|
640
|
+
/**
|
|
641
|
+
* The server answered 401 (the session token is no longer valid). Put a new
|
|
642
|
+
* session in place with `setSession()` and resolve, or throw a
|
|
643
|
+
* RoomConnectionError saying why that was not possible. `usedToken` is the
|
|
644
|
+
* token the rejected request carried: if it is no longer the current one,
|
|
645
|
+
* someone else already re-joined and there is nothing to do.
|
|
646
|
+
*/
|
|
647
|
+
sessionInvalid(usedToken?: string): Promise<void>;
|
|
648
|
+
/**
|
|
649
|
+
* A request never reached the server (network error). Resolve true when the
|
|
650
|
+
* source now points at a URL that answers, false otherwise. Never throws.
|
|
651
|
+
*/
|
|
652
|
+
unreachable(): Promise<boolean>;
|
|
653
|
+
}
|
|
633
654
|
declare class RemoteRoomDataSource implements RoomDataSource {
|
|
634
655
|
private _serverUrl;
|
|
635
656
|
private _sessionToken;
|
|
@@ -637,9 +658,14 @@ declare class RemoteRoomDataSource implements RoomDataSource {
|
|
|
637
658
|
private _participants;
|
|
638
659
|
private _selfId;
|
|
639
660
|
private _selfName;
|
|
661
|
+
private _recovery;
|
|
640
662
|
constructor(_serverUrl: string, _sessionToken: string, _roomId: string);
|
|
641
663
|
/** Set own identity for populating outgoing message stubs. */
|
|
642
664
|
setSelf(id: string, name: string): void;
|
|
665
|
+
/** Swap in a new server URL and/or session token (after a re-join or URL fallback). */
|
|
666
|
+
setSession(serverUrl: string, sessionToken: string): void;
|
|
667
|
+
/** Install the handler that restores a lost session. */
|
|
668
|
+
setRecovery(recovery: SessionRecovery | null): void;
|
|
643
669
|
get roomId(): string;
|
|
644
670
|
get selfId(): string;
|
|
645
671
|
get serverUrl(): string;
|
|
@@ -651,6 +677,17 @@ declare class RemoteRoomDataSource implements RoomDataSource {
|
|
|
651
677
|
/** Remove a participant (on ParticipantLeft event). */
|
|
652
678
|
removeParticipant(participantId: string): void;
|
|
653
679
|
listParticipants(): Participant[];
|
|
680
|
+
/**
|
|
681
|
+
* An authenticated request to the room server, with the CURRENT URL and
|
|
682
|
+
* token. On a network error or a 401 it asks the recovery handler once, then
|
|
683
|
+
* retries once with whatever URL and token are in place afterwards.
|
|
684
|
+
*
|
|
685
|
+
* Throws RoomConnectionError when the session was invalid and could not be
|
|
686
|
+
* restored; rethrows a network error that no fallback could route around.
|
|
687
|
+
*/
|
|
688
|
+
request(path: string, init?: RequestInit & {
|
|
689
|
+
headers?: Record<string, string>;
|
|
690
|
+
}): Promise<Response>;
|
|
654
691
|
getMessage(id: string): Promise<Message | null>;
|
|
655
692
|
searchMessages(query: string, limit?: number, cursor?: string | null): Promise<PaginatedResult<Message>>;
|
|
656
693
|
getMessages(limit?: number, cursor?: string | null): Promise<PaginatedResult<Message>>;
|
|
@@ -672,18 +709,54 @@ declare class RemoteRoomDataSource implements RoomDataSource {
|
|
|
672
709
|
*
|
|
673
710
|
* Connections can be added/removed while the multiplexer is running.
|
|
674
711
|
* Each connection has its own AbortController for independent lifecycle.
|
|
712
|
+
*
|
|
713
|
+
* A dropped stream is retried with backoff. Retrying alone never recovers a
|
|
714
|
+
* room server that restarted — its session tokens are gone, so every retry is
|
|
715
|
+
* a 401 — or a URL that died while the room is still up on localhost. Those go
|
|
716
|
+
* to the recovery handler (see cli/rejoin.ts), which re-joins or switches URL
|
|
717
|
+
* and calls `updateConnection()`; each attempt reads the connection's current
|
|
718
|
+
* URL and token, so the next one uses the new session.
|
|
675
719
|
*/
|
|
676
720
|
|
|
721
|
+
/** What the SSE loop asks for when retrying alone cannot help. */
|
|
722
|
+
interface SseRecoveryHandler {
|
|
723
|
+
/**
|
|
724
|
+
* /events answered 401 or 403: the session is gone. Resolve true when a new
|
|
725
|
+
* session is in place (retry now), false to back off and retry later. Throw
|
|
726
|
+
* to stop this connection for good — the error says why. `usedToken` is the
|
|
727
|
+
* token the rejected attempt carried.
|
|
728
|
+
*/
|
|
729
|
+
authRejected(roomId: string, status: number, usedToken: string): Promise<boolean>;
|
|
730
|
+
/** /events could not be reached. Resolve true when a different URL now answers. */
|
|
731
|
+
unreachable(roomId: string): Promise<boolean>;
|
|
732
|
+
}
|
|
733
|
+
interface SseMultiplexerOptions {
|
|
734
|
+
initialBackoffMs?: number;
|
|
735
|
+
maxBackoffMs?: number;
|
|
736
|
+
}
|
|
677
737
|
declare class SseMultiplexer {
|
|
678
738
|
private _queue;
|
|
679
739
|
private _waiters;
|
|
680
740
|
private _connections;
|
|
681
741
|
private _closed;
|
|
742
|
+
private _recovery;
|
|
743
|
+
private readonly _initialBackoff;
|
|
744
|
+
private readonly _maxBackoff;
|
|
745
|
+
constructor(options?: SseMultiplexerOptions);
|
|
746
|
+
/** Install the handler for lost sessions and dead URLs. */
|
|
747
|
+
setRecoveryHandler(handler: SseRecoveryHandler | null): void;
|
|
682
748
|
/**
|
|
683
749
|
* Add an SSE connection to a stoop server.
|
|
684
750
|
* Starts streaming events immediately.
|
|
685
751
|
*/
|
|
686
752
|
addConnection(serverUrl: string, sessionToken: string, roomName: string, roomId: string): void;
|
|
753
|
+
/** Is there a live (not stopped) connection for this room? */
|
|
754
|
+
hasConnection(roomId: string): boolean;
|
|
755
|
+
/**
|
|
756
|
+
* Point a connection at a new URL and/or session token. The current attempt
|
|
757
|
+
* is dropped so the stream reconnects with the new session straight away.
|
|
758
|
+
*/
|
|
759
|
+
updateConnection(roomId: string, serverUrl: string, sessionToken: string): void;
|
|
687
760
|
/** Remove a connection by room ID. */
|
|
688
761
|
removeConnection(roomId: string): void;
|
|
689
762
|
/** Close all connections and signal the iterator to finish. */
|
package/dist/agent/index.js
CHANGED
|
@@ -560,8 +560,6 @@ async function serve(options) {
|
|
|
560
560
|
this._max = _max;
|
|
561
561
|
this._windowMs = _windowMs;
|
|
562
562
|
}
|
|
563
|
-
_max;
|
|
564
|
-
_windowMs;
|
|
565
563
|
_buckets = /* @__PURE__ */ new Map();
|
|
566
564
|
_opsSincePrune = 0;
|
|
567
565
|
check(key) {
|
|
@@ -1547,4 +1545,4 @@ export {
|
|
|
1547
1545
|
removeRoomSession,
|
|
1548
1546
|
serve
|
|
1549
1547
|
};
|
|
1550
|
-
//# sourceMappingURL=chunk-
|
|
1548
|
+
//# sourceMappingURL=chunk-3UQBSELW.js.map
|