@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,85 @@
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 { execFile } from 'node:child_process';
12
+ import { promisify } from 'node:util';
13
+ const execFileAsync = promisify(execFile);
14
+ /**
15
+ * Shell-based SessionPoker. Uses `execFile` (no shell) so nothing in the
16
+ * message body can trigger shell expansion or injection.
17
+ */
18
+ export class ShellSessionPoker {
19
+ binary;
20
+ cwd;
21
+ constructor(options = {}) {
22
+ this.binary = options.binary ?? 'prlt';
23
+ this.cwd = options.cwd;
24
+ }
25
+ async poke(agent, message, options) {
26
+ const timeoutSec = options?.waitTimeoutSec ?? 90;
27
+ const args = [
28
+ 'session',
29
+ 'poke',
30
+ agent,
31
+ message,
32
+ '--wait',
33
+ '--timeout',
34
+ String(timeoutSec),
35
+ '--json',
36
+ ];
37
+ const { stdout } = await execFileAsync(this.binary, args, {
38
+ cwd: this.cwd,
39
+ // Give the child a little longer than the internal wait so we
40
+ // don't race the --timeout.
41
+ timeout: (timeoutSec + 15) * 1000,
42
+ maxBuffer: 8 * 1024 * 1024,
43
+ });
44
+ // `prlt session poke --json` emits a single JSON object on success.
45
+ const parsed = parseFirstJsonObject(stdout);
46
+ if (!parsed)
47
+ return null;
48
+ if (typeof parsed.response === 'string')
49
+ return parsed.response;
50
+ return null;
51
+ }
52
+ }
53
+ /**
54
+ * Pull the first JSON object out of a mixed stdout stream. Needed because
55
+ * some oclif commands still emit human banners alongside `--json` output
56
+ * depending on terminal state.
57
+ */
58
+ function parseFirstJsonObject(stdout) {
59
+ const trimmed = stdout.trim();
60
+ if (!trimmed)
61
+ return null;
62
+ // Fast path: whole payload is JSON.
63
+ try {
64
+ const parsed = JSON.parse(trimmed);
65
+ if (parsed && typeof parsed === 'object')
66
+ return parsed;
67
+ }
68
+ catch {
69
+ // Fall through to scan.
70
+ }
71
+ const start = trimmed.indexOf('{');
72
+ const end = trimmed.lastIndexOf('}');
73
+ if (start === -1 || end === -1 || end < start)
74
+ return null;
75
+ try {
76
+ const parsed = JSON.parse(trimmed.slice(start, end + 1));
77
+ if (parsed && typeof parsed === 'object')
78
+ return parsed;
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ return null;
84
+ }
85
+ //# sourceMappingURL=session-poker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-poker.js","sourceRoot":"","sources":["../../../src/lib/gateway/session-poker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAGrC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAA;AAezC;;;GAGG;AACH,MAAM,OAAO,iBAAiB;IACX,MAAM,CAAQ;IACd,GAAG,CAAoB;IAExC,YAAY,UAAoC,EAAE;QAChD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAA;QACtC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAA;IACxB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAa,EAAE,OAAe,EAAE,OAAqC;QAC9E,MAAM,UAAU,GAAG,OAAO,EAAE,cAAc,IAAI,EAAE,CAAA;QAChD,MAAM,IAAI,GAAG;YACX,SAAS;YACT,MAAM;YACN,KAAK;YACL,OAAO;YACP,QAAQ;YACR,WAAW;YACX,MAAM,CAAC,UAAU,CAAC;YAClB,QAAQ;SACT,CAAA;QAED,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;YACxD,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,8DAA8D;YAC9D,4BAA4B;YAC5B,OAAO,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,GAAG,IAAI;YACjC,SAAS,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;SAC3B,CAAC,CAAA;QAEF,oEAAoE;QACpE,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAA;QAC3C,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA;QACxB,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ;YAAE,OAAO,MAAM,CAAC,QAAQ,CAAA;QAC/D,OAAO,IAAI,CAAA;IACb,CAAC;CACF;AAED;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,MAAc;IAC1C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAA;IAC7B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAA;IAEzB,oCAAoC;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAClC,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,MAAiC,CAAA;IACpF,CAAC;IAAC,MAAM,CAAC;QACP,wBAAwB;IAC1B,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAClC,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;IACpC,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,GAAG,GAAG,KAAK;QAAE,OAAO,IAAI,CAAA;IAE1D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;QACxD,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,MAAiC,CAAA;IACpF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Messaging Gateway Types
3
+ *
4
+ * Defines the channel-agnostic abstraction that sits between external
5
+ * messaging platforms (Telegram, Slack, Discord, WhatsApp, ...) and the
6
+ * internal `prlt session poke` path that forwards user input to agent
7
+ * sessions.
8
+ *
9
+ * The gateway is designed so adding a new platform is a new adapter that
10
+ * implements `MessagingChannel` — zero changes to the router, storage,
11
+ * or commands.
12
+ *
13
+ * Epic: PRLT-1251 (Messaging Gateway)
14
+ * Ticket: PRLT-1255 (Telegram bot MVP)
15
+ */
16
+ /**
17
+ * A platform-specific identifier for a user or conversation on a channel.
18
+ *
19
+ * `channel` is the channel name (e.g. "telegram", "slack"), and `id` is the
20
+ * platform's own identifier. For Telegram, `id` is the numeric chat id
21
+ * encoded as a string (e.g. `"123456789"`).
22
+ *
23
+ * `displayName` is a best-effort human-readable label for logs/UI. It must
24
+ * never be used for routing.
25
+ */
26
+ export interface ChannelAddress {
27
+ channel: string;
28
+ id: string;
29
+ displayName?: string;
30
+ }
31
+ /**
32
+ * A normalized inbound message from any channel.
33
+ *
34
+ * Adapters convert platform payloads into this shape before handing them
35
+ * to the router. Adapters are responsible for idempotency via `id` — the
36
+ * router uses it to de-duplicate re-delivered messages.
37
+ */
38
+ export interface Message {
39
+ /** Stable per-channel message id (platform's id, stringified). */
40
+ id: string;
41
+ /** Channel name (matches MessagingChannel.name). */
42
+ channel: string;
43
+ /** Who sent the message. */
44
+ from: ChannelAddress;
45
+ /** Optional text body. Either `text` or `audio` should be set. */
46
+ text?: string;
47
+ /** Optional raw audio payload (voice note / audio message). */
48
+ audio?: Buffer;
49
+ /** Server-side timestamp of the message. */
50
+ timestamp: Date;
51
+ }
52
+ /**
53
+ * Handler signature used by channels to deliver inbound messages to the
54
+ * router. Channels call this from their read loop.
55
+ */
56
+ export type MessageHandler = (msg: Message) => Promise<void>;
57
+ /**
58
+ * A bidirectional adapter to an external messaging platform.
59
+ *
60
+ * Implementations MUST:
61
+ * - Normalize their inbound payloads into `Message` objects.
62
+ * - Deliver inbound messages by invoking the handler registered via
63
+ * `onMessage`.
64
+ * - Honor `start()` / `stop()` lifecycle cleanly so the gateway can shut
65
+ * down without leaking sockets, timers, or worker threads.
66
+ *
67
+ * Adapters MUST NOT touch the database, the session system, or the
68
+ * router directly. Their only public entry points are the methods
69
+ * defined here.
70
+ */
71
+ export interface MessagingChannel {
72
+ /** Stable channel name (e.g. "telegram"). Used as a primary key. */
73
+ readonly name: string;
74
+ /** Begin reading messages (e.g. open socket, start polling loop). */
75
+ start(): Promise<void>;
76
+ /** Stop reading and release all resources. Must be idempotent. */
77
+ stop(): Promise<void>;
78
+ /** Register the inbound-message handler. Called before `start()`. */
79
+ onMessage(handler: MessageHandler): void;
80
+ /** Send an outbound text message to a ChannelAddress. */
81
+ sendMessage(to: ChannelAddress, text: string): Promise<void>;
82
+ /** Optional: send an outbound voice note (PRLT-1234 territory). */
83
+ sendVoiceNote?(to: ChannelAddress, audio: Buffer): Promise<void>;
84
+ }
85
+ /**
86
+ * Persisted configuration for a registered channel.
87
+ *
88
+ * Stored in `machine.db` → `messaging_channels`. `configJson` is an
89
+ * adapter-specific blob — for Telegram it contains the bot token and
90
+ * allowlist of permitted user ids.
91
+ */
92
+ export interface MessagingChannelRecord {
93
+ name: string;
94
+ type: string;
95
+ configJson: string;
96
+ active: boolean;
97
+ lastMessageAt: Date | undefined;
98
+ }
99
+ /**
100
+ * A user → agent routing binding. The router creates one on first
101
+ * contact and reuses it for all subsequent messages.
102
+ */
103
+ export interface MessagingRouteRecord {
104
+ channel: string;
105
+ userId: string;
106
+ agentSessionId: string;
107
+ createdAt: Date;
108
+ lastUsedAt: Date | undefined;
109
+ }
110
+ /**
111
+ * Shape of `messaging_channels.config_json` when `type === "telegram"`.
112
+ *
113
+ * - `token`: Telegram Bot API token from @BotFather.
114
+ * - `allowlist`: numeric Telegram user ids (stringified) that are allowed
115
+ * to poke agents. Messages from non-allowlisted users are rejected at
116
+ * the router boundary.
117
+ * - `pollIntervalMs`: how often to long-poll getUpdates. Defaults to 0
118
+ * (use Telegram's long-poll timeout of 25 seconds).
119
+ */
120
+ export interface TelegramChannelConfig {
121
+ token: string;
122
+ allowlist: string[];
123
+ pollIntervalMs?: number;
124
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Messaging Gateway Types
3
+ *
4
+ * Defines the channel-agnostic abstraction that sits between external
5
+ * messaging platforms (Telegram, Slack, Discord, WhatsApp, ...) and the
6
+ * internal `prlt session poke` path that forwards user input to agent
7
+ * sessions.
8
+ *
9
+ * The gateway is designed so adding a new platform is a new adapter that
10
+ * implements `MessagingChannel` — zero changes to the router, storage,
11
+ * or commands.
12
+ *
13
+ * Epic: PRLT-1251 (Messaging Gateway)
14
+ * Ticket: PRLT-1255 (Telegram bot MVP)
15
+ */
16
+ export {};
17
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/gateway/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Machine DB Mirror — bridges workspace.db executions to machine.db.
3
+ *
4
+ * `prlt work run` and `prlt orchestrate machine` write directly to machine.db.
5
+ * `prlt work start` and `prlt orchestrator start` write to workspace.db for
6
+ * ticket-linked state, but also need to mirror those executions to machine.db
7
+ * so that:
8
+ *
9
+ * - `prlt session list` from outside an HQ sees them
10
+ * - `prlt orchestrator status` from `/tmp` sees orchestrators in every HQ
11
+ * - The machine-level supervision story (PRLT-1249) actually works
12
+ *
13
+ * Machine.db writes are always best-effort. workspace.db is still authoritative
14
+ * for ticketed work; if mirroring fails we silently swallow the error and keep
15
+ * the primary flow running.
16
+ */
17
+ import { MachineDB, type MachineExecution, type MachineExecutionStatus } from './machine-db.js';
18
+ export interface MirrorCreateInput {
19
+ /** Ticket ID (e.g. TKT-123, PRLT-456). Use 'ORCH' / 'prlt' for orchestrators. */
20
+ ticketId: string;
21
+ /** Agent name. For orchestrators use `orchestrator-{name}`. */
22
+ agentName: string;
23
+ executor: string;
24
+ environment: string;
25
+ /** Working directory — repo path for work, HQ path for orchestrators. */
26
+ repoPath: string;
27
+ branch?: string;
28
+ persistent?: boolean;
29
+ /** Optional human-readable prompt. Falls back to "ticketId agentName". */
30
+ prompt?: string;
31
+ /**
32
+ * Optional override for the machine.db path. When omitted, uses
33
+ * `~/.proletariat/machine.db`. Used by tests to avoid colliding with the
34
+ * user's real machine DB.
35
+ */
36
+ machineDbPath?: string;
37
+ }
38
+ export interface MirrorHandle {
39
+ machineDb: MachineDB;
40
+ execution: MachineExecution;
41
+ }
42
+ /**
43
+ * Create a machine.db execution row mirroring a workspace.db execution.
44
+ *
45
+ * Returns a handle for later status/process-info updates, or `null` if the
46
+ * machine DB could not be opened or the row could not be inserted.
47
+ *
48
+ * Callers MUST pass the handle to {@link closeMirrorExecution} when done.
49
+ */
50
+ export declare function createMirrorExecution(input: MirrorCreateInput): MirrorHandle | null;
51
+ /**
52
+ * Update a mirrored execution with new status and/or process info. All errors
53
+ * are swallowed: machine.db is the secondary store and must never break the
54
+ * primary flow.
55
+ */
56
+ export declare function updateMirrorExecution(handle: MirrorHandle | null, update: {
57
+ status?: MachineExecutionStatus;
58
+ sessionId?: string;
59
+ containerId?: string;
60
+ branch?: string;
61
+ errorMessage?: string;
62
+ }): void;
63
+ /** Close the underlying machine DB. Safe to call with a `null` handle. */
64
+ export declare function closeMirrorExecution(handle: MirrorHandle | null): void;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Machine DB Mirror — bridges workspace.db executions to machine.db.
3
+ *
4
+ * `prlt work run` and `prlt orchestrate machine` write directly to machine.db.
5
+ * `prlt work start` and `prlt orchestrator start` write to workspace.db for
6
+ * ticket-linked state, but also need to mirror those executions to machine.db
7
+ * so that:
8
+ *
9
+ * - `prlt session list` from outside an HQ sees them
10
+ * - `prlt orchestrator status` from `/tmp` sees orchestrators in every HQ
11
+ * - The machine-level supervision story (PRLT-1249) actually works
12
+ *
13
+ * Machine.db writes are always best-effort. workspace.db is still authoritative
14
+ * for ticketed work; if mirroring fails we silently swallow the error and keep
15
+ * the primary flow running.
16
+ */
17
+ import { MachineDB } from './machine-db.js';
18
+ /**
19
+ * Create a machine.db execution row mirroring a workspace.db execution.
20
+ *
21
+ * Returns a handle for later status/process-info updates, or `null` if the
22
+ * machine DB could not be opened or the row could not be inserted.
23
+ *
24
+ * Callers MUST pass the handle to {@link closeMirrorExecution} when done.
25
+ */
26
+ export function createMirrorExecution(input) {
27
+ try {
28
+ const machineDb = new MachineDB(input.machineDbPath);
29
+ const execution = machineDb.createExecution({
30
+ prompt: input.prompt ?? `${input.ticketId} ${input.agentName}`.trim(),
31
+ repoPath: input.repoPath,
32
+ agentName: input.agentName,
33
+ executor: input.executor,
34
+ environment: input.environment,
35
+ branch: input.branch,
36
+ ticketId: input.ticketId,
37
+ persistent: input.persistent ?? false,
38
+ });
39
+ return { machineDb, execution };
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ }
45
+ /**
46
+ * Update a mirrored execution with new status and/or process info. All errors
47
+ * are swallowed: machine.db is the secondary store and must never break the
48
+ * primary flow.
49
+ */
50
+ export function updateMirrorExecution(handle, update) {
51
+ if (!handle)
52
+ return;
53
+ try {
54
+ if (update.status) {
55
+ handle.machineDb.updateStatus(handle.execution.id, update.status, undefined, update.errorMessage);
56
+ }
57
+ if (update.sessionId !== undefined ||
58
+ update.containerId !== undefined ||
59
+ update.branch !== undefined) {
60
+ handle.machineDb.updateProcessInfo(handle.execution.id, {
61
+ sessionId: update.sessionId,
62
+ containerId: update.containerId,
63
+ branch: update.branch,
64
+ });
65
+ }
66
+ }
67
+ catch {
68
+ // Non-fatal — machine.db is secondary.
69
+ }
70
+ }
71
+ /** Close the underlying machine DB. Safe to call with a `null` handle. */
72
+ export function closeMirrorExecution(handle) {
73
+ if (!handle)
74
+ return;
75
+ try {
76
+ handle.machineDb.close();
77
+ }
78
+ catch {
79
+ // ignore
80
+ }
81
+ }
82
+ //# sourceMappingURL=machine-db-mirror.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machine-db-mirror.js","sourceRoot":"","sources":["../../src/lib/machine-db-mirror.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,SAAS,EAAsD,MAAM,iBAAiB,CAAA;AA4B/F;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAwB;IAC5D,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;QACpD,MAAM,SAAS,GAAG,SAAS,CAAC,eAAe,CAAC;YAC1C,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE;YACrE,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,KAAK;SACtC,CAAC,CAAA;QACF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,CAAA;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAA2B,EAC3B,MAMC;IAED,IAAI,CAAC,MAAM;QAAE,OAAM;IACnB,IAAI,CAAC;QACH,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,MAAM,CAAC,SAAS,CAAC,YAAY,CAC3B,MAAM,CAAC,SAAS,CAAC,EAAE,EACnB,MAAM,CAAC,MAAM,EACb,SAAS,EACT,MAAM,CAAC,YAAY,CACpB,CAAA;QACH,CAAC;QACD,IACE,MAAM,CAAC,SAAS,KAAK,SAAS;YAC9B,MAAM,CAAC,WAAW,KAAK,SAAS;YAChC,MAAM,CAAC,MAAM,KAAK,SAAS,EAC3B,CAAC;YACD,MAAM,CAAC,SAAS,CAAC,iBAAiB,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,EAAE;gBACtD,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,MAAM,EAAE,MAAM,CAAC,MAAM;aACtB,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,uCAAuC;IACzC,CAAC;AACH,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,oBAAoB,CAAC,MAA2B;IAC9D,IAAI,CAAC,MAAM;QAAE,OAAM;IACnB,IAAI,CAAC;QACH,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,CAAA;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;AACH,CAAC"}
@@ -16,6 +16,43 @@
16
16
  export type MachineExecutionStatus = 'starting' | 'running' | 'completed' | 'failed' | 'stopped';
17
17
  export type MachineLifecycleState = 'healthy' | 'idle' | 'died' | 'completed';
18
18
  export type MachineCleanupPolicy = 'on-exit' | 'persistent' | 'on-error-keep';
19
+ /**
20
+ * A registered messaging channel (Telegram, Slack, Discord, ...).
21
+ * `configJson` is an adapter-specific blob stored as text.
22
+ */
23
+ export interface MessagingChannelRow {
24
+ name: string;
25
+ type: string;
26
+ config_json: string;
27
+ active: number;
28
+ last_message_at: number | null;
29
+ }
30
+ export interface MessagingChannelRecord {
31
+ name: string;
32
+ type: string;
33
+ configJson: string;
34
+ active: boolean;
35
+ lastMessageAt: Date | undefined;
36
+ }
37
+ /**
38
+ * A persistent binding of `(channel, userId)` to an agent session.
39
+ * The router looks this up on every inbound message so a given user
40
+ * always lands on the same agent.
41
+ */
42
+ export interface MessagingRouteRow {
43
+ channel: string;
44
+ user_id: string;
45
+ agent_session_id: string;
46
+ created_at: number;
47
+ last_used_at: number | null;
48
+ }
49
+ export interface MessagingRouteRecord {
50
+ channel: string;
51
+ userId: string;
52
+ agentSessionId: string;
53
+ createdAt: Date;
54
+ lastUsedAt: Date | undefined;
55
+ }
19
56
  export interface MachineExecution {
20
57
  id: string;
21
58
  prompt: string;
@@ -100,6 +137,17 @@ export declare class MachineDB {
100
137
  * Get all active (starting/running) executions.
101
138
  */
102
139
  getActiveExecutions(): MachineExecution[];
140
+ /**
141
+ * Get all active executions where the agent name starts with the given
142
+ * prefix. Used by `prlt orchestrator status/attach/stop` to find all
143
+ * orchestrator executions (`orchestrator-{name}`) machine-wide.
144
+ */
145
+ getActiveByAgentPrefix(prefix: string): MachineExecution[];
146
+ /**
147
+ * Find an execution by session ID (unique across the machine).
148
+ * Returns the most recently started match, or null.
149
+ */
150
+ findBySessionId(sessionId: string): MachineExecution | null;
103
151
  /**
104
152
  * Delete an execution record.
105
153
  */
@@ -140,5 +188,55 @@ export declare class MachineDB {
140
188
  * Get executions with stale heartbeats (older than timeoutMinutes).
141
189
  */
142
190
  getStaleExecutions(timeoutMinutes: number): MachineExecution[];
191
+ /**
192
+ * Insert or replace a registered messaging channel.
193
+ *
194
+ * Channels are keyed by `name` (e.g. "telegram"). Re-registering the
195
+ * same channel updates its config and marks it active — this is what
196
+ * `prlt gateway connect` does on repeat calls.
197
+ */
198
+ upsertMessagingChannel(params: {
199
+ name: string;
200
+ type: string;
201
+ configJson: string;
202
+ active?: boolean;
203
+ }): void;
204
+ /** Look up a single channel by name. */
205
+ getMessagingChannel(name: string): MessagingChannelRecord | null;
206
+ /**
207
+ * List channels. When `onlyActive` is true (the default), only channels
208
+ * with `active = 1` are returned — this is what the gateway daemon
209
+ * uses at startup.
210
+ */
211
+ listMessagingChannels(options?: {
212
+ onlyActive?: boolean;
213
+ }): MessagingChannelRecord[];
214
+ /** Mark a channel inactive (keeps config around for re-enable). */
215
+ setMessagingChannelActive(name: string, active: boolean): void;
216
+ /** Remove a channel's registration entirely. */
217
+ deleteMessagingChannel(name: string): void;
218
+ /**
219
+ * Stamp a channel's `last_message_at` to now — called each time the
220
+ * router successfully routes an inbound message. Used by
221
+ * `prlt gateway status` to show channel activity.
222
+ */
223
+ touchMessagingChannel(name: string, timestamp?: number): void;
224
+ /** Look up an existing route, or null if this user has never been seen. */
225
+ getMessagingRoute(channel: string, userId: string): MessagingRouteRecord | null;
226
+ /**
227
+ * Bind a `(channel, user)` pair to an agent session. Idempotent — if a
228
+ * binding already exists it is updated to point at the new agent.
229
+ */
230
+ upsertMessagingRoute(params: {
231
+ channel: string;
232
+ userId: string;
233
+ agentSessionId: string;
234
+ }): MessagingRouteRecord;
235
+ /** Stamp `last_used_at` to now — called every time a route is used. */
236
+ touchMessagingRoute(channel: string, userId: string, timestamp?: number): void;
237
+ /** List routes for a channel (or all channels when `channel` omitted). */
238
+ listMessagingRoutes(channel?: string): MessagingRouteRecord[];
239
+ /** Count messages handled by a channel since `since` (for status output). */
240
+ countMessagingRoutesForChannel(channel: string): number;
143
241
  close(): void;
144
242
  }
@@ -102,6 +102,28 @@ export class MachineDB {
102
102
  lifecycle_state TEXT NOT NULL DEFAULT 'healthy',
103
103
  retries INTEGER NOT NULL DEFAULT 0
104
104
  );
105
+
106
+ -- Messaging gateway (PRLT-1255) — bridges external messaging platforms
107
+ -- (Telegram, Slack, ...) to agent sessions via prlt session poke.
108
+ CREATE TABLE IF NOT EXISTS messaging_channels (
109
+ name TEXT PRIMARY KEY,
110
+ type TEXT NOT NULL,
111
+ config_json TEXT NOT NULL,
112
+ active INTEGER NOT NULL DEFAULT 1,
113
+ last_message_at INTEGER
114
+ );
115
+
116
+ CREATE TABLE IF NOT EXISTS messaging_routes (
117
+ channel TEXT NOT NULL,
118
+ user_id TEXT NOT NULL,
119
+ agent_session_id TEXT NOT NULL,
120
+ created_at INTEGER NOT NULL,
121
+ last_used_at INTEGER,
122
+ PRIMARY KEY (channel, user_id)
123
+ );
124
+
125
+ CREATE INDEX IF NOT EXISTS idx_messaging_routes_agent
126
+ ON messaging_routes(agent_session_id);
105
127
  `);
106
128
  // Migrate existing databases that don't have the persistent columns
107
129
  this.addColumnIfMissing('executions', 'persistent', 'INTEGER NOT NULL DEFAULT 0');
@@ -227,6 +249,23 @@ export class MachineDB {
227
249
  const rows = this.db.prepare("SELECT * FROM executions WHERE status IN ('starting', 'running') ORDER BY started_at DESC").all();
228
250
  return rows.map(rowToExecution);
229
251
  }
252
+ /**
253
+ * Get all active executions where the agent name starts with the given
254
+ * prefix. Used by `prlt orchestrator status/attach/stop` to find all
255
+ * orchestrator executions (`orchestrator-{name}`) machine-wide.
256
+ */
257
+ getActiveByAgentPrefix(prefix) {
258
+ const rows = this.db.prepare("SELECT * FROM executions WHERE status IN ('starting', 'running') AND agent_name LIKE ? ORDER BY started_at DESC").all(`${prefix}%`);
259
+ return rows.map(rowToExecution);
260
+ }
261
+ /**
262
+ * Find an execution by session ID (unique across the machine).
263
+ * Returns the most recently started match, or null.
264
+ */
265
+ findBySessionId(sessionId) {
266
+ const row = this.db.prepare('SELECT * FROM executions WHERE session_id = ? ORDER BY started_at DESC LIMIT 1').get(sessionId);
267
+ return row ? rowToExecution(row) : null;
268
+ }
230
269
  /**
231
270
  * Delete an execution record.
232
271
  */
@@ -331,8 +370,121 @@ export class MachineDB {
331
370
  `).all(cutoff, Date.now() - timeoutMinutes * 60 * 1000);
332
371
  return rows.map(rowToExecution);
333
372
  }
373
+ // ===========================================================================
374
+ // Messaging gateway (PRLT-1255)
375
+ // ===========================================================================
376
+ /**
377
+ * Insert or replace a registered messaging channel.
378
+ *
379
+ * Channels are keyed by `name` (e.g. "telegram"). Re-registering the
380
+ * same channel updates its config and marks it active — this is what
381
+ * `prlt gateway connect` does on repeat calls.
382
+ */
383
+ upsertMessagingChannel(params) {
384
+ const active = params.active === false ? 0 : 1;
385
+ this.db.prepare(`
386
+ INSERT INTO messaging_channels (name, type, config_json, active, last_message_at)
387
+ VALUES (?, ?, ?, ?, NULL)
388
+ ON CONFLICT(name) DO UPDATE SET
389
+ type = excluded.type,
390
+ config_json = excluded.config_json,
391
+ active = excluded.active
392
+ `).run(params.name, params.type, params.configJson, active);
393
+ }
394
+ /** Look up a single channel by name. */
395
+ getMessagingChannel(name) {
396
+ const row = this.db.prepare('SELECT * FROM messaging_channels WHERE name = ?').get(name);
397
+ return row ? messagingChannelRowToRecord(row) : null;
398
+ }
399
+ /**
400
+ * List channels. When `onlyActive` is true (the default), only channels
401
+ * with `active = 1` are returned — this is what the gateway daemon
402
+ * uses at startup.
403
+ */
404
+ listMessagingChannels(options) {
405
+ const onlyActive = options?.onlyActive !== false;
406
+ const rows = onlyActive
407
+ ? this.db.prepare('SELECT * FROM messaging_channels WHERE active = 1 ORDER BY name ASC').all()
408
+ : this.db.prepare('SELECT * FROM messaging_channels ORDER BY name ASC').all();
409
+ return rows.map(messagingChannelRowToRecord);
410
+ }
411
+ /** Mark a channel inactive (keeps config around for re-enable). */
412
+ setMessagingChannelActive(name, active) {
413
+ this.db.prepare('UPDATE messaging_channels SET active = ? WHERE name = ?').run(active ? 1 : 0, name);
414
+ }
415
+ /** Remove a channel's registration entirely. */
416
+ deleteMessagingChannel(name) {
417
+ this.db.prepare('DELETE FROM messaging_channels WHERE name = ?').run(name);
418
+ }
419
+ /**
420
+ * Stamp a channel's `last_message_at` to now — called each time the
421
+ * router successfully routes an inbound message. Used by
422
+ * `prlt gateway status` to show channel activity.
423
+ */
424
+ touchMessagingChannel(name, timestamp = Date.now()) {
425
+ this.db.prepare('UPDATE messaging_channels SET last_message_at = ? WHERE name = ?').run(timestamp, name);
426
+ }
427
+ // ---------------------------------------------------------------------------
428
+ // Routes — (channel, user) → agent session
429
+ // ---------------------------------------------------------------------------
430
+ /** Look up an existing route, or null if this user has never been seen. */
431
+ getMessagingRoute(channel, userId) {
432
+ const row = this.db.prepare('SELECT * FROM messaging_routes WHERE channel = ? AND user_id = ?').get(channel, userId);
433
+ return row ? messagingRouteRowToRecord(row) : null;
434
+ }
435
+ /**
436
+ * Bind a `(channel, user)` pair to an agent session. Idempotent — if a
437
+ * binding already exists it is updated to point at the new agent.
438
+ */
439
+ upsertMessagingRoute(params) {
440
+ const now = Date.now();
441
+ this.db.prepare(`
442
+ INSERT INTO messaging_routes (channel, user_id, agent_session_id, created_at, last_used_at)
443
+ VALUES (?, ?, ?, ?, NULL)
444
+ ON CONFLICT(channel, user_id) DO UPDATE SET
445
+ agent_session_id = excluded.agent_session_id
446
+ `).run(params.channel, params.userId, params.agentSessionId, now);
447
+ return this.getMessagingRoute(params.channel, params.userId);
448
+ }
449
+ /** Stamp `last_used_at` to now — called every time a route is used. */
450
+ touchMessagingRoute(channel, userId, timestamp = Date.now()) {
451
+ this.db.prepare('UPDATE messaging_routes SET last_used_at = ? WHERE channel = ? AND user_id = ?').run(timestamp, channel, userId);
452
+ }
453
+ /** List routes for a channel (or all channels when `channel` omitted). */
454
+ listMessagingRoutes(channel) {
455
+ const rows = channel
456
+ ? this.db.prepare('SELECT * FROM messaging_routes WHERE channel = ? ORDER BY created_at DESC').all(channel)
457
+ : this.db.prepare('SELECT * FROM messaging_routes ORDER BY created_at DESC').all();
458
+ return rows.map(messagingRouteRowToRecord);
459
+ }
460
+ /** Count messages handled by a channel since `since` (for status output). */
461
+ countMessagingRoutesForChannel(channel) {
462
+ const row = this.db.prepare('SELECT COUNT(*) AS n FROM messaging_routes WHERE channel = ?').get(channel);
463
+ return row?.n ?? 0;
464
+ }
334
465
  close() {
335
466
  this.db.close();
336
467
  }
337
468
  }
469
+ // =============================================================================
470
+ // Row → record converters for messaging tables
471
+ // =============================================================================
472
+ function messagingChannelRowToRecord(row) {
473
+ return {
474
+ name: row.name,
475
+ type: row.type,
476
+ configJson: row.config_json,
477
+ active: row.active === 1,
478
+ lastMessageAt: row.last_message_at ? new Date(row.last_message_at) : undefined,
479
+ };
480
+ }
481
+ function messagingRouteRowToRecord(row) {
482
+ return {
483
+ channel: row.channel,
484
+ userId: row.user_id,
485
+ agentSessionId: row.agent_session_id,
486
+ createdAt: new Date(row.created_at),
487
+ lastUsedAt: row.last_used_at ? new Date(row.last_used_at) : undefined,
488
+ };
489
+ }
338
490
  //# sourceMappingURL=machine-db.js.map