@c4t4/heyamigo 0.10.0 → 0.10.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.
- package/README.md +40 -248
- package/config/access.example.json +12 -2
- package/config/config.example.json +16 -0
- package/config/memory-instructions.md +1 -1
- package/config/personalities/casual.md +1 -1
- package/config/personalities/professional.md +1 -1
- package/config/personalities/sharp.md +2 -2
- package/dist/ai/claude.js +1 -0
- package/dist/ai/codex.js +1 -0
- package/dist/ai/grok.js +310 -0
- package/dist/ai/provider.js +5 -5
- package/dist/ai/providers.js +2 -0
- package/dist/ai/sessions.js +5 -1
- package/dist/boot.js +15 -6
- package/dist/channels/index.js +2 -1
- package/dist/channels/runtime.js +1 -0
- package/dist/channels/telegram.js +393 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/setup.js +168 -70
- package/dist/cli/start.js +25 -4
- package/dist/config.js +41 -6
- package/dist/db/address.js +13 -0
- package/dist/db/identity-sync.js +8 -0
- package/dist/gateway/bootstrap.js +15 -22
- package/dist/gateway/commands.js +13 -15
- package/dist/gateway/incoming.js +107 -254
- package/dist/gateway/ingest.js +240 -0
- package/dist/gateway/outgoing.js +3 -5
- package/dist/gateway/triggers.js +7 -40
- package/dist/memory/digest.js +5 -5
- package/dist/queue/async-tasks.js +11 -4
- package/dist/queue/browser-worker.js +5 -3
- package/dist/queue/cron-dispatch.js +6 -7
- package/dist/queue/job-address.js +4 -0
- package/dist/queue/outbound-postsend.js +4 -7
- package/dist/queue/worker.js +11 -5
- package/dist/wa/whitelist.js +40 -5
- package/package.json +3 -2
package/dist/ai/grok.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// Grok Build CLI provider. Maps the neutral AiProvider contract onto
|
|
2
|
+
// `grok` headless mode (`--prompt-file` + `--output-format json`).
|
|
3
|
+
//
|
|
4
|
+
// Grok Build is a local coding-agent CLI, not a plain API model. It already
|
|
5
|
+
// knows how to inspect repo config, use MCP, run shell tools, and resume
|
|
6
|
+
// sessions. This adapter keeps the same heyamigo contract Claude/Codex use:
|
|
7
|
+
// one prompt in, one reply out, opaque provider-native session ids.
|
|
8
|
+
import { mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync, } from 'fs';
|
|
9
|
+
import { tmpdir } from 'os';
|
|
10
|
+
import { join, resolve } from 'path';
|
|
11
|
+
import { config } from '../config.js';
|
|
12
|
+
import { logger } from '../logger.js';
|
|
13
|
+
import { logPrompt } from '../promptlog.js';
|
|
14
|
+
import { runClaude, TIMEOUT_MS } from './spawn.js';
|
|
15
|
+
let cachedSystemPrompt = null;
|
|
16
|
+
function systemPrompt() {
|
|
17
|
+
if (cachedSystemPrompt !== null)
|
|
18
|
+
return cachedSystemPrompt;
|
|
19
|
+
const personality = readFileSync(resolve(process.cwd(), config.claude.personalityFile), 'utf-8');
|
|
20
|
+
let memoryInstructions = '';
|
|
21
|
+
try {
|
|
22
|
+
memoryInstructions = readFileSync(resolve(process.cwd(), config.memory.instructionsFile), 'utf-8');
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// memory instructions optional
|
|
26
|
+
}
|
|
27
|
+
cachedSystemPrompt = memoryInstructions
|
|
28
|
+
? `${personality}\n\n---\n\n${memoryInstructions}`
|
|
29
|
+
: personality;
|
|
30
|
+
return cachedSystemPrompt;
|
|
31
|
+
}
|
|
32
|
+
function reloadSystemPrompt() {
|
|
33
|
+
cachedSystemPrompt = null;
|
|
34
|
+
}
|
|
35
|
+
function permissionModeFor(mode) {
|
|
36
|
+
switch (mode) {
|
|
37
|
+
case 'read-only':
|
|
38
|
+
return 'plan';
|
|
39
|
+
case 'auto':
|
|
40
|
+
return 'acceptEdits';
|
|
41
|
+
case 'full':
|
|
42
|
+
return 'bypassPermissions';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function laneTimeoutMs(lane) {
|
|
46
|
+
return TIMEOUT_MS[lane];
|
|
47
|
+
}
|
|
48
|
+
function hasWebTool(tools) {
|
|
49
|
+
return tools.some((tool) => /web(fetch|search)?/i.test(tool));
|
|
50
|
+
}
|
|
51
|
+
function buildArgs(params) {
|
|
52
|
+
const cfg = config.grok;
|
|
53
|
+
let prompt = params.prompt;
|
|
54
|
+
const args = [
|
|
55
|
+
'--cwd',
|
|
56
|
+
process.cwd(),
|
|
57
|
+
'--output-format',
|
|
58
|
+
'json',
|
|
59
|
+
'--permission-mode',
|
|
60
|
+
permissionModeFor(params.mode),
|
|
61
|
+
'--verbatim',
|
|
62
|
+
];
|
|
63
|
+
if (cfg.model)
|
|
64
|
+
args.push('-m', cfg.model);
|
|
65
|
+
if (params.mode === 'read-only') {
|
|
66
|
+
args.push('--sandbox', 'read-only');
|
|
67
|
+
}
|
|
68
|
+
else if (cfg.alwaysApprove) {
|
|
69
|
+
args.push('--always-approve');
|
|
70
|
+
}
|
|
71
|
+
if (cfg.memory) {
|
|
72
|
+
args.push('--experimental-memory');
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
args.push('--no-memory');
|
|
76
|
+
}
|
|
77
|
+
if (params.allowedTools && params.allowedTools !== 'all') {
|
|
78
|
+
if (params.allowedTools.length > 0) {
|
|
79
|
+
args.push('--allow', params.allowedTools.join(','));
|
|
80
|
+
}
|
|
81
|
+
if (!hasWebTool(params.allowedTools)) {
|
|
82
|
+
args.push('--disable-web-search');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const extra of cfg.extraArgs)
|
|
86
|
+
args.push(extra);
|
|
87
|
+
if (params.sessionId) {
|
|
88
|
+
args.push('--resume', params.sessionId);
|
|
89
|
+
}
|
|
90
|
+
else if (params.includeSystemPrompt) {
|
|
91
|
+
// Keep this in the prompt file instead of argv so large personalities and
|
|
92
|
+
// memory instructions don't hit ARG_MAX.
|
|
93
|
+
prompt = `${systemPrompt()}\n\n---\n\n${prompt}`;
|
|
94
|
+
}
|
|
95
|
+
args.push('--prompt-file', params.promptFile);
|
|
96
|
+
return { args, prompt };
|
|
97
|
+
}
|
|
98
|
+
function usageFrom(raw) {
|
|
99
|
+
const usage = raw.usage;
|
|
100
|
+
return {
|
|
101
|
+
inputTokens: usage?.inputTokens ?? usage?.input_tokens ?? usage?.prompt_tokens ?? 0,
|
|
102
|
+
cacheReadTokens: usage?.cacheReadTokens ?? usage?.cached_input_tokens ?? 0,
|
|
103
|
+
cacheCreationTokens: usage?.cacheCreationTokens ?? 0,
|
|
104
|
+
outputTokens: usage?.outputTokens ?? usage?.output_tokens ?? usage?.completion_tokens ?? 0,
|
|
105
|
+
numTurns: 0,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function textFrom(raw) {
|
|
109
|
+
for (const value of [
|
|
110
|
+
raw.text,
|
|
111
|
+
raw.output_text,
|
|
112
|
+
raw.result,
|
|
113
|
+
raw.reply,
|
|
114
|
+
raw.message,
|
|
115
|
+
]) {
|
|
116
|
+
if (typeof value === 'string')
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
function parseJsonObject(stdout) {
|
|
122
|
+
const trimmed = stdout.trim();
|
|
123
|
+
if (!trimmed)
|
|
124
|
+
return null;
|
|
125
|
+
try {
|
|
126
|
+
return JSON.parse(trimmed);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Grok may emit log lines before/after JSON on some failures. Try the
|
|
130
|
+
// broadest JSON-looking slice before giving up.
|
|
131
|
+
const first = trimmed.indexOf('{');
|
|
132
|
+
const last = trimmed.lastIndexOf('}');
|
|
133
|
+
if (first >= 0 && last > first) {
|
|
134
|
+
try {
|
|
135
|
+
return JSON.parse(trimmed.slice(first, last + 1));
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function parseStreamingJson(stdout) {
|
|
145
|
+
let reply = '';
|
|
146
|
+
let sessionId;
|
|
147
|
+
let error = null;
|
|
148
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
149
|
+
const trimmed = line.trim();
|
|
150
|
+
if (!trimmed)
|
|
151
|
+
continue;
|
|
152
|
+
let ev;
|
|
153
|
+
try {
|
|
154
|
+
ev = JSON.parse(trimmed);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (ev.type === 'text' && typeof ev.data === 'string') {
|
|
160
|
+
reply += ev.data;
|
|
161
|
+
}
|
|
162
|
+
else if (ev.type === 'end') {
|
|
163
|
+
const id = ev.sessionId ?? ev.session_id;
|
|
164
|
+
if (typeof id === 'string')
|
|
165
|
+
sessionId = id;
|
|
166
|
+
}
|
|
167
|
+
else if (ev.type === 'error') {
|
|
168
|
+
error = textFrom(ev) ?? (typeof ev.data === 'string' ? ev.data : null);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (error)
|
|
172
|
+
throw new Error(`grok returned error: ${error}`);
|
|
173
|
+
if (!reply)
|
|
174
|
+
return null;
|
|
175
|
+
return {
|
|
176
|
+
reply: reply.trim(),
|
|
177
|
+
sessionId,
|
|
178
|
+
usage: {
|
|
179
|
+
inputTokens: 0,
|
|
180
|
+
cacheReadTokens: 0,
|
|
181
|
+
cacheCreationTokens: 0,
|
|
182
|
+
outputTokens: 0,
|
|
183
|
+
numTurns: 0,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function parseGrokOutput(stdout) {
|
|
188
|
+
const raw = parseJsonObject(stdout);
|
|
189
|
+
if (raw) {
|
|
190
|
+
if (raw.type === 'error') {
|
|
191
|
+
throw new Error(`grok returned error: ${textFrom(raw) ?? stdout.slice(0, 500)}`);
|
|
192
|
+
}
|
|
193
|
+
const reply = textFrom(raw);
|
|
194
|
+
if (reply !== null) {
|
|
195
|
+
const id = raw.sessionId ?? raw.session_id;
|
|
196
|
+
return {
|
|
197
|
+
reply: reply.trim(),
|
|
198
|
+
sessionId: typeof id === 'string' ? id : undefined,
|
|
199
|
+
usage: usageFrom(raw),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return parseStreamingJson(stdout);
|
|
204
|
+
}
|
|
205
|
+
function createPromptFile(prompt) {
|
|
206
|
+
const dir = mkdtempSync(join(tmpdir(), 'heyamigo-grok-'));
|
|
207
|
+
const path = join(dir, 'prompt.txt');
|
|
208
|
+
writeFileSync(path, prompt, 'utf-8');
|
|
209
|
+
return { dir, path };
|
|
210
|
+
}
|
|
211
|
+
function removePromptFile(tmp) {
|
|
212
|
+
try {
|
|
213
|
+
unlinkSync(tmp.path);
|
|
214
|
+
}
|
|
215
|
+
catch { }
|
|
216
|
+
try {
|
|
217
|
+
rmSync(tmp.dir, { recursive: true, force: true });
|
|
218
|
+
}
|
|
219
|
+
catch { }
|
|
220
|
+
}
|
|
221
|
+
async function runGrokTask(params) {
|
|
222
|
+
const tmp = createPromptFile(params.input);
|
|
223
|
+
let args = [];
|
|
224
|
+
let promptForFile = params.input;
|
|
225
|
+
try {
|
|
226
|
+
const built = buildArgs({
|
|
227
|
+
mode: params.mode,
|
|
228
|
+
sessionId: params.sessionId,
|
|
229
|
+
includeSystemPrompt: params.includeSystemPrompt,
|
|
230
|
+
prompt: params.input,
|
|
231
|
+
allowedTools: params.allowedTools,
|
|
232
|
+
promptFile: tmp.path,
|
|
233
|
+
});
|
|
234
|
+
args = built.args;
|
|
235
|
+
promptForFile = built.prompt;
|
|
236
|
+
writeFileSync(tmp.path, promptForFile, 'utf-8');
|
|
237
|
+
logger.info({
|
|
238
|
+
caller: params.caller,
|
|
239
|
+
resume: !!params.sessionId,
|
|
240
|
+
argv: args,
|
|
241
|
+
promptChars: promptForFile.length,
|
|
242
|
+
}, 'spawning grok');
|
|
243
|
+
const { stdout, stderr, durationMs } = await runClaude({
|
|
244
|
+
args,
|
|
245
|
+
input: '',
|
|
246
|
+
timeoutMs: laneTimeoutMs(params.lane),
|
|
247
|
+
caller: params.caller,
|
|
248
|
+
bin: config.grok.bin,
|
|
249
|
+
});
|
|
250
|
+
const startedAt = Date.now() - durationMs;
|
|
251
|
+
const parsed = parseGrokOutput(stdout);
|
|
252
|
+
if (!parsed) {
|
|
253
|
+
throw new Error(`grok produced no parseable result; stdout: ${stdout.slice(0, 500)}`);
|
|
254
|
+
}
|
|
255
|
+
void logPrompt({
|
|
256
|
+
ts: Math.floor(startedAt / 1000),
|
|
257
|
+
caller: params.caller,
|
|
258
|
+
args,
|
|
259
|
+
input: params.input,
|
|
260
|
+
output: parsed.reply,
|
|
261
|
+
sessionId: parsed.sessionId,
|
|
262
|
+
usage: parsed.usage,
|
|
263
|
+
durationMs,
|
|
264
|
+
stderr,
|
|
265
|
+
});
|
|
266
|
+
return parsed;
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
removePromptFile(tmp);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async function askGrok(params) {
|
|
273
|
+
const result = await runGrokTask({
|
|
274
|
+
input: params.input,
|
|
275
|
+
caller: 'worker',
|
|
276
|
+
mode: 'auto',
|
|
277
|
+
lane: 'main',
|
|
278
|
+
sessionId: params.sessionId,
|
|
279
|
+
includeSystemPrompt: true,
|
|
280
|
+
allowedTools: params.allowedTools,
|
|
281
|
+
addDirs: [
|
|
282
|
+
config.memory.dir,
|
|
283
|
+
config.storage.mediaDir,
|
|
284
|
+
],
|
|
285
|
+
});
|
|
286
|
+
if (!result.sessionId) {
|
|
287
|
+
throw new Error('grok ask: response missing session id');
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
reply: result.reply,
|
|
291
|
+
sessionId: result.sessionId,
|
|
292
|
+
usage: result.usage ?? {
|
|
293
|
+
inputTokens: 0,
|
|
294
|
+
cacheReadTokens: 0,
|
|
295
|
+
cacheCreationTokens: 0,
|
|
296
|
+
outputTokens: 0,
|
|
297
|
+
numTurns: 0,
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
export const grokProvider = {
|
|
302
|
+
name: 'grok',
|
|
303
|
+
contextWindow: config.grok.contextWindow,
|
|
304
|
+
// The current Grok Build headless JSON output does not expose reliable
|
|
305
|
+
// per-turn token usage, so treat any reported counts as this invocation only.
|
|
306
|
+
usageReportingMode: 'per-turn',
|
|
307
|
+
ask: askGrok,
|
|
308
|
+
runTask: runGrokTask,
|
|
309
|
+
reloadSystemPrompt,
|
|
310
|
+
};
|
package/dist/ai/provider.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Provider abstraction for the user-facing chat ask path. Lets the worker
|
|
2
|
-
// route conversation turns to
|
|
2
|
+
// route conversation turns to Claude, Codex, Grok, or any future CLI
|
|
3
3
|
// without knowing the wire details.
|
|
4
4
|
//
|
|
5
|
-
// Scope: covers the interactive worker call
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// Scope: covers the interactive worker call and general provider-backed agent
|
|
6
|
+
// tasks (memory digests, async/background work, browser tasks). A few legacy
|
|
7
|
+
// utilities may still call a specific CLI directly, but runtime work should
|
|
8
|
+
// flow through this interface.
|
|
9
9
|
export {};
|
package/dist/ai/providers.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { config } from '../config.js';
|
|
2
2
|
import { claudeProvider } from './claude.js';
|
|
3
3
|
import { codexProvider } from './codex.js';
|
|
4
|
+
import { grokProvider } from './grok.js';
|
|
4
5
|
const REGISTRY = {
|
|
5
6
|
claude: claudeProvider,
|
|
6
7
|
codex: codexProvider,
|
|
8
|
+
grok: grokProvider,
|
|
7
9
|
};
|
|
8
10
|
// Resolve the active provider. Defaults to claude if no override is set in
|
|
9
11
|
// config; pass an explicit name to force one (useful for per-role routing
|
package/dist/ai/sessions.js
CHANGED
|
@@ -34,7 +34,11 @@ function load() {
|
|
|
34
34
|
('codex' in obj &&
|
|
35
35
|
typeof obj.codex === 'object' &&
|
|
36
36
|
obj.codex !== null &&
|
|
37
|
-
'sessionId' in obj.codex)
|
|
37
|
+
'sessionId' in obj.codex) ||
|
|
38
|
+
('grok' in obj &&
|
|
39
|
+
typeof obj.grok === 'object' &&
|
|
40
|
+
obj.grok !== null &&
|
|
41
|
+
'sessionId' in obj.grok);
|
|
38
42
|
if (isNamespaced) {
|
|
39
43
|
out[jid] = obj;
|
|
40
44
|
}
|
package/dist/boot.js
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
// startup order — there used to be two parallel main() functions that
|
|
4
4
|
// drifted; this prevents that.
|
|
5
5
|
import { setBaileysSocket } from './channels/index.js';
|
|
6
|
+
import { telegramRuntime } from './channels/telegram.js';
|
|
7
|
+
import { config } from './config.js';
|
|
6
8
|
import { closeDb, initDb } from './db/index.js';
|
|
7
9
|
import { syncIdentitiesFromAccess } from './db/identity-sync.js';
|
|
8
10
|
import { attachIncoming } from './gateway/incoming.js';
|
|
11
|
+
import { processIncomingMessage } from './gateway/ingest.js';
|
|
9
12
|
import { logger } from './logger.js';
|
|
10
13
|
import { startScheduler } from './memory/scheduler.js';
|
|
11
14
|
import { startBrowserWorkers, stopBrowserWorkers } from './queue/browser-worker.js';
|
|
@@ -35,6 +38,7 @@ export async function bootBot() {
|
|
|
35
38
|
stopBrowserWorkers();
|
|
36
39
|
stopSenderWorker();
|
|
37
40
|
stopMemoryWorker();
|
|
41
|
+
void telegramRuntime.stop();
|
|
38
42
|
stopOrchestrator();
|
|
39
43
|
closeDb();
|
|
40
44
|
},
|
|
@@ -47,12 +51,17 @@ export async function bootBot() {
|
|
|
47
51
|
startBrowserWorkers();
|
|
48
52
|
startChatWorkers();
|
|
49
53
|
startScheduler();
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
if (config.telegram.enabled) {
|
|
55
|
+
await telegramRuntime.start(processIncomingMessage);
|
|
56
|
+
}
|
|
57
|
+
if (config.whatsapp.enabled !== false) {
|
|
58
|
+
await startSocket((sock) => {
|
|
59
|
+
attachIncoming(sock);
|
|
60
|
+
// Point the Baileys adapter at the live socket. Called on each
|
|
61
|
+
// reconnect with a fresh sock; the adapter just keeps the latest.
|
|
62
|
+
setBaileysSocket(sock);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
56
65
|
}
|
|
57
66
|
// Install once. Both signals trigger the same graceful drain:
|
|
58
67
|
// orchestrator picks up the shutdown control row, waits for busy
|
package/dist/channels/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// Channel adapter registry. Sender worker calls getChannelAdapter(name)
|
|
2
2
|
// keyed off the parsed address.channel.
|
|
3
3
|
import { baileysAdapter } from './baileys.js';
|
|
4
|
+
import { telegramAdapter } from './telegram.js';
|
|
4
5
|
const REGISTRY = {
|
|
5
6
|
wa: baileysAdapter,
|
|
6
|
-
|
|
7
|
+
tg: telegramAdapter,
|
|
7
8
|
};
|
|
8
9
|
export function getChannelAdapter(channel) {
|
|
9
10
|
const adapter = REGISTRY[channel];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|