agentp 0.11.11 → 1.12.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/README.md +50 -7
- package/bin/agentp +272 -83
- package/bin/tgagentp +356 -276
- package/lib/ocmux.js +27 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,8 @@ It is designed for prompt-driven workflows where you want to do things like:
|
|
|
22
22
|
|
|
23
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
24
|
|
|
25
|
+
**Stability note:** `agentp` and `ocmux` are stable and used daily. `tgagentp` is still in a highly experimental stage — expect breaking changes and occasional bugs.
|
|
26
|
+
|
|
25
27
|
## Install
|
|
26
28
|
|
|
27
29
|
From npm:
|
|
@@ -57,6 +59,7 @@ agentp [options] [url]
|
|
|
57
59
|
Options:
|
|
58
60
|
|
|
59
61
|
- `--qa`: print the original prompt and answer with labels (useful when used as a filter)
|
|
62
|
+
- `--defer`: deferred execution — submit prompt, get immediate ticket, retrieve result later
|
|
60
63
|
- `--tg`: forward the answer to Telegram via tgagentp gateway (error if unreachable)
|
|
61
64
|
- `--no-tg`: do not forward to Telegram
|
|
62
65
|
- `--flush`: flush tgagentp's recorded buffer without prepending it to output
|
|
@@ -142,6 +145,39 @@ From Vim/Neovim, flush the recorded buffer without prepending context:
|
|
|
142
145
|
Useful when you've finished a conversation thread and want to reset the recorded
|
|
143
146
|
context for a new topic.
|
|
144
147
|
|
|
148
|
+
Deferred execution with `--defer`:
|
|
149
|
+
|
|
150
|
+
Submit a prompt and get an immediate ticket to retrieve the result later:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Submit a prompt and get a deferred reference
|
|
154
|
+
DEFERRED=$(printf "Refactor the authentication module" | agentp --defer)
|
|
155
|
+
# Output: <agentp-deferred>/tmp/agentp_deferred_20260803_1430_a1b2.tmp</agentp-deferred>
|
|
156
|
+
|
|
157
|
+
# Continue working... retrieve the result when ready
|
|
158
|
+
printf '%s\n' "$DEFERRED" | agentp --defer
|
|
159
|
+
# Output: (the agent's response)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Works as a Vim/Neovim filter with deferred execution:
|
|
163
|
+
|
|
164
|
+
```vim
|
|
165
|
+
" Submit selection, get ticket immediately, continue editing
|
|
166
|
+
:'<,'>!agentp --defer --qa
|
|
167
|
+
|
|
168
|
+
" Later, retrieve the result
|
|
169
|
+
:r !printf '%s\n' "<agentp-deferred>/tmp/agentp_deferred_...tmp</agentp-deferred>" | agentp --defer
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The deferred workflow:
|
|
173
|
+
1. Submit prompt with `--defer` → get immediate ticket (`<agentp-deferred>...</agentp-deferred>`)
|
|
174
|
+
2. Continue working (the agent processes in background)
|
|
175
|
+
3. When ready, pipe the ticket back to `agentp --defer` to retrieve the result
|
|
176
|
+
4. If the agent is still processing, you get your input back (identity filter)
|
|
177
|
+
4. If complete, you get the agent's response and the temp file is cleaned up
|
|
178
|
+
|
|
179
|
+
Useful for long-running tasks where you don't want to block your editor.
|
|
180
|
+
|
|
145
181
|
Retrieve the last 3 assistant answers from session history:
|
|
146
182
|
|
|
147
183
|
```bash
|
|
@@ -231,21 +267,28 @@ Non-text Telegram updates (photos, stickers, etc.) are silently ignored.
|
|
|
231
267
|
### Slash commands
|
|
232
268
|
|
|
233
269
|
| Command | Action |
|
|
234
|
-
|
|
270
|
+
|---|---|---|
|
|
235
271
|
| `/help [topic]` | Show general help or help for a topic (`servers`, `sessions`, `agents`, `models`, `allow`, `think`, `record`, `queue`) |
|
|
236
272
|
| `/servers` | List all ocmux-served projects (▶ active, 🔌 disconnected, 💀 dead) |
|
|
237
|
-
| `/
|
|
273
|
+
| `/server <name>` | Switch active server; matches by full path, basename, or substring |
|
|
274
|
+
| `/server --force <name>` | Take over a server from another chat |
|
|
238
275
|
| `/resurrect [path]` | Restart a crashed server from its `.ocmux.json`; accepts optional directory path |
|
|
276
|
+
| `/sessions` | List sessions (numbered, newest first) |
|
|
277
|
+
| `/session <name-or-number>` | Switch to a session by name or position |
|
|
278
|
+
| `/session new [name]` | Create a new session |
|
|
279
|
+
| `/session rename <name>` | Rename the active session |
|
|
280
|
+
| `/agents` | List available agents/models |
|
|
281
|
+
| `/agent <name>` | Switch the active agent for subsequent messages (synced to server via API) |
|
|
282
|
+
| `/models` | List providers and models (▶ marks current) |
|
|
283
|
+
| `/model <providerID/modelID>` | Switch session model |
|
|
239
284
|
| `/allow` | Approve a permission request once |
|
|
240
285
|
| `/reject` | Deny a permission request |
|
|
241
286
|
| `/always` | Approve and remember for the session |
|
|
242
287
|
| `/answer <number>` | Respond to a question asked by the AI (structured multiple-choice) |
|
|
243
288
|
| `/markdown` | Send the original markdown of the last response as a `.md` file; reply to a message to get that specific response |
|
|
244
|
-
| `/agents list` | List available agents/models |
|
|
245
|
-
| `/agents switch <name>` | Switch the active agent for subsequent messages (synced to server via API) |
|
|
246
289
|
| `/shutdown [force\|clear]` | (requires `--dev`) Stop tgagentp; `clear` also wipes saved connections |
|
|
247
290
|
|
|
248
|
-
> **Note on model/agent switching:** Changing the model in the OpenCode TUI's prompt dropdown is a **local UI action** — it only takes effect server-side after a message is sent *through the TUI*. If you change the model in the TUI and then send a prompt via `agentp` or Telegram, the old model will still be used. Use `/
|
|
291
|
+
> **Note on model/agent switching:** Changing the model in the OpenCode TUI's prompt dropdown is a **local UI action** — it only takes effect server-side after a message is sent *through the TUI*. If you change the model in the TUI and then send a prompt via `agentp` or Telegram, the old model will still be used. Use `/agent <name>` or `/model <providerID/modelID>` from Telegram (or `tgagentp`) to change models — this explicitly calls the API and syncs correctly.
|
|
249
292
|
|
|
250
293
|
#### TUI Command Passthrough
|
|
251
294
|
|
|
@@ -255,7 +298,7 @@ Messages starting with `//` are forwarded to the OpenCode TUI as raw keystrokes
|
|
|
255
298
|
|
|
256
299
|
### Chat-server ownership
|
|
257
300
|
|
|
258
|
-
Each server can be owned by at most one chat at a time. New chats start disconnected. Use `/
|
|
301
|
+
Each server can be owned by at most one chat at a time. New chats start disconnected. Use `/server <name>` to connect; `--force` takes over and notifies the previous owner. Connections are persisted to `/tmp/tgagentp-connections.json` and restored automatically on restart (server URL is re-discovered from `.ocmux.json`).
|
|
259
302
|
|
|
260
303
|
### Per-server state
|
|
261
304
|
|
|
@@ -287,7 +330,7 @@ tgagentp starts a tiny HTTP server on `127.0.0.1` that accepts `POST /send` requ
|
|
|
287
330
|
- Port is written to `/tmp/tgagentp-port` for agentp discovery.
|
|
288
331
|
- Authentication reuses `OPENCODE_SERVER_PASSWORD`.
|
|
289
332
|
- Messages for the owning chat's active server are delivered immediately.
|
|
290
|
-
- Messages for non-active servers are queued per-server with debounced notifications (configurable via `TGAGENTP_DEBOUNCE_MS`); delivered on `/
|
|
333
|
+
- Messages for non-active servers are queued per-server with debounced notifications (configurable via `TGAGENTP_DEBOUNCE_MS`); delivered on `/server <name>`.
|
|
291
334
|
- Server health detection pre-sends: if a server is unreachable, messages are auto-queued and delivered when it comes back. `/flush` clears all queues.
|
|
292
335
|
- When [/record](#tgagentp) is active, the gateway response includes the recorded conversation buffer. `agentp --qa` prepends this buffer (with rulers) to its stdout so the full Telegram context is available to OpenCode. Use `agentp --qa --flush` to flush the buffer without prepending.
|
|
293
336
|
|
package/bin/agentp
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const http = require('http');
|
|
4
4
|
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const crypto = require('crypto');
|
|
5
8
|
const readline = require('readline');
|
|
6
9
|
const child_process = require('child_process');
|
|
7
10
|
const { version } = require('../package.json');
|
|
@@ -60,6 +63,51 @@ function notifyAgentpGateway(port, server, text) {
|
|
|
60
63
|
});
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
function isFileLocked(filePath) {
|
|
67
|
+
const lockPath = filePath + '.lock';
|
|
68
|
+
try {
|
|
69
|
+
if (!fs.existsSync(lockPath)) return false;
|
|
70
|
+
const pid = parseInt(fs.readFileSync(lockPath, 'utf8').trim(), 10);
|
|
71
|
+
if (isNaN(pid)) return false;
|
|
72
|
+
// Check if process is still alive
|
|
73
|
+
try {
|
|
74
|
+
process.kill(pid, 0);
|
|
75
|
+
return true; // Process exists, file is locked
|
|
76
|
+
} catch (err) {
|
|
77
|
+
// Process doesn't exist, lock is stale
|
|
78
|
+
fs.unlinkSync(lockPath);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function lockFile(filePath) {
|
|
87
|
+
const lockPath = filePath + '.lock';
|
|
88
|
+
fs.writeFileSync(lockPath, String(process.pid));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function unlockFile(filePath) {
|
|
92
|
+
const lockPath = filePath + '.lock';
|
|
93
|
+
try {
|
|
94
|
+
fs.unlinkSync(lockPath);
|
|
95
|
+
} catch {
|
|
96
|
+
// Ignore errors
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function generateTempFilename() {
|
|
101
|
+
const now = new Date();
|
|
102
|
+
const timestamp = now.getFullYear().toString() +
|
|
103
|
+
String(now.getMonth() + 1).padStart(2, '0') +
|
|
104
|
+
String(now.getDate()).padStart(2, '0') + '_' +
|
|
105
|
+
String(now.getHours()).padStart(2, '0') +
|
|
106
|
+
String(now.getMinutes()).padStart(2, '0');
|
|
107
|
+
const hash = crypto.randomBytes(2).toString('hex').slice(0, 4);
|
|
108
|
+
return path.join(os.tmpdir(), `agentp_deferred_${timestamp}_${hash}.tmp`);
|
|
109
|
+
}
|
|
110
|
+
|
|
63
111
|
async function readAllStdin() {
|
|
64
112
|
let text = '';
|
|
65
113
|
const rl = readline.createInterface({ input: process.stdin });
|
|
@@ -78,6 +126,10 @@ async function main() {
|
|
|
78
126
|
let serverBase = 'http://localhost:4096';
|
|
79
127
|
let qaMode = false;
|
|
80
128
|
let flushMode = false;
|
|
129
|
+
let deferMode = false;
|
|
130
|
+
let deferChildMode = false;
|
|
131
|
+
let promptFile = null;
|
|
132
|
+
let outputFile = null;
|
|
81
133
|
let sessionName = null;
|
|
82
134
|
let newSession = false;
|
|
83
135
|
// tgMode: false = disabled, 'auto' = auto-detect, true = explicit --tg
|
|
@@ -96,6 +148,7 @@ async function main() {
|
|
|
96
148
|
console.error('Options:');
|
|
97
149
|
console.error(' --version Show version');
|
|
98
150
|
console.error(' --qa Print the original prompt and answer with labels (useful as a filter)');
|
|
151
|
+
console.error(' --defer Deferred execution: write prompt to temp file, return path immediately');
|
|
99
152
|
console.error(' --tg Forward the answer to Telegram via agentp gateway (error if unavailable)');
|
|
100
153
|
console.error(' --no-tg Do not forward to Telegram');
|
|
101
154
|
console.error(' --flush Flush tgagentp\'s recorded buffer without prepending it to output');
|
|
@@ -139,6 +192,16 @@ async function main() {
|
|
|
139
192
|
process.exit(0);
|
|
140
193
|
} else if (args[i] === '--qa') {
|
|
141
194
|
qaMode = true;
|
|
195
|
+
} else if (args[i] === '--defer') {
|
|
196
|
+
deferMode = true;
|
|
197
|
+
} else if (args[i] === '--defer-child') {
|
|
198
|
+
deferChildMode = true;
|
|
199
|
+
} else if (args[i] === '--prompt-file') {
|
|
200
|
+
i++;
|
|
201
|
+
promptFile = args[i];
|
|
202
|
+
} else if (args[i] === '--output-file') {
|
|
203
|
+
i++;
|
|
204
|
+
outputFile = args[i];
|
|
142
205
|
} else if (args[i] === '--flush') {
|
|
143
206
|
flushMode = true;
|
|
144
207
|
} else if (args[i] === '--session') {
|
|
@@ -289,113 +352,239 @@ async function main() {
|
|
|
289
352
|
|
|
290
353
|
const promptText = await readAllStdin();
|
|
291
354
|
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
355
|
+
// Handle --defer mode
|
|
356
|
+
if (deferMode) {
|
|
357
|
+
// Check if stdin is a deferred reference
|
|
358
|
+
const deferredMatch = promptText.match(/<agentp-deferred>(.*?)<\/agentp-deferred>/);
|
|
359
|
+
|
|
360
|
+
if (deferredMatch) {
|
|
361
|
+
// This is a deferred reference - retrieve the result
|
|
362
|
+
const deferredPath = deferredMatch[1].trim();
|
|
363
|
+
|
|
364
|
+
if (!fs.existsSync(deferredPath)) {
|
|
365
|
+
// File doesn't exist - error
|
|
366
|
+
process.stdout.write(`Error: deferred file not found: ${deferredPath}\n`);
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Check if file is locked (still processing) or empty (not ready yet)
|
|
371
|
+
const isLocked = isFileLocked(deferredPath);
|
|
372
|
+
let content = '';
|
|
373
|
+
try {
|
|
374
|
+
content = fs.readFileSync(deferredPath, 'utf8');
|
|
375
|
+
} catch {
|
|
376
|
+
content = '';
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (isLocked || content === '') {
|
|
380
|
+
// File is locked (still processing) or empty (not ready yet) - identity filter
|
|
381
|
+
process.stdout.write(promptText);
|
|
382
|
+
process.exit(0);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// File exists, not locked, and has content - read and delete
|
|
386
|
+
try {
|
|
387
|
+
fs.unlinkSync(deferredPath);
|
|
388
|
+
process.stdout.write(content);
|
|
389
|
+
process.exit(0);
|
|
390
|
+
} catch (err) {
|
|
391
|
+
process.stdout.write(`Error reading deferred file: ${err.message}\n`);
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
// This is a regular prompt - create deferred execution
|
|
396
|
+
const tempPath = generateTempFilename();
|
|
397
|
+
const promptTempPath = tempPath + '.prompt';
|
|
398
|
+
|
|
399
|
+
// Write prompt to temp file for child process
|
|
400
|
+
fs.writeFileSync(promptTempPath, promptText);
|
|
401
|
+
|
|
402
|
+
// Create and lock the output file
|
|
403
|
+
fs.writeFileSync(tempPath, '');
|
|
404
|
+
lockFile(tempPath);
|
|
405
|
+
|
|
406
|
+
// Output the deferred reference
|
|
407
|
+
process.stdout.write(`<agentp-deferred>${tempPath}</agentp-deferred>\n`);
|
|
408
|
+
|
|
409
|
+
// Spawn detached child process
|
|
410
|
+
const childArgs = [
|
|
411
|
+
__filename,
|
|
412
|
+
'--defer-child',
|
|
413
|
+
'--prompt-file', promptTempPath,
|
|
414
|
+
'--output-file', tempPath,
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
// Pass through other flags
|
|
418
|
+
if (qaMode) childArgs.push('--qa');
|
|
419
|
+
if (tgMode === true) childArgs.push('--tg');
|
|
420
|
+
if (tgMode === false) childArgs.push('--no-tg');
|
|
421
|
+
if (flushMode) childArgs.push('--flush');
|
|
422
|
+
if (sessionName) {
|
|
423
|
+
childArgs.push('--session', sessionName);
|
|
424
|
+
if (newSession) childArgs.push('--new');
|
|
425
|
+
}
|
|
426
|
+
childArgs.push(serverBase);
|
|
427
|
+
|
|
428
|
+
const child = child_process.spawn(process.execPath, childArgs, {
|
|
429
|
+
detached: true,
|
|
430
|
+
stdio: 'ignore',
|
|
431
|
+
});
|
|
432
|
+
child.unref();
|
|
433
|
+
|
|
434
|
+
process.exit(0);
|
|
435
|
+
}
|
|
299
436
|
}
|
|
300
437
|
|
|
301
|
-
|
|
438
|
+
// Handle --defer-child mode (child process)
|
|
439
|
+
let actualPromptText = promptText;
|
|
440
|
+
if (deferChildMode && promptFile) {
|
|
441
|
+
try {
|
|
442
|
+
actualPromptText = fs.readFileSync(promptFile, 'utf8');
|
|
443
|
+
fs.unlinkSync(promptFile);
|
|
444
|
+
} catch (err) {
|
|
445
|
+
const errMsg = `Error reading prompt file: ${err.message}\n`;
|
|
446
|
+
if (outputFile) { fs.writeFileSync(outputFile, errMsg); unlockFile(outputFile); }
|
|
447
|
+
process.stdout.write(errMsg);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
302
451
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
452
|
+
// Wrap main processing in try-catch for deferChildMode to write errors to output file
|
|
453
|
+
async function runMain() {
|
|
454
|
+
// Find most recent session or create one
|
|
455
|
+
let sessions;
|
|
456
|
+
try {
|
|
457
|
+
sessions = await listSessions(serverBase);
|
|
458
|
+
} catch (err) {
|
|
459
|
+
throw new Error(`Error connecting to server: ${err.message}`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let sessionId = null;
|
|
463
|
+
|
|
464
|
+
if (sessionName) {
|
|
465
|
+
// Find session by name
|
|
466
|
+
const matches = sessions.filter(s => {
|
|
312
467
|
const title = s.title || s.slug || s.id;
|
|
313
|
-
return title.toLowerCase()
|
|
468
|
+
return title.toLowerCase() === sessionName.toLowerCase();
|
|
314
469
|
});
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
470
|
+
if (matches.length === 0) {
|
|
471
|
+
// Try partial match
|
|
472
|
+
const partial = sessions.filter(s => {
|
|
473
|
+
const title = s.title || s.slug || s.id;
|
|
474
|
+
return title.toLowerCase().includes(sessionName.toLowerCase());
|
|
475
|
+
});
|
|
476
|
+
if (partial.length === 1) {
|
|
477
|
+
sessionId = partial[0].id;
|
|
478
|
+
} else if (partial.length > 1) {
|
|
479
|
+
throw new Error(`Error: multiple sessions match "${sessionName}"`);
|
|
480
|
+
} else if (newSession) {
|
|
481
|
+
// Create new session
|
|
482
|
+
try {
|
|
483
|
+
const session = await createSession(serverBase, sessionName);
|
|
484
|
+
sessionId = session.id;
|
|
485
|
+
await selectSession(serverBase, sessionId);
|
|
486
|
+
} catch (err) {
|
|
487
|
+
throw new Error(`Error creating session: ${err.message}`);
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
throw new Error(`Error: no session found matching "${sessionName}"`);
|
|
330
491
|
}
|
|
331
492
|
} else {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
493
|
+
sessionId = matches[0].id;
|
|
494
|
+
}
|
|
495
|
+
} else if (sessions && sessions.length > 0) {
|
|
496
|
+
const sorted = sessions
|
|
497
|
+
.filter(s => s.time && (s.time.updated || s.time.created))
|
|
498
|
+
.sort((a, b) => {
|
|
499
|
+
const aTime = (a.time && a.time.updated) || (a.time && a.time.created) || 0;
|
|
500
|
+
const bTime = (b.time && b.time.updated) || (b.time && b.time.created) || 0;
|
|
501
|
+
return bTime - aTime;
|
|
502
|
+
});
|
|
503
|
+
if (sorted.length > 0) sessionId = sorted[0].id;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (!sessionId) {
|
|
507
|
+
try {
|
|
508
|
+
const session = await createSession(serverBase, 'agentp');
|
|
509
|
+
sessionId = session.id;
|
|
510
|
+
} catch (err) {
|
|
511
|
+
throw new Error(`Error creating session: ${err.message}`);
|
|
335
512
|
}
|
|
336
|
-
} else {
|
|
337
|
-
sessionId = matches[0].id;
|
|
338
513
|
}
|
|
339
|
-
} else if (sessions && sessions.length > 0) {
|
|
340
|
-
const sorted = sessions
|
|
341
|
-
.filter(s => s.time && (s.time.updated || s.time.created))
|
|
342
|
-
.sort((a, b) => {
|
|
343
|
-
const aTime = (a.time && a.time.updated) || (a.time && a.time.created) || 0;
|
|
344
|
-
const bTime = (b.time && b.time.updated) || (b.time && b.time.created) || 0;
|
|
345
|
-
return bTime - aTime;
|
|
346
|
-
});
|
|
347
|
-
if (sorted.length > 0) sessionId = sorted[0].id;
|
|
348
|
-
}
|
|
349
514
|
|
|
350
|
-
|
|
515
|
+
let answer;
|
|
351
516
|
try {
|
|
352
|
-
|
|
353
|
-
sessionId = session.id;
|
|
517
|
+
answer = await sendToSession(serverBase, sessionId, actualPromptText);
|
|
354
518
|
} catch (err) {
|
|
355
|
-
|
|
356
|
-
|
|
519
|
+
throw new Error(`Error sending prompt: ${err.message}`);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Build the message to send to Telegram (QA pair with rulers)
|
|
523
|
+
let tgMessage = answer;
|
|
524
|
+
if (qaMode) {
|
|
525
|
+
tgMessage = HUMAN_ROW + actualPromptText + AGENT_ROW + answer;
|
|
526
|
+
if (!answer.endsWith('\n')) tgMessage += '\n';
|
|
527
|
+
tgMessage += FINAL_ROW;
|
|
357
528
|
}
|
|
529
|
+
|
|
530
|
+
// Post-send: notify tgagentp, get recorded context back
|
|
531
|
+
let buffered = [];
|
|
532
|
+
let tgError = null;
|
|
533
|
+
if (tgPort) {
|
|
534
|
+
try {
|
|
535
|
+
buffered = await notifyAgentpGateway(tgPort, serverBase, tgMessage);
|
|
536
|
+
} catch (err) {
|
|
537
|
+
tgError = err;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Build output (prepend recorded context if any, unless --flush)
|
|
542
|
+
let output = '';
|
|
543
|
+
if (qaMode) {
|
|
544
|
+
if (!flushMode && buffered.length > 0) {
|
|
545
|
+
for (const msg of buffered) {
|
|
546
|
+
const row = msg.role === 'user' ? HUMAN_ROW : AGENT_ROW;
|
|
547
|
+
output += row + msg.text + '\n';
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
output += tgMessage;
|
|
551
|
+
} else {
|
|
552
|
+
output = tgMessage;
|
|
553
|
+
if (!tgMessage.endsWith('\n')) output += '\n';
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return output;
|
|
358
557
|
}
|
|
359
558
|
|
|
360
|
-
|
|
559
|
+
// Execute main processing with error handling for child mode
|
|
560
|
+
let output = '';
|
|
361
561
|
try {
|
|
362
|
-
|
|
562
|
+
output = await runMain();
|
|
363
563
|
} catch (err) {
|
|
364
|
-
|
|
564
|
+
const errMsg = err.message + '\n';
|
|
565
|
+
if (deferChildMode && outputFile) {
|
|
566
|
+
fs.writeFileSync(outputFile, errMsg);
|
|
567
|
+
unlockFile(outputFile);
|
|
568
|
+
} else {
|
|
569
|
+
process.stdout.write(errMsg);
|
|
570
|
+
}
|
|
365
571
|
process.exit(1);
|
|
366
572
|
}
|
|
367
573
|
|
|
368
|
-
//
|
|
369
|
-
let
|
|
370
|
-
if (
|
|
371
|
-
tgMessage = HUMAN_ROW + promptText + AGENT_ROW + answer;
|
|
372
|
-
if (!answer.endsWith('\n')) tgMessage += '\n';
|
|
373
|
-
tgMessage += FINAL_ROW;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Post-send: notify tgagentp, get recorded context back
|
|
377
|
-
let buffered = [];
|
|
378
|
-
let tgError = null;
|
|
379
|
-
if (tgPort) {
|
|
574
|
+
// Write output to file (child mode) or stdout (normal mode)
|
|
575
|
+
let finalOutput = output;
|
|
576
|
+
if (deferChildMode && outputFile) {
|
|
380
577
|
try {
|
|
381
|
-
|
|
578
|
+
fs.writeFileSync(outputFile, output);
|
|
579
|
+
unlockFile(outputFile);
|
|
382
580
|
} catch (err) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
// Build stdout output (prepend recorded context if any, unless --flush)
|
|
388
|
-
if (qaMode) {
|
|
389
|
-
if (!flushMode && buffered.length > 0) {
|
|
390
|
-
for (const msg of buffered) {
|
|
391
|
-
const row = msg.role === 'user' ? HUMAN_ROW : AGENT_ROW;
|
|
392
|
-
process.stdout.write(row + msg.text + '\n');
|
|
393
|
-
}
|
|
581
|
+
const errMsg = `Error writing output file: ${err.message}\n`;
|
|
582
|
+
try { fs.writeFileSync(outputFile, errMsg); } catch {}
|
|
583
|
+
unlockFile(outputFile);
|
|
584
|
+
process.exit(1);
|
|
394
585
|
}
|
|
395
|
-
process.stdout.write(tgMessage);
|
|
396
586
|
} else {
|
|
397
|
-
process.stdout.write(
|
|
398
|
-
if (!tgMessage.endsWith('\n')) process.stdout.write('\n');
|
|
587
|
+
process.stdout.write(output);
|
|
399
588
|
}
|
|
400
589
|
|
|
401
590
|
// Warnings at the end (stderr)
|