aibroker 0.5.1 → 0.6.2

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 (133) hide show
  1. package/README.md +263 -104
  2. package/dist/adapters/iterm/iterm2-api.d.ts +20 -0
  3. package/dist/adapters/iterm/iterm2-api.d.ts.map +1 -0
  4. package/dist/adapters/iterm/iterm2-api.js +244 -0
  5. package/dist/adapters/iterm/iterm2-api.js.map +1 -0
  6. package/dist/adapters/iterm/sessions.d.ts +1 -0
  7. package/dist/adapters/iterm/sessions.d.ts.map +1 -1
  8. package/dist/adapters/iterm/sessions.js +26 -2
  9. package/dist/adapters/iterm/sessions.js.map +1 -1
  10. package/dist/adapters/kokoro/media.js +2 -2
  11. package/dist/adapters/kokoro/media.js.map +1 -1
  12. package/dist/adapters/pailot/gateway.d.ts +5 -6
  13. package/dist/adapters/pailot/gateway.d.ts.map +1 -1
  14. package/dist/adapters/pailot/gateway.js +575 -34
  15. package/dist/adapters/pailot/gateway.js.map +1 -1
  16. package/dist/aibp/bridge.d.ts +123 -0
  17. package/dist/aibp/bridge.d.ts.map +1 -0
  18. package/dist/aibp/bridge.js +363 -0
  19. package/dist/aibp/bridge.js.map +1 -0
  20. package/dist/aibp/envelope.d.ts +26 -0
  21. package/dist/aibp/envelope.d.ts.map +1 -0
  22. package/dist/aibp/envelope.js +101 -0
  23. package/dist/aibp/envelope.js.map +1 -0
  24. package/dist/aibp/index.d.ts +11 -0
  25. package/dist/aibp/index.d.ts.map +1 -0
  26. package/dist/aibp/index.js +10 -0
  27. package/dist/aibp/index.js.map +1 -0
  28. package/dist/aibp/registry.d.ts +71 -0
  29. package/dist/aibp/registry.d.ts.map +1 -0
  30. package/dist/aibp/registry.js +408 -0
  31. package/dist/aibp/registry.js.map +1 -0
  32. package/dist/aibp/types.d.ts +91 -0
  33. package/dist/aibp/types.d.ts.map +1 -0
  34. package/dist/aibp/types.js +8 -0
  35. package/dist/aibp/types.js.map +1 -0
  36. package/dist/core/hybrid.d.ts +2 -0
  37. package/dist/core/hybrid.d.ts.map +1 -1
  38. package/dist/core/hybrid.js +8 -0
  39. package/dist/core/hybrid.js.map +1 -1
  40. package/dist/core/state.d.ts +12 -0
  41. package/dist/core/state.d.ts.map +1 -1
  42. package/dist/core/state.js +34 -0
  43. package/dist/core/state.js.map +1 -1
  44. package/dist/core/status-cache.d.ts +51 -0
  45. package/dist/core/status-cache.d.ts.map +1 -0
  46. package/dist/core/status-cache.js +62 -0
  47. package/dist/core/status-cache.js.map +1 -0
  48. package/dist/daemon/adapter-registry.d.ts +5 -0
  49. package/dist/daemon/adapter-registry.d.ts.map +1 -1
  50. package/dist/daemon/adapter-registry.js +94 -4
  51. package/dist/daemon/adapter-registry.js.map +1 -1
  52. package/dist/daemon/cli.d.ts +1 -0
  53. package/dist/daemon/cli.d.ts.map +1 -1
  54. package/dist/daemon/cli.js +95 -3
  55. package/dist/daemon/cli.js.map +1 -1
  56. package/dist/daemon/command-context.d.ts +28 -0
  57. package/dist/daemon/command-context.d.ts.map +1 -0
  58. package/dist/daemon/command-context.js +13 -0
  59. package/dist/daemon/command-context.js.map +1 -0
  60. package/dist/daemon/commands.d.ts +22 -0
  61. package/dist/daemon/commands.d.ts.map +1 -0
  62. package/dist/daemon/commands.js +849 -0
  63. package/dist/daemon/commands.js.map +1 -0
  64. package/dist/daemon/core-handlers.d.ts.map +1 -1
  65. package/dist/daemon/core-handlers.js +758 -3
  66. package/dist/daemon/core-handlers.js.map +1 -1
  67. package/dist/daemon/create-adapter.js +2 -1
  68. package/dist/daemon/create-adapter.js.map +1 -1
  69. package/dist/daemon/image-context.d.ts +56 -0
  70. package/dist/daemon/image-context.d.ts.map +1 -0
  71. package/dist/daemon/image-context.js +116 -0
  72. package/dist/daemon/image-context.js.map +1 -0
  73. package/dist/daemon/image-gen/index.d.ts +22 -0
  74. package/dist/daemon/image-gen/index.d.ts.map +1 -0
  75. package/dist/daemon/image-gen/index.js +129 -0
  76. package/dist/daemon/image-gen/index.js.map +1 -0
  77. package/dist/daemon/image-gen/providers/cloudflare.d.ts +13 -0
  78. package/dist/daemon/image-gen/providers/cloudflare.d.ts.map +1 -0
  79. package/dist/daemon/image-gen/providers/cloudflare.js +63 -0
  80. package/dist/daemon/image-gen/providers/cloudflare.js.map +1 -0
  81. package/dist/daemon/image-gen/providers/huggingface.d.ts +12 -0
  82. package/dist/daemon/image-gen/providers/huggingface.d.ts.map +1 -0
  83. package/dist/daemon/image-gen/providers/huggingface.js +58 -0
  84. package/dist/daemon/image-gen/providers/huggingface.js.map +1 -0
  85. package/dist/daemon/image-gen/providers/pollinations.d.ts +11 -0
  86. package/dist/daemon/image-gen/providers/pollinations.d.ts.map +1 -0
  87. package/dist/daemon/image-gen/providers/pollinations.js +39 -0
  88. package/dist/daemon/image-gen/providers/pollinations.js.map +1 -0
  89. package/dist/daemon/image-gen/providers/replicate.d.ts +9 -0
  90. package/dist/daemon/image-gen/providers/replicate.d.ts.map +1 -0
  91. package/dist/daemon/image-gen/providers/replicate.js +158 -0
  92. package/dist/daemon/image-gen/providers/replicate.js.map +1 -0
  93. package/dist/daemon/image-gen/types.d.ts +41 -0
  94. package/dist/daemon/image-gen/types.d.ts.map +1 -0
  95. package/dist/daemon/image-gen/types.js +5 -0
  96. package/dist/daemon/image-gen/types.js.map +1 -0
  97. package/dist/daemon/index.d.ts.map +1 -1
  98. package/dist/daemon/index.js +260 -6
  99. package/dist/daemon/index.js.map +1 -1
  100. package/dist/daemon/screenshot.d.ts +12 -0
  101. package/dist/daemon/screenshot.d.ts.map +1 -0
  102. package/dist/daemon/screenshot.js +252 -0
  103. package/dist/daemon/screenshot.js.map +1 -0
  104. package/dist/daemon/session-content.d.ts +27 -0
  105. package/dist/daemon/session-content.d.ts.map +1 -0
  106. package/dist/daemon/session-content.js +76 -0
  107. package/dist/daemon/session-content.js.map +1 -0
  108. package/dist/daemon/vision.d.ts +46 -0
  109. package/dist/daemon/vision.d.ts.map +1 -0
  110. package/dist/daemon/vision.js +176 -0
  111. package/dist/daemon/vision.js.map +1 -0
  112. package/dist/index.d.ts +6 -1
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +4 -1
  115. package/dist/index.js.map +1 -1
  116. package/dist/ipc/validate.d.ts +52 -0
  117. package/dist/ipc/validate.d.ts.map +1 -0
  118. package/dist/ipc/validate.js +129 -0
  119. package/dist/ipc/validate.js.map +1 -0
  120. package/dist/mcp/index.d.ts +23 -0
  121. package/dist/mcp/index.d.ts.map +1 -0
  122. package/dist/mcp/index.js +787 -0
  123. package/dist/mcp/index.js.map +1 -0
  124. package/dist/types/broker.d.ts +3 -1
  125. package/dist/types/broker.d.ts.map +1 -1
  126. package/dist/types/broker.js.map +1 -1
  127. package/package.json +5 -2
  128. package/templates/adapter/ONBOARDING_PROMPT.md +51 -29
  129. package/templates/adapter/README.md.tmpl +14 -31
  130. package/templates/adapter/package.json.tmpl +1 -1
  131. package/templates/adapter/src/watcher/commands.ts.tmpl +24 -126
  132. package/templates/adapter/src/watcher/index.ts.tmpl +112 -88
  133. package/templates/adapter/src/watcher/ipc-server.ts.tmpl +27 -3
