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 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);
@@ -16,7 +16,7 @@ All tools share `lib/opencode.js` (HTTP session API client) and `lib/ocmux.js` (
16
16
 
17
17
  ### `package.json`
18
18
 
19
- **Version:** 0.11.3
19
+ **Version:** 0.11.4
20
20
 
21
21
  Fields:
22
22
  - `"bin"` — registers `agentp`, `ocmux`, `tgagentp`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentp",
3
- "version": "0.11.3",
3
+ "version": "0.11.4",
4
4
  "description": "Pipe prompts into a running OpenCode TUI session and stream the final answer",
5
5
  "bin": {
6
6
  "agentp": "bin/agentp",