@moxxy/channel-kit 0.27.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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/dist/frame-pump.d.ts +77 -0
  3. package/dist/frame-pump.d.ts.map +1 -0
  4. package/dist/frame-pump.js +118 -0
  5. package/dist/frame-pump.js.map +1 -0
  6. package/dist/index.d.ts +19 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +19 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/ingest/dedupe.d.ts +31 -0
  11. package/dist/ingest/dedupe.d.ts.map +1 -0
  12. package/dist/ingest/dedupe.js +66 -0
  13. package/dist/ingest/dedupe.js.map +1 -0
  14. package/dist/ingest/http-server.d.ts +76 -0
  15. package/dist/ingest/http-server.d.ts.map +1 -0
  16. package/dist/ingest/http-server.js +101 -0
  17. package/dist/ingest/http-server.js.map +1 -0
  18. package/dist/pairing/host-code.d.ts +94 -0
  19. package/dist/pairing/host-code.d.ts.map +1 -0
  20. package/dist/pairing/host-code.js +107 -0
  21. package/dist/pairing/host-code.js.map +1 -0
  22. package/dist/pairing/tofu.d.ts +33 -0
  23. package/dist/pairing/tofu.d.ts.map +1 -0
  24. package/dist/pairing/tofu.js +41 -0
  25. package/dist/pairing/tofu.js.map +1 -0
  26. package/dist/permission.d.ts +28 -0
  27. package/dist/permission.d.ts.map +1 -0
  28. package/dist/permission.js +17 -0
  29. package/dist/permission.js.map +1 -0
  30. package/dist/plain-turn-renderer.d.ts +17 -0
  31. package/dist/plain-turn-renderer.d.ts.map +1 -0
  32. package/dist/plain-turn-renderer.js +28 -0
  33. package/dist/plain-turn-renderer.js.map +1 -0
  34. package/dist/secrets.d.ts +18 -0
  35. package/dist/secrets.d.ts.map +1 -0
  36. package/dist/secrets.js +16 -0
  37. package/dist/secrets.js.map +1 -0
  38. package/dist/turn.d.ts +96 -0
  39. package/dist/turn.d.ts.map +1 -0
  40. package/dist/turn.js +106 -0
  41. package/dist/turn.js.map +1 -0
  42. package/dist/voice-reply.d.ts +139 -0
  43. package/dist/voice-reply.d.ts.map +1 -0
  44. package/dist/voice-reply.js +333 -0
  45. package/dist/voice-reply.js.map +1 -0
  46. package/package.json +54 -0
  47. package/src/frame-pump.test.ts +209 -0
  48. package/src/frame-pump.ts +154 -0
  49. package/src/index.ts +66 -0
  50. package/src/ingest/dedupe.test.ts +58 -0
  51. package/src/ingest/dedupe.ts +66 -0
  52. package/src/ingest/http-server.test.ts +120 -0
  53. package/src/ingest/http-server.ts +173 -0
  54. package/src/pairing/host-code.test.ts +121 -0
  55. package/src/pairing/host-code.ts +159 -0
  56. package/src/pairing/tofu.test.ts +62 -0
  57. package/src/pairing/tofu.ts +60 -0
  58. package/src/permission.test.ts +69 -0
  59. package/src/permission.ts +46 -0
  60. package/src/plain-turn-renderer.ts +31 -0
  61. package/src/secrets.test.ts +59 -0
  62. package/src/secrets.ts +26 -0
  63. package/src/turn.test.ts +165 -0
  64. package/src/turn.ts +162 -0
  65. package/src/voice-reply.test.ts +316 -0
  66. package/src/voice-reply.ts +449 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Host-issued-code pairing — the pure state machine behind Telegram's QR /
