agentp 0.11.3 → 0.11.4
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/bin/agentp +226 -0
- package/docs/specification.md +1 -1
- package/package.json +1 -1
package/bin/agentp
CHANGED
|
@@ -77,6 +77,8 @@ async function main() {
|
|
|
77
77
|
let serverBase = 'http://localhost:4096';
|
|
78
78
|
let qaMode = false;
|
|
79
79
|
let flushMode = false;
|
|
80
|
+
let sessionName = null;
|
|
81
|
+
let newSession = false;
|
|
80
82
|
// tgMode: false = disabled, 'auto' = auto-detect, true = explicit --tg
|
|
81
83
|
let tgMode = null;
|
|
82
84
|
|
|
@@ -97,6 +99,8 @@ async function main() {
|
|
|
97
99
|
console.error(' --no-tg Do not forward to Telegram');
|
|
98
100
|
console.error(' --flush Flush tgagentp\'s recorded buffer without prepending it to output');
|
|
99
101
|
console.error(' --getLast Retrieve last N assistant answers (or QA pairs with --qa) from session history');
|
|
102
|
+
console.error(' --session Target a specific session by name (creates it if not found with --new)');
|
|
103
|
+
console.error(' --new Create a new session with the given title (requires --session)');
|
|
100
104
|
console.error(' --help Show this help message');
|
|
101
105
|
console.error('');
|
|
102
106
|
console.error('By default, --qa auto-detects tgagentp; standalone mode implies --no-tg.');
|
|
@@ -109,6 +113,10 @@ async function main() {
|
|
|
109
113
|
console.error('--flush clears the tgagentp recorded buffer and also prevents the prepended');
|
|
110
114
|
console.error('context from appearing in --qa output. Use it when you want fresh context.');
|
|
111
115
|
console.error('');
|
|
116
|
+
console.error('--session <name> targets a specific session by exact or partial name match.');
|
|
117
|
+
console.error('If the session does not exist, agentp errors (use --new to create it).');
|
|
118
|
+
console.error('--new creates a new session with the given title (does not match existing sessions).');
|
|
119
|
+
console.error('');
|
|
112
120
|
console.error('Arguments:');
|
|
113
121
|
console.error(' url OpenCode server URL or port number (default: 4096)');
|
|
114
122
|
console.error(' Examples: 4096, http://localhost:4096, http://192.168.1.50:4096');
|
|
@@ -119,6 +127,8 @@ async function main() {
|
|
|
119
127
|
console.error('Examples:');
|
|
120
128
|
console.error(' printf "Summarize this file" | agentp');
|
|
121
129
|
console.error(' cat prompt.txt | agentp $(ocmux) # talk to current project\'s server');
|
|
130
|
+
console.error(' cat prompt.txt | agentp --session "My Task" $(ocmux)');
|
|
131
|
+
console.error(' cat prompt.txt | agentp --session "New Task" --new $(ocmux)');
|
|
122
132
|
console.error(' # Vim/Neovim filter, preserving prompt + answer:');
|
|
123
133
|
console.error(" :'<,'>!agentp --qa $(ocmux)");
|
|
124
134
|
console.error(' # Retrieve last 3 assistant answers:');
|
|
@@ -130,6 +140,15 @@ async function main() {
|
|
|
130
140
|
qaMode = true;
|
|
131
141
|
} else if (args[i] === '--flush') {
|
|
132
142
|
flushMode = true;
|
|
143
|
+
} else if (args[i] === '--session') {
|
|
144
|
+
i++;
|
|
145
|
+
sessionName = args[i];
|
|
146
|
+
if (!sessionName) {
|
|
147
|
+
console.error('Error: --session requires a session name');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
} else if (args[i] === '--new') {
|
|
151
|
+
newSession = true;
|
|
133
152
|
} else if (args[i] === '--getLast') {
|
|
134
153
|
i++;
|
|
135
154
|
const nStr = args[i];
|
|
@@ -137,6 +156,213 @@ async function main() {
|
|
|
137
156
|
console.error('Error: --getLast requires a number (e.g. --getLast 5)');
|
|
138
157
|
process.exit(1);
|
|
139
158
|
}
|
|
159
|
+
const n = parseInt(nStr, 10);
|
|
160
|
+
try {
|
|
161
|
+
const sessions = await listSessions(serverBase);
|
|
162
|
+
if (!sessions || sessions.length === 0) {
|
|
163
|
+
console.error('No sessions found.');
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
const sorted = sessions
|
|
167
|
+
.filter(s => s.time && (s.time.updated || s.time.created))
|
|
168
|
+
.sort((a, b) => {
|
|
169
|
+
const aTime = (a.time && a.time.updated) || (a.time && a.time.created) || 0;
|
|
170
|
+
const bTime = (b.time && b.time.updated) || (b.time && b.time.created) || 0;
|
|
171
|
+
return bTime - aTime;
|
|
172
|
+
});
|
|
173
|
+
const sessionId = sorted[0].id;
|
|
174
|
+
|
|
175
|
+
function buildPairsFromMessages(messages) {
|
|
176
|
+
const pairs = [];
|
|
177
|
+
let cur = null;
|
|
178
|
+
for (const raw of messages) {
|
|
179
|
+
// Normalize: export gives {role, parts: [{type, text}]}, SQLite gives {role, text}
|
|
180
|
+
const m = raw.parts ? raw : { role: raw.role, parts: [{ type: 'text', text: raw.text }] };
|
|
181
|
+
const role = m.role;
|
|
182
|
+
const text = (m.parts || []).filter(p => p.type === 'text').map(p => p.text).join('').trim();
|
|
183
|
+
if (!text) continue;
|
|
184
|
+
if (role === 'user') {
|
|
185
|
+
if (cur) pairs.push(cur);
|
|
186
|
+
cur = { prompt: text, answer: '' };
|
|
187
|
+
} else if (role === 'assistant' && cur) {
|
|
188
|
+
if (cur.answer) cur.answer += '\n';
|
|
189
|
+
cur.answer += text;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Don't push incomplete pair (no answer yet)
|
|
193
|
+
return pairs;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function formatPairs(pairs) {
|
|
197
|
+
return pairs.map(p =>
|
|
198
|
+
HUMAN_ROW + p.prompt + '\n' + AGENT_ROW + p.answer + '\n' + FINAL_ROW
|
|
199
|
+
).join('\n');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const formatAsQa = qaMode || args.includes('--qa');
|
|
203
|
+
function extractAnswers(pairs) {
|
|
204
|
+
return pairs.map(p => p.answer);
|
|
205
|
+
}
|
|
206
|
+
let output = '';
|
|
207
|
+
try {
|
|
208
|
+
const raw = child_process.execFileSync('opencode', ['export', sessionId], { encoding: 'utf8', maxBuffer: 50 * 1024 * 1024 });
|
|
209
|
+
const jsonStart = raw.indexOf('{');
|
|
210
|
+
const body = jsonStart >= 0 ? raw.slice(jsonStart) : raw;
|
|
211
|
+
const exportData = JSON.parse(body);
|
|
212
|
+
const messages = exportData.messages || [];
|
|
213
|
+
const pairs = buildPairsFromMessages(messages);
|
|
214
|
+
const lastN = pairs.slice(-n);
|
|
215
|
+
output = formatAsQa ? formatPairs(lastN) : extractAnswers(lastN).join('\n');
|
|
216
|
+
} catch {
|
|
217
|
+
// export JSON is malformed (opencode bug);
|
|
218
|
+
// fall back to SQLite
|
|
219
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
220
|
+
const dbCandidates = [
|
|
221
|
+
`${home}/.local/share/opencode/opencode.db`,
|
|
222
|
+
`${home}/Library/Application Support/opencode/opencode.db`,
|
|
223
|
+
`${home}/.opencode/opencode.db`,
|
|
224
|
+
];
|
|
225
|
+
let dbPath = null;
|
|
226
|
+
for (const p of dbCandidates) {
|
|
227
|
+
try { fs.accessSync(p); dbPath = p; break; } catch {}
|
|
228
|
+
}
|
|
229
|
+
if (dbPath) {
|
|
230
|
+
const sql = `SELECT m.data->>'role' as role, group_concat(p.data->>'text', '') as text FROM message m JOIN part p ON p.message_id = m.id WHERE m.session_id = '${sessionId.replace(/'/g, "''")}' AND p.data->>'type' = 'text' AND m.data->>'role' IN ('user','assistant') GROUP BY m.id ORDER BY m.time_created ASC`;
|
|
231
|
+
const out = child_process.execFileSync('sqlite3', ['-json', dbPath, sql], { encoding: 'utf8', maxBuffer: 50 * 1024 * 1024 });
|
|
232
|
+
const messages = JSON.parse(out);
|
|
233
|
+
const pairs = buildPairsFromMessages(messages);
|
|
234
|
+
const lastN = pairs.slice(-n);
|
|
235
|
+
output = formatAsQa ? formatPairs(lastN) : extractAnswers(lastN).join('\n');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!output) {
|
|
239
|
+
console.error(formatAsQa ? 'No complete prompt-answer pairs found in session history.' : 'No assistant answers found in session history.');
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
process.stdout.write(output);
|
|
243
|
+
if (!output.endsWith('\n')) process.stdout.write('\n');
|
|
244
|
+
} catch (err) {
|
|
245
|
+
console.error('Error retrieving session history:', err.message);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
} else if (args[i] === '--tg') {
|
|
250
|
+
tgMode = true;
|
|
251
|
+
} else if (args[i] === '--no-tg') {
|
|
252
|
+
tgMode = false;
|
|
253
|
+
} else if (args[i].includes('://')) {
|
|
254
|
+
serverBase = args[i].replace(/\/+$/, '');
|
|
255
|
+
} else if (/^\d+$/.test(args[i])) {
|
|
256
|
+
serverBase = 'http://localhost:' + args[i];
|
|
257
|
+
} else if (args[i].startsWith('--')) {
|
|
258
|
+
console.error(`Error: unknown option '${args[i]}'`);
|
|
259
|
+
console.error(`Usage: agentp [options] [url]`);
|
|
260
|
+
console.error(`Try 'agentp --help' for more information.`);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
} else {
|
|
263
|
+
console.error(`Error: unexpected argument '${args[i]}'`);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Resolve default: --qa → auto-detect, standalone → disabled
|
|
269
|
+
if (tgMode === null) {
|
|
270
|
+
tgMode = qaMode ? 'auto' : false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Pre-send: probe tgagentp gateway if enabled
|
|
274
|
+
let tgPort = null;
|
|
275
|
+
if (tgMode) {
|
|
276
|
+
try {
|
|
277
|
+
tgPort = fs.readFileSync(TGAGENTP_PORT_FILE, 'utf8').trim();
|
|
278
|
+
if (!tgPort || !/^\d+$/.test(tgPort)) tgPort = null;
|
|
279
|
+
} catch {
|
|
280
|
+
tgPort = null;
|
|
281
|
+
}
|
|
282
|
+
if (!tgPort && tgMode === true) {
|
|
283
|
+
console.error('Error: tgagentp gateway not found. Is tgagentp running?');
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
// auto mode: tgPort stays null → silently skip tgagentp
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const promptText = await readAllStdin();
|
|
290
|
+
|
|
291
|
+
// Find most recent session or create one
|
|
292
|
+
let sessions;
|
|
293
|
+
try {
|
|
294
|
+
sessions = await listSessions(serverBase);
|
|
295
|
+
} catch (err) {
|
|
296
|
+
console.error('Error connecting to server:', err.message);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let sessionId = null;
|
|
301
|
+
|
|
302
|
+
if (sessionName) {
|
|
303
|
+
// Find session by name
|
|
304
|
+
const matches = sessions.filter(s => {
|
|
305
|
+
const title = s.title || s.slug || s.id;
|
|
306
|
+
return title.toLowerCase() === sessionName.toLowerCase();
|
|
307
|
+
});
|
|
308
|
+
if (matches.length === 0) {
|
|
309
|
+
// Try partial match
|
|
310
|
+
const partial = sessions.filter(s => {
|
|
311
|
+
const title = s.title || s.slug || s.id;
|
|
312
|
+
return title.toLowerCase().includes(sessionName.toLowerCase());
|
|
313
|
+
});
|
|
314
|
+
if (partial.length === 1) {
|
|
315
|
+
sessionId = partial[0].id;
|
|
316
|
+
} else if (partial.length > 1) {
|
|
317
|
+
console.error(`Error: multiple sessions match "${sessionName}":`);
|
|
318
|
+
partial.forEach(s => console.error(` - ${s.title || s.slug || s.id}`));
|
|
319
|
+
process.exit(1);
|
|
320
|
+
} else if (newSession) {
|
|
321
|
+
// Create new session
|
|
322
|
+
try {
|
|
323
|
+
const session = await createSession(serverBase, sessionName);
|
|
324
|
+
sessionId = session.id;
|
|
325
|
+
await selectSession(serverBase, sessionId);
|
|
326
|
+
} catch (err) {
|
|
327
|
+
console.error('Error creating session:', err.message);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
console.error(`Error: no session found matching "${sessionName}"`);
|
|
332
|
+
console.error('Use --new to create a new session with this name');
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
sessionId = matches[0].id;
|
|
337
|
+
}
|
|
338
|
+
} else if (sessions && sessions.length > 0) {
|
|
339
|
+
const sorted = sessions
|
|
340
|
+
.filter(s => s.time && (s.time.updated || s.time.created))
|
|
341
|
+
.sort((a, b) => {
|
|
342
|
+
const aTime = (a.time && a.time.updated) || (a.time && a.time.created) || 0;
|
|
343
|
+
const bTime = (b.time && b.time.updated) || (b.time && b.time.created) || 0;
|
|
344
|
+
return bTime - aTime;
|
|
345
|
+
});
|
|
346
|
+
if (sorted.length > 0) sessionId = sorted[0].id;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!sessionId) {
|
|
350
|
+
try {
|
|
351
|
+
const session = await createSession(serverBase, 'agentp');
|
|
352
|
+
sessionId = session.id;
|
|
353
|
+
} catch (err) {
|
|
354
|
+
console.error('Error creating session:', err.message);
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
let answer;
|
|
360
|
+
try {
|
|
361
|
+
answer = await sendToSession(serverBase, sessionId, promptText);
|
|
362
|
+
} catch (err) {
|
|
363
|
+
console.error('Error sending prompt:', err.message);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
140
366
|
const n = parseInt(nStr, 10);
|
|
141
367
|
try {
|
|
142
368
|
const sessions = await listSessions(serverBase);
|
package/docs/specification.md
CHANGED