@@ -1,17 +1,11 @@
1
1
  /**
2
2
  * watcher/index.ts — {{DISPLAY_NAME}} adapter entry point
3
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
4
+ * Thin transport adapter: connects to {{DISPLAY_NAME}}, forwards all messages
5
+ * to the AIBroker hub daemon for processing, and delivers hub responses back
6
+ * via the upstream service.
10
7
  *
11
- * Lifecycle:
12
- * watch() → detectHubMode() → connectWatcher() → startIpcServer()
13
- * → [hub mode] register_adapter on hub
14
- * → await forever (exits on SIGINT / SIGTERM)
8
+ * Requires the AIBroker daemon to be running. Does not function standalone.
15
9
  */
16
10
 
17
11
  import { unlinkSync } from "node:fs";
@@ -22,11 +16,6 @@ import {
22
16
  WatcherClient,
23
17
  DAEMON_SOCKET_PATH,
24
18
  setAppDir,
25
- loadSessionRegistry,
26
- APIBackend,
27
- HybridSessionManager,
28
- setHybridManager,
29
- router,
30
19
  log,
31
20
  setLogPrefix,
32
21
  createBrokerMessage,
@@ -41,84 +30,97 @@ import {
41
30
  setConnectionStatus,
42
31
  } from "./state.js";
43
32
 
44
- // ── Hub detection ─────────────────────────────────────────────────────────────
33
+ // ── Hub connection ──────────────────────────────────────────────────────────
45
34
 
46
35
  /**
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.
36
+ * Connect to the AIBroker hub daemon. Retries up to 3 times with 2s delay.
37
+ * Throws if the hub is not reachable — adapter cannot function without it.
51
38
  */
52
- async function detectHubMode(): Promise<boolean> {
39
+ async function connectToHub(): Promise<WatcherClient> {
53
40
  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;
41
+ const MAX_RETRIES = 3;
42
+ const RETRY_DELAY = 2000;
43
+
44
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
45
+ try {
46
+ const result = await Promise.race([
47
+ client.call_raw("ping", {}),
48
+ new Promise<null>((_, reject) =>
49
+ setTimeout(() => reject(new Error("timeout")), 2000),
50
+ ),
51
+ ]);
52
+ if (result !== null) return client;
53
+ } catch {
54
+ if (attempt < MAX_RETRIES) {
55
+ log(`Hub not reachable (attempt ${attempt}/${MAX_RETRIES}), retrying in ${RETRY_DELAY}ms...`);
56
+ await new Promise((r) => setTimeout(r, RETRY_DELAY));
57
+ }
58
+ }
62
59
  }
60
+
61
+ throw new Error(
62
+ `AIBroker daemon not reachable at ${DAEMON_SOCKET_PATH}. ` +
63
+ `Start it with: aibroker start`
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Slash commands handled locally (require direct upstream service access).
69
+ * Everything else goes to the hub.
70
+ */
71
+ const LOCAL_SLASH_COMMANDS = new Set(["/restart", "/login"]);
72
+
73
+ function isLocalSlashCommand(text: string): boolean {
74
+ return LOCAL_SLASH_COMMANDS.has(text.trim());
63
75
  }
64
76
 
65
77
  // ── Main ──────────────────────────────────────────────────────────────────────
66
78
 
67
79
  /**
68
- * Start the {{DISPLAY_NAME}} adapter watcher.
80
+ * Start the {{DISPLAY_NAME}} adapter watcher — thin transport adapter.
69
81
  *
70
- * Suspends the event loop indefinitely via `await new Promise(() => {})`.
71
- * Exits only on SIGINT or SIGTERM.
82
+ * All command handling, session management, TTS, and screenshots are owned
83
+ * by the AIBroker hub daemon. This process only:
84
+ * 1. Connects to the upstream service
85
+ * 2. Forwards incoming messages to the hub
86
+ * 3. Delivers hub-originated messages via the upstream service (through IPC "deliver")
87
+ * 4. Handles /restart and /login locally
72
88
  */
73
89
  export async function watch(): Promise<void> {
74
90
  setLogPrefix("{{ADAPTER_NAME}}-watch");
75
91
  setAppDir(join(homedir(), ".{{ADAPTER_NAME}}"));
76
92
 
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;
93
+ // Connect to AIBroker hub (required — no standalone mode)
94
+ let hubClient: WatcherClient;
95
+ try {
96
+ hubClient = await connectToHub();
97
+ } catch (err) {
98
+ console.error(`[{{ADAPTER_NAME}}-watch] FATAL: ${err instanceof Error ? err.message : String(err)}`);
99
+ console.error("[{{ADAPTER_NAME}}-watch] The AIBroker daemon must be running. Exiting.");
100
+ process.exit(1);
101
+ }
96
102
 
97
103
  console.log(`{{DISPLAY_NAME}} Adapter Watch`);
98
104
  console.log(` Socket: ${ADAPTER_SOCKET_PATH}`);
99
- console.log(` Mode: ${hubMode ? "hub (daemon detected)" : "embedded (standalone)"}`);
105
+ console.log(` Hub: ${DAEMON_SOCKET_PATH}`);
100
106
  console.log();
101
107
 
102
- // Restore persisted session state
103
- loadSessionRegistry();
104
-
105
108
  // Graceful shutdown
106
109
  let cleanupConnection: (() => void) | null = null;
107
110
 
108
111
  const shutdown = (signal: string) => {
109
112
  console.log(`\n[{{ADAPTER_NAME}}-watch] ${signal} received. Stopping.`);
113
+ clearInterval(heartbeatTimer);
110
114
  setConnectionStatus("disconnected");
111
115
  if (cleanupConnection) cleanupConnection();
112
- if (hubClient) {
113
- hubClient.call_raw("unregister_adapter", { name: "{{ADAPTER_NAME}}" }).catch(() => {});
114
- }
116
+ hubClient.call_raw("unregister_adapter", { name: "{{ADAPTER_NAME}}" }).catch(() => {});
115
117
  try { unlinkSync(ADAPTER_SOCKET_PATH); } catch { /* ignore */ }
116
118
  process.exit(0);
117
119
  };
118
120
  process.on("SIGINT", () => shutdown("SIGINT"));
119
121
  process.on("SIGTERM", () => shutdown("SIGTERM"));
120
122
 
121
- // Build the message handler
123
+ // Local handler for /restart and /login only
122
124
  const handleMessage = createMessageHandler();
123
125
 
124
126
  // Connect to the upstream service
@@ -131,25 +133,25 @@ export async function watch(): Promise<void> {
131
133
  adapterStats.messagesReceived++;
132
134
  adapterStats.lastMessageAt = timestamp;
133
135
 
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
136
+ // Local commands stay in the adapter
137
+ if (isLocalSlashCommand(text)) {
151
138
  handleMessage(text, timestamp);
139
+ return;
152
140
  }
141
+
142
+ // Everything else → hub
143
+ const message = createBrokerMessage(
144
+ "{{ADAPTER_NAME}}",
145
+ text.trim().startsWith("/") ? "command" : "text",
146
+ { text },
147
+ );
148
+ message.timestamp = timestamp;
149
+
150
+ hubClient.call_raw("route_message", {
151
+ message: message as unknown as Record<string, unknown>,
152
+ }).catch((err) => {
153
+ log(`Hub route_message failed: ${err instanceof Error ? err.message : String(err)}`);
154
+ });
153
155
  },
154
156
  );
155
157
 
@@ -159,18 +161,40 @@ export async function watch(): Promise<void> {
159
161
  // Start the adapter IPC server
160
162
  startIpcServer(triggerLogin);
161
163
 
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
- }
164
+ // Register with hub
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
+ // Hub heartbeat — re-register if the daemon restarts
175
+ const HUB_HEARTBEAT_INTERVAL = 30_000; // 30 seconds
176
+ const heartbeatTimer = setInterval(async () => {
177
+ try {
178
+ const result = await Promise.race([
179
+ hubClient.call_raw("ping", {}),
180
+ new Promise<null>((_, reject) =>
181
+ setTimeout(() => reject(new Error("timeout")), 5000),
182
+ ),
183
+ ]);
184
+ if (result === null) throw new Error("null response");
185
+ } catch {
186
+ // Hub unreachable — try to re-register
187
+ log("Hub heartbeat failed — attempting re-registration...");
188
+ hubClient.call_raw("register_adapter", {
189
+ name: "{{ADAPTER_NAME}}",
190
+ socketPath: ADAPTER_SOCKET_PATH,
191
+ }).then(() => {
192
+ log("Re-registered with AIBroker hub daemon");
193
+ }).catch((err) => {
194
+ log(`Hub re-registration failed: ${err instanceof Error ? err.message : String(err)}`);
195
+ });
196
+ }
197
+ }, HUB_HEARTBEAT_INTERVAL);
174
198
 
175
199
  // Keep process alive
176
200
  await new Promise(() => {});
@@ -68,9 +68,21 @@ export function startIpcServer(triggerLogin: () => Promise<string>): IpcServer {
68
68
  break;
69
69
  }
70
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);
71
+ // Hub sends pre-encoded audio as base64 buffer, or audioPath as fallback
72
+ const voiceB64 = typeof payload.buffer === "string" ? payload.buffer : "";
73
+ if (voiceB64) {
74
+ // Pre-encoded audio from hub — write to temp file and send
75
+ const { writeFileSync } = await import("node:fs");
76
+ const { join } = await import("node:path");
77
+ const { tmpdir } = await import("node:os");
78
+ const tmpPath = join(tmpdir(), `{{ADAPTER_NAME}}-voice-${Date.now()}.ogg`);
79
+ writeFileSync(tmpPath, Buffer.from(voiceB64, "base64"));
80
+ await sendVoice(tmpPath, recipient);
81
+ } else {
82
+ const audioPath = typeof payload.audioPath === "string" ? payload.audioPath : null;
83
+ if (!audioPath) return { ok: false, error: "deliver(voice): buffer or audioPath is required" };
84
+ await sendVoice(audioPath, recipient);
85
+ }
74
86
  break;
75
87
  }
76
88
  case "file": {
@@ -81,6 +93,18 @@ export function startIpcServer(triggerLogin: () => Promise<string>): IpcServer {
81
93
  await sendFile(filePath, caption, mimetype, recipient);
82
94
  break;
83
95
  }
96
+ case "image": {
97
+ // Image delivery — payload contains base64-encoded buffer and optional caption
98
+ const buffer = typeof payload.buffer === "string"
99
+ ? Buffer.from(payload.buffer, "base64")
100
+ : null;
101
+ if (!buffer) return { ok: false, error: "deliver(image): buffer is required" };
102
+ const imgCaption = typeof payload.text === "string" ? payload.text : undefined;
103
+ // TODO: implement image sending via your SDK. For now, fall back to caption text.
104
+ if (imgCaption) await sendText(imgCaption, recipient);
105
+ log(`[{{ADAPTER_NAME}}] deliver(image): ${buffer.length} bytes — implement image sending`);
106
+ break;
107
+ }
84
108
  case "command": {
85
109
  // Commands from the hub (e.g. routed from another adapter) — deliver as text
86
110
  const text = typeof payload.text === "string" ? payload.text : String(payload.text ?? "");