3
+ * deep-link chat pairing, generic over the peer identifier (a Telegram chat id,
4
+ * a Discord DM channel, a WhatsApp JID, ...).
5
+ *
6
+ * A control surface (desktop Channels panel, `moxxy channels <name> pair` in a
7
+ * terminal) opens a window with {@link openHostCodeWindow}: the code is minted
8
+ * UP FRONT by the caller so the surface can embed it in a deep link and render
9
+ * that as a QR. The user scans / opens the link (the messenger round-trips the
10
+ * code back to the bot) or simply sends the code as a message; the channel
11
+ * calls {@link submitPeerCode}, and on match that peer is authorized.
12
+ *
13
+ * Security property: "prove you can see the host's screen" — the code only ever
14
+ * lives on the surface. No TTL by default: the window's lifetime is the channel
15
+ * process's lifetime while unpaired; a caller may still bound it with `ttlMs`.
16
+ *
17
+ * Decisions carry semantic `kind`s only — user-facing messages (and their
18
+ * channel-specific wording, e.g. "run `moxxy channels telegram pair`") are
19
+ * mapped by each channel.
20
+ */
21
+ export type HostCodePhase = 'idle' | 'awaiting-host-code' | 'paired' | 'expired';
22
+ export interface HostCodeState<P> {
23
+ readonly phase: HostCodePhase;
24
+ readonly code: string | null;
25
+ readonly expiresAt: number | null;
26
+ readonly authorizedPeer: P | null;
27
+ }
28
+ export type HostCodeAction<P> =
29
+ /** The presented code matched — the peer is now authorized. */
30
+ {
31
+ readonly kind: 'paired';
32
+ readonly peer: P;
33
+ }
34
+ /** The already-authorized peer showed up again (idempotent; greet it). */
35
+ | {
36
+ readonly kind: 'still-paired';
37
+ readonly peer: P;
38
+ }
39
+ /** A DIFFERENT peer while one is authorized — access denied. */
40
+ | {
41
+ readonly kind: 'rejected-foreign-peer';
42
+ }
43
+ /** Bare hello while a window is open — nudge toward the QR / code. */
44
+ | {
45
+ readonly kind: 'window-open-hint';
46
+ }
47
+ /** Bare hello with no window open — nudge toward starting pairing. */
48
+ | {
49
+ readonly kind: 'no-window';
50
+ }
51
+ /** A code was presented but no window is open. */
52
+ | {
53
+ readonly kind: 'not-pending';
54
+ }
55
+ /** The window's optional TTL elapsed. */
56
+ | {
57
+ readonly kind: 'expired';
58
+ }
59
+ /** The presented code didn't match. */
60
+ | {
61
+ readonly kind: 'mismatch';
62
+ };
63
+ export interface HostCodeDecision<P> {
64
+ readonly state: HostCodeState<P>;
65
+ readonly action: HostCodeAction<P>;
66
+ }
67
+ export declare function createHostCodeState<P>(opts?: {
68
+ authorizedPeer?: P | null;
69
+ }): HostCodeState<P>;
70
+ /**
71
+ * Open a host-issued pairing window with a pre-minted code (the caller mints it
72
+ * so it can embed the code in the deep link / QR it shows the user).
73
+ */
74
+ export declare function openHostCodeWindow<P>(state: HostCodeState<P>, opts: {
75
+ code: string;
76
+ now?: number;
77
+ ttlMs?: number | null;
78
+ }): {
79
+ state: HostCodeState<P>;
80
+ code: string;
81
+ };
82
+ /**
83
+ * A peer said hello WITHOUT presenting a code (e.g. a bare Telegram `/start` —
84
+ * the user opened the chat manually rather than via the pairing deep link).
85
+ */
86
+ export declare function greetPeer<P>(state: HostCodeState<P>, peer: P): HostCodeDecision<P>;
87
+ /**
88
+ * A peer PRESENTED a code (deep-link payload or a plain message). Whitespace is
89
+ * stripped before comparing; on match the peer is authorized.
90
+ */
91
+ export declare function submitPeerCode<P>(state: HostCodeState<P>, peer: P, rawCode: string, now?: number): HostCodeDecision<P>;
92
+ export declare function isPeerAuthorized<P>(state: HostCodeState<P>, peer: P): boolean;
93
+ export declare function clearHostCodePairing<P>(_state: HostCodeState<P>): HostCodeState<P>;
94
+ //# sourceMappingURL=host-code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-code.d.ts","sourceRoot":"","sources":["../../src/pairing/host-code.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,MAAM,aAAa,GACrB,MAAM,GAEN,oBAAoB,GACpB,QAAQ,GACR,SAAS,CAAC;AAEd,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,CAAC,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,MAAM,cAAc,CAAC,CAAC;AAC1B,+DAA+D;AAC7D;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;CAAE;AAC/C,0EAA0E;GACxE;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;CAAE;AACrD,gEAAgE;GAC9D;IAAE,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAA;CAAE;AAC5C,sEAAsE;GACpE;IAAE,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;CAAE;AACvC,sEAAsE;GACpE;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE;AAChC,kDAAkD;GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;CAAE;AAClC,yCAAyC;GACvC;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE;AAC9B,uCAAuC;GACrC;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAElC,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;CACpC;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,IAAI,GAAE;IAAE,cAAc,CAAC,EAAE,CAAC,GAAG,IAAI,CAAA;CAAO,GACvC,aAAa,CAAC,CAAC,CAAC,CAOlB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC1D;IAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAY3C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAWlF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,MAAM,EACf,GAAG,GAAE,MAAmB,GACvB,gBAAgB,CAAC,CAAC,CAAC,CA6BrB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAE7E;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAOlF"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Host-issued-code pairing — the pure state machine behind Telegram's QR /
3
+ * deep-link chat pairing, generic over the peer identifier (a Telegram chat id,
4
+ * a Discord DM channel, a WhatsApp JID, ...).
5
+ *
6
+ * A control surface (desktop Channels panel, `moxxy channels <name> pair` in a
7
+ * terminal) opens a window with {@link openHostCodeWindow}: the code is minted
8
+ * UP FRONT by the caller so the surface can embed it in a deep link and render
9
+ * that as a QR. The user scans / opens the link (the messenger round-trips the
10
+ * code back to the bot) or simply sends the code as a message; the channel
11
+ * calls {@link submitPeerCode}, and on match that peer is authorized.
12
+ *
13
+ * Security property: "prove you can see the host's screen" — the code only ever
14
+ * lives on the surface. No TTL by default: the window's lifetime is the channel
15
+ * process's lifetime while unpaired; a caller may still bound it with `ttlMs`.
16
+ *
17
+ * Decisions carry semantic `kind`s only — user-facing messages (and their
18
+ * channel-specific wording, e.g. "run `moxxy channels telegram pair`") are
19
+ * mapped by each channel.
20
+ */
21
+ export function createHostCodeState(opts = {}) {
22
+ return {
23
+ phase: opts.authorizedPeer != null ? 'paired' : 'idle',
24
+ code: null,
25
+ expiresAt: null,
26
+ authorizedPeer: opts.authorizedPeer ?? null,
27
+ };
28
+ }
29
+ /**
30
+ * Open a host-issued pairing window with a pre-minted code (the caller mints it
31
+ * so it can embed the code in the deep link / QR it shows the user).
32
+ */
33
+ export function openHostCodeWindow(state, opts) {
34
+ const ttlMs = opts.ttlMs ?? null;
35
+ const now = opts.now ?? Date.now();
36
+ return {
37
+ state: {
38
+ ...state,
39
+ phase: 'awaiting-host-code',
40
+ code: opts.code,
41
+ expiresAt: ttlMs == null ? null : now + ttlMs,
42
+ },
43
+ code: opts.code,
44
+ };
45
+ }
46
+ /**
47
+ * A peer said hello WITHOUT presenting a code (e.g. a bare Telegram `/start` —
48
+ * the user opened the chat manually rather than via the pairing deep link).
49
+ */
50
+ export function greetPeer(state, peer) {
51
+ if (state.authorizedPeer === peer && state.phase === 'paired') {
52
+ return { state, action: { kind: 'still-paired', peer } };
53
+ }
54
+ if (state.authorizedPeer !== null && state.authorizedPeer !== peer) {
55
+ return { state, action: { kind: 'rejected-foreign-peer' } };
56
+ }
57
+ if (state.phase === 'awaiting-host-code') {
58
+ return { state, action: { kind: 'window-open-hint' } };
59
+ }
60
+ return { state, action: { kind: 'no-window' } };
61
+ }
62
+ /**
63
+ * A peer PRESENTED a code (deep-link payload or a plain message). Whitespace is
64
+ * stripped before comparing; on match the peer is authorized.
65
+ */
66
+ export function submitPeerCode(state, peer, rawCode, now = Date.now()) {
67
+ if (state.phase === 'paired' && state.authorizedPeer === peer) {
68
+ return { state, action: { kind: 'still-paired', peer } };
69
+ }
70
+ if (state.authorizedPeer !== null && state.authorizedPeer !== peer) {
71
+ return { state, action: { kind: 'rejected-foreign-peer' } };
72
+ }
73
+ if (state.phase !== 'awaiting-host-code') {
74
+ return { state, action: { kind: 'not-pending' } };
75
+ }
76
+ if (state.expiresAt !== null && now > state.expiresAt) {
77
+ return {
78
+ state: { ...state, phase: 'expired', code: null, expiresAt: null },
79
+ action: { kind: 'expired' },
80
+ };
81
+ }
82
+ const normalized = rawCode.replace(/\s+/g, '');
83
+ if (!normalized || normalized !== state.code) {
84
+ return { state, action: { kind: 'mismatch' } };
85
+ }
86
+ return {
87
+ state: {
88
+ phase: 'paired',
89
+ code: null,
90
+ expiresAt: null,
91
+ authorizedPeer: peer,
92
+ },
93
+ action: { kind: 'paired', peer },
94
+ };
95
+ }
96
+ export function isPeerAuthorized(state, peer) {
97
+ return state.phase === 'paired' && state.authorizedPeer === peer;
98
+ }
99
+ export function clearHostCodePairing(_state) {
100
+ return {
101
+ phase: 'idle',
102
+ code: null,
103
+ expiresAt: null,
104
+ authorizedPeer: null,
105
+ };
106
+ }
107
+ //# sourceMappingURL=host-code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-code.js","sourceRoot":"","sources":["../../src/pairing/host-code.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Trust-on-first-use pairing window — the mechanism behind Slack's pair flow,
3
+ * generic over the candidate shape (Slack: `{ teamId, channelId }`).
4
+ *
5
+ * A pair flow arms the window, then the channel's ingest path `offer`s every
6
+ * verified inbound event's origin. While armed, the first candidate is captured
7
+ * (listeners — the interactive pair flow — are notified and ask the operator to
8
+ * confirm) and CONSUMED, so the very first message just establishes trust
9
+ * rather than driving a turn. Confirmation + persistence stay with the channel
10
+ * (it owns the vault key format); it calls `disarm()` once authorized.
11
+ */
12
+ export interface TofuPairingWindowOptions {
13
+ /** Observe a listener throwing (channels log it); never propagates. */
14
+ readonly onListenerError?: (err: unknown) => void;
15
+ }
16
+ export declare class TofuPairingWindow<C> {
17
+ private armed;
18
+ private readonly listeners;
19
+ private readonly opts;
20
+ constructor(opts?: TofuPairingWindowOptions);
21
+ get isArmed(): boolean;
22
+ arm(): void;
23
+ disarm(): void;
24
+ /** Subscribe to captured candidates. Returns an unsubscribe function. */
25
+ onCandidate(listener: (candidate: C) => void): () => void;
26
+ /**
27
+ * Present a verified inbound origin. While armed, notifies listeners and
28
+ * returns true — the event was consumed by pairing and must NOT also drive a
29
+ * turn. Returns false when no window is armed.
30
+ */
31
+ offer(candidate: C): boolean;
32
+ }
33
+ //# sourceMappingURL=tofu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tofu.d.ts","sourceRoot":"","sources":["../../src/pairing/tofu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,wBAAwB;IACvC,uEAAuE;IACvE,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CACnD;AAED,qBAAa,iBAAiB,CAAC,CAAC;IAC9B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqC;IAC/D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA2B;gBAEpC,IAAI,GAAE,wBAA6B;IAI/C,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,GAAG,IAAI,IAAI;IAIX,MAAM,IAAI,IAAI;IAId,yEAAyE;IACzE,WAAW,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IAKzD;;;;OAIG;IACH,KAAK,CAAC,SAAS,EAAE,CAAC,GAAG,OAAO;CAW7B"}
@@ -0,0 +1,41 @@
1
+ export class TofuPairingWindow {
2
+ armed = false;
3
+ listeners = new Set();
4
+ opts;
5
+ constructor(opts = {}) {
6
+ this.opts = opts;
7
+ }
8
+ get isArmed() {
9
+ return this.armed;
10
+ }
11
+ arm() {
12
+ this.armed = true;
13
+ }
14
+ disarm() {
15
+ this.armed = false;
16
+ }
17
+ /** Subscribe to captured candidates. Returns an unsubscribe function. */
18
+ onCandidate(listener) {
19
+ this.listeners.add(listener);
20
+ return () => this.listeners.delete(listener);
21
+ }
22
+ /**
23
+ * Present a verified inbound origin. While armed, notifies listeners and
24
+ * returns true — the event was consumed by pairing and must NOT also drive a
25
+ * turn. Returns false when no window is armed.
26
+ */
27
+ offer(candidate) {
28
+ if (!this.armed)
29
+ return false;
30
+ for (const listener of this.listeners) {
31
+ try {
32
+ listener(candidate);
33
+ }
34
+ catch (err) {
35
+ this.opts.onListenerError?.(err);
36
+ }
37
+ }
38
+ return true;
39
+ }
40
+ }
41
+ //# sourceMappingURL=tofu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tofu.js","sourceRoot":"","sources":["../../src/pairing/tofu.ts"],"names":[],"mappings":"AAgBA,MAAM,OAAO,iBAAiB;IACpB,KAAK,GAAG,KAAK,CAAC;IACL,SAAS,GAAG,IAAI,GAAG,EAA0B,CAAC;IAC9C,IAAI,CAA2B;IAEhD,YAAY,OAAiC,EAAE;QAC7C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,GAAG;QACD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAED,MAAM;QACJ,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,yEAAyE;IACzE,WAAW,CAAC,QAAgC;QAC1C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,SAAY;QAChB,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QAC9B,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACtC,IAAI,CAAC;gBACH,QAAQ,CAAC,SAAS,CAAC,CAAC;YACtB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,GAAG,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -0,0 +1,28 @@
1
+ import type { PendingToolCall, PermissionResolver } from '@moxxy/sdk';
2
+ /**
3
+ * Autonomous allow-list permission resolver with an audit hook — the shared
4
+ * wiring for hands-off channels (Slack-style: no human in the loop; the
5
+ * operator declares trust upfront via an `allowedTools` list).
6
+ *
7
+ * Reuses the SDK's {@link createAllowListResolver} (exact-name match →
8
+ * `allow_session`, else `deny`) rather than re-implementing the trust check,
9
+ * and adds the two things every autonomous channel needs:
10
+ *
11
+ * - `'*'` expansion — `['*']` means "allow every registered tool", expanded
12
+ * against `allToolNames` at channel-start time (mirrors the CLI's
13
+ * `--allow-all`). An empty list denies everything (effectively read-only).
14
+ * - an audit hook — `onAutoApprove` fires for every non-denied call so an
15
+ * autonomous run leaves a trail of what it ran (channels log it).
16
+ */
17
+ export interface AuditedAllowListOptions {
18
+ /** Resolver name surfaced in permission events (e.g. 'slack-allow-list'). */
19
+ readonly name: string;
20
+ readonly allowedTools: ReadonlyArray<string>;
21
+ /** Every registered tool name — the expansion target for `'*'`. */
22
+ readonly allToolNames: ReadonlyArray<string>;
23
+ readonly onAutoApprove?: (call: PendingToolCall, info: {
24
+ readonly wildcard: boolean;
25
+ }) => void;
26
+ }
27
+ export declare function createAuditedAllowListResolver(opts: AuditedAllowListOptions): PermissionResolver;
28
+ //# sourceMappingURL=permission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEtE;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,uBAAuB;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7C,mEAAmE;IACnE,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,CACvB,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,KACjC,IAAI,CAAC;CACX;AAED,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,uBAAuB,GAAG,kBAAkB,CAehG"}
@@ -0,0 +1,17 @@
1
+ import { createAllowListResolver } from '@moxxy/sdk';
2
+ export function createAuditedAllowListResolver(opts) {
3
+ const wildcard = opts.allowedTools.includes('*');
4
+ const effective = wildcard ? [...opts.allToolNames] : [...opts.allowedTools];
5
+ const inner = createAllowListResolver(effective);
6
+ return {
7
+ name: opts.name,
8
+ async check(call, ctx) {
9
+ const decision = await inner.check(call, ctx);
10
+ if (decision.mode !== 'deny') {
11
+ opts.onAutoApprove?.(call, { wildcard });
12
+ }
13
+ return decision;
14
+ },
15
+ };
16
+ }
17
+ //# sourceMappingURL=permission.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission.js","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AA8BrD,MAAM,UAAU,8BAA8B,CAAC,IAA6B;IAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC;IAC7E,MAAM,KAAK,GAAG,uBAAuB,CAAC,SAAS,CAAC,CAAC;IAEjD,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG;YACnB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC9C,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC7B,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC3C,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { MoxxyEvent } from '@moxxy/sdk';
2
+ /**
3
+ * Accumulate streamed assistant text from the event log into a single growing
4
+ * snapshot — the minimal plain-text turn renderer for channels without a rich
5
+ * frame format (Slack v1 and future thin adapters). `assistant_chunk` deltas
6
+ * render live; the final `assistant_message` content (which supersedes the
7
+ * streamed deltas for the same turn) wins so the last edit always carries the
8
+ * complete reply.
9
+ */
10
+ export declare class PlainTurnRenderer {
11
+ private streamed;
12
+ private finalText;
13
+ /** Returns true when the event changed the snapshot (schedule an edit). */
14
+ accept(event: MoxxyEvent): boolean;
15
+ snapshot(): string;
16
+ }
17
+ //# sourceMappingURL=plain-turn-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plain-turn-renderer.d.ts","sourceRoot":"","sources":["../src/plain-turn-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;GAOG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,SAAS,CAAuB;IAExC,2EAA2E;IAC3E,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO;IAYlC,QAAQ,IAAI,MAAM;CAGnB"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Accumulate streamed assistant text from the event log into a single growing
3
+ * snapshot — the minimal plain-text turn renderer for channels without a rich
4
+ * frame format (Slack v1 and future thin adapters). `assistant_chunk` deltas
5
+ * render live; the final `assistant_message` content (which supersedes the
6
+ * streamed deltas for the same turn) wins so the last edit always carries the
7
+ * complete reply.
8
+ */
9
+ export class PlainTurnRenderer {
10
+ streamed = '';
11
+ finalText = null;
12
+ /** Returns true when the event changed the snapshot (schedule an edit). */
13
+ accept(event) {
14
+ if (event.type === 'assistant_chunk') {
15
+ this.streamed += event.delta;
16
+ return true;
17
+ }
18
+ if (event.type === 'assistant_message') {
19
+ this.finalText = event.content;
20
+ return true;
21
+ }
22
+ return false;
23
+ }
24
+ snapshot() {
25
+ return (this.finalText ?? this.streamed).trim();
26
+ }
27
+ }
28
+ //# sourceMappingURL=plain-turn-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plain-turn-renderer.js","sourceRoot":"","sources":["../src/plain-turn-renderer.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAiB;IACpB,QAAQ,GAAG,EAAE,CAAC;IACd,SAAS,GAAkB,IAAI,CAAC;IAExC,2EAA2E;IAC3E,MAAM,CAAC,KAAiB;QACtB,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACrC,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,KAAK,CAAC;YAC7B,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;YACvC,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC;YAC/B,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,QAAQ;QACN,OAAO,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;IAClD,CAAC;CACF"}
@@ -0,0 +1,18 @@
1
+ /** The read slice of the vault a channel secret lookup needs. */
2
+ export interface SecretReader {
3
+ get(name: string): Promise<string | null>;
4
+ }
5
+ export interface SecretSpec {
6
+ /** Env override — beats the vault, matching every channel's precedence. */
7
+ readonly envVar?: string;
8
+ /** Vault key the channel stores the secret under. */
9
+ readonly vaultKey: string;
10
+ }
11
+ /**
12
+ * Resolve a channel secret: env override first, then the vault. Values are
13
+ * trimmed and empty strings are treated as unset (an env var set to whitespace
14
+ * falls through to the vault rather than masking it). Returns null when
15
+ * neither source has a value.
16
+ */
17
+ export declare function resolveSecret(vault: SecretReader, spec: SecretSpec): Promise<string | null>;
18
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,UAAU;IACzB,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOjG"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Resolve a channel secret: env override first, then the vault. Values are
3
+ * trimmed and empty strings are treated as unset (an env var set to whitespace
4
+ * falls through to the vault rather than masking it). Returns null when
5
+ * neither source has a value.
6
+ */
7
+ export async function resolveSecret(vault, spec) {
8
+ if (spec.envVar) {
9
+ const fromEnv = process.env[spec.envVar]?.trim();
10
+ if (fromEnv)
11
+ return fromEnv;
12
+ }
13
+ const stored = (await vault.get(spec.vaultKey))?.trim();
14
+ return stored || null;
15
+ }
16
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAYA;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAmB,EAAE,IAAgB;IACvE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;QACjD,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;IAC9B,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;IACxD,OAAO,MAAM,IAAI,IAAI,CAAC;AACxB,CAAC"}
package/dist/turn.d.ts ADDED
@@ -0,0 +1,96 @@
1
+ import type { MoxxyEvent, RunTurnOptions, TurnId } from '@moxxy/sdk';
2
+ /**
3
+ * Turn machinery shared by messaging channels that multiplex onto ONE shared
4
+ * Session (AGENTS.md invariant #8: filter event-log subscribers by turnId —
5
+ * `session.log` fans out to every listener, so concurrent turns on the same
6
+ * Session cross-contaminate without it).
7
+ *
8
+ * Pieces are deliberately composable rather than one monolithic runner so each
9
+ * channel keeps its exact flush / typing / unwind ordering:
10
+ * - {@link TurnCoordinator} — single-flight `busy` guard, per-turn
11
+ * AbortController, bounded own-turn-id tracking, foreign-turn mirror gate.
12
+ * - {@link subscribeTurn} — turnId-filtered `session.log.subscribe`.
13
+ * - {@link driveTurn} — drain `session.runTurn` for a pre-minted turnId.
14
+ */
15
+ /** The slice of a session a channel turn needs: the live event log. */
16
+ export interface TurnEventSource {
17
+ readonly log: {
18
+ subscribe(fn: (event: MoxxyEvent) => void | Promise<void>): () => void;
19
+ };
20
+ }
21
+ /** The slice of a session `driveTurn` needs. `ClientSession` satisfies it. */
22
+ export interface TurnSession extends TurnEventSource {
23
+ runTurn(prompt: string, opts?: RunTurnOptions): AsyncIterable<MoxxyEvent>;
24
+ }
25
+ /**
26
+ * Subscribe to ONLY the given turn's events. Returns the unsubscribe function.
27
+ * The caller controls unsubscribe placement (channels keep their final flush
28
+ * inside the subscription window so trailing events still feed the renderer).
29
+ */
30
+ export declare function subscribeTurn(source: TurnEventSource, turnId: TurnId, onEvent: (event: MoxxyEvent) => void): () => void;
31
+ export interface DriveTurnOptions {
32
+ readonly turnId: TurnId;
33
+ readonly prompt: string;
34
+ readonly model?: string | undefined;
35
+ readonly signal: AbortSignal;
36
+ }
37
+ /**
38
+ * Run one turn to completion, draining the `runTurn` iterator. Rendering is
39
+ * expected to happen via a {@link subscribeTurn} subscription, not the yielded
40
+ * events (which are identical); errors propagate to the caller.
41
+ */
42
+ export declare function driveTurn(session: TurnSession, opts: DriveTurnOptions): Promise<void>;
43
+ /** A granted turn slot. Call `end()` in a `finally` to release single-flight. */
44
+ export interface TurnLease {
45
+ readonly turnId: TurnId;
46
+ /** Per-turn controller so a /cancel or channel stop aborts ONLY this turn
47
+ * without poisoning the session-level signal other channels share. */
48
+ readonly controller: AbortController;
49
+ end(): void;
50
+ }
51
+ export interface TurnCoordinatorOptions {
52
+ /**
53
+ * Bound on remembered own-turn ids so a long-lived channel can't leak; a
54
+ * handful of recent ids is enough to dedup late/replayed events. Default 64.
55
+ */
56
+ readonly maxOwnTurnIds?: number;
57
+ }
58
+ /**
59
+ * Single-flight turn state for a channel: one turn at a time, a per-turn
60
+ * AbortController, and a bounded set of turnIds THIS channel initiated.
61
+ *
62
+ * The own-turn-id set is what {@link mirrorText} filters on (invariant #8)
63
+ * rather than the coarse `busy` flag alone — so an `assistant_message`
64
+ * dispatched for our own turn AFTER `busy` flips false (async event ordering /
65
+ * RemoteSession replay) isn't re-mirrored as foreign.
66
+ */
67
+ export declare class TurnCoordinator {
68
+ private busyFlag;
69
+ private active;
70
+ private readonly ownTurnIds;
71
+ private readonly maxOwnTurnIds;
72
+ constructor(opts?: TurnCoordinatorOptions);
73
+ get busy(): boolean;
74
+ /** The in-flight turn's controller (null when idle) — for /cancel handlers. */
75
+ get controller(): AbortController | null;
76
+ /**
77
+ * Atomically claim the single turn slot. Synchronous on purpose: set `busy`
78
+ * BEFORE any await so a concurrently dispatched second turn can't slip past
79
+ * the guard. Returns null when a turn is already running (the channel replies
80
+ * "still working"); otherwise a lease whose `end()` releases the slot.
81
+ */
82
+ begin(turnId: TurnId): TurnLease | null;
83
+ /** Abort the in-flight turn (stop / cancel paths). No-op when idle. */
84
+ abort(reason: string): void;
85
+ isOwn(turnId: string): boolean;
86
+ /**
87
+ * Foreign-turn mirror gate: returns the assistant prose to mirror for a turn
88
+ * this channel did NOT initiate (e.g. a co-attached surface ran one), or null
89
+ * when the event must not be mirrored — not an `assistant_message`, one of
90
+ * our own turnIds (invariant #8), a turn of ours currently rendering via the
91
+ * frame pump (`busy`), or empty content. The channel adds its own
92
+ * "have I served a target yet" check and does the send.
93
+ */
94
+ mirrorText(event: MoxxyEvent): string | null;
95
+ }
96
+ //# sourceMappingURL=turn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn.d.ts","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAErE;;;;;;;;;;;;GAYG;AAEH,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE;QACZ,SAAS,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC;KACxE,CAAC;CACH;AAED,8EAA8E;AAC9E,MAAM,WAAW,WAAY,SAAQ,eAAe;IAClD,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;CAC3E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GACnC,MAAM,IAAI,CAKZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ3F;AAED,iFAAiF;AACjF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;2EACuE;IACvE,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IACrC,GAAG,IAAI,IAAI,CAAC;CACb;AAED,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,IAAI,GAAE,sBAA2B;IAI7C,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,+EAA+E;IAC/E,IAAI,UAAU,IAAI,eAAe,GAAG,IAAI,CAEvC;IAED;;;;;OAKG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAoBvC,uEAAuE;IACvE,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAI9B;;;;;;;OAOG;IACH,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI;CAO7C"}