@cotal-ai/connector-claude-code 0.16.0 → 0.17.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/dist/hook.cjs +3031 -1206
- package/dist/hooks.d.ts +80 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/index.js +20 -13
- package/dist/mcp.cjs +5912 -1246
- package/package.json +3 -3
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type MeshAgent, type HookEvent, type HookHandle } from "@cotal-ai/connector-core";
|
|
2
|
+
import type { TranscriptMirror } from "./transcript.js";
|
|
3
|
+
export interface ClaudeHandleDeps {
|
|
4
|
+
/** The session's transcript mirror, read lazily — `mcp.ts` assigns it after the handler exists. */
|
|
5
|
+
mirror?: () => TranscriptMirror | undefined;
|
|
6
|
+
}
|
|
7
|
+
/** The hook side of the connector: the handler plus the delivery callback that commits what it
|
|
8
|
+
* injected. Both must be wired into {@link startControlServer} — the handler surfaces, the
|
|
9
|
+
* callback is the only place a peer message is ever acked. */
|
|
10
|
+
export interface ClaudeHooks {
|
|
11
|
+
handle: HookHandle;
|
|
12
|
+
/** Pass as {@link ControlServerOpts.onReply}. */
|
|
13
|
+
onReply: (ev: HookEvent, delivered: boolean) => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Claude Code lifecycle events → presence + (on inject-capable events) queued peer messages.
|
|
17
|
+
*
|
|
18
|
+
* Two properties keep an unattended peer answering:
|
|
19
|
+
*
|
|
20
|
+
* **Presence never gates delivery.** `setStatus`/`setAttention` are broker round-trips that throw
|
|
21
|
+
* while the endpoint is mid-reconnect ({@link MeshAgent.setStatus} calls `assertConnected`). They
|
|
22
|
+
* used to sit inside the same try/catch as the delivery work that followed them, so a single failed
|
|
23
|
+
* presence write skipped the injection on `UserPromptSubmit` and — worse — the `Stop` wake flush,
|
|
24
|
+
* leaving held messages with nothing left to deliver them. Presence is observability; the wake path
|
|
25
|
+
* is the product, so presence failures are swallowed here and nothing is sequenced behind them.
|
|
26
|
+
*
|
|
27
|
+
* **Peer messages are surfaced, then committed on handoff.** The injection is built with
|
|
28
|
+
* {@link MeshAgent.peekInbox} and the ids are remembered, not acked. A hook reply still has to reach
|
|
29
|
+
* the runtime through the relay (which abandons the exchange after 2s) and the ids are committed
|
|
30
|
+
* only once {@link ClaudeHooks.onReply} says it did. Acking at format time was silent loss: the
|
|
31
|
+
* message was already in `handledIds`, so its durable redelivery was acked and discarded on arrival
|
|
32
|
+
* and no retry could ever surface it — the peer just never replied.
|
|
33
|
+
*
|
|
34
|
+
* **This is a deliberate choice of at-least-once over at-most-once.** The delivery verdict is not
|
|
35
|
+
* perfectly two-sided, so pick which way it errs:
|
|
36
|
+
* • reply landed, confirmation lost ⇒ the batch is surfaced again and the model reads it twice;
|
|
37
|
+
* • reply lost, treated as delivered ⇒ the message is buried and the peer never answers.
|
|
38
|
+
* The first costs a duplicate injection (labelled — see {@link REPEAT_NOTE}); the second costs the
|
|
39
|
+
* workflow. Do not "fix" the duplicate by committing optimistically: that is the burial this whole
|
|
40
|
+
* file exists to prevent.
|
|
41
|
+
*
|
|
42
|
+
* Deferring the ack to turn completion (the OpenCode connector's `ackSurfaced` on `session.idle`)
|
|
43
|
+
* does NOT substitute for this. `Stop` fires whether or not the reply survived, so it would commit a
|
|
44
|
+
* batch the model never saw — the same loss, later. OpenCode can bind its ack to the turn because
|
|
45
|
+
* `drive()` OWNS delivery; this connector only hands a reply off, so the ack binds to the handoff.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createClaudeHandle(deps?: ClaudeHandleDeps): ClaudeHooks;
|
|
48
|
+
/** One `claude/channel` push. A rejection is surfaced to the caller. */
|
|
49
|
+
export type ChannelNotify = (params: {
|
|
50
|
+
content: string;
|
|
51
|
+
meta: Record<string, string> | {
|
|
52
|
+
kind: string;
|
|
53
|
+
};
|
|
54
|
+
}) => Promise<void>;
|
|
55
|
+
export interface WakePolicy {
|
|
56
|
+
/** Flip once the MCP handshake confirms the client speaks `claude/channel`. */
|
|
57
|
+
setChannelActive(active: boolean): void;
|
|
58
|
+
/** Teardown: stop the retry timer. */
|
|
59
|
+
stop(): void;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The push side of the wake path: turn mesh events into `claude/channel` notifications.
|
|
63
|
+
*
|
|
64
|
+
* A nudge only ever *wakes* a turn — the body is surfaced by the hook handler above (or by an
|
|
65
|
+
* explicit `cotal_inbox` pull). It stays gated on a *mutable* `channelActive` flag (flipped true
|
|
66
|
+
* only after the MCP handshake confirms the client speaks claude/channel). If it fires before
|
|
67
|
+
* then it simply no-ops; a *buffered* message waits in the inbox and is surfaced at the next
|
|
68
|
+
* UserPromptSubmit, so nothing is lost. One exception: a focus @mention's body was already
|
|
69
|
+
* ack-dropped at ingest (not buffered), so a missed mention-wake is recoverable only by an
|
|
70
|
+
* explicit cotal_inbox pull (recall) — there is no buffered copy to surface.
|
|
71
|
+
*
|
|
72
|
+
* **A rejected push is retried.** The notification can fail (a closed or wedged stdio pipe), and it
|
|
73
|
+
* is the ONLY thing that wakes an idle session: no later hook fires on its own, so a dropped nudge
|
|
74
|
+
* used to mean silence until a human typed. A bounded backoff re-nudges while anything is still
|
|
75
|
+
* pending, mirroring the OpenCode connector's `scheduleErrorRetry`. It is driven off
|
|
76
|
+
* {@link MeshAgent.pendingWake}, so it stops as soon as the batch is delivered and committed, and
|
|
77
|
+
* it never wakes for held ambient the agent's attention mode says to hold.
|
|
78
|
+
*/
|
|
79
|
+
export declare function createWakePolicy(agent: MeshAgent, notify: ChannelNotify, log?: (msg: string) => void): WakePolicy;
|
|
80
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAcA,OAAO,EAIL,KAAK,SAAS,EAEd,KAAK,SAAS,EACd,KAAK,UAAU,EAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAYxD,MAAM,WAAW,gBAAgB;IAC/B,mGAAmG;IACnG,MAAM,CAAC,EAAE,MAAM,gBAAgB,GAAG,SAAS,CAAC;CAC7C;AAOD;;+DAE+D;AAC/D,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,UAAU,CAAC;IACnB,iDAAiD;IACjD,OAAO,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;CACtD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,gBAAqB,GAAG,WAAW,CAqK3E;AAED,wEAAwE;AACxE,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5H,MAAM,WAAW,UAAU;IACzB,+EAA+E;IAC/E,gBAAgB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACxC,sCAAsC;IACtC,IAAI,IAAI,IAAI,CAAC;CACd;AAKD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAe,GAAG,UAAU,CA0F3H"}
|