@proletariat/cli 0.3.110 → 0.3.112

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 (86) hide show
  1. package/dist/commands/gateway/connect.d.ts +33 -0
  2. package/dist/commands/gateway/connect.js +130 -0
  3. package/dist/commands/gateway/connect.js.map +1 -0
  4. package/dist/commands/gateway/disconnect.d.ts +21 -0
  5. package/dist/commands/gateway/disconnect.js +69 -0
  6. package/dist/commands/gateway/disconnect.js.map +1 -0
  7. package/dist/commands/gateway/start.d.ts +23 -0
  8. package/dist/commands/gateway/start.js +133 -0
  9. package/dist/commands/gateway/start.js.map +1 -0
  10. package/dist/commands/gateway/status.d.ts +16 -0
  11. package/dist/commands/gateway/status.js +76 -0
  12. package/dist/commands/gateway/status.js.map +1 -0
  13. package/dist/commands/gateway/test.d.ts +22 -0
  14. package/dist/commands/gateway/test.js +83 -0
  15. package/dist/commands/gateway/test.js.map +1 -0
  16. package/dist/commands/orchestrator/attach.d.ts +2 -0
  17. package/dist/commands/orchestrator/attach.js +80 -118
  18. package/dist/commands/orchestrator/attach.js.map +1 -1
  19. package/dist/commands/orchestrator/start.js +21 -0
  20. package/dist/commands/orchestrator/start.js.map +1 -1
  21. package/dist/commands/orchestrator/status.d.ts +3 -0
  22. package/dist/commands/orchestrator/status.js +104 -130
  23. package/dist/commands/orchestrator/status.js.map +1 -1
  24. package/dist/commands/orchestrator/stop.d.ts +2 -0
  25. package/dist/commands/orchestrator/stop.js +105 -107
  26. package/dist/commands/orchestrator/stop.js.map +1 -1
  27. package/dist/commands/reconcile.d.ts +29 -0
  28. package/dist/commands/reconcile.js +140 -0
  29. package/dist/commands/reconcile.js.map +1 -0
  30. package/dist/commands/session/attach.d.ts +2 -6
  31. package/dist/commands/session/attach.js +68 -97
  32. package/dist/commands/session/attach.js.map +1 -1
  33. package/dist/commands/session/list.d.ts +4 -1
  34. package/dist/commands/session/list.js +160 -326
  35. package/dist/commands/session/list.js.map +1 -1
  36. package/dist/commands/work/ship.js +131 -61
  37. package/dist/commands/work/ship.js.map +1 -1
  38. package/dist/commands/work/start.js +104 -49
  39. package/dist/commands/work/start.js.map +1 -1
  40. package/dist/lib/execution/session-utils.d.ts +4 -1
  41. package/dist/lib/execution/session-utils.js +3 -0
  42. package/dist/lib/execution/session-utils.js.map +1 -1
  43. package/dist/lib/gateway/channel-factory.d.ts +13 -0
  44. package/dist/lib/gateway/channel-factory.js +37 -0
  45. package/dist/lib/gateway/channel-factory.js.map +1 -0
  46. package/dist/lib/gateway/channels/telegram.d.ts +115 -0
  47. package/dist/lib/gateway/channels/telegram.js +215 -0
  48. package/dist/lib/gateway/channels/telegram.js.map +1 -0
  49. package/dist/lib/gateway/router.d.ts +84 -0
  50. package/dist/lib/gateway/router.js +140 -0
  51. package/dist/lib/gateway/router.js.map +1 -0
  52. package/dist/lib/gateway/session-poker.d.ts +35 -0
  53. package/dist/lib/gateway/session-poker.js +85 -0
  54. package/dist/lib/gateway/session-poker.js.map +1 -0
  55. package/dist/lib/gateway/types.d.ts +124 -0
  56. package/dist/lib/gateway/types.js +17 -0
  57. package/dist/lib/gateway/types.js.map +1 -0
  58. package/dist/lib/machine-db-mirror.d.ts +64 -0
  59. package/dist/lib/machine-db-mirror.js +82 -0
  60. package/dist/lib/machine-db-mirror.js.map +1 -0
  61. package/dist/lib/machine-db.d.ts +98 -0
  62. package/dist/lib/machine-db.js +152 -0
  63. package/dist/lib/machine-db.js.map +1 -1
  64. package/dist/lib/orchestrate/prompt-chain.d.ts +19 -4
  65. package/dist/lib/orchestrate/prompt-chain.js +19 -4
  66. package/dist/lib/orchestrate/prompt-chain.js.map +1 -1
  67. package/dist/lib/pr/index.d.ts +34 -2
  68. package/dist/lib/pr/index.js +95 -4
  69. package/dist/lib/pr/index.js.map +1 -1
  70. package/dist/lib/reconcile/core.d.ts +62 -0
  71. package/dist/lib/reconcile/core.js +137 -0
  72. package/dist/lib/reconcile/core.js.map +1 -0
  73. package/dist/lib/reconcile/index.d.ts +54 -0
  74. package/dist/lib/reconcile/index.js +377 -0
  75. package/dist/lib/reconcile/index.js.map +1 -0
  76. package/dist/lib/reconcile/types.d.ts +133 -0
  77. package/dist/lib/reconcile/types.js +16 -0
  78. package/dist/lib/reconcile/types.js.map +1 -0
  79. package/dist/lib/session/renderer.d.ts +121 -0
  80. package/dist/lib/session/renderer.js +547 -0
  81. package/dist/lib/session/renderer.js.map +1 -0
  82. package/dist/lib/update-check.d.ts +64 -7
  83. package/dist/lib/update-check.js +164 -20
  84. package/dist/lib/update-check.js.map +1 -1
  85. package/oclif.manifest.json +1203 -750
  86. package/package.json +1 -1
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Channel factory (PRLT-1255)
3
+ *
4
+ * Single place that knows how to decode a persisted MessagingChannelRecord
5
+ * into a live MessagingChannel instance. Commands and the gateway daemon
6
+ * call this so they never import individual adapters directly.
7
+ *
8
+ * To add Slack/Discord/WhatsApp later: import the new adapter and add a
9
+ * case to `buildChannelFromRecord`. No other file needs to change.
10
+ */
11
+ import { TelegramChannel } from './channels/telegram.js';
12
+ export function buildChannelFromRecord(record) {
13
+ switch (record.type) {
14
+ case 'telegram': {
15
+ const config = parseConfig(record.configJson, record.name);
16
+ if (!config.token) {
17
+ throw new Error(`Channel "${record.name}" has no Telegram token configured`);
18
+ }
19
+ if (!Array.isArray(config.allowlist)) {
20
+ throw new TypeError(`Channel "${record.name}" has a malformed allowlist`);
21
+ }
22
+ return new TelegramChannel({ config });
23
+ }
24
+ default:
25
+ throw new Error(`Unknown channel type "${record.type}" for "${record.name}"`);
26
+ }
27
+ }
28
+ function parseConfig(json, channelName) {
29
+ try {
30
+ return JSON.parse(json);
31
+ }
32
+ catch (err) {
33
+ const msg = err instanceof Error ? err.message : String(err);
34
+ throw new Error(`Channel "${channelName}" has invalid config JSON: ${msg}`);
35
+ }
36
+ }
37
+ //# sourceMappingURL=channel-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-factory.js","sourceRoot":"","sources":["../../../src/lib/gateway/channel-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,MAAM,UAAU,sBAAsB,CAAC,MAA8B;IACnE,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,MAAM,GAAG,WAAW,CAAwB,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;YACjF,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,YAAY,MAAM,CAAC,IAAI,oCAAoC,CAAC,CAAA;YAC9E,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;gBACrC,MAAM,IAAI,SAAS,CAAC,YAAY,MAAM,CAAC,IAAI,6BAA6B,CAAC,CAAA;YAC3E,CAAC;YACD,OAAO,IAAI,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;QACxC,CAAC;QACD;YACE,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,CAAC,IAAI,UAAU,MAAM,CAAC,IAAI,GAAG,CAAC,CAAA;IACjF,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAI,IAAY,EAAE,WAAmB;IACvD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAA;IAC9B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC5D,MAAM,IAAI,KAAK,CAAC,YAAY,WAAW,8BAA8B,GAAG,EAAE,CAAC,CAAA;IAC7E,CAAC;AACH,CAAC"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Telegram Channel Adapter (PRLT-1255)
3
+ *
4
+ * Polling-mode Telegram bot that implements MessagingChannel.
5
+ *
6
+ * Uses Telegram's `getUpdates` long-polling API — no webhook, no server
7
+ * required. Messages arrive over an outgoing HTTPS request, so the bot
8
+ * works fine from behind NAT, on a laptop, or in a container.
9
+ *
10
+ * This adapter is the ONLY Telegram-aware code in the gateway. To add
11
+ * Slack/Discord/WhatsApp later: write a new adapter next to this file,
12
+ * implementing the same interface. The router and commands do not need
13
+ * to change.
14
+ *
15
+ * @see PRLT-1251 (Messaging Gateway epic)
16
+ */
17
+ import type { ChannelAddress, MessageHandler, MessagingChannel, TelegramChannelConfig } from '../types.js';
18
+ interface TelegramUser {
19
+ id: number;
20
+ is_bot?: boolean;
21
+ first_name?: string;
22
+ last_name?: string;
23
+ username?: string;
24
+ }
25
+ interface TelegramChat {
26
+ id: number;
27
+ type: 'private' | 'group' | 'supergroup' | 'channel';
28
+ title?: string;
29
+ username?: string;
30
+ first_name?: string;
31
+ }
32
+ interface TelegramMessage {
33
+ message_id: number;
34
+ from?: TelegramUser;
35
+ chat: TelegramChat;
36
+ date: number;
37
+ text?: string;
38
+ }
39
+ export interface TelegramUpdate {
40
+ update_id: number;
41
+ message?: TelegramMessage;
42
+ edited_message?: TelegramMessage;
43
+ }
44
+ /**
45
+ * Minimal Telegram Bot API client. Broken out so tests can substitute a
46
+ * fake client without touching the network.
47
+ */
48
+ export interface TelegramClient {
49
+ getUpdates(options: {
50
+ offset?: number;
51
+ timeoutSec?: number;
52
+ }): Promise<TelegramUpdate[]>;
53
+ sendMessage(chatId: string | number, text: string): Promise<void>;
54
+ }
55
+ /**
56
+ * Options for constructing a TelegramChannel.
57
+ *
58
+ * - `config`: TelegramChannelConfig loaded from machine.db.
59
+ * - `client`: optional injectable Telegram client (tests use this).
60
+ * - `logger`: optional error/info sink. Defaults to console.
61
+ */
62
+ export interface TelegramChannelOptions {
63
+ config: TelegramChannelConfig;
64
+ client?: TelegramClient;
65
+ logger?: {
66
+ info?: (msg: string) => void;
67
+ error?: (msg: string, err?: unknown) => void;
68
+ };
69
+ /**
70
+ * How long to back off after a failed poll. Defaults to 5000ms.
71
+ * Tests shorten this so they don't have to sit on a 5s sleep.
72
+ */
73
+ errorBackoffMs?: number;
74
+ }
75
+ /**
76
+ * Polling-mode Telegram adapter.
77
+ *
78
+ * The read loop is a vanilla `while (running) await getUpdates()`. We
79
+ * acknowledge updates by advancing the `offset` cursor to the id after
80
+ * the highest update seen in the batch — this is Telegram's documented
81
+ * delete semantics for getUpdates.
82
+ */
83
+ export declare class TelegramChannel implements MessagingChannel {
84
+ readonly name = "telegram";
85
+ private handler;
86
+ private running;
87
+ private loopPromise;
88
+ private offset;
89
+ private readonly client;
90
+ private readonly config;
91
+ private readonly logger;
92
+ private readonly errorBackoffMs;
93
+ constructor(options: TelegramChannelOptions);
94
+ onMessage(handler: MessageHandler): void;
95
+ start(): Promise<void>;
96
+ stop(): Promise<void>;
97
+ sendMessage(to: ChannelAddress, text: string): Promise<void>;
98
+ /**
99
+ * Main polling loop. Runs until `stop()` flips `running` false.
100
+ *
101
+ * Errors are caught, logged, and followed by a small backoff so a
102
+ * transient network blip doesn't hot-spin the bot against Telegram.
103
+ *
104
+ * The awaits are deliberately sequential: Telegram long-poll demands
105
+ * one outstanding getUpdates at a time per bot, and we must drain
106
+ * each batch before advancing the cursor.
107
+ */
108
+ private runLoop;
109
+ /**
110
+ * Normalize a Telegram update into a generic `Message` and hand it to
111
+ * the router. Drops non-text messages silently (MVP is text-only).
112
+ */
113
+ private handleUpdate;
114
+ }
115
+ export {};
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Telegram Channel Adapter (PRLT-1255)
3
+ *
4
+ * Polling-mode Telegram bot that implements MessagingChannel.
5
+ *
6
+ * Uses Telegram's `getUpdates` long-polling API — no webhook, no server
7
+ * required. Messages arrive over an outgoing HTTPS request, so the bot
8
+ * works fine from behind NAT, on a laptop, or in a container.
9
+ *
10
+ * This adapter is the ONLY Telegram-aware code in the gateway. To add
11
+ * Slack/Discord/WhatsApp later: write a new adapter next to this file,
12
+ * implementing the same interface. The router and commands do not need
13
+ * to change.
14
+ *
15
+ * @see PRLT-1251 (Messaging Gateway epic)
16
+ */
17
+ class HttpTelegramClient {
18
+ token;
19
+ constructor(token) {
20
+ this.token = token;
21
+ }
22
+ url(method) {
23
+ return `https://api.telegram.org/bot${this.token}/${method}`;
24
+ }
25
+ async getUpdates(options) {
26
+ const params = new URLSearchParams();
27
+ if (options.offset !== undefined)
28
+ params.set('offset', String(options.offset));
29
+ params.set('timeout', String(options.timeoutSec ?? 25));
30
+ // Only request message updates — we don't need callback queries, polls, etc.
31
+ params.set('allowed_updates', JSON.stringify(['message']));
32
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins -- global fetch is stable in Node 20+
33
+ const res = await fetch(`${this.url('getUpdates')}?${params.toString()}`);
34
+ if (!res.ok) {
35
+ throw new Error(`Telegram getUpdates failed: HTTP ${res.status}`);
36
+ }
37
+ const body = (await res.json());
38
+ if (!body.ok) {
39
+ throw new Error(`Telegram getUpdates failed: ${body.description ?? 'unknown'}`);
40
+ }
41
+ return body.result ?? [];
42
+ }
43
+ async sendMessage(chatId, text) {
44
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins -- global fetch is stable in Node 20+
45
+ const res = await fetch(this.url('sendMessage'), {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: JSON.stringify({ chat_id: chatId, text }),
49
+ });
50
+ if (!res.ok) {
51
+ throw new Error(`Telegram sendMessage failed: HTTP ${res.status}`);
52
+ }
53
+ const body = (await res.json());
54
+ if (!body.ok) {
55
+ throw new Error(`Telegram sendMessage failed: ${body.description ?? 'unknown'}`);
56
+ }
57
+ }
58
+ }
59
+ /**
60
+ * Polling-mode Telegram adapter.
61
+ *
62
+ * The read loop is a vanilla `while (running) await getUpdates()`. We
63
+ * acknowledge updates by advancing the `offset` cursor to the id after
64
+ * the highest update seen in the batch — this is Telegram's documented
65
+ * delete semantics for getUpdates.
66
+ */
67
+ export class TelegramChannel {
68
+ name = 'telegram';
69
+ handler = null;
70
+ running = false;
71
+ loopPromise = null;
72
+ offset;
73
+ client;
74
+ config;
75
+ logger;
76
+ errorBackoffMs;
77
+ constructor(options) {
78
+ this.config = options.config;
79
+ this.client = options.client ?? new HttpTelegramClient(options.config.token);
80
+ this.logger = options.logger ?? {};
81
+ this.errorBackoffMs = options.errorBackoffMs ?? 5000;
82
+ }
83
+ onMessage(handler) {
84
+ this.handler = handler;
85
+ }
86
+ async start() {
87
+ if (this.running)
88
+ return;
89
+ if (!this.handler) {
90
+ throw new Error('TelegramChannel.start() called before onMessage() — no handler registered');
91
+ }
92
+ this.running = true;
93
+ this.loopPromise = this.runLoop();
94
+ }
95
+ async stop() {
96
+ if (!this.running)
97
+ return;
98
+ this.running = false;
99
+ if (this.loopPromise) {
100
+ try {
101
+ await this.loopPromise;
102
+ }
103
+ catch {
104
+ // Loop errors are already logged; ignore here so stop() is always clean.
105
+ }
106
+ this.loopPromise = null;
107
+ }
108
+ }
109
+ async sendMessage(to, text) {
110
+ if (to.channel !== this.name) {
111
+ throw new Error(`TelegramChannel cannot send to channel=${to.channel}`);
112
+ }
113
+ await this.client.sendMessage(to.id, text);
114
+ }
115
+ // ---------------------------------------------------------------------------
116
+ // Internals
117
+ // ---------------------------------------------------------------------------
118
+ /**
119
+ * Main polling loop. Runs until `stop()` flips `running` false.
120
+ *
121
+ * Errors are caught, logged, and followed by a small backoff so a
122
+ * transient network blip doesn't hot-spin the bot against Telegram.
123
+ *
124
+ * The awaits are deliberately sequential: Telegram long-poll demands
125
+ * one outstanding getUpdates at a time per bot, and we must drain
126
+ * each batch before advancing the cursor.
127
+ */
128
+ /* eslint-disable no-await-in-loop -- polling loop is intentionally sequential */
129
+ async runLoop() {
130
+ const pollInterval = this.config.pollIntervalMs ?? 0;
131
+ while (this.running) {
132
+ try {
133
+ const updates = await this.client.getUpdates({
134
+ offset: this.offset,
135
+ timeoutSec: 25,
136
+ });
137
+ for (const update of updates) {
138
+ await this.handleUpdate(update);
139
+ // Advance cursor past the highest update id we've seen.
140
+ this.offset = update.update_id + 1;
141
+ }
142
+ if (pollInterval > 0)
143
+ await sleep(pollInterval);
144
+ }
145
+ catch (err) {
146
+ this.logger.error?.('telegram: poll loop error', err);
147
+ // Backoff on errors so we don't hammer the API when the network
148
+ // is flaky or the token got revoked.
149
+ await sleep(this.errorBackoffMs);
150
+ }
151
+ }
152
+ }
153
+ /* eslint-enable no-await-in-loop */
154
+ /**
155
+ * Normalize a Telegram update into a generic `Message` and hand it to
156
+ * the router. Drops non-text messages silently (MVP is text-only).
157
+ */
158
+ async handleUpdate(update) {
159
+ const msg = update.message ?? update.edited_message;
160
+ if (!msg)
161
+ return;
162
+ if (!msg.text)
163
+ return; // MVP: text-only
164
+ if (!this.handler)
165
+ return;
166
+ // Reject messages from users not on the allowlist. We match on the
167
+ // sender's user id (falling back to chat id for groups where there
168
+ // is no `from`). Allowlist is explicit — empty list means "nobody".
169
+ const senderId = String(msg.from?.id ?? msg.chat.id);
170
+ if (!this.config.allowlist.includes(senderId)) {
171
+ this.logger.info?.(`telegram: dropping message from non-allowlisted user ${senderId}`);
172
+ return;
173
+ }
174
+ const address = {
175
+ channel: this.name,
176
+ // We always send replies to the chat, not the user — that way
177
+ // group chats work correctly in a future multi-user mode.
178
+ id: String(msg.chat.id),
179
+ displayName: resolveDisplayName(msg),
180
+ };
181
+ const normalized = {
182
+ id: `telegram:${msg.message_id}`,
183
+ channel: this.name,
184
+ from: address,
185
+ text: msg.text,
186
+ timestamp: new Date(msg.date * 1000),
187
+ };
188
+ try {
189
+ await this.handler(normalized);
190
+ }
191
+ catch (err) {
192
+ this.logger.error?.('telegram: handler failed', err);
193
+ }
194
+ }
195
+ }
196
+ // =============================================================================
197
+ // Helpers
198
+ // =============================================================================
199
+ function resolveDisplayName(msg) {
200
+ const from = msg.from;
201
+ if (from?.username)
202
+ return `@${from.username}`;
203
+ if (from?.first_name) {
204
+ return from.last_name ? `${from.first_name} ${from.last_name}` : from.first_name;
205
+ }
206
+ if (msg.chat.title)
207
+ return msg.chat.title;
208
+ if (msg.chat.username)
209
+ return `@${msg.chat.username}`;
210
+ return undefined;
211
+ }
212
+ function sleep(ms) {
213
+ return new Promise(resolve => setTimeout(resolve, ms));
214
+ }
215
+ //# sourceMappingURL=telegram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram.js","sourceRoot":"","sources":["../../../../src/lib/gateway/channels/telegram.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Messaging Gateway Router (PRLT-1255)
3
+ *
4
+ * Sits between N `MessagingChannel` adapters and the `prlt session poke`
5
+ * path. The router is intentionally small — it does four things:
6
+ *
7
+ * 1. Look up (or create) the agent mapping for `(channel, user)`.
8
+ * 2. Forward the user's text to that agent via a `SessionPoker`.
9
+ * 3. Ship the agent's response back via the channel's `sendMessage`.
10
+ * 4. Stamp channel + route usage timestamps in machine.db.
11
+ *
12
+ * Everything Telegram-specific lives in `channels/telegram.ts`. Everything
13
+ * storage-specific lives in `MachineDB`. This file owns the glue.
14
+ */
15
+ import type { MachineDB } from '../machine-db.js';
16
+ import type { MessagingChannel, Message } from './types.js';
17
+ /**
18
+ * Indirection over `prlt session poke`. The default implementation shells
19
+ * out to the globally-installed `prlt` binary, but tests and embedders can
20
+ * inject a custom poker.
21
+ *
22
+ * Returning `null` means "message delivered, no response captured".
23
+ * Throwing means "failed to reach the agent at all".
24
+ */
25
+ export interface SessionPoker {
26
+ poke(agent: string, message: string, options?: {
27
+ waitTimeoutSec?: number;
28
+ }): Promise<string | null>;
29
+ }
30
+ export interface MessagingGatewayOptions {
31
+ /** Machine DB handle used for channel/route persistence. */
32
+ db: MachineDB;
33
+ /** How to forward messages to an agent. Defaults to the shell poker. */
34
+ sessionPoker: SessionPoker;
35
+ /**
36
+ * Called when there is no existing route for a user. Must return the
37
+ * agent session id (typically an agent name) that should own this
38
+ * conversation going forward. Returning `null` rejects the message —
39
+ * useful for "deny if no mapping" policies.
40
+ */
41
+ resolveAgentForNewUser: (msg: Message) => Promise<string | null> | string | null;
42
+ /** Optional logger sink. Defaults to console. */
43
+ logger?: {
44
+ info?: (msg: string) => void;
45
+ error?: (msg: string, err?: unknown) => void;
46
+ };
47
+ /**
48
+ * How long to wait for an agent response before giving up and sending
49
+ * a "still working on it" message back. Defaults to 90 seconds.
50
+ */
51
+ waitTimeoutSec?: number;
52
+ }
53
+ export declare class MessagingGateway {
54
+ private channels;
55
+ private readonly db;
56
+ private readonly poker;
57
+ private readonly resolveAgentForNewUser;
58
+ private readonly logger;
59
+ private readonly waitTimeoutSec;
60
+ private started;
61
+ constructor(options: MessagingGatewayOptions);
62
+ /**
63
+ * Register a channel adapter. Wires the inbound handler but does NOT
64
+ * start the channel — the caller decides when to start() everything.
65
+ */
66
+ registerChannel(channel: MessagingChannel): void;
67
+ /** Get a previously-registered channel by name. */
68
+ getChannel(name: string): MessagingChannel | undefined;
69
+ /** List names of all registered channels. */
70
+ listChannelNames(): string[];
71
+ /** Start every registered channel in parallel. */
72
+ start(): Promise<void>;
73
+ /** Stop every registered channel in parallel. Safe to call multiple times. */
74
+ stop(): Promise<void>;
75
+ /**
76
+ * Route a single inbound message.
77
+ *
78
+ * This is exposed (not `private`) so tests can drive it directly
79
+ * without standing up a real channel adapter.
80
+ */
81
+ routeInbound(msg: Message): Promise<void>;
82
+ private createRouteForNewUser;
83
+ private safeReply;
84
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Messaging Gateway Router (PRLT-1255)
3
+ *
4
+ * Sits between N `MessagingChannel` adapters and the `prlt session poke`
5
+ * path. The router is intentionally small — it does four things:
6
+ *
7
+ * 1. Look up (or create) the agent mapping for `(channel, user)`.
8
+ * 2. Forward the user's text to that agent via a `SessionPoker`.
9
+ * 3. Ship the agent's response back via the channel's `sendMessage`.
10
+ * 4. Stamp channel + route usage timestamps in machine.db.
11
+ *
12
+ * Everything Telegram-specific lives in `channels/telegram.ts`. Everything
13
+ * storage-specific lives in `MachineDB`. This file owns the glue.
14
+ */
15
+ // =============================================================================
16
+ // MessagingGateway
17
+ // =============================================================================
18
+ export class MessagingGateway {
19
+ channels = new Map();
20
+ db;
21
+ poker;
22
+ resolveAgentForNewUser;
23
+ logger;
24
+ waitTimeoutSec;
25
+ started = false;
26
+ constructor(options) {
27
+ this.db = options.db;
28
+ this.poker = options.sessionPoker;
29
+ this.resolveAgentForNewUser = options.resolveAgentForNewUser;
30
+ this.logger = options.logger ?? {};
31
+ this.waitTimeoutSec = options.waitTimeoutSec ?? 90;
32
+ }
33
+ /**
34
+ * Register a channel adapter. Wires the inbound handler but does NOT
35
+ * start the channel — the caller decides when to start() everything.
36
+ */
37
+ registerChannel(channel) {
38
+ if (this.channels.has(channel.name)) {
39
+ throw new Error(`Channel already registered: ${channel.name}`);
40
+ }
41
+ channel.onMessage(msg => this.routeInbound(msg));
42
+ this.channels.set(channel.name, channel);
43
+ }
44
+ /** Get a previously-registered channel by name. */
45
+ getChannel(name) {
46
+ return this.channels.get(name);
47
+ }
48
+ /** List names of all registered channels. */
49
+ listChannelNames() {
50
+ return [...this.channels.keys()];
51
+ }
52
+ /** Start every registered channel in parallel. */
53
+ async start() {
54
+ if (this.started)
55
+ return;
56
+ this.started = true;
57
+ await Promise.all([...this.channels.values()].map(ch => ch.start()));
58
+ }
59
+ /** Stop every registered channel in parallel. Safe to call multiple times. */
60
+ async stop() {
61
+ if (!this.started)
62
+ return;
63
+ this.started = false;
64
+ await Promise.all([...this.channels.values()].map(ch => ch.stop()));
65
+ }
66
+ // ---------------------------------------------------------------------------
67
+ // Core routing
68
+ // ---------------------------------------------------------------------------
69
+ /**
70
+ * Route a single inbound message.
71
+ *
72
+ * This is exposed (not `private`) so tests can drive it directly
73
+ * without standing up a real channel adapter.
74
+ */
75
+ async routeInbound(msg) {
76
+ const channel = this.channels.get(msg.channel);
77
+ if (!channel) {
78
+ this.logger.error?.(`router: no channel registered for ${msg.channel}`);
79
+ return;
80
+ }
81
+ if (!msg.text || msg.text.trim().length === 0) {
82
+ // MVP: text-only. Voice notes land in PRLT-1234.
83
+ this.logger.info?.(`router: dropping empty message ${msg.id}`);
84
+ return;
85
+ }
86
+ // 1. Find or create the route.
87
+ let route = this.db.getMessagingRoute(msg.channel, msg.from.id);
88
+ if (!route) {
89
+ route = await this.createRouteForNewUser(msg);
90
+ if (!route) {
91
+ // Denied by policy — silently drop, do not leak whether the user
92
+ // exists to a possibly-hostile sender.
93
+ return;
94
+ }
95
+ }
96
+ // 2. Forward to the agent session.
97
+ let response = null;
98
+ try {
99
+ response = await this.poker.poke(route.agentSessionId, msg.text, {
100
+ waitTimeoutSec: this.waitTimeoutSec,
101
+ });
102
+ }
103
+ catch (err) {
104
+ this.logger.error?.(`router: poke failed for ${route.agentSessionId}`, err);
105
+ await this.safeReply(channel, msg, `Sorry, I couldn't reach ${route.agentSessionId}. Try again in a moment.`);
106
+ return;
107
+ }
108
+ // 3. Stamp usage.
109
+ this.db.touchMessagingRoute(msg.channel, msg.from.id);
110
+ this.db.touchMessagingChannel(msg.channel);
111
+ // 4. Reply (if we captured anything).
112
+ if (response && response.trim().length > 0) {
113
+ await this.safeReply(channel, msg, response);
114
+ }
115
+ }
116
+ // ---------------------------------------------------------------------------
117
+ // Helpers
118
+ // ---------------------------------------------------------------------------
119
+ async createRouteForNewUser(msg) {
120
+ const agentSessionId = await this.resolveAgentForNewUser(msg);
121
+ if (!agentSessionId) {
122
+ this.logger.info?.(`router: no agent resolved for ${msg.channel}:${msg.from.id}`);
123
+ return null;
124
+ }
125
+ return this.db.upsertMessagingRoute({
126
+ channel: msg.channel,
127
+ userId: msg.from.id,
128
+ agentSessionId,
129
+ });
130
+ }
131
+ async safeReply(channel, msg, text) {
132
+ try {
133
+ await channel.sendMessage(msg.from, text);
134
+ }
135
+ catch (err) {
136
+ this.logger.error?.(`router: sendMessage failed for ${channel.name}`, err);
137
+ }
138
+ }
139
+ }
140
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../../src/lib/gateway/router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Default SessionPoker implementation (PRLT-1255)
3
+ *
4
+ * Shells out to `prlt session poke <agent> <message> --wait --timeout N --json`
5
+ * and extracts the captured response from the JSON output.
6
+ *
7
+ * The router code itself is agnostic to this — tests inject a fake
8
+ * SessionPoker. This file exists so production code can pick up the real
9
+ * poker with zero wiring.
10
+ */
11
+ import type { SessionPoker } from './router.js';
12
+ export interface ShellSessionPokerOptions {
13
+ /**
14
+ * Name of the binary to exec. Defaults to `prlt` on PATH. Tests can
15
+ * point this at a shim script.
16
+ */
17
+ binary?: string;
18
+ /**
19
+ * Working directory for the exec call. Defaults to the current process
20
+ * cwd — `prlt session poke` resolves workspace context from there.
21
+ */
22
+ cwd?: string;
23
+ }
24
+ /**
25
+ * Shell-based SessionPoker. Uses `execFile` (no shell) so nothing in the
26
+ * message body can trigger shell expansion or injection.
27
+ */
28
+ export declare class ShellSessionPoker implements SessionPoker {
29
+ private readonly binary;
30
+ private readonly cwd;
31
+ constructor(options?: ShellSessionPokerOptions);
32
+ poke(agent: string, message: string, options?: {
33
+ waitTimeoutSec?: number;
34
+ }): Promise<string | null>;
35
+ }