aibroker 0.2.6 → 0.5.1

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 (83) hide show
  1. package/README.md +164 -4
  2. package/dist/adapters/iterm/core.d.ts +2 -0
  3. package/dist/adapters/iterm/core.d.ts.map +1 -1
  4. package/dist/adapters/iterm/core.js +13 -5
  5. package/dist/adapters/iterm/core.js.map +1 -1
  6. package/dist/adapters/kokoro/media.d.ts +2 -1
  7. package/dist/adapters/kokoro/media.d.ts.map +1 -1
  8. package/dist/adapters/kokoro/media.js +51 -3
  9. package/dist/adapters/kokoro/media.js.map +1 -1
  10. package/dist/adapters/pailot/gateway.d.ts +49 -0
  11. package/dist/adapters/pailot/gateway.d.ts.map +1 -0
  12. package/dist/adapters/pailot/gateway.js +502 -0
  13. package/dist/adapters/pailot/gateway.js.map +1 -0
  14. package/dist/backend/api.d.ts +5 -1
  15. package/dist/backend/api.d.ts.map +1 -1
  16. package/dist/backend/api.js +74 -3
  17. package/dist/backend/api.js.map +1 -1
  18. package/dist/core/hybrid.d.ts +5 -0
  19. package/dist/core/hybrid.d.ts.map +1 -1
  20. package/dist/core/hybrid.js +25 -0
  21. package/dist/core/hybrid.js.map +1 -1
  22. package/dist/core/state.d.ts +3 -0
  23. package/dist/core/state.d.ts.map +1 -1
  24. package/dist/core/state.js +4 -0
  25. package/dist/core/state.js.map +1 -1
  26. package/dist/daemon/adapter-registry.d.ts +58 -0
  27. package/dist/daemon/adapter-registry.d.ts.map +1 -0
  28. package/dist/daemon/adapter-registry.js +179 -0
  29. package/dist/daemon/adapter-registry.js.map +1 -0
  30. package/dist/daemon/cli.d.ts +13 -0
  31. package/dist/daemon/cli.d.ts.map +1 -0
  32. package/dist/daemon/cli.js +58 -0
  33. package/dist/daemon/cli.js.map +1 -0
  34. package/dist/daemon/core-handlers.d.ts +24 -0
  35. package/dist/daemon/core-handlers.d.ts.map +1 -0
  36. package/dist/daemon/core-handlers.js +146 -0
  37. package/dist/daemon/core-handlers.js.map +1 -0
  38. package/dist/daemon/create-adapter.d.ts +22 -0
  39. package/dist/daemon/create-adapter.d.ts.map +1 -0
  40. package/dist/daemon/create-adapter.js +152 -0
  41. package/dist/daemon/create-adapter.js.map +1 -0
  42. package/dist/daemon/index.d.ts +12 -0
  43. package/dist/daemon/index.d.ts.map +1 -0
  44. package/dist/daemon/index.js +83 -0
  45. package/dist/daemon/index.js.map +1 -0
  46. package/dist/daemon/pai-projects.d.ts +68 -0
  47. package/dist/daemon/pai-projects.d.ts.map +1 -0
  48. package/dist/daemon/pai-projects.js +174 -0
  49. package/dist/daemon/pai-projects.js.map +1 -0
  50. package/dist/index.d.ts +11 -2
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +9 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/ipc/client.d.ts +4 -1
  55. package/dist/ipc/client.d.ts.map +1 -1
  56. package/dist/ipc/client.js +10 -1
  57. package/dist/ipc/client.js.map +1 -1
  58. package/dist/types/adapter.d.ts +41 -0
  59. package/dist/types/adapter.d.ts.map +1 -0
  60. package/dist/types/adapter.js +2 -0
  61. package/dist/types/adapter.js.map +1 -0
  62. package/dist/types/backend.d.ts +29 -1
  63. package/dist/types/backend.d.ts.map +1 -1
  64. package/dist/types/broker.d.ts +45 -0
  65. package/dist/types/broker.d.ts.map +1 -0
  66. package/dist/types/broker.js +21 -0
  67. package/dist/types/broker.js.map +1 -0
  68. package/dist/types/index.d.ts +2 -0
  69. package/dist/types/index.d.ts.map +1 -1
  70. package/dist/types/index.js +2 -0
  71. package/dist/types/index.js.map +1 -1
  72. package/package.json +9 -2
  73. package/templates/adapter/ONBOARDING_PROMPT.md +287 -0
  74. package/templates/adapter/README.md.tmpl +98 -0
  75. package/templates/adapter/package.json.tmpl +23 -0
  76. package/templates/adapter/src/watcher/cli.ts.tmpl +12 -0
  77. package/templates/adapter/src/watcher/commands.ts.tmpl +146 -0
  78. package/templates/adapter/src/watcher/connection.ts.tmpl +59 -0
  79. package/templates/adapter/src/watcher/index.ts.tmpl +177 -0
  80. package/templates/adapter/src/watcher/ipc-server.ts.tmpl +226 -0
  81. package/templates/adapter/src/watcher/send.ts.tmpl +62 -0
  82. package/templates/adapter/src/watcher/state.ts.tmpl +39 -0
  83. package/templates/adapter/tsconfig.json.tmpl +14 -0
