owpenwork 0.1.11 → 0.1.12
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.
- package/dist/bridge.js +33 -12
- package/dist/cli.js +44 -10
- package/dist/config.js +2 -0
- package/dist/logger.js +8 -1
- package/dist/whatsapp.js +12 -1
- package/package.json +1 -1
package/dist/bridge.js
CHANGED
|
@@ -19,7 +19,8 @@ const TOOL_LABELS = {
|
|
|
19
19
|
task: "agent",
|
|
20
20
|
webfetch: "webfetch",
|
|
21
21
|
};
|
|
22
|
-
export async function startBridge(config, logger) {
|
|
22
|
+
export async function startBridge(config, logger, reporter) {
|
|
23
|
+
const reportStatus = reporter?.onStatus;
|
|
23
24
|
const client = createClient(config);
|
|
24
25
|
const store = new BridgeStore(config.dbPath);
|
|
25
26
|
store.seedAllowlist("telegram", config.allowlist.telegram);
|
|
@@ -31,12 +32,14 @@ export async function startBridge(config, logger) {
|
|
|
31
32
|
}
|
|
32
33
|
else {
|
|
33
34
|
logger.info("telegram adapter disabled");
|
|
35
|
+
reportStatus?.("Telegram adapter disabled.");
|
|
34
36
|
}
|
|
35
37
|
if (config.whatsappEnabled) {
|
|
36
|
-
adapters.set("whatsapp", createWhatsAppAdapter(config, logger, handleInbound, { printQr: true }));
|
|
38
|
+
adapters.set("whatsapp", createWhatsAppAdapter(config, logger, handleInbound, { printQr: true, onStatus: reportStatus }));
|
|
37
39
|
}
|
|
38
40
|
else {
|
|
39
41
|
logger.info("whatsapp adapter disabled");
|
|
42
|
+
reportStatus?.("WhatsApp adapter disabled.");
|
|
40
43
|
}
|
|
41
44
|
const sessionQueue = new Map();
|
|
42
45
|
const activeRuns = new Map();
|
|
@@ -102,7 +105,7 @@ export async function startBridge(config, logger) {
|
|
|
102
105
|
if (output)
|
|
103
106
|
message += `\n${output}`;
|
|
104
107
|
}
|
|
105
|
-
await sendText(run.channel, run.peerId, message);
|
|
108
|
+
await sendText(run.channel, run.peerId, message, { kind: "tool" });
|
|
106
109
|
}
|
|
107
110
|
if (event.type === "permission.asked") {
|
|
108
111
|
const permission = event.properties;
|
|
@@ -117,7 +120,9 @@ export async function startBridge(config, logger) {
|
|
|
117
120
|
if (response === "reject") {
|
|
118
121
|
const run = activeRuns.get(permission.sessionID);
|
|
119
122
|
if (run) {
|
|
120
|
-
await sendText(run.channel, run.peerId, "Permission denied. Update configuration to allow tools."
|
|
123
|
+
await sendText(run.channel, run.peerId, "Permission denied. Update configuration to allow tools.", {
|
|
124
|
+
kind: "system",
|
|
125
|
+
});
|
|
121
126
|
}
|
|
122
127
|
}
|
|
123
128
|
}
|
|
@@ -125,10 +130,14 @@ export async function startBridge(config, logger) {
|
|
|
125
130
|
})().catch((error) => {
|
|
126
131
|
logger.error({ error }, "event stream closed");
|
|
127
132
|
});
|
|
128
|
-
async function sendText(channel, peerId, text) {
|
|
133
|
+
async function sendText(channel, peerId, text, options = {}) {
|
|
129
134
|
const adapter = adapters.get(channel);
|
|
130
135
|
if (!adapter)
|
|
131
136
|
return;
|
|
137
|
+
const kind = options.kind ?? "system";
|
|
138
|
+
if (options.display !== false) {
|
|
139
|
+
reporter?.onOutbound?.({ channel, peerId, text, kind });
|
|
140
|
+
}
|
|
132
141
|
const chunks = chunkText(text, adapter.maxTextLength);
|
|
133
142
|
for (const chunk of chunks) {
|
|
134
143
|
logger.info({ channel, peerId, length: chunk.length }, "sending message");
|
|
@@ -151,30 +160,36 @@ export async function startBridge(config, logger) {
|
|
|
151
160
|
const allowed = allowAll || isSelf || store.isAllowed("whatsapp", peerKey);
|
|
152
161
|
if (!allowed) {
|
|
153
162
|
if (config.whatsappDmPolicy === "allowlist") {
|
|
154
|
-
await sendText(inbound.channel, inbound.peerId, "Access denied. Ask the owner to allowlist your number.");
|
|
163
|
+
await sendText(inbound.channel, inbound.peerId, "Access denied. Ask the owner to allowlist your number.", { kind: "system" });
|
|
155
164
|
return;
|
|
156
165
|
}
|
|
157
166
|
store.prunePairingRequests();
|
|
158
167
|
const active = store.getPairingRequest("whatsapp", peerKey);
|
|
159
168
|
const pending = store.listPairingRequests("whatsapp");
|
|
160
169
|
if (!active && pending.length >= 3) {
|
|
161
|
-
await sendText(inbound.channel, inbound.peerId, "Pairing queue full. Ask the owner to approve pending requests.");
|
|
170
|
+
await sendText(inbound.channel, inbound.peerId, "Pairing queue full. Ask the owner to approve pending requests.", { kind: "system" });
|
|
162
171
|
return;
|
|
163
172
|
}
|
|
164
173
|
const code = active?.code ?? String(Math.floor(100000 + Math.random() * 900000));
|
|
165
174
|
if (!active) {
|
|
166
175
|
store.createPairingRequest("whatsapp", peerKey, code, 60 * 60_000);
|
|
167
176
|
}
|
|
168
|
-
await sendText(inbound.channel, inbound.peerId, `Pairing required. Ask the owner to approve code: ${code}
|
|
177
|
+
await sendText(inbound.channel, inbound.peerId, `Pairing required. Ask the owner to approve code: ${code}`, { kind: "system" });
|
|
169
178
|
return;
|
|
170
179
|
}
|
|
171
180
|
}
|
|
172
181
|
else if (config.allowlist[inbound.channel].size > 0) {
|
|
173
182
|
if (!store.isAllowed(inbound.channel, peerKey)) {
|
|
174
|
-
await sendText(inbound.channel, inbound.peerId, "Access denied.");
|
|
183
|
+
await sendText(inbound.channel, inbound.peerId, "Access denied.", { kind: "system" });
|
|
175
184
|
return;
|
|
176
185
|
}
|
|
177
186
|
}
|
|
187
|
+
reporter?.onInbound?.({
|
|
188
|
+
channel: inbound.channel,
|
|
189
|
+
peerId: inbound.peerId,
|
|
190
|
+
text: inbound.text,
|
|
191
|
+
fromMe: inbound.fromMe,
|
|
192
|
+
});
|
|
178
193
|
const session = store.getSession(inbound.channel, peerKey);
|
|
179
194
|
const sessionID = session?.session_id ?? (await createSession({ ...inbound, peerId: peerKey }));
|
|
180
195
|
enqueue(sessionID, async () => {
|
|
@@ -198,15 +213,19 @@ export async function startBridge(config, logger) {
|
|
|
198
213
|
.join("\n")
|
|
199
214
|
.trim();
|
|
200
215
|
if (reply) {
|
|
201
|
-
await sendText(inbound.channel, inbound.peerId, reply);
|
|
216
|
+
await sendText(inbound.channel, inbound.peerId, reply, { kind: "reply" });
|
|
202
217
|
}
|
|
203
218
|
else {
|
|
204
|
-
await sendText(inbound.channel, inbound.peerId, "No response generated. Try again."
|
|
219
|
+
await sendText(inbound.channel, inbound.peerId, "No response generated. Try again.", {
|
|
220
|
+
kind: "system",
|
|
221
|
+
});
|
|
205
222
|
}
|
|
206
223
|
}
|
|
207
224
|
catch (error) {
|
|
208
225
|
logger.error({ error }, "prompt failed");
|
|
209
|
-
await sendText(inbound.channel, inbound.peerId, "Error: failed to reach OpenCode."
|
|
226
|
+
await sendText(inbound.channel, inbound.peerId, "Error: failed to reach OpenCode.", {
|
|
227
|
+
kind: "system",
|
|
228
|
+
});
|
|
210
229
|
}
|
|
211
230
|
finally {
|
|
212
231
|
activeRuns.delete(sessionID);
|
|
@@ -242,8 +261,10 @@ export async function startBridge(config, logger) {
|
|
|
242
261
|
}
|
|
243
262
|
for (const adapter of adapters.values()) {
|
|
244
263
|
await adapter.start();
|
|
264
|
+
reportStatus?.(`${adapter.name === "whatsapp" ? "WhatsApp" : "Telegram"} adapter started.`);
|
|
245
265
|
}
|
|
246
266
|
logger.info({ channels: Array.from(adapters.keys()) }, "bridge started");
|
|
267
|
+
reportStatus?.(`Bridge running. Logs: ${config.logFile}`);
|
|
247
268
|
return {
|
|
248
269
|
async stop() {
|
|
249
270
|
eventAbort.abort();
|
package/dist/cli.js
CHANGED
|
@@ -8,8 +8,9 @@ import { loadConfig, normalizeWhatsAppId, readConfigFile, writeConfigFile, } fro
|
|
|
8
8
|
import { BridgeStore } from "./db.js";
|
|
9
9
|
import { createLogger } from "./logger.js";
|
|
10
10
|
import { createClient } from "./opencode.js";
|
|
11
|
+
import { truncateText } from "./text.js";
|
|
11
12
|
import { loginWhatsApp, unpairWhatsApp } from "./whatsapp.js";
|
|
12
|
-
const VERSION = "0.1.
|
|
13
|
+
const VERSION = "0.1.12";
|
|
13
14
|
const STEP_OPTIONS = [
|
|
14
15
|
{
|
|
15
16
|
value: "config",
|
|
@@ -62,6 +63,38 @@ function defaultSelections(configExists, whatsappLinked) {
|
|
|
62
63
|
selections.push("start");
|
|
63
64
|
return selections;
|
|
64
65
|
}
|
|
66
|
+
function createAppLogger(config) {
|
|
67
|
+
return createLogger(config.logLevel, { logFile: config.logFile });
|
|
68
|
+
}
|
|
69
|
+
function createConsoleReporter() {
|
|
70
|
+
const formatChannel = (channel) => (channel === "whatsapp" ? "WhatsApp" : "Telegram");
|
|
71
|
+
const formatPeer = (channel, peerId, fromMe) => {
|
|
72
|
+
const base = channel === "whatsapp" ? normalizeWhatsAppId(peerId) : peerId;
|
|
73
|
+
return fromMe ? `${base} (me)` : base;
|
|
74
|
+
};
|
|
75
|
+
const printBlock = (prefix, text) => {
|
|
76
|
+
const lines = text.split(/\r?\n/).map((line) => truncateText(line.trim(), 240));
|
|
77
|
+
const [first, ...rest] = lines.length ? lines : ["(empty)"];
|
|
78
|
+
console.log(`${prefix} ${first}`);
|
|
79
|
+
for (const line of rest) {
|
|
80
|
+
console.log(`${" ".repeat(prefix.length)} ${line}`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
onStatus(message) {
|
|
85
|
+
console.log(message);
|
|
86
|
+
},
|
|
87
|
+
onInbound({ channel, peerId, text, fromMe }) {
|
|
88
|
+
const prefix = `[${formatChannel(channel)}] ${formatPeer(channel, peerId, fromMe)} >`;
|
|
89
|
+
printBlock(prefix, text);
|
|
90
|
+
},
|
|
91
|
+
onOutbound({ channel, peerId, text, kind }) {
|
|
92
|
+
const marker = kind === "reply" ? "<" : kind === "tool" ? "*" : "!";
|
|
93
|
+
const prefix = `[${formatChannel(channel)}] ${formatPeer(channel, peerId)} ${marker}`;
|
|
94
|
+
printBlock(prefix, text);
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
65
98
|
function updateConfig(configPath, updater) {
|
|
66
99
|
const { config } = readConfigFile(configPath);
|
|
67
100
|
const base = config ?? { version: 1 };
|
|
@@ -195,12 +228,13 @@ async function runStart(pathOverride) {
|
|
|
195
228
|
process.env.OPENCODE_DIRECTORY = pathOverride.trim();
|
|
196
229
|
}
|
|
197
230
|
const config = loadConfig();
|
|
198
|
-
const logger =
|
|
231
|
+
const logger = createAppLogger(config);
|
|
232
|
+
const reporter = createConsoleReporter();
|
|
199
233
|
if (!process.env.OPENCODE_DIRECTORY) {
|
|
200
234
|
process.env.OPENCODE_DIRECTORY = config.opencodeDirectory;
|
|
201
235
|
}
|
|
202
|
-
const bridge = await startBridge(config, logger);
|
|
203
|
-
|
|
236
|
+
const bridge = await startBridge(config, logger, reporter);
|
|
237
|
+
reporter.onStatus?.("Commands: owpenwork whatsapp login, owpenwork pairing list, owpenwork status");
|
|
204
238
|
const shutdown = async () => {
|
|
205
239
|
logger.info("shutting down");
|
|
206
240
|
await bridge.stop();
|
|
@@ -277,7 +311,7 @@ async function runGuidedFlow(pathArg, opts) {
|
|
|
277
311
|
if (!relink)
|
|
278
312
|
return;
|
|
279
313
|
}
|
|
280
|
-
await loginWhatsApp(config,
|
|
314
|
+
await loginWhatsApp(config, createAppLogger(config), { onStatus: console.log });
|
|
281
315
|
});
|
|
282
316
|
continue;
|
|
283
317
|
}
|
|
@@ -338,7 +372,7 @@ login
|
|
|
338
372
|
.description("Login to WhatsApp via QR code")
|
|
339
373
|
.action(async () => {
|
|
340
374
|
const config = loadConfig(process.env, { requireOpencode: false });
|
|
341
|
-
await loginWhatsApp(config,
|
|
375
|
+
await loginWhatsApp(config, createAppLogger(config), { onStatus: console.log });
|
|
342
376
|
});
|
|
343
377
|
login
|
|
344
378
|
.command("telegram")
|
|
@@ -372,28 +406,28 @@ whatsapp
|
|
|
372
406
|
.description("Login to WhatsApp via QR code")
|
|
373
407
|
.action(async () => {
|
|
374
408
|
const config = loadConfig(process.env, { requireOpencode: false });
|
|
375
|
-
await loginWhatsApp(config,
|
|
409
|
+
await loginWhatsApp(config, createAppLogger(config), { onStatus: console.log });
|
|
376
410
|
});
|
|
377
411
|
whatsapp
|
|
378
412
|
.command("logout")
|
|
379
413
|
.description("Logout of WhatsApp and clear auth state")
|
|
380
414
|
.action(() => {
|
|
381
415
|
const config = loadConfig(process.env, { requireOpencode: false });
|
|
382
|
-
unpairWhatsApp(config,
|
|
416
|
+
unpairWhatsApp(config, createAppLogger(config));
|
|
383
417
|
});
|
|
384
418
|
program
|
|
385
419
|
.command("qr")
|
|
386
420
|
.description("Print a WhatsApp QR code to pair")
|
|
387
421
|
.action(async () => {
|
|
388
422
|
const config = loadConfig(process.env, { requireOpencode: false });
|
|
389
|
-
await loginWhatsApp(config,
|
|
423
|
+
await loginWhatsApp(config, createAppLogger(config), { onStatus: console.log });
|
|
390
424
|
});
|
|
391
425
|
program
|
|
392
426
|
.command("unpair")
|
|
393
427
|
.description("Clear WhatsApp pairing data")
|
|
394
428
|
.action(() => {
|
|
395
429
|
const config = loadConfig(process.env, { requireOpencode: false });
|
|
396
|
-
unpairWhatsApp(config,
|
|
430
|
+
unpairWhatsApp(config, createAppLogger(config));
|
|
397
431
|
});
|
|
398
432
|
const pairing = program.command("pairing").description("Pairing requests");
|
|
399
433
|
pairing
|
package/dist/config.js
CHANGED
|
@@ -125,6 +125,7 @@ export function loadConfig(env = process.env, options = {}) {
|
|
|
125
125
|
const resolvedDirectory = opencodeDirectory || process.cwd();
|
|
126
126
|
const dataDir = expandHome(env.OWPENBOT_DATA_DIR ?? "~/.owpenbot");
|
|
127
127
|
const dbPath = expandHome(env.OWPENBOT_DB_PATH ?? path.join(dataDir, "owpenbot.db"));
|
|
128
|
+
const logFile = expandHome(env.OWPENBOT_LOG_FILE ?? path.join(dataDir, "logs", "owpenbot.log"));
|
|
128
129
|
const configPath = resolveConfigPath(dataDir, env);
|
|
129
130
|
const { config: configFile } = readConfigFile(configPath);
|
|
130
131
|
const whatsappFile = configFile.channels?.whatsapp ?? {};
|
|
@@ -159,6 +160,7 @@ export function loadConfig(env = process.env, options = {}) {
|
|
|
159
160
|
whatsappEnabled: parseBoolean(env.WHATSAPP_ENABLED, true),
|
|
160
161
|
dataDir,
|
|
161
162
|
dbPath,
|
|
163
|
+
logFile,
|
|
162
164
|
allowlist: envAllowlist,
|
|
163
165
|
toolUpdatesEnabled: parseBoolean(env.TOOL_UPDATES_ENABLED, false),
|
|
164
166
|
groupsEnabled: parseBoolean(env.GROUPS_ENABLED, false),
|
package/dist/logger.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import pino from "pino";
|
|
2
|
-
export function createLogger(level) {
|
|
4
|
+
export function createLogger(level, options) {
|
|
5
|
+
if (options?.logFile) {
|
|
6
|
+
fs.mkdirSync(path.dirname(options.logFile), { recursive: true });
|
|
7
|
+
const destination = pino.destination({ dest: options.logFile, sync: false });
|
|
8
|
+
return pino({ level, base: undefined }, destination);
|
|
9
|
+
}
|
|
3
10
|
return pino({
|
|
4
11
|
level,
|
|
5
12
|
base: undefined,
|
package/dist/whatsapp.js
CHANGED
|
@@ -43,15 +43,18 @@ export function createWhatsAppAdapter(config, logger, onMessage, opts = {}) {
|
|
|
43
43
|
if (update.qr && opts.printQr) {
|
|
44
44
|
qrcode.generate(update.qr, { small: true });
|
|
45
45
|
log.info("scan the QR code to connect WhatsApp");
|
|
46
|
+
opts.onStatus?.("Scan the QR code to connect WhatsApp.");
|
|
46
47
|
}
|
|
47
48
|
if (update.connection === "open") {
|
|
48
49
|
log.info("whatsapp connected");
|
|
50
|
+
opts.onStatus?.("WhatsApp connected.");
|
|
49
51
|
}
|
|
50
52
|
if (update.connection === "close") {
|
|
51
53
|
const lastDisconnect = update.lastDisconnect;
|
|
52
54
|
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
53
55
|
if (statusCode === 515 && !stopped) {
|
|
54
56
|
log.warn("whatsapp stream error; restarting connection");
|
|
57
|
+
opts.onStatus?.("WhatsApp stream error; reconnecting.");
|
|
55
58
|
setTimeout(() => {
|
|
56
59
|
void connect();
|
|
57
60
|
}, 1000);
|
|
@@ -60,10 +63,12 @@ export function createWhatsAppAdapter(config, logger, onMessage, opts = {}) {
|
|
|
60
63
|
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
|
|
61
64
|
if (shouldReconnect && !stopped) {
|
|
62
65
|
log.warn("whatsapp connection closed, reconnecting");
|
|
66
|
+
opts.onStatus?.("WhatsApp connection closed; reconnecting.");
|
|
63
67
|
void connect();
|
|
64
68
|
}
|
|
65
69
|
else if (!shouldReconnect) {
|
|
66
70
|
log.warn("whatsapp logged out, run 'owpenbot whatsapp login'");
|
|
71
|
+
opts.onStatus?.("WhatsApp logged out. Run: owpenwork whatsapp login.");
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
74
|
});
|
|
@@ -114,7 +119,7 @@ export function createWhatsAppAdapter(config, logger, onMessage, opts = {}) {
|
|
|
114
119
|
},
|
|
115
120
|
};
|
|
116
121
|
}
|
|
117
|
-
export async function loginWhatsApp(config, logger) {
|
|
122
|
+
export async function loginWhatsApp(config, logger, options = {}) {
|
|
118
123
|
const authDir = path.resolve(config.whatsappAuthDir);
|
|
119
124
|
ensureDir(authDir);
|
|
120
125
|
const log = logger.child({ channel: "whatsapp" });
|
|
@@ -155,8 +160,10 @@ export async function loginWhatsApp(config, logger) {
|
|
|
155
160
|
if (update.qr) {
|
|
156
161
|
qrcode.generate(update.qr, { small: true });
|
|
157
162
|
log.info("scan the QR code to connect WhatsApp");
|
|
163
|
+
options.onStatus?.("Scan the QR code to connect WhatsApp.");
|
|
158
164
|
}
|
|
159
165
|
if (update.connection === "open") {
|
|
166
|
+
options.onStatus?.("WhatsApp linked.");
|
|
160
167
|
finish("connection.open", "linked");
|
|
161
168
|
}
|
|
162
169
|
if (update.connection === "close") {
|
|
@@ -165,15 +172,18 @@ export async function loginWhatsApp(config, logger) {
|
|
|
165
172
|
if (statusCode === 515) {
|
|
166
173
|
if (state.creds?.registered || fs.existsSync(credsPath)) {
|
|
167
174
|
log.info("whatsapp login requires reconnect; completing login");
|
|
175
|
+
options.onStatus?.("WhatsApp login requires reconnect; completing login.");
|
|
168
176
|
finish("connection.restart.required", "linked");
|
|
169
177
|
}
|
|
170
178
|
else {
|
|
171
179
|
log.warn("whatsapp restart requested before creds registered");
|
|
180
|
+
options.onStatus?.("WhatsApp login needs another scan.");
|
|
172
181
|
finish("connection.restart.required", "restart");
|
|
173
182
|
}
|
|
174
183
|
return;
|
|
175
184
|
}
|
|
176
185
|
if (state.creds?.registered) {
|
|
186
|
+
options.onStatus?.("WhatsApp linked.");
|
|
177
187
|
finish("connection.close.registered", "linked");
|
|
178
188
|
}
|
|
179
189
|
}
|
|
@@ -184,6 +194,7 @@ export async function loginWhatsApp(config, logger) {
|
|
|
184
194
|
}
|
|
185
195
|
if (attempt < maxAttempts) {
|
|
186
196
|
log.warn("retrying whatsapp login after restart");
|
|
197
|
+
options.onStatus?.("Retrying WhatsApp login...");
|
|
187
198
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
188
199
|
}
|
|
189
200
|
}
|