orquesta-cli 0.2.101 → 0.2.103
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.
|
@@ -221,6 +221,7 @@ export class LLMClient {
|
|
|
221
221
|
});
|
|
222
222
|
const url = '/chat/completions';
|
|
223
223
|
let controller = null;
|
|
224
|
+
let gapTimedOut = false;
|
|
224
225
|
try {
|
|
225
226
|
logger.flow('Starting message preprocessing');
|
|
226
227
|
const modelId = options.model || this.model;
|
|
@@ -272,61 +273,80 @@ export class LLMClient {
|
|
|
272
273
|
const toolCallsMap = new Map();
|
|
273
274
|
let responseId = '';
|
|
274
275
|
let responseModel = '';
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
buffer = lines.pop() || '';
|
|
282
|
-
for (const line of lines) {
|
|
283
|
-
const trimmed = line.trim();
|
|
284
|
-
if (!trimmed || trimmed === 'data: [DONE]')
|
|
285
|
-
continue;
|
|
286
|
-
if (!trimmed.startsWith('data: '))
|
|
287
|
-
continue;
|
|
276
|
+
const GAP_MS = Number(process.env['ORQUESTA_STREAM_GAP_MS']) || 45000;
|
|
277
|
+
let lastChunkAt = Date.now();
|
|
278
|
+
const gapTimer = setInterval(() => {
|
|
279
|
+
if (Date.now() - lastChunkAt > GAP_MS) {
|
|
280
|
+
gapTimedOut = true;
|
|
281
|
+
clearInterval(gapTimer);
|
|
288
282
|
try {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
283
|
+
controller?.abort();
|
|
284
|
+
}
|
|
285
|
+
catch { }
|
|
286
|
+
}
|
|
287
|
+
}, 5000);
|
|
288
|
+
gapTimer.unref?.();
|
|
289
|
+
try {
|
|
290
|
+
for await (const chunk of stream) {
|
|
291
|
+
lastChunkAt = Date.now();
|
|
292
|
+
if (this.isInterrupted) {
|
|
293
|
+
throw new Error('INTERRUPTED');
|
|
294
|
+
}
|
|
295
|
+
buffer += chunk.toString();
|
|
296
|
+
const lines = buffer.split('\n');
|
|
297
|
+
buffer = lines.pop() || '';
|
|
298
|
+
for (const line of lines) {
|
|
299
|
+
const trimmed = line.trim();
|
|
300
|
+
if (!trimmed || trimmed === 'data: [DONE]')
|
|
296
301
|
continue;
|
|
297
|
-
if (
|
|
298
|
-
finishReason = choice.finish_reason;
|
|
299
|
-
const delta = choice.delta;
|
|
300
|
-
if (!delta)
|
|
302
|
+
if (!trimmed.startsWith('data: '))
|
|
301
303
|
continue;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
304
|
+
try {
|
|
305
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
306
|
+
if (data.id)
|
|
307
|
+
responseId = data.id;
|
|
308
|
+
if (data.model)
|
|
309
|
+
responseModel = data.model;
|
|
310
|
+
const choice = data.choices?.[0];
|
|
311
|
+
if (!choice)
|
|
312
|
+
continue;
|
|
313
|
+
if (choice.finish_reason)
|
|
314
|
+
finishReason = choice.finish_reason;
|
|
315
|
+
const delta = choice.delta;
|
|
316
|
+
if (!delta)
|
|
317
|
+
continue;
|
|
318
|
+
if (delta.role)
|
|
319
|
+
role = delta.role;
|
|
320
|
+
if (delta.content) {
|
|
321
|
+
contentAccum += delta.content;
|
|
322
|
+
this.onStreamingContent(delta.content);
|
|
323
|
+
}
|
|
324
|
+
if (delta.reasoning) {
|
|
325
|
+
reasoningAccum += delta.reasoning;
|
|
326
|
+
}
|
|
327
|
+
if (delta.tool_calls) {
|
|
328
|
+
for (const tc of delta.tool_calls) {
|
|
329
|
+
const idx = tc.index ?? 0;
|
|
330
|
+
if (!toolCallsMap.has(idx)) {
|
|
331
|
+
toolCallsMap.set(idx, { id: tc.id || '', type: 'function', function: { name: '', arguments: '' } });
|
|
332
|
+
}
|
|
333
|
+
const existing = toolCallsMap.get(idx);
|
|
334
|
+
if (tc.id)
|
|
335
|
+
existing.id = tc.id;
|
|
336
|
+
if (tc.function?.name)
|
|
337
|
+
existing.function.name += tc.function.name;
|
|
338
|
+
if (tc.function?.arguments)
|
|
339
|
+
existing.function.arguments += tc.function.arguments;
|
|
316
340
|
}
|
|
317
|
-
const existing = toolCallsMap.get(idx);
|
|
318
|
-
if (tc.id)
|
|
319
|
-
existing.id = tc.id;
|
|
320
|
-
if (tc.function?.name)
|
|
321
|
-
existing.function.name += tc.function.name;
|
|
322
|
-
if (tc.function?.arguments)
|
|
323
|
-
existing.function.arguments += tc.function.arguments;
|
|
324
341
|
}
|
|
325
342
|
}
|
|
343
|
+
catch { }
|
|
326
344
|
}
|
|
327
|
-
catch { }
|
|
328
345
|
}
|
|
329
346
|
}
|
|
347
|
+
finally {
|
|
348
|
+
clearInterval(gapTimer);
|
|
349
|
+
}
|
|
330
350
|
const toolCalls = Array.from(toolCallsMap.values())
|
|
331
351
|
.filter(tc => tc.id && tc.function.name)
|
|
332
352
|
.map(tc => ({ id: tc.id, type: 'function', function: { name: tc.function.name, arguments: tc.function.arguments } }));
|
|
@@ -397,6 +417,11 @@ export class LLMClient {
|
|
|
397
417
|
return response.data;
|
|
398
418
|
}
|
|
399
419
|
catch (error) {
|
|
420
|
+
if (gapTimedOut) {
|
|
421
|
+
logger.flow('Stream stalled — gap watchdog aborted the request');
|
|
422
|
+
logger.exit('chatCompletion', { success: false, stalled: true });
|
|
423
|
+
throw new Error('Upstream stalled: no streaming data received within the gap window. Try again or switch endpoint.');
|
|
424
|
+
}
|
|
400
425
|
if (axios.isCancel(error) || (error instanceof Error && error.name === 'CanceledError')) {
|
|
401
426
|
logger.flow('API call canceled (user interrupt)');
|
|
402
427
|
logger.exit('chatCompletion', { success: false, aborted: true });
|
|
@@ -47,10 +47,13 @@ export function planWaves(todos) {
|
|
|
47
47
|
}
|
|
48
48
|
function buildWorkerMessages(ctx) {
|
|
49
49
|
const upstreamSection = Object.keys(ctx.upstreamOutputs).length > 0
|
|
50
|
-
? `\n\
|
|
50
|
+
? `\n\nFindings from already-completed tasks (REUSE these — do not re-discover what they already found, e.g. file locations):\n${Object.entries(ctx.upstreamOutputs)
|
|
51
51
|
.map(([id, out]) => `- [${id}] ${truncate(out, 800)}`)
|
|
52
52
|
.join('\n')}`
|
|
53
53
|
: '';
|
|
54
|
+
const siblingSection = ctx.siblingTitles && ctx.siblingTitles.length > 0
|
|
55
|
+
? `\n\nRunning in parallel RIGHT NOW (other workers own these — do NOT do their work, and avoid redundant whole-repo scans they'd also run):\n${ctx.siblingTitles.map(t => `- ${t}`).join('\n')}`
|
|
56
|
+
: '';
|
|
54
57
|
const cwdNote = ctx.workingDirectory && ctx.workingDirectory !== process.cwd()
|
|
55
58
|
? `\n\nIMPORTANT: Run all filesystem and shell operations from this directory: ${ctx.workingDirectory}\nDO NOT cd elsewhere.`
|
|
56
59
|
: '';
|
|
@@ -60,7 +63,7 @@ function buildWorkerMessages(ctx) {
|
|
|
60
63
|
role: 'user',
|
|
61
64
|
content: `Execute ONLY this single task and then call final_response with a short summary of what you did. Do not start other tasks.
|
|
62
65
|
|
|
63
|
-
Task [${ctx.todo.id}]: ${ctx.todo.title}${upstreamSection}${cwdNote}`,
|
|
66
|
+
Task [${ctx.todo.id}]: ${ctx.todo.title}${upstreamSection}${siblingSection}${cwdNote}`,
|
|
64
67
|
},
|
|
65
68
|
];
|
|
66
69
|
}
|
|
@@ -197,17 +200,15 @@ export async function runParallelGraph(opts) {
|
|
|
197
200
|
}
|
|
198
201
|
}
|
|
199
202
|
}
|
|
203
|
+
const priorFindings = { ...outputs };
|
|
200
204
|
const settled = await Promise.allSettled(batch.map(t => runWorker({
|
|
201
205
|
todo: todoById.get(t.id),
|
|
202
206
|
sessionId,
|
|
203
207
|
llmClient,
|
|
204
208
|
baseSystemPrompt,
|
|
205
209
|
executorModel,
|
|
206
|
-
upstreamOutputs:
|
|
207
|
-
|
|
208
|
-
acc[depId] = outputs[depId];
|
|
209
|
-
return acc;
|
|
210
|
-
}, {}),
|
|
210
|
+
upstreamOutputs: priorFindings,
|
|
211
|
+
siblingTitles: batch.filter(s => s.id !== t.id).map(s => todoById.get(s.id).title),
|
|
211
212
|
workingDirectory: allocations.get(t.id),
|
|
212
213
|
})));
|
|
213
214
|
for (let k = 0; k < settled.length; k++) {
|