@@ -0,0 +1,177 @@
1
+ /**
2
+ * watcher/index.ts — {{DISPLAY_NAME}} adapter entry point
3
+ *
4
+ * Wires together:
5
+ * 1. Hub detection — probe AIBroker daemon; enter hub or embedded mode
6
+ * 2. Connection — connectWatcher() establishes the upstream service link
7
+ * 3. Command router — createMessageHandler() dispatches text / slash commands
8
+ * 4. IPC server — startIpcServer() accepts calls from hub and MCP tools
9
+ * 5. Hub registration — if hub is running, announce this adapter's socket
10
+ *
11
+ * Lifecycle:
12
+ * watch() → detectHubMode() → connectWatcher() → startIpcServer()
13
+ * → [hub mode] register_adapter on hub
14
+ * → await forever (exits on SIGINT / SIGTERM)
15
+ */
16
+
17
+ import { unlinkSync } from "node:fs";
18
+ import { homedir } from "node:os";
19
+ import { join } from "node:path";
20
+
21
+ import {
22
+ WatcherClient,
23
+ DAEMON_SOCKET_PATH,
24
+ setAppDir,
25
+ loadSessionRegistry,
26
+ APIBackend,
27
+ HybridSessionManager,
28
+ setHybridManager,
29
+ router,
30
+ log,
31
+ setLogPrefix,
32
+ createBrokerMessage,
33
+ } from "aibroker";
34
+
35
+ import { connectWatcher } from "./connection.js";
36
+ import { startIpcServer } from "./ipc-server.js";
37
+ import { createMessageHandler } from "./commands.js";
38
+ import {
39
+ ADAPTER_SOCKET_PATH,
40
+ adapterStats,
41
+ setConnectionStatus,
42
+ } from "./state.js";
43
+
44
+ // ── Hub detection ─────────────────────────────────────────────────────────────
45
+
46
+ /**
47
+ * Probe the AIBroker hub daemon.
48
+ *
49
+ * Returns true if the hub is reachable on its standard socket within 2 seconds.
50
+ * On any failure the adapter runs in embedded (standalone) mode.
51
+ */
52
+ async function detectHubMode(): Promise<boolean> {
53
+ const client = new WatcherClient(DAEMON_SOCKET_PATH);
54
+ try {
55
+ const result = await Promise.race([
56
+ client.call_raw("status", {}),
57
+ new Promise<null>((_, reject) => setTimeout(() => reject(new Error("timeout")), 2000)),
58
+ ]);
59
+ return result !== null;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ // ── Main ──────────────────────────────────────────────────────────────────────
66
+
67
+ /**
68
+ * Start the {{DISPLAY_NAME}} adapter watcher.
69
+ *
70
+ * Suspends the event loop indefinitely via `await new Promise(() => {})`.
71
+ * Exits only on SIGINT or SIGTERM.
72
+ */
73
+ export async function watch(): Promise<void> {
74
+ setLogPrefix("{{ADAPTER_NAME}}-watch");
75
+ setAppDir(join(homedir(), ".{{ADAPTER_NAME}}"));
76
+
77
+ // Initialise hybrid session manager
78
+ const apiBackend = new APIBackend({
79
+ type: "api",
80
+ provider: "anthropic",
81
+ model: process.env.AIBROKER_MODEL ?? "sonnet",
82
+ cwd: process.env.AIBROKER_CWD,
83
+ maxTurns: Number(process.env.AIBROKER_MAX_TURNS) || 30,
84
+ maxBudgetUsd: Number(process.env.AIBROKER_MAX_BUDGET) || 1.0,
85
+ permissionMode: process.env.AIBROKER_PERMISSION_MODE ?? "acceptEdits",
86
+ skipDefaultSession: true,
87
+ });
88
+ const manager = new HybridSessionManager(apiBackend);
89
+ setHybridManager(manager);
90
+ manager.createApiSession("Default", process.env.AIBROKER_CWD ?? homedir());
91
+ router.setDefaultBackend(apiBackend);
92
+
93
+ // Detect whether the AIBroker hub daemon is running
94
+ const hubMode = await detectHubMode();
95
+ const hubClient = hubMode ? new WatcherClient(DAEMON_SOCKET_PATH) : null;
96
+
97
+ console.log(`{{DISPLAY_NAME}} Adapter Watch`);
98
+ console.log(` Socket: ${ADAPTER_SOCKET_PATH}`);
99
+ console.log(` Mode: ${hubMode ? "hub (daemon detected)" : "embedded (standalone)"}`);
100
+ console.log();
101
+
102
+ // Restore persisted session state
103
+ loadSessionRegistry();
104
+
105
+ // Graceful shutdown
106
+ let cleanupConnection: (() => void) | null = null;
107
+
108
+ const shutdown = (signal: string) => {
109
+ console.log(`\n[{{ADAPTER_NAME}}-watch] ${signal} received. Stopping.`);
110
+ setConnectionStatus("disconnected");
111
+ if (cleanupConnection) cleanupConnection();
112
+ if (hubClient) {
113
+ hubClient.call_raw("unregister_adapter", { name: "{{ADAPTER_NAME}}" }).catch(() => {});
114
+ }
115
+ try { unlinkSync(ADAPTER_SOCKET_PATH); } catch { /* ignore */ }
116
+ process.exit(0);
117
+ };
118
+ process.on("SIGINT", () => shutdown("SIGINT"));
119
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
120
+
121
+ // Build the message handler
122
+ const handleMessage = createMessageHandler();
123
+
124
+ // Connect to the upstream service
125
+ console.log(`Connecting to {{DISPLAY_NAME}}...\n`);
126
+ setConnectionStatus("connecting");
127
+
128
+ const { cleanup, triggerLogin } = await connectWatcher(
129
+ (text: string, timestamp: number) => {
130
+ log(`[{{ADAPTER_NAME}}] incoming: ${text.slice(0, 80)}`);
131
+ adapterStats.messagesReceived++;
132
+ adapterStats.lastMessageAt = timestamp;
133
+
134
+ if (hubMode && hubClient) {
135
+ // Hub mode: forward non-local messages to the hub for routing
136
+ const message = createBrokerMessage(
137
+ "{{ADAPTER_NAME}}",
138
+ text.trim().startsWith("/") ? "command" : "text",
139
+ { text },
140
+ );
141
+ message.timestamp = timestamp;
142
+
143
+ hubClient.call_raw("route_message", {
144
+ message: message as unknown as Record<string, unknown>,
145
+ }).catch((err) => {
146
+ log(`Hub route_message failed, handling locally: ${err instanceof Error ? err.message : String(err)}`);
147
+ handleMessage(text, timestamp);
148
+ });
149
+ } else {
150
+ // Embedded mode: handle everything locally
151
+ handleMessage(text, timestamp);
152
+ }
153
+ },
154
+ );
155
+
156
+ cleanupConnection = cleanup;
157
+ setConnectionStatus("connected");
158
+
159
+ // Start the adapter IPC server
160
+ startIpcServer(triggerLogin);
161
+
162
+ // Register with hub if running
163
+ if (hubMode && hubClient) {
164
+ log("Hub mode: registering with AIBroker hub daemon");
165
+ hubClient.call_raw("register_adapter", {
166
+ name: "{{ADAPTER_NAME}}",
167
+ socketPath: ADAPTER_SOCKET_PATH,
168
+ }).then(() => {
169
+ log("Registered with AIBroker hub daemon");
170
+ }).catch((err) => {
171
+ log(`Hub registration failed: ${err instanceof Error ? err.message : String(err)}`);
172
+ });
173
+ }
174
+
175
+ // Keep process alive
176
+ await new Promise(() => {});
177
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * ipc-server.ts — Adapter-side IPC server for {{DISPLAY_NAME}}
3
+ *
4
+ * Registers the handlers that the AIBroker hub (and MCP tools) can call
5
+ * on this adapter's Unix Domain Socket.
6
+ *
7
+ * Required handlers (called by the hub):
8
+ * deliver — hub pushes a BrokerMessage to this adapter for outbound delivery
9
+ * health — hub polls adapter health
10
+ * connection_status — returns the upstream connection status string
11
+ *
12
+ * Optional handlers (called by MCP tools or the hub):
13
+ * send — send a text message
14
+ * send_voice — send a voice note (OGG Opus path)
15
+ * send_file — send a file attachment
16
+ * login — trigger a new login / QR pairing flow
17
+ * status — human-readable status summary
18
+ */
19
+
20
+ import { IpcServer, log } from "aibroker";
21
+ import type { BrokerMessage } from "aibroker";
22
+
23
+ import { ADAPTER_SOCKET_PATH, adapterStats, connectionStatus, getLastMessageAgo } from "./state.js";
24
+ import { sendText, sendVoice, sendFile } from "./send.js";
25
+
26
+ let triggerLoginFn: (() => Promise<string>) | null = null;
27
+
28
+ /**
29
+ * Create, register handlers on, and start the adapter IPC server.
30
+ *
31
+ * @param triggerLogin - Async function that starts a fresh login flow and
32
+ * returns a human-readable status string.
33
+ * @returns The IpcServer instance (already started).
34
+ */
35
+ export function startIpcServer(triggerLogin: () => Promise<string>): IpcServer {
36
+ triggerLoginFn = triggerLogin;
37
+
38
+ const server = new IpcServer(ADAPTER_SOCKET_PATH);
39
+
40
+ // ── Hub-required handlers ──────────────────────────────────────────────────
41
+
42
+ /**
43
+ * deliver — Hub pushes a BrokerMessage for outbound delivery.
44
+ *
45
+ * The hub calls this when it has routed a message to this adapter.
46
+ * Inspect message.type to decide how to deliver:
47
+ * "text" → sendText
48
+ * "voice" → sendVoice
49
+ * "file" → sendFile
50
+ * "command" → run through local command handler
51
+ */
52
+ server.on("deliver", async (req) => {
53
+ const { message } = req.params as { message: BrokerMessage };
54
+ if (!message || !message.type) {
55
+ return { ok: false, error: "Invalid BrokerMessage: type is required" };
56
+ }
57
+
58
+ log(`[{{ADAPTER_NAME}}] deliver: type=${message.type} source=${message.source}`);
59
+
60
+ try {
61
+ const payload = message.payload ?? {};
62
+ const recipient = typeof payload.recipient === "string" ? payload.recipient : undefined;
63
+
64
+ switch (message.type) {
65
+ case "text": {
66
+ const text = typeof payload.text === "string" ? payload.text : String(payload.text ?? "");
67
+ await sendText(text, recipient);
68
+ break;
69
+ }
70
+ case "voice": {
71
+ const audioPath = typeof payload.audioPath === "string" ? payload.audioPath : null;
72
+ if (!audioPath) return { ok: false, error: "deliver(voice): audioPath is required" };
73
+ await sendVoice(audioPath, recipient);
74
+ break;
75
+ }
76
+ case "file": {
77
+ const filePath = typeof payload.filePath === "string" ? payload.filePath : null;
78
+ if (!filePath) return { ok: false, error: "deliver(file): filePath is required" };
79
+ const caption = typeof payload.caption === "string" ? payload.caption : undefined;
80
+ const mimetype = typeof payload.mimetype === "string" ? payload.mimetype : undefined;
81
+ await sendFile(filePath, caption, mimetype, recipient);
82
+ break;
83
+ }
84
+ case "command": {
85
+ // Commands from the hub (e.g. routed from another adapter) — deliver as text
86
+ const text = typeof payload.text === "string" ? payload.text : String(payload.text ?? "");
87
+ await sendText(text, recipient);
88
+ break;
89
+ }
90
+ default:
91
+ log(`[{{ADAPTER_NAME}}] deliver: unhandled message type "${message.type}"`);
92
+ return { ok: false, error: `Unhandled message type: ${message.type}` };
93
+ }
94
+
95
+ return { ok: true, result: { delivered: true } };
96
+ } catch (err) {
97
+ const msg = err instanceof Error ? err.message : String(err);
98
+ log(`[{{ADAPTER_NAME}}] deliver error: ${msg}`);
99
+ adapterStats.errors++;
100
+ return { ok: false, error: msg };
101
+ }
102
+ });
103
+
104
+ /**
105
+ * health — Hub polls this to determine adapter liveness.
106
+ *
107
+ * Returns AdapterHealth-compatible shape.
108
+ */
109
+ server.on("health", async (_req) => {
110
+ const isConnected = connectionStatus === "connected";
111
+ return {
112
+ ok: true,
113
+ result: {
114
+ status: isConnected ? "ok" : connectionStatus === "connecting" ? "degraded" : "down",
115
+ connectionStatus,
116
+ stats: {
117
+ messagesReceived: adapterStats.messagesReceived,
118
+ messagesSent: adapterStats.messagesSent,
119
+ errors: adapterStats.errors,
120
+ },
121
+ lastMessageAgo: getLastMessageAgo(),
122
+ },
123
+ };
124
+ });
125
+
126
+ /**
127
+ * connection_status — Returns the upstream connection status as a string.
128
+ */
129
+ server.on("connection_status", async (_req) => {
130
+ return {
131
+ ok: true,
132
+ result: { status: connectionStatus },
133
+ };
134
+ });
135
+
136
+ // ── Optional outbound handlers ─────────────────────────────────────────────
137
+
138
+ /**
139
+ * send — Send a text message.
140
+ */
141
+ server.on("send", async (req) => {
142
+ const { message, recipient } = req.params as { message?: string; recipient?: string };
143
+ if (!message) return { ok: false, error: "message is required" };
144
+ try {
145
+ await sendText(message, recipient);
146
+ return { ok: true, result: { sent: true } };
147
+ } catch (err) {
148
+ adapterStats.errors++;
149
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
150
+ }
151
+ });
152
+
153
+ /**
154
+ * send_voice — Send a voice note from a file path (OGG Opus).
155
+ */
156
+ server.on("send_voice", async (req) => {
157
+ const { audioPath, recipient } = req.params as { audioPath?: string; recipient?: string };
158
+ if (!audioPath) return { ok: false, error: "audioPath is required" };
159
+ try {
160
+ await sendVoice(audioPath, recipient);
161
+ return { ok: true, result: { sent: true } };
162
+ } catch (err) {
163
+ adapterStats.errors++;
164
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
165
+ }
166
+ });
167
+
168
+ /**
169
+ * send_file — Send a file attachment.
170
+ */
171
+ server.on("send_file", async (req) => {
172
+ const { filePath, caption, mimetype, recipient } = req.params as {
173
+ filePath?: string;
174
+ caption?: string;
175
+ mimetype?: string;
176
+ recipient?: string;
177
+ };
178
+ if (!filePath) return { ok: false, error: "filePath is required" };
179
+ try {
180
+ await sendFile(filePath, caption, mimetype, recipient);
181
+ return { ok: true, result: { sent: true } };
182
+ } catch (err) {
183
+ adapterStats.errors++;
184
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
185
+ }
186
+ });
187
+
188
+ /**
189
+ * login — Trigger a fresh login / QR pairing flow.
190
+ */
191
+ server.on("login", async (_req) => {
192
+ if (!triggerLoginFn) return { ok: false, error: "Login not available" };
193
+ try {
194
+ const result = await triggerLoginFn();
195
+ return { ok: true, result: { message: result } };
196
+ } catch (err) {
197
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
198
+ }
199
+ });
200
+
201
+ /**
202
+ * status — Human-readable adapter status summary.
203
+ */
204
+ server.on("status", async (_req) => {
205
+ const agoMs = getLastMessageAgo();
206
+ const agoStr = agoMs !== null
207
+ ? `${Math.round(agoMs / 1000)}s ago`
208
+ : "never";
209
+ return {
210
+ ok: true,
211
+ result: {
212
+ adapter: "{{ADAPTER_NAME}}",
213
+ displayName: "{{DISPLAY_NAME}}",
214
+ connectionStatus,
215
+ messagesReceived: adapterStats.messagesReceived,
216
+ messagesSent: adapterStats.messagesSent,
217
+ errors: adapterStats.errors,
218
+ lastMessageAgo: agoStr,
219
+ },
220
+ };
221
+ });
222
+
223
+ server.start();
224
+ log(`[{{ADAPTER_NAME}}] IPC server started on ${ADAPTER_SOCKET_PATH}`);
225
+ return server;
226
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * send.ts — Outbound message delivery for {{DISPLAY_NAME}}
3
+ *
4
+ * These functions are called by the IPC server handlers when the hub (or an
5
+ * MCP tool) wants to send a message to the upstream service.
6
+ *
7
+ * TODO: Replace each stub with real delivery logic using your SDK.
8
+ */
9
+
10
+ import { adapterStats } from "./state.js";
11
+
12
+ // ── Text ─────────────────────────────────────────────────────────────────────
13
+
14
+ /**
15
+ * Send a plain-text message to the upstream service.
16
+ *
17
+ * @param text The message body.
18
+ * @param recipient Optional channel/user/room identifier. Defaults to self-chat.
19
+ */
20
+ export async function sendText(text: string, recipient?: string): Promise<void> {
21
+ // TODO: deliver text via your SDK
22
+ console.log(`[{{ADAPTER_NAME}}] sendText stub — text="${text.slice(0, 60)}" recipient=${recipient ?? "(default)"}`);
23
+ adapterStats.messagesSent++;
24
+ }
25
+
26
+ // ── Voice ────────────────────────────────────────────────────────────────────
27
+
28
+ /**
29
+ * Send an audio file as a voice note.
30
+ *
31
+ * The file at audioPath is an OGG/Opus buffer produced by Kokoro TTS.
32
+ * Most messaging platforms accept OGG Opus for voice notes.
33
+ *
34
+ * @param audioPath Path to the OGG Opus audio file on disk.
35
+ * @param recipient Optional channel/user/room identifier.
36
+ */
37
+ export async function sendVoice(audioPath: string, recipient?: string): Promise<void> {
38
+ // TODO: upload and send audio file via your SDK
39
+ console.log(`[{{ADAPTER_NAME}}] sendVoice stub — path="${audioPath}" recipient=${recipient ?? "(default)"}`);
40
+ adapterStats.messagesSent++;
41
+ }
42
+
43
+ // ── File ─────────────────────────────────────────────────────────────────────
44
+
45
+ /**
46
+ * Send an arbitrary file as a document attachment.
47
+ *
48
+ * @param filePath Path to the file on disk.
49
+ * @param caption Optional caption shown beneath the attachment.
50
+ * @param mimetype Optional MIME type override. Detected automatically if omitted.
51
+ * @param recipient Optional channel/user/room identifier.
52
+ */
53
+ export async function sendFile(
54
+ filePath: string,
55
+ caption?: string,
56
+ mimetype?: string,
57
+ recipient?: string,
58
+ ): Promise<void> {
59
+ // TODO: upload and send file via your SDK
60
+ console.log(`[{{ADAPTER_NAME}}] sendFile stub — path="${filePath}" caption="${caption ?? ""}" mime="${mimetype ?? "auto"}" recipient=${recipient ?? "(default)"}`);
61
+ adapterStats.messagesSent++;
62
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * state.ts — {{DISPLAY_NAME}} adapter local state
3
+ *
4
+ * Tracks connection status, per-run statistics, and the socket path
5
+ * for the adapter's IPC server. All fields are module-level singletons
6
+ * written by the watcher and read by the IPC server handlers.
7
+ */
8
+
9
+ import type { AdapterConnectionStatus } from "aibroker";
10
+
11
+ // ── Connection tracking ──────────────────────────────────────────────────────
12
+
13
+ /** Current upstream connection status. */
14
+ export let connectionStatus: AdapterConnectionStatus = "disconnected";
15
+
16
+ export function setConnectionStatus(s: AdapterConnectionStatus): void {
17
+ connectionStatus = s;
18
+ }
19
+
20
+ // ── Per-run statistics ───────────────────────────────────────────────────────
21
+
22
+ /** Statistics reset on each watcher restart (not persisted). */
23
+ export const adapterStats = {
24
+ messagesReceived: 0,
25
+ messagesSent: 0,
26
+ errors: 0,
27
+ lastMessageAt: null as number | null,
28
+ };
29
+
30
+ // ── Socket path ──────────────────────────────────────────────────────────────
31
+
32
+ /** Unix Domain Socket path for the adapter's IPC server. */
33
+ export const ADAPTER_SOCKET_PATH = "/tmp/{{ADAPTER_NAME}}-watcher.sock";
34
+
35
+ /** Timestamp of the last incoming message (epoch ms). */
36
+ export function getLastMessageAgo(): number | null {
37
+ if (adapterStats.lastMessageAt === null) return null;
38
+ return Date.now() - adapterStats.lastMessageAt;
39
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true
12
+ },
13
+ "include": ["src"]
14
+ }