agentp 1.12.1 → 1.14.0
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 +90 -10
- package/bin/agentp +372 -135
- package/bin/ocmux +334 -7
- package/docs/specification.md +26 -4
- package/lib/ocmux.js +30 -5
- package/lib/opencode.js +518 -17
- package/package.json +1 -1
package/bin/agentp
CHANGED
|
@@ -10,6 +10,7 @@ const child_process = require('child_process');
|
|
|
10
10
|
const { version } = require('../package.json');
|
|
11
11
|
const {
|
|
12
12
|
sendToSession,
|
|
13
|
+
sendToSessionAsync,
|
|
13
14
|
listSessions,
|
|
14
15
|
createSession,
|
|
15
16
|
getSession,
|
|
@@ -108,6 +109,216 @@ function generateTempFilename() {
|
|
|
108
109
|
return path.join(os.tmpdir(), `agentp_deferred_${timestamp}_${hash}.tmp`);
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
// Poll interval for --defer waiting (ms). Overridable for tests.
|
|
113
|
+
const DEFER_POLL_MS = 250;
|
|
114
|
+
const FOLLOWUP_ROW = '📝 —————————————————\n';
|
|
115
|
+
|
|
116
|
+
function stripFollowupRow(text) {
|
|
117
|
+
return text.startsWith(FOLLOWUP_ROW) ? text.slice(FOLLOWUP_ROW.length) : text;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Parse an `agentp_ticket <json>` reference from the start of input text.
|
|
121
|
+
// The ticket may be wrapped in whitespace/newlines and may be followed by
|
|
122
|
+
// additional text, which is returned separately for queued follow-ups.
|
|
123
|
+
function parseDeferredTicketInput(text) {
|
|
124
|
+
const trimmed = String(text).trim();
|
|
125
|
+
if (!trimmed.startsWith('agentp_ticket')) return null;
|
|
126
|
+
const afterPrefix = trimmed.slice('agentp_ticket'.length).trimStart();
|
|
127
|
+
if (!afterPrefix.startsWith('{')) return null;
|
|
128
|
+
let depth = 0;
|
|
129
|
+
let inString = false;
|
|
130
|
+
let escaped = false;
|
|
131
|
+
let jsonEnd = -1;
|
|
132
|
+
for (let i = 0; i < afterPrefix.length; i++) {
|
|
133
|
+
const ch = afterPrefix[i];
|
|
134
|
+
if (inString) {
|
|
135
|
+
if (escaped) {
|
|
136
|
+
escaped = false;
|
|
137
|
+
} else if (ch === '\\') {
|
|
138
|
+
escaped = true;
|
|
139
|
+
} else if (ch === '"') {
|
|
140
|
+
inString = false;
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (ch === '"') {
|
|
145
|
+
inString = true;
|
|
146
|
+
} else if (ch === '{') {
|
|
147
|
+
depth++;
|
|
148
|
+
} else if (ch === '}') {
|
|
149
|
+
depth--;
|
|
150
|
+
if (depth === 0) {
|
|
151
|
+
jsonEnd = i + 1;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (jsonEnd === -1) return null;
|
|
157
|
+
let data;
|
|
158
|
+
try {
|
|
159
|
+
data = JSON.parse(afterPrefix.slice(0, jsonEnd));
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
if (!data || typeof data.path !== 'string' || !data.path) return null;
|
|
164
|
+
const rest = afterPrefix.slice(jsonEnd);
|
|
165
|
+
const followupText = rest.trim() ? stripFollowupRow(rest.trimStart()) : '';
|
|
166
|
+
return {
|
|
167
|
+
ctime: typeof data.ctime === 'string' ? data.ctime : null,
|
|
168
|
+
path: data.path,
|
|
169
|
+
defer: typeof data.defer === 'number' && data.defer > 0 ? data.defer : 0,
|
|
170
|
+
server: typeof data.server === 'string' ? data.server : null,
|
|
171
|
+
sessionId: typeof data.sessionId === 'string' ? data.sessionId : null,
|
|
172
|
+
followupText,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parseDeferredTicket(text) {
|
|
177
|
+
const parsed = parseDeferredTicketInput(text);
|
|
178
|
+
if (!parsed) return null;
|
|
179
|
+
const { followupText, ...ticket } = parsed;
|
|
180
|
+
return ticket;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Format a deferred ticket for printing. `elapsed` is only included on
|
|
184
|
+
// re-prints; `defer` only when a timeout > 0 was requested. Pretty-printed
|
|
185
|
+
// JSON by default (easier to read/edit); pass `compact` for a single line.
|
|
186
|
+
function formatTicket(ticket, elapsed, compact) {
|
|
187
|
+
const fields = { ctime: ticket.ctime, path: ticket.path };
|
|
188
|
+
if (ticket.server) fields.server = ticket.server;
|
|
189
|
+
if (ticket.sessionId) fields.sessionId = ticket.sessionId;
|
|
190
|
+
if (elapsed !== undefined) fields.elapsed = elapsed;
|
|
191
|
+
if (ticket.defer > 0) fields.defer = ticket.defer;
|
|
192
|
+
return 'agentp_ticket ' + (compact ? JSON.stringify(fields) : JSON.stringify(fields, null, 2));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function resolveTargetSession(serverBase, sessionName, newSession) {
|
|
196
|
+
let sessions;
|
|
197
|
+
try {
|
|
198
|
+
sessions = await listSessions(serverBase);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
throw new Error(`Error connecting to server: ${err.message}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (sessionName) {
|
|
204
|
+
const matches = sessions.filter(s => {
|
|
205
|
+
const title = s.title || s.slug || s.id;
|
|
206
|
+
return title.toLowerCase() === sessionName.toLowerCase();
|
|
207
|
+
});
|
|
208
|
+
if (matches.length === 0) {
|
|
209
|
+
const partial = sessions.filter(s => {
|
|
210
|
+
const title = s.title || s.slug || s.id;
|
|
211
|
+
return title.toLowerCase().includes(sessionName.toLowerCase());
|
|
212
|
+
});
|
|
213
|
+
if (partial.length === 1) return partial[0].id;
|
|
214
|
+
if (partial.length > 1) throw new Error(`Error: multiple sessions match "${sessionName}"`);
|
|
215
|
+
if (!newSession) throw new Error(`Error: no session found matching "${sessionName}"`);
|
|
216
|
+
try {
|
|
217
|
+
const session = await createSession(serverBase, sessionName);
|
|
218
|
+
await selectSession(serverBase, session.id);
|
|
219
|
+
return session.id;
|
|
220
|
+
} catch (err) {
|
|
221
|
+
throw new Error(`Error creating session: ${err.message}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return matches[0].id;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (sessions && sessions.length > 0) {
|
|
228
|
+
const sorted = sessions
|
|
229
|
+
.filter(s => s.time && (s.time.updated || s.time.created))
|
|
230
|
+
.sort((a, b) => {
|
|
231
|
+
const aTime = (a.time && a.time.updated) || (a.time && a.time.created) || 0;
|
|
232
|
+
const bTime = (b.time && b.time.updated) || (b.time && b.time.created) || 0;
|
|
233
|
+
return bTime - aTime;
|
|
234
|
+
});
|
|
235
|
+
if (sorted.length > 0) return sorted[0].id;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const session = await createSession(serverBase, 'agentp');
|
|
240
|
+
return session.id;
|
|
241
|
+
} catch (err) {
|
|
242
|
+
throw new Error(`Error creating session: ${err.message}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Read the deferred output file. Returns the answer string, or null if the
|
|
247
|
+
// answer is not ready yet (file missing, still locked, or still empty).
|
|
248
|
+
function readDeferredResult(filePath) {
|
|
249
|
+
if (!fs.existsSync(filePath)) return null;
|
|
250
|
+
if (isFileLocked(filePath)) return null;
|
|
251
|
+
let content = '';
|
|
252
|
+
try {
|
|
253
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
254
|
+
} catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
return content === '' ? null : content;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Poll up to timeoutMs for a ready deferred result. Returns the answer or null.
|
|
261
|
+
async function waitForDeferredResult(filePath, timeoutMs) {
|
|
262
|
+
const deadline = Date.now() + timeoutMs;
|
|
263
|
+
for (;;) {
|
|
264
|
+
const result = readDeferredResult(filePath);
|
|
265
|
+
if (result !== null) return result;
|
|
266
|
+
const remaining = deadline - Date.now();
|
|
267
|
+
if (remaining <= 0) return null;
|
|
268
|
+
await sleepMs(Math.min(DEFER_POLL_MS, remaining));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function sleepMs(ms) {
|
|
273
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function appendUnusedFollowup(result, followupText) {
|
|
277
|
+
if (!followupText) return result;
|
|
278
|
+
return result + (result.endsWith('\n') ? '' : '\n') + followupText;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function queuedFollowupsPath(filePath) {
|
|
282
|
+
return filePath + '.followups';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function readQueuedFollowups(filePath) {
|
|
286
|
+
try {
|
|
287
|
+
const text = fs.readFileSync(queuedFollowupsPath(filePath), 'utf8');
|
|
288
|
+
const parsed = JSON.parse(text);
|
|
289
|
+
return Array.isArray(parsed) ? parsed.filter(item => typeof item === 'string' && item) : [];
|
|
290
|
+
} catch {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function appendQueuedFollowup(filePath, followupText) {
|
|
296
|
+
if (!followupText) return;
|
|
297
|
+
const followups = readQueuedFollowups(filePath);
|
|
298
|
+
followups.push(followupText);
|
|
299
|
+
fs.writeFileSync(queuedFollowupsPath(filePath), JSON.stringify(followups, null, 2));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function removeQueuedFollowups(filePath) {
|
|
303
|
+
try { fs.unlinkSync(queuedFollowupsPath(filePath)); } catch {}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function injectQueuedFollowups(result, followups) {
|
|
307
|
+
if (!followups.length) return result;
|
|
308
|
+
const addition = followups.map(text => FOLLOWUP_ROW + text + (text.endsWith('\n') ? '' : '\n')).join('');
|
|
309
|
+
const idx = result.indexOf(AGENT_ROW);
|
|
310
|
+
if (idx === -1) return appendUnusedFollowup(result, addition.trimEnd());
|
|
311
|
+
const before = result.slice(0, idx);
|
|
312
|
+
const after = result.slice(idx);
|
|
313
|
+
return before + (before.endsWith('\n') ? '' : '\n') + addition + after;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function formatDeferredResult(filePath, result, qaMode, unusedFollowupText) {
|
|
317
|
+
const followups = qaMode ? readQueuedFollowups(filePath) : [];
|
|
318
|
+
const output = qaMode ? injectQueuedFollowups(result, followups) : result;
|
|
319
|
+
return appendUnusedFollowup(output, unusedFollowupText);
|
|
320
|
+
}
|
|
321
|
+
|
|
111
322
|
async function readAllStdin() {
|
|
112
323
|
let text = '';
|
|
113
324
|
const rl = readline.createInterface({ input: process.stdin });
|
|
@@ -127,10 +338,13 @@ async function main() {
|
|
|
127
338
|
let qaMode = false;
|
|
128
339
|
let flushMode = false;
|
|
129
340
|
let deferMode = false;
|
|
341
|
+
let deferSeconds = 0;
|
|
130
342
|
let deferChildMode = false;
|
|
343
|
+
let onlineTicket = false;
|
|
131
344
|
let promptFile = null;
|
|
132
345
|
let outputFile = null;
|
|
133
346
|
let sessionName = null;
|
|
347
|
+
let resolvedSessionId = null;
|
|
134
348
|
let newSession = false;
|
|
135
349
|
// tgMode: false = disabled, 'auto' = auto-detect, true = explicit --tg
|
|
136
350
|
let tgMode = null;
|
|
@@ -148,7 +362,11 @@ async function main() {
|
|
|
148
362
|
console.error('Options:');
|
|
149
363
|
console.error(' --version Show version');
|
|
150
364
|
console.error(' --qa Print the original prompt and answer with labels (useful as a filter)');
|
|
151
|
-
console.error(' --defer
|
|
365
|
+
console.error(' --defer [N] Deferred execution: submit prompt, get a ticket immediately');
|
|
366
|
+
console.error(' (or wait up to N seconds for the answer and print it if it arrives)');
|
|
367
|
+
console.error(' Pipe the ticket back to agentp --defer to retrieve the result later');
|
|
368
|
+
console.error(' --onlineTicket');
|
|
369
|
+
console.error(' Print deferred tickets on a single line (default: pretty-printed JSON)');
|
|
152
370
|
console.error(' --tg Forward the answer to Telegram via agentp gateway (error if unavailable)');
|
|
153
371
|
console.error(' --no-tg Do not forward to Telegram');
|
|
154
372
|
console.error(' --flush Flush tgagentp\'s recorded buffer without prepending it to output');
|
|
@@ -174,7 +392,7 @@ async function main() {
|
|
|
174
392
|
console.error('Arguments:');
|
|
175
393
|
console.error(' url OpenCode server URL or port number (default: 4096)');
|
|
176
394
|
console.error(' Examples: 4096, http://localhost:4096, http://192.168.1.50:4096');
|
|
177
|
-
console.error('
|
|
395
|
+
console.error(' Tip: $(ocmux) expands to the URL of the current project\'s server.');
|
|
178
396
|
console.error('');
|
|
179
397
|
console.error('See also: tgagentp -- Telegram bridge (tgagentp --help), ocmux -- manage OpenCode tmux sessions (ocmux --help)');
|
|
180
398
|
console.error('');
|
|
@@ -194,8 +412,17 @@ async function main() {
|
|
|
194
412
|
qaMode = true;
|
|
195
413
|
} else if (args[i] === '--defer') {
|
|
196
414
|
deferMode = true;
|
|
415
|
+
const next = args[i + 1];
|
|
416
|
+
if (next !== undefined && /^\d+$/.test(next)) {
|
|
417
|
+
deferSeconds = parseInt(next, 10);
|
|
418
|
+
i++;
|
|
419
|
+
} else {
|
|
420
|
+
deferSeconds = 0;
|
|
421
|
+
}
|
|
197
422
|
} else if (args[i] === '--defer-child') {
|
|
198
423
|
deferChildMode = true;
|
|
424
|
+
} else if (args[i] === '--onlineTicket') {
|
|
425
|
+
onlineTicket = true;
|
|
199
426
|
} else if (args[i] === '--prompt-file') {
|
|
200
427
|
i++;
|
|
201
428
|
promptFile = args[i];
|
|
@@ -211,6 +438,13 @@ async function main() {
|
|
|
211
438
|
console.error('Error: --session requires a session name');
|
|
212
439
|
process.exit(1);
|
|
213
440
|
}
|
|
441
|
+
} else if (args[i] === '--resolved-session-id') {
|
|
442
|
+
i++;
|
|
443
|
+
resolvedSessionId = args[i];
|
|
444
|
+
if (!resolvedSessionId) {
|
|
445
|
+
console.error('Error: --resolved-session-id requires a session id');
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
214
448
|
} else if (args[i] === '--new') {
|
|
215
449
|
newSession = true;
|
|
216
450
|
} else if (args[i] === '--getLast') {
|
|
@@ -354,85 +588,148 @@ async function main() {
|
|
|
354
588
|
|
|
355
589
|
// Handle --defer mode
|
|
356
590
|
if (deferMode) {
|
|
357
|
-
// Check if stdin is a deferred
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
if (
|
|
361
|
-
// This is a
|
|
362
|
-
|
|
363
|
-
|
|
591
|
+
// Check if stdin is a deferred ticket
|
|
592
|
+
const ticket = parseDeferredTicketInput(promptText);
|
|
593
|
+
|
|
594
|
+
if (ticket) {
|
|
595
|
+
// This is a ticket — retrieve the result. The --defer argument is
|
|
596
|
+
// ignored; the ticket's own "defer" timeout is used instead (default 0).
|
|
597
|
+
const deferredPath = ticket.path;
|
|
598
|
+
|
|
364
599
|
if (!fs.existsSync(deferredPath)) {
|
|
365
|
-
// File doesn't exist - error
|
|
366
600
|
process.stdout.write(`Error: deferred file not found: ${deferredPath}\n`);
|
|
367
601
|
process.exit(1);
|
|
368
602
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
603
|
+
|
|
604
|
+
let result = ticket.followupText
|
|
605
|
+
? readDeferredResult(deferredPath)
|
|
606
|
+
: await waitForDeferredResult(deferredPath, ticket.defer * 1000);
|
|
607
|
+
|
|
608
|
+
if (result !== null) {
|
|
609
|
+
// Answer available — return it and remove the temp file
|
|
610
|
+
try {
|
|
611
|
+
fs.unlinkSync(deferredPath);
|
|
612
|
+
} catch (err) {
|
|
613
|
+
process.stdout.write(`Error reading deferred file: ${err.message}\n`);
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
616
|
+
try { fs.unlinkSync(deferredPath + '.lock'); } catch {}
|
|
617
|
+
process.stdout.write(formatDeferredResult(deferredPath, result, qaMode, ticket.followupText));
|
|
618
|
+
removeQueuedFollowups(deferredPath);
|
|
382
619
|
process.exit(0);
|
|
383
620
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
621
|
+
|
|
622
|
+
if (ticket.followupText) {
|
|
623
|
+
if (!ticket.server || !ticket.sessionId) {
|
|
624
|
+
process.stdout.write('Error: deferred ticket does not include server/sessionId; cannot queue follow-up text. Re-submit only the ticket to check the result.\n');
|
|
625
|
+
process.exit(1);
|
|
626
|
+
}
|
|
627
|
+
try {
|
|
628
|
+
await sendToSessionAsync(ticket.server, ticket.sessionId, ticket.followupText);
|
|
629
|
+
appendQueuedFollowup(deferredPath, ticket.followupText);
|
|
630
|
+
} catch (err) {
|
|
631
|
+
process.stdout.write(`Error queueing follow-up prompt: ${err.message}\n`);
|
|
632
|
+
process.exit(1);
|
|
633
|
+
}
|
|
393
634
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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');
|
|
635
|
+
|
|
636
|
+
if (result === null && ticket.defer > 0) {
|
|
637
|
+
result = await waitForDeferredResult(deferredPath, ticket.defer * 1000);
|
|
638
|
+
if (result !== null) {
|
|
639
|
+
try {
|
|
640
|
+
fs.unlinkSync(deferredPath);
|
|
641
|
+
} catch (err) {
|
|
642
|
+
process.stdout.write(`Error reading deferred file: ${err.message}\n`);
|
|
643
|
+
process.exit(1);
|
|
644
|
+
}
|
|
645
|
+
try { fs.unlinkSync(deferredPath + '.lock'); } catch {}
|
|
646
|
+
process.stdout.write(formatDeferredResult(deferredPath, result, qaMode, ''));
|
|
647
|
+
removeQueuedFollowups(deferredPath);
|
|
648
|
+
process.exit(0);
|
|
649
|
+
}
|
|
425
650
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
child.unref();
|
|
433
|
-
|
|
651
|
+
|
|
652
|
+
// Answer not available yet — re-print the ticket with updated elapsed
|
|
653
|
+
const elapsed = ticket.ctime
|
|
654
|
+
? Math.max(0, Math.floor((Date.now() - Date.parse(ticket.ctime)) / 1000))
|
|
655
|
+
: 0;
|
|
656
|
+
process.stdout.write(formatTicket(ticket, elapsed, onlineTicket) + '\n');
|
|
434
657
|
process.exit(0);
|
|
435
658
|
}
|
|
659
|
+
|
|
660
|
+
// This is a regular prompt — create deferred execution
|
|
661
|
+
const tempPath = generateTempFilename();
|
|
662
|
+
const promptTempPath = tempPath + '.prompt';
|
|
663
|
+
|
|
664
|
+
let targetSessionId;
|
|
665
|
+
try {
|
|
666
|
+
targetSessionId = await resolveTargetSession(serverBase, sessionName, newSession);
|
|
667
|
+
} catch (err) {
|
|
668
|
+
console.error(err.message);
|
|
669
|
+
process.exit(1);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Write prompt to temp file for child process
|
|
673
|
+
fs.writeFileSync(promptTempPath, promptText);
|
|
674
|
+
|
|
675
|
+
// Create and lock the output file
|
|
676
|
+
fs.writeFileSync(tempPath, '');
|
|
677
|
+
lockFile(tempPath);
|
|
678
|
+
|
|
679
|
+
const newTicket = {
|
|
680
|
+
ctime: new Date().toISOString(),
|
|
681
|
+
path: tempPath,
|
|
682
|
+
defer: deferSeconds,
|
|
683
|
+
server: serverBase,
|
|
684
|
+
sessionId: targetSessionId,
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
// Spawn detached child process
|
|
688
|
+
const childArgs = [
|
|
689
|
+
__filename,
|
|
690
|
+
'--defer-child',
|
|
691
|
+
'--prompt-file', promptTempPath,
|
|
692
|
+
'--output-file', tempPath,
|
|
693
|
+
];
|
|
694
|
+
|
|
695
|
+
// Pass through other flags
|
|
696
|
+
if (qaMode) childArgs.push('--qa');
|
|
697
|
+
if (tgMode === true) childArgs.push('--tg');
|
|
698
|
+
if (tgMode === false) childArgs.push('--no-tg');
|
|
699
|
+
if (flushMode) childArgs.push('--flush');
|
|
700
|
+
if (sessionName) {
|
|
701
|
+
childArgs.push('--session', sessionName);
|
|
702
|
+
if (newSession) childArgs.push('--new');
|
|
703
|
+
}
|
|
704
|
+
childArgs.push('--resolved-session-id', targetSessionId);
|
|
705
|
+
childArgs.push(serverBase);
|
|
706
|
+
|
|
707
|
+
const child = child_process.spawn(process.execPath, childArgs, {
|
|
708
|
+
detached: true,
|
|
709
|
+
stdio: 'ignore',
|
|
710
|
+
});
|
|
711
|
+
child.unref();
|
|
712
|
+
|
|
713
|
+
// If a timeout was given, block until the answer arrives or the timeout
|
|
714
|
+
// expires. Return the response when it arrives; otherwise print a ticket.
|
|
715
|
+
if (deferSeconds > 0) {
|
|
716
|
+
const result = await waitForDeferredResult(tempPath, deferSeconds * 1000);
|
|
717
|
+
if (result !== null) {
|
|
718
|
+
try {
|
|
719
|
+
fs.unlinkSync(tempPath);
|
|
720
|
+
} catch (err) {
|
|
721
|
+
process.stdout.write(`Error reading deferred file: ${err.message}\n`);
|
|
722
|
+
process.exit(1);
|
|
723
|
+
}
|
|
724
|
+
try { fs.unlinkSync(tempPath + '.lock'); } catch {}
|
|
725
|
+
process.stdout.write(result);
|
|
726
|
+
process.exit(0);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Output the deferred ticket
|
|
731
|
+
process.stdout.write(formatTicket(newTicket, undefined, onlineTicket) + '\n');
|
|
732
|
+
process.exit(0);
|
|
436
733
|
}
|
|
437
734
|
|
|
438
735
|
// Handle --defer-child mode (child process)
|
|
@@ -450,67 +747,9 @@ async function main() {
|
|
|
450
747
|
}
|
|
451
748
|
|
|
452
749
|
// Wrap main processing in try-catch for deferChildMode to write errors to output file
|
|
750
|
+
let tgError = null;
|
|
453
751
|
async function runMain() {
|
|
454
|
-
|
|
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 => {
|
|
467
|
-
const title = s.title || s.slug || s.id;
|
|
468
|
-
return title.toLowerCase() === sessionName.toLowerCase();
|
|
469
|
-
});
|
|
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}"`);
|
|
491
|
-
}
|
|
492
|
-
} else {
|
|
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}`);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
752
|
+
const sessionId = resolvedSessionId || await resolveTargetSession(serverBase, sessionName, newSession);
|
|
514
753
|
|
|
515
754
|
let answer;
|
|
516
755
|
try {
|
|
@@ -529,7 +768,6 @@ async function main() {
|
|
|
529
768
|
|
|
530
769
|
// Post-send: notify tgagentp, get recorded context back
|
|
531
770
|
let buffered = [];
|
|
532
|
-
let tgError = null;
|
|
533
771
|
if (tgPort) {
|
|
534
772
|
try {
|
|
535
773
|
buffered = await notifyAgentpGateway(tgPort, serverBase, tgMessage);
|
|
@@ -566,7 +804,7 @@ async function main() {
|
|
|
566
804
|
fs.writeFileSync(outputFile, errMsg);
|
|
567
805
|
unlockFile(outputFile);
|
|
568
806
|
} else {
|
|
569
|
-
|
|
807
|
+
console.error(err.message);
|
|
570
808
|
}
|
|
571
809
|
process.exit(1);
|
|
572
810
|
}
|
|
@@ -605,5 +843,4 @@ if (require.main === module) {
|
|
|
605
843
|
});
|
|
606
844
|
}
|
|
607
845
|
|
|
608
|
-
module.exports = { main, notifyAgentpGateway, readAllStdin };
|
|
609
|
-
|
|
846
|
+
module.exports = { main, notifyAgentpGateway, readAllStdin, parseDeferredTicket, parseDeferredTicketInput, formatTicket, waitForDeferredResult };
|