agentp 0.11.6 → 0.11.7
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 +4 -0
- package/bin/tgagentp +201 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,10 @@ It is designed for prompt-driven workflows where you want to do things like:
|
|
|
18
18
|
- capture output in files or pipe to other tools
|
|
19
19
|
- **drive prompts directly from editors** like Vim/Neovim
|
|
20
20
|
|
|
21
|
+
## Author's note
|
|
22
|
+
|
|
23
|
+
These tools are built for my own daily workflow. They are heavily AI-assisted — including the tests — and I review things before shipping, but the real test is using them every day. Bugs happen; I value a working feature more than a flawless one. MIT license, no warranty. Issues, suggestions, and PRs are welcome.
|
|
24
|
+
|
|
21
25
|
## Install
|
|
22
26
|
|
|
23
27
|
From npm:
|
package/bin/tgagentp
CHANGED
|
@@ -22,6 +22,27 @@ const { listServers, activateServer, resurrectServer, statefileFor, readState, t
|
|
|
22
22
|
const { executeTuiCommand } = require('../lib/tui-cmd.js');
|
|
23
23
|
const { getUpdates, downloadTelegramFile, sendTelegramDocument } = require('../lib/telegram-api.js');
|
|
24
24
|
const { sendLongMessage } = require('../lib/telegram-format.js');
|
|
25
|
+
const { spawnSync } = require('child_process');
|
|
26
|
+
|
|
27
|
+
const TGAGENTP_ROOT = process.env.TGAGENTP_ROOT;
|
|
28
|
+
let tgagentpRootValid = false;
|
|
29
|
+
if (TGAGENTP_ROOT) {
|
|
30
|
+
try {
|
|
31
|
+
const stats = fs.statSync(TGAGENTP_ROOT);
|
|
32
|
+
if (!stats.isDirectory()) {
|
|
33
|
+
log.error(`TGAGENTP_ROOT is not a directory: ${TGAGENTP_ROOT} — /serve and /new disabled`);
|
|
34
|
+
} else {
|
|
35
|
+
try {
|
|
36
|
+
fs.accessSync(TGAGENTP_ROOT, fs.constants.W_OK);
|
|
37
|
+
tgagentpRootValid = true;
|
|
38
|
+
} catch {
|
|
39
|
+
log.error(`TGAGENTP_ROOT is not writable: ${TGAGENTP_ROOT} — /serve and /new disabled`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
log.error(`TGAGENTP_ROOT does not exist: ${TGAGENTP_ROOT} — /serve and /new disabled`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
25
46
|
|
|
26
47
|
// Persist chat↔server directory mappings across restarts
|
|
27
48
|
function saveConnection(chatId, threadId, dir) {
|
|
@@ -362,8 +383,30 @@ async function cmdHelp(token, chatId, args, chatState) {
|
|
|
362
383
|
'Max 100 messages / 100KB. Oldest messages are dropped first.');
|
|
363
384
|
return;
|
|
364
385
|
}
|
|
386
|
+
if (topic === 'serve') {
|
|
387
|
+
const hint = TGAGENTP_ROOT ? '' :
|
|
388
|
+
'\nℹ️ Currently disabled. Set the `TGAGENTP_ROOT` environment variable to a writable directory to enable `/serve` and `/new`.';
|
|
389
|
+
await sendLongMessage(token, chatState,
|
|
390
|
+
'`/serve <path>` ─ start a server in an existing directory\n\n' +
|
|
391
|
+
'The path is relative to `TGAGENTP_ROOT`.\n' +
|
|
392
|
+
'Example: `/serve projects/my-app`\n\n' +
|
|
393
|
+
'Requires the `TGAGENTP_ROOT` environment variable to be set to a writable directory.' +
|
|
394
|
+
hint);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (topic === 'new') {
|
|
398
|
+
const hint = TGAGENTP_ROOT ? '' :
|
|
399
|
+
'\nℹ️ Currently disabled. Set the `TGAGENTP_ROOT` environment variable to a writable directory to enable `/serve` and `/new`.';
|
|
400
|
+
await sendLongMessage(token, chatState,
|
|
401
|
+
'`/new <path>` ─ create a directory, initialize git, and start a server\n\n' +
|
|
402
|
+
'The path is relative to `TGAGENTP_ROOT`.\n' +
|
|
403
|
+
'Example: `/new projects/my-app`\n\n' +
|
|
404
|
+
'Requires the `TGAGENTP_ROOT` environment variable to be set to a writable directory.' +
|
|
405
|
+
hint);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
365
408
|
await sendLongMessage(token, chatState,
|
|
366
|
-
'Unknown topic. Try `/help servers`, `/help sessions`, `/help agents`, `/help models`, `/help allow`, `/help think`, `/help record`, `/help queue`, `/help flush`, `/help note`, `/help comment`, `/help disconnect`, or just `/help`.');
|
|
409
|
+
'Unknown topic. Try `/help servers`, `/help sessions`, `/help agents`, `/help models`, `/help allow`, `/help think`, `/help record`, `/help queue`, `/help flush`, `/help note`, `/help comment`, `/help disconnect`, `/help serve`, `/help new`, or just `/help`.');
|
|
367
410
|
return;
|
|
368
411
|
}
|
|
369
412
|
await sendLongMessage(token, chatState,
|
|
@@ -390,7 +433,11 @@ async function cmdHelp(token, chatId, args, chatState) {
|
|
|
390
433
|
'`/comment <text>` ─ save a comment in the chat (not forwarded to the agent)\n' +
|
|
391
434
|
'`/disconnect` ─ disconnect this chat from its server\n' +
|
|
392
435
|
'`/allow` / `/reject` / `/always` ─ respond to permission requests\n' +
|
|
393
|
-
'`/shutdown` ─ stop tgagentp (only with `--dev` flag)\n
|
|
436
|
+
'`/shutdown` ─ stop tgagentp (only with `--dev` flag)\n' +
|
|
437
|
+
(tgagentpRootValid
|
|
438
|
+
? '`/serve <path>` ─ start a server in an existing directory\n' +
|
|
439
|
+
'`/new <path>` ─ create a directory, init git, and start a server\n\n'
|
|
440
|
+
: 'ℹ️ Set `TGAGENTP_ROOT` to a writable directory to enable `/serve` and `/new`.\n\n') +
|
|
394
441
|
'Everything else is forwarded to the active server as `[Telegram] <text>`.');
|
|
395
442
|
}
|
|
396
443
|
|
|
@@ -1059,6 +1106,7 @@ async function cmdAnswer(token, chatId, args, chatState, setServerForChat, setAc
|
|
|
1059
1106
|
async function cmdThink(token, chatId, args, chatState, setServerForChat, setActiveSessionForChat) {
|
|
1060
1107
|
const st = getState(chatState.serverBase);
|
|
1061
1108
|
const sub = args && args.length > 0 ? args[0].toLowerCase() : 'switch';
|
|
1109
|
+
const wasOn = st.thinkingEnabled;
|
|
1062
1110
|
if (sub === 'on') {
|
|
1063
1111
|
st.thinkingEnabled = true;
|
|
1064
1112
|
await sendLongMessage(token, chatState, '💭 Thinking messages **on** (forwarded to chat).');
|
|
@@ -1072,6 +1120,13 @@ async function cmdThink(token, chatId, args, chatState, setServerForChat, setAct
|
|
|
1072
1120
|
} else {
|
|
1073
1121
|
await sendLongMessage(token, chatState, 'Usage: `/think [on|off|switch]` (default: switch).');
|
|
1074
1122
|
}
|
|
1123
|
+
// When toggled on, flush any thinking text already buffered by the current request
|
|
1124
|
+
if (!wasOn && st.thinkingEnabled && st._thinkingBuf) {
|
|
1125
|
+
const buf = st._thinkingBuf;
|
|
1126
|
+
st._thinkingBuf = '';
|
|
1127
|
+
const html = '💭 ' + buf.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1128
|
+
sendLongMessage(token, chatState, html, null, chatState.threadId).catch(() => {});
|
|
1129
|
+
}
|
|
1075
1130
|
}
|
|
1076
1131
|
|
|
1077
1132
|
async function cmdQueue(token, chatId, args, chatState, setServerForChat, setActiveSessionForChat, replyTo) {
|
|
@@ -1308,6 +1363,133 @@ async function cmdDisconnect(token, chatId, args, chatState) {
|
|
|
1308
1363
|
log.info(`[chat ${chatId}] Disconnected from server: ${url}`);
|
|
1309
1364
|
}
|
|
1310
1365
|
|
|
1366
|
+
async function cmdServe(token, chatId, args, chatState, setServerForChat, setActiveSessionForChat) {
|
|
1367
|
+
if (!tgagentpRootValid) {
|
|
1368
|
+
const msg = TGAGENTP_ROOT
|
|
1369
|
+
? '❌ `TGAGENTP_ROOT` is not a valid writable directory.\nCheck the logs for details.'
|
|
1370
|
+
: '❌ `TGAGENTP_ROOT` environment variable is not set.\nSet it to a writable directory to enable `/serve` and `/new` commands.';
|
|
1371
|
+
await sendLongMessage(token, chatState, msg);
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
const relPath = args.join(' ').trim();
|
|
1375
|
+
if (!relPath) {
|
|
1376
|
+
await sendLongMessage(token, chatState,
|
|
1377
|
+
'Usage: `/serve <path>`\n\n' +
|
|
1378
|
+
'Run `ocmux serve` on a directory under `TGAGENTP_ROOT`.\n' +
|
|
1379
|
+
'The path is relative to the root directory.');
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
const targetDir = path.resolve(TGAGENTP_ROOT, relPath);
|
|
1383
|
+
if (!targetDir.startsWith(TGAGENTP_ROOT)) {
|
|
1384
|
+
await sendLongMessage(token, chatState,
|
|
1385
|
+
'❌ Path must be within `TGAGENTP_ROOT`.\n' +
|
|
1386
|
+
`Requested: \`${relPath}\``);
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
try {
|
|
1390
|
+
const stats = fs.statSync(targetDir);
|
|
1391
|
+
if (!stats.isDirectory()) {
|
|
1392
|
+
await sendLongMessage(token, chatState,
|
|
1393
|
+
`❌ Not a directory: \`${relPath}\``);
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
} catch {
|
|
1397
|
+
await sendLongMessage(token, chatState,
|
|
1398
|
+
`❌ Directory does not exist: \`${relPath}\``);
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
const sf = statefileFor(targetDir);
|
|
1402
|
+
if (fs.existsSync(sf)) {
|
|
1403
|
+
await sendLongMessage(token, chatState,
|
|
1404
|
+
`⚠️ Server already exists for \`${relPath}\`.\n` +
|
|
1405
|
+
'Use `/servers switch <name>` to connect to it.');
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
await sendLongMessage(token, chatState,
|
|
1409
|
+
`🚀 Starting server for \`${relPath}\`...`);
|
|
1410
|
+
const result = spawnSync('ocmux', ['serve', targetDir], { encoding: 'utf8', timeout: 15000 });
|
|
1411
|
+
if (result.status !== 0) {
|
|
1412
|
+
const err = result.stderr || result.stdout || 'unknown error';
|
|
1413
|
+
await sendLongMessage(token, chatState,
|
|
1414
|
+
`❌ Failed to start server:\n\`\`\`\n${err}\n\`\`\``);
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
const url = (result.stdout || '').trim();
|
|
1418
|
+
if (!url) {
|
|
1419
|
+
await sendLongMessage(token, chatState,
|
|
1420
|
+
'❌ Server started but did not return a URL.');
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
await setServerForChat(chatState, url);
|
|
1424
|
+
await sendLongMessage(token, chatState,
|
|
1425
|
+
`✅ Server started.\nURL: \`${url}\`\nDir: \`${targetDir}\``);
|
|
1426
|
+
log.info(`[chat ${chatId}] Served ${targetDir} → ${url}`);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
async function cmdNew(token, chatId, args, chatState, setServerForChat, setActiveSessionForChat) {
|
|
1430
|
+
if (!tgagentpRootValid) {
|
|
1431
|
+
const msg = TGAGENTP_ROOT
|
|
1432
|
+
? '❌ `TGAGENTP_ROOT` is not a valid writable directory.\nCheck the logs for details.'
|
|
1433
|
+
: '❌ `TGAGENTP_ROOT` environment variable is not set.\nSet it to a writable directory to enable `/serve` and `/new` commands.';
|
|
1434
|
+
await sendLongMessage(token, chatState, msg);
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
const relPath = args.join(' ').trim();
|
|
1438
|
+
if (!relPath) {
|
|
1439
|
+
await sendLongMessage(token, chatState,
|
|
1440
|
+
'Usage: `/new <path>`\n\n' +
|
|
1441
|
+
'Create a new directory under `TGAGENTP_ROOT`, initialize git, and start a server.\n' +
|
|
1442
|
+
'The path is relative to the root directory.');
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
const targetDir = path.resolve(TGAGENTP_ROOT, relPath);
|
|
1446
|
+
if (!targetDir.startsWith(TGAGENTP_ROOT)) {
|
|
1447
|
+
await sendLongMessage(token, chatState,
|
|
1448
|
+
'❌ Path must be within `TGAGENTP_ROOT`.\n' +
|
|
1449
|
+
`Requested: \`${relPath}\``);
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
if (fs.existsSync(targetDir)) {
|
|
1453
|
+
await sendLongMessage(token, chatState,
|
|
1454
|
+
`❌ Directory already exists: \`${relPath}\`.\n` +
|
|
1455
|
+
'Use `/serve` to start a server in an existing directory.');
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
try {
|
|
1459
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
1460
|
+
} catch (err) {
|
|
1461
|
+
await sendLongMessage(token, chatState,
|
|
1462
|
+
`❌ Failed to create directory: ${err.message}`);
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
const gitResult = spawnSync('git', ['init'], { cwd: targetDir, encoding: 'utf8', timeout: 10000 });
|
|
1466
|
+
if (gitResult.status !== 0) {
|
|
1467
|
+
const err = gitResult.stderr || gitResult.stdout || 'unknown error';
|
|
1468
|
+
await sendLongMessage(token, chatState,
|
|
1469
|
+
`❌ Failed to initialize git:\n\`\`\`\n${err}\n\`\`\``);
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
await sendLongMessage(token, chatState,
|
|
1473
|
+
`🚀 Starting server for \`${relPath}\`...`);
|
|
1474
|
+
const result = spawnSync('ocmux', ['serve', targetDir], { encoding: 'utf8', timeout: 15000 });
|
|
1475
|
+
if (result.status !== 0) {
|
|
1476
|
+
const err = result.stderr || result.stdout || 'unknown error';
|
|
1477
|
+
await sendLongMessage(token, chatState,
|
|
1478
|
+
`❌ Failed to start server:\n\`\`\`\n${err}\n\`\`\``);
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
const url = (result.stdout || '').trim();
|
|
1482
|
+
if (!url) {
|
|
1483
|
+
await sendLongMessage(token, chatState,
|
|
1484
|
+
'❌ Server started but did not return a URL.');
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
await setServerForChat(chatState, url);
|
|
1488
|
+
await sendLongMessage(token, chatState,
|
|
1489
|
+
`✅ Directory created and server started.\nURL: \`${url}\`\nDir: \`${targetDir}\``);
|
|
1490
|
+
log.info(`[chat ${chatId}] Created ${targetDir} → ${url}`);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1311
1493
|
const COMMANDS = {
|
|
1312
1494
|
'help': cmdHelp,
|
|
1313
1495
|
'servers': cmdServers,
|
|
@@ -1327,6 +1509,8 @@ const COMMANDS = {
|
|
|
1327
1509
|
'reject': cmdReject,
|
|
1328
1510
|
'always': cmdAlways,
|
|
1329
1511
|
'answer': cmdAnswer,
|
|
1512
|
+
'serve': cmdServe,
|
|
1513
|
+
'new': cmdNew,
|
|
1330
1514
|
};
|
|
1331
1515
|
|
|
1332
1516
|
// --------------------------------------------------------------------------
|
|
@@ -1335,7 +1519,7 @@ const COMMANDS = {
|
|
|
1335
1519
|
|
|
1336
1520
|
const serverState = {};
|
|
1337
1521
|
function getState(url) {
|
|
1338
|
-
return serverState[url] = serverState[url] || { busy: false, serverDead: false, pendingResponse: null, activeSessionId: null, activeSessionName: null, activeAgentName: null, taskId: 0, pendingPermission: null, pendingQuestion: null, thinkingEnabled: false, messageQueue: [] };
|
|
1522
|
+
return serverState[url] = serverState[url] || { busy: false, serverDead: false, pendingResponse: null, activeSessionId: null, activeSessionName: null, activeAgentName: null, taskId: 0, pendingPermission: null, pendingQuestion: null, thinkingEnabled: false, _thinkingBuf: '', messageQueue: [] };
|
|
1339
1523
|
}
|
|
1340
1524
|
|
|
1341
1525
|
function sessionLabel(st) {
|
|
@@ -1563,7 +1747,6 @@ async function main() {
|
|
|
1563
1747
|
let answer;
|
|
1564
1748
|
if (useSession) {
|
|
1565
1749
|
const notifiedPerms = new Set();
|
|
1566
|
-
let thinkingBuf = '';
|
|
1567
1750
|
let sseConnected;
|
|
1568
1751
|
const onConnected = Promise.race([
|
|
1569
1752
|
new Promise((resolve) => { sseConnected = resolve; }),
|
|
@@ -1573,14 +1756,16 @@ async function main() {
|
|
|
1573
1756
|
onText: null,
|
|
1574
1757
|
onConnected: sseConnected,
|
|
1575
1758
|
onThinking: (chunk) => {
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
if (
|
|
1579
|
-
|
|
1759
|
+
const st2 = getState(url);
|
|
1760
|
+
st2._thinkingBuf += chunk;
|
|
1761
|
+
if (!st2.thinkingEnabled) return;
|
|
1762
|
+
const buf = st2._thinkingBuf;
|
|
1763
|
+
if (buf.length >= 50 || chunk.endsWith('\n')) {
|
|
1764
|
+
st2._thinkingBuf = '';
|
|
1765
|
+
const html = '💭 ' + buf.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1580
1766
|
sendLongMessage(token, chatState, html, replyTo, threadId)
|
|
1581
1767
|
.then(() => {})
|
|
1582
1768
|
.catch(() => {});
|
|
1583
|
-
thinkingBuf = '';
|
|
1584
1769
|
}
|
|
1585
1770
|
},
|
|
1586
1771
|
onPermission: (perm) => {
|
|
@@ -1628,10 +1813,10 @@ async function main() {
|
|
|
1628
1813
|
}, cancelRef).catch(() => {});
|
|
1629
1814
|
await onConnected;
|
|
1630
1815
|
answer = await sendToSession(url, sessionId, `[Telegram] ${text}`, st.activeAgentName, sessionCancelRef);
|
|
1631
|
-
if (
|
|
1632
|
-
const html = '💭 ' +
|
|
1816
|
+
if (st._thinkingBuf) {
|
|
1817
|
+
const html = '💭 ' + st._thinkingBuf.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1633
1818
|
sendLongMessage(token, chatState, html, replyTo, threadId).catch(() => {});
|
|
1634
|
-
|
|
1819
|
+
st._thinkingBuf = '';
|
|
1635
1820
|
}
|
|
1636
1821
|
// Keep the SSE listener alive so subsequent permission/question events
|
|
1637
1822
|
// that arrive after sendToSession returns are still forwarded to Telegram.
|
|
@@ -1691,6 +1876,7 @@ async function main() {
|
|
|
1691
1876
|
try { await sendLongMessage(token, chatState, msg, replyTo, threadId); } catch (_) {}
|
|
1692
1877
|
}
|
|
1693
1878
|
} finally {
|
|
1879
|
+
st._thinkingBuf = '';
|
|
1694
1880
|
st.pendingPermission = null;
|
|
1695
1881
|
// Only clear the reference if the SSE listener was destroyed or finished.
|
|
1696
1882
|
// If it is still running, keep the reference so the next message call
|
|
@@ -2206,8 +2392,8 @@ async function main() {
|
|
|
2206
2392
|
const cmdArgs = parts.slice(1);
|
|
2207
2393
|
const handler = cmds[cmd];
|
|
2208
2394
|
if (handler) {
|
|
2209
|
-
// Disconnected guard: only /help, /servers, /force-switch, /start, /comment, and /
|
|
2210
|
-
if (!cs.serverBase && cmd !== 'help' && cmd !== 'servers' && cmd !== 'force-switch' && cmd !== 'start' && cmd !== 'comment' && cmd !== 'shutdown') {
|
|
2395
|
+
// Disconnected guard: only /help, /servers, /force-switch, /start, /comment, /shutdown, /serve, and /new work without a server
|
|
2396
|
+
if (!cs.serverBase && cmd !== 'help' && cmd !== 'servers' && cmd !== 'force-switch' && cmd !== 'start' && cmd !== 'comment' && cmd !== 'shutdown' && cmd !== 'serve' && cmd !== 'new') {
|
|
2211
2397
|
await sendLongMessage(token, cs,
|
|
2212
2398
|
'🔌 Not connected to any server.\n\n' +
|
|
2213
2399
|
'Use `/servers` to see available servers and `/servers switch <name>` to connect.');
|
|
@@ -2221,7 +2407,7 @@ async function main() {
|
|
|
2221
2407
|
}
|
|
2222
2408
|
} else {
|
|
2223
2409
|
await sendLongMessage(token, cs,
|
|
2224
|
-
`Unknown command: /${cmd}\n\nAvailable commands: /help, /status, /servers, /sessions, /agents, /models, /think, /record, /queue, /flush, /note, /comment, /disconnect`);
|
|
2410
|
+
`Unknown command: /${cmd}\n\nAvailable commands: /help, /status, /servers, /sessions, /agents, /models, /think, /record, /queue, /flush, /note, /comment, /disconnect, /serve, /new`);
|
|
2225
2411
|
}
|
|
2226
2412
|
continue;
|
|
2227
2413
|
}
|