orquesta-cli 0.2.8 → 0.2.10
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.
|
@@ -425,7 +425,11 @@ export class LLMClient {
|
|
|
425
425
|
const MAX_NO_TOOL_CALL_RETRIES = 3;
|
|
426
426
|
const MAX_FINAL_RESPONSE_FAILURES = 3;
|
|
427
427
|
const recentToolSignatures = [];
|
|
428
|
+
const recentNormalizedSignatures = [];
|
|
428
429
|
const LOOP_WINDOW = 5;
|
|
430
|
+
const SEMANTIC_WINDOW = 8;
|
|
431
|
+
const SEMANTIC_DISTINCT = 2;
|
|
432
|
+
const normalizeSig = (s) => s.toLowerCase().replace(/['"`]/g, '').replace(/\d+/g, 'N').replace(/\s+/g, ' ').trim();
|
|
429
433
|
while (true) {
|
|
430
434
|
if (this.isInterrupted) {
|
|
431
435
|
logger.flow('Interrupt detected - stopping tool loop');
|
|
@@ -506,15 +510,28 @@ export class LLMClient {
|
|
|
506
510
|
const toolName = toolCall.function.name;
|
|
507
511
|
let toolArgs;
|
|
508
512
|
const sig = `${toolName}::${toolCall.function.arguments}`;
|
|
513
|
+
const normSig = normalizeSig(sig);
|
|
509
514
|
recentToolSignatures.push(sig);
|
|
515
|
+
recentNormalizedSignatures.push(normSig);
|
|
510
516
|
if (recentToolSignatures.length > LOOP_WINDOW)
|
|
511
517
|
recentToolSignatures.shift();
|
|
512
|
-
if (
|
|
518
|
+
if (recentNormalizedSignatures.length > SEMANTIC_WINDOW)
|
|
519
|
+
recentNormalizedSignatures.shift();
|
|
520
|
+
const fail = (detail) => {
|
|
513
521
|
const preview = sig.length > 240 ? sig.slice(0, 240) + '…' : sig;
|
|
514
522
|
logger.error('Tool-call loop detected — aborting', new Error(`LOOP_DETECTED: ${preview}`));
|
|
515
|
-
throw new Error(`LOOP_DETECTED:
|
|
523
|
+
throw new Error(`LOOP_DETECTED: ${detail} ` +
|
|
516
524
|
`Common causes: upstream returned a non-progress message (e.g. Claude Max session cap), tool result not being threaded back into context, or a stuck plan. ` +
|
|
517
525
|
`Aborting to protect the session. Last signature: ${preview}`);
|
|
526
|
+
};
|
|
527
|
+
if (recentToolSignatures.length === LOOP_WINDOW && recentToolSignatures.every(s => s === sig)) {
|
|
528
|
+
fail(`tool '${toolName}' called ${LOOP_WINDOW} times in a row with identical arguments.`);
|
|
529
|
+
}
|
|
530
|
+
if (recentNormalizedSignatures.length === SEMANTIC_WINDOW) {
|
|
531
|
+
const distinct = new Set(recentNormalizedSignatures).size;
|
|
532
|
+
if (distinct <= SEMANTIC_DISTINCT) {
|
|
533
|
+
fail(`the last ${SEMANTIC_WINDOW} tool calls cycled only ${distinct} distinct command(s) without progress (e.g. re-running the same checks).`);
|
|
534
|
+
}
|
|
518
535
|
}
|
|
519
536
|
try {
|
|
520
537
|
toolArgs = JSON.parse(toolCall.function.arguments);
|
|
@@ -17,11 +17,15 @@ import { formatErrorMessage, buildTodoContext, findActiveTodo, getTodoStats } fr
|
|
|
17
17
|
import { runParallelGraph, shouldUseParallelOrchestrator } from './parallel-orchestrator.js';
|
|
18
18
|
import { memoryStore } from './memory-store.js';
|
|
19
19
|
import { auditLog } from './audit-log.js';
|
|
20
|
+
function buildEnvironmentContext() {
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
return `\n\n## Environment\n\nWorking directory (where ALL your tools execute): \`${cwd}\`\nTools (read_file, list_files, bash, edit_file, …) run on the USER'S machine in this directory.\n- Use paths RELATIVE to this directory ("package.json", "src/foo.ts", "."). They resolve against the working directory automatically.\n- Do NOT prepend the absolute working-directory path to tool arguments, and do NOT invent absolute paths from documentation — the repo is already at the working directory above.\n- Ignore any other "current directory" that may appear elsewhere in your context (e.g. a proxy's /tmp); the directory above is authoritative.\n`;
|
|
23
|
+
}
|
|
20
24
|
function buildSystemPrompt() {
|
|
21
25
|
const isGitRepo = detectGitRepo();
|
|
22
26
|
const projectContext = getProjectContext();
|
|
23
27
|
const base = isGitRepo ? `${PLAN_PROMPT}\n\n${GIT_COMMIT_RULES}` : PLAN_PROMPT;
|
|
24
|
-
return base + projectContext;
|
|
28
|
+
return base + buildEnvironmentContext() + projectContext;
|
|
25
29
|
}
|
|
26
30
|
export class PlanExecutor {
|
|
27
31
|
currentLLMClient = null;
|