@bill10/agent-007 0.13.0 → 0.14.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.
- package/.env.example +2 -2
- package/VERSION +1 -1
- package/bin/agent-007.js +2 -1
- package/package.json +1 -1
- package/server/http.js +4 -1
- package/server/mcp.js +24 -1
- package/server/owner.js +24 -2
- package/server/voice.js +12 -9
- package/templates/billion/charter.md +8 -0
package/.env.example
CHANGED
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
# Settings > Accessibility > Spoken Content > System voice > Manage Voices),
|
|
66
66
|
# else say's default. A voice that is not installed is logged and skipped.
|
|
67
67
|
# SAY_VOICE=Ava (Premium)
|
|
68
|
-
# How fast it speaks, in words per minute (120-300). Unset:
|
|
69
|
-
#
|
|
68
|
+
# How fast it speaks, in words per minute (120-300). Unset: no -r is passed, so
|
|
69
|
+
# each voice speaks at its own system default speed.
|
|
70
70
|
# SAY_RATE=205
|
|
71
71
|
# Your voice notes are transcribed with whisper.cpp (brew install whisper-cpp).
|
|
72
72
|
# WHISPER_MODEL is the full path to a ggml model, e.g. ggml-base.en.bin;
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.14.0.1
|
package/bin/agent-007.js
CHANGED
|
@@ -48,7 +48,8 @@ Settings (default in brackets):
|
|
|
48
48
|
voice (macOS say + ffmpeg) [mirror]
|
|
49
49
|
SAY_VOICE macOS voice for that, from say -v '?' [best
|
|
50
50
|
installed English Premium/Enhanced voice]
|
|
51
|
-
SAY_RATE How fast it speaks, words per minute, 120-300
|
|
51
|
+
SAY_RATE How fast it speaks, words per minute, 120-300
|
|
52
|
+
[unset: the voice's own system speed]
|
|
52
53
|
WHISPER_MODEL whisper.cpp model file; your voice notes are
|
|
53
54
|
transcribed locally [off]
|
|
54
55
|
WHISPER_CPP_BIN whisper.cpp CLI if not on PATH [whisper-cli]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bill10/agent-007",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "From web terminals for your coding agents to a self-running agent company: Claude Code and Codex in parallel git worktrees, a job board they pick work from, and one agent that runs the board from a goal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
package/server/http.js
CHANGED
|
@@ -17,7 +17,7 @@ import { expandHome } from '../lib/helpers.js';
|
|
|
17
17
|
import { requestApproval, answerApproval, readApproval } from './approvals.js';
|
|
18
18
|
import { agentSummaries, sendMessage, flushMessages, pendingMessages, readAgentScreen } from './messages.js';
|
|
19
19
|
import { handleMcpMessage } from './mcp.js';
|
|
20
|
-
import { notifyOwner } from './owner.js';
|
|
20
|
+
import { notifyOwner, tellOwner } from './owner.js';
|
|
21
21
|
import { availableModels } from './models.js';
|
|
22
22
|
|
|
23
23
|
// --- Origin Check Middleware (B2) ---
|
|
@@ -138,6 +138,9 @@ export function setupRoutes(app, staticDir, { broadcast, killSession, respawnAge
|
|
|
138
138
|
notifyOwner: (text, { choices, recommended } = {}) => (req.agentSession.isBillion
|
|
139
139
|
? notifyOwner(text, { choices, recommended, broadcast })
|
|
140
140
|
: { error: 'Only Billion can notify the owner.' }),
|
|
141
|
+
tellOwner: (text) => (req.agentSession.isBillion
|
|
142
|
+
? tellOwner(text)
|
|
143
|
+
: { error: 'Only Billion can message the owner.' }),
|
|
141
144
|
// Never logged: a screen can hold a secret that scrolled by.
|
|
142
145
|
readAgentScreen: ({ name, lines }) => readAgentScreen({
|
|
143
146
|
from: req.agentSession, name, lines, sessions,
|
package/server/mcp.js
CHANGED
|
@@ -382,6 +382,23 @@ export const NOTIFY_OWNER_TOOL = {
|
|
|
382
382
|
},
|
|
383
383
|
};
|
|
384
384
|
|
|
385
|
+
export const TELL_OWNER_TOOL = {
|
|
386
|
+
name: 'tell_owner',
|
|
387
|
+
description:
|
|
388
|
+
'Send the owner a reply or status update that needs no answer ("Got it", '
|
|
389
|
+
+ '"Restart looks clean") over Telegram. Unlike notify_owner it files nothing '
|
|
390
|
+
+ 'under "Waiting on you". Use it to answer an [Owner via Telegram] message '
|
|
391
|
+
+ 'that is not a question. Shares notify_owner\'s limit of a few per minute.',
|
|
392
|
+
inputSchema: {
|
|
393
|
+
type: 'object',
|
|
394
|
+
properties: {
|
|
395
|
+
text: { type: 'string', description: 'The message, written to be read on a phone.' },
|
|
396
|
+
},
|
|
397
|
+
required: ['text'],
|
|
398
|
+
additionalProperties: false,
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
|
|
385
402
|
// Billion's too: reading is narrower than messaging (server/messages.js,
|
|
386
403
|
// readAgentScreen), so it is only for the workers on Billion's own cards.
|
|
387
404
|
export const READ_AGENT_SCREEN_TOOL = {
|
|
@@ -428,7 +445,7 @@ export const RESPAWN_AGENT_TOOL = {
|
|
|
428
445
|
};
|
|
429
446
|
|
|
430
447
|
export const TOOLS = [POST_JOB_TOOL, LIST_JOBS_TOOL, READ_JOB_TOOL, EDIT_JOB_TOOL, FINISH_JOB_TOOL, LIST_AGENTS_TOOL, SEND_MESSAGE_TOOL];
|
|
431
|
-
const BILLION_TOOLS = [BILLION_READY_TOOL, ADD_REPO_TOOL, CLOSE_JOB_TOOL, ANSWER_PERMISSION_TOOL, READ_APPROVAL_TOOL, NOTIFY_OWNER_TOOL, READ_AGENT_SCREEN_TOOL, RESPAWN_AGENT_TOOL];
|
|
448
|
+
const BILLION_TOOLS = [BILLION_READY_TOOL, ADD_REPO_TOOL, CLOSE_JOB_TOOL, ANSWER_PERMISSION_TOOL, READ_APPROVAL_TOOL, NOTIFY_OWNER_TOOL, TELL_OWNER_TOOL, READ_AGENT_SCREEN_TOOL, RESPAWN_AGENT_TOOL];
|
|
432
449
|
|
|
433
450
|
// `models` is { claude: [...], codex: [...] } as server/models.js last found them.
|
|
434
451
|
export function toolsFor(session, models) {
|
|
@@ -670,6 +687,12 @@ const CALLS = {
|
|
|
670
687
|
return toolText(`Sent to the owner on Telegram and put under "Waiting on you" as Q${result.n}. Keep working on everything else; their answer, if any, arrives here as [Owner via app] Q${result.n}: … or [Owner via Telegram] Q${result.n}: ….`);
|
|
671
688
|
},
|
|
672
689
|
|
|
690
|
+
[TELL_OWNER_TOOL.name]: async (args, ctx) => {
|
|
691
|
+
const result = ctx.tellOwner ? await ctx.tellOwner(args.text) : { error: 'Only Billion can message the owner.' };
|
|
692
|
+
if (result.error) return toolText(result.error, true);
|
|
693
|
+
return toolText('Sent to the owner on Telegram.');
|
|
694
|
+
},
|
|
695
|
+
|
|
673
696
|
// Quoted line by line, like a message body, so the screen cannot pass for
|
|
674
697
|
// anything but a quote — nor close the block and carry on as the server.
|
|
675
698
|
[READ_AGENT_SCREEN_TOOL.name]: (args, ctx) => {
|
package/server/owner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Reaching the owner when they are away from the terminal: Billion's
|
|
2
|
-
// notify_owner tool, the "Waiting on you" tab it fills in the browser (where
|
|
2
|
+
// notify_owner tool (questions) and tell_owner (replies that need no answer), the "Waiting on you" tab it fills in the browser (where
|
|
3
3
|
// the owner can answer too), and a Telegram bot that carries both ways (docs/BILLION.md, "Telegram").
|
|
4
4
|
//
|
|
5
5
|
// Telegram is optional: with TELEGRAM_BOT_TOKEN unset nothing here talks to
|
|
@@ -320,6 +320,25 @@ export async function notifyOwner(text, { choices, recommended, broadcast, env =
|
|
|
320
320
|
return { ok: true, n: item?.n };
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
// --- tell_owner: a reply or status update, no Waiting item, no badge ---
|
|
324
|
+
|
|
325
|
+
export async function tellOwner(text, { env = process.env, now = Date.now(), platform = process.platform } = {}) {
|
|
326
|
+
const body = typeof text === 'string' ? text.trim() : '';
|
|
327
|
+
if (!body) return { error: 'The message is empty.' };
|
|
328
|
+
if (body.length > MAX_NOTIFY_CHARS) return { error: `The message is ${body.length} characters; keep it under ${MAX_NOTIFY_CHARS}.` };
|
|
329
|
+
const { token, chatId } = telegramSettings(env);
|
|
330
|
+
if (!token || !chatId) return { error: 'Telegram is not set up; say it in your terminal.' };
|
|
331
|
+
// Shares notify_owner's limit: both land on the same phone.
|
|
332
|
+
sent = sent.filter(t => now - t < NOTIFY_WINDOW_MS);
|
|
333
|
+
if (sent.length >= NOTIFY_LIMIT) {
|
|
334
|
+
return { error: `Not sent: you have messaged the owner ${NOTIFY_LIMIT} times in the last minute. Put the rest in one message later.` };
|
|
335
|
+
}
|
|
336
|
+
sent.push(now);
|
|
337
|
+
const result = await sendToOwner(`Billion: ${body}`, { env, platform });
|
|
338
|
+
if (result.error) return { error: `The Telegram send failed: ${result.error}` };
|
|
339
|
+
return { ok: true };
|
|
340
|
+
}
|
|
341
|
+
|
|
323
342
|
// --- Replies: long-polling getUpdates ---
|
|
324
343
|
|
|
325
344
|
const discovered = new Set(); // chat ids already shown, so a stranger's first message cannot hide the owner's
|
|
@@ -441,7 +460,10 @@ export function startTelegram({ broadcast, env = process.env } = {}) {
|
|
|
441
460
|
if (!chatId) console.log(' Telegram: send any message to your bot, then set TELEGRAM_CHAT_ID=<id> (the id is shown here when it arrives)');
|
|
442
461
|
else console.log(' Telegram: on');
|
|
443
462
|
if (voiceSetting(env) !== 'never' && !speechUnavailable(env)) {
|
|
444
|
-
sayVoice(env).then(v =>
|
|
463
|
+
sayVoice(env).then(v => {
|
|
464
|
+
const rate = sayRate(env);
|
|
465
|
+
console.log(` Telegram: speaking with ${v ? `the ${v} voice` : "say's default voice"} at ${rate ? `${rate} wpm` : 'the system default speed'}`);
|
|
466
|
+
});
|
|
445
467
|
}
|
|
446
468
|
const controller = new AbortController();
|
|
447
469
|
stopper = controller;
|
package/server/voice.js
CHANGED
|
@@ -120,17 +120,19 @@ export function sayVoice(env = process.env) {
|
|
|
120
120
|
return voicePick;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
// SAY_RATE in words per minute, 120–300. Unset:
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
let ratePick;
|
|
123
|
+
// SAY_RATE in words per minute, 120–300. Unset (or invalid): null, so `say`
|
|
124
|
+
// is not given `-r` at all and speaks at the voice's own system rate.
|
|
125
|
+
let ratePick, ratePicked = false;
|
|
127
126
|
export function sayRate(env = process.env) {
|
|
128
|
-
if (
|
|
127
|
+
if (ratePicked) return ratePick;
|
|
128
|
+
ratePicked = true;
|
|
129
129
|
const raw = (env.SAY_RATE || '').trim();
|
|
130
130
|
const n = Number(raw);
|
|
131
|
-
if (raw && !Number.isInteger(n))
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
if (raw && !Number.isInteger(n)) {
|
|
132
|
+
console.log(` Telegram: SAY_RATE "${raw}" is not a whole number of words per minute; using the system default speed`);
|
|
133
|
+
return ratePick = null;
|
|
134
|
+
}
|
|
135
|
+
return ratePick = raw ? Math.min(300, Math.max(120, n)) : null;
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
// text → OGG/Opus bytes. URLs are said as "link"; the caption carries them.
|
|
@@ -139,7 +141,8 @@ export function synthesize(text, env = process.env) {
|
|
|
139
141
|
const txt = join(dir, 'say.txt'), aiff = join(dir, 'say.aiff'), ogg = join(dir, 'say.ogg');
|
|
140
142
|
await writeFile(txt, text.replace(URL_RE, 'link'));
|
|
141
143
|
const voice = await sayVoice(env);
|
|
142
|
-
|
|
144
|
+
const rate = sayRate(env);
|
|
145
|
+
await run('say', [...(voice ? ['-v', voice] : []), '-o', aiff, ...(rate ? ['-r', String(rate)] : []), '-f', txt]);
|
|
143
146
|
await run('ffmpeg', ['-y', '-loglevel', 'error', '-protocol_whitelist', 'file', '-i', aiff, '-c:a', 'libopus', '-b:a', '32k', ogg]);
|
|
144
147
|
return readFile(ogg);
|
|
145
148
|
});
|
|
@@ -233,6 +233,12 @@ owner's words too, transcribed by machine: read it as theirs but allow for
|
|
|
233
233
|
transcription errors, and ask back if something is ambiguous and risky. A
|
|
234
234
|
`(caption: ...)` at its end is text the owner typed on the note.
|
|
235
235
|
|
|
236
|
+
`notify_owner` is for questions and decisions only: every call files a
|
|
237
|
+
numbered item the owner has to clear. For replies and status updates that
|
|
238
|
+
need no answer ("Got it, restart looks clean"), use `tell_owner`: it reaches
|
|
239
|
+
the owner's phone the same way but files nothing. Answer an
|
|
240
|
+
`[Owner via Telegram]` message that isn't a question with `tell_owner`.
|
|
241
|
+
|
|
236
242
|
## Tools and limits
|
|
237
243
|
|
|
238
244
|
The `agent-007-board` MCP tools:
|
|
@@ -251,6 +257,8 @@ The `agent-007-board` MCP tools:
|
|
|
251
257
|
- `billion_ready`: opens your inbox (see **Operating loop**).
|
|
252
258
|
- `add_repo`: puts a repository on the board so cards can be posted in it.
|
|
253
259
|
- `notify_owner`: puts a question in front of the owner (see **Escalate**).
|
|
260
|
+
- `tell_owner`: a reply or status update to the owner's phone that needs no
|
|
261
|
+
answer; files no *Waiting on you* item (see **Escalate**).
|
|
254
262
|
- `answer_permission`: your answer to a worker's permission request (see
|
|
255
263
|
**Approvals**).
|
|
256
264
|
- `read_approval`: a waiting permission request in full, so you can judge
|