@pentoshi/clai 3.11.29 → 3.11.31
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/dist/agent/loop-guard.d.ts +12 -3
- package/dist/agent/loop-guard.js +237 -41
- package/dist/agent/loop-guard.js.map +1 -1
- package/dist/agent/runner.js +50 -26
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/tool-call-parser.js +70 -0
- package/dist/agent/tool-call-parser.js.map +1 -1
- package/dist/llm/capabilities.d.ts +2 -2
- package/dist/llm/capabilities.js +9 -11
- package/dist/llm/capabilities.js.map +1 -1
- package/dist/llm/http.js +3 -1
- package/dist/llm/http.js.map +1 -1
- package/dist/llm/router.js +9 -5
- package/dist/llm/router.js.map +1 -1
- package/dist/tui-v2/rendering/incremental-strip.js +1 -1
- package/dist/tui-v2/rendering/incremental-strip.js.map +1 -1
- package/dist/tui-v2/rendering/strip-tool-surfaces.js +4 -0
- package/dist/tui-v2/rendering/strip-tool-surfaces.js.map +1 -1
- package/dist/version.generated.d.ts +2 -2
- package/dist/version.generated.js +2 -2
- package/package.json +1 -1
package/dist/agent/runner.js
CHANGED
|
@@ -6,7 +6,7 @@ import { classifyStreamFailure, planStreamRecovery, recordRecoveryAttempt, creat
|
|
|
6
6
|
import { modelSupportsVision, resolveToolDialect } from "../llm/capabilities.js";
|
|
7
7
|
import { syntheticToolCallId, isTextOnlyModel, markTextOnlyModel, fromWireName, } from "../llm/tool-protocol.js";
|
|
8
8
|
import { sanitizeAssistantText } from "../ui/ansi-box.js";
|
|
9
|
-
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
10
10
|
import { jobManager, } from "../tools/jobs.js";
|
|
11
11
|
import { isResponderResultLedgerMessage, responderContextMessage, upsertResponderContextMessage, upsertResponderResultLedger, } from "./responder-context.js";
|
|
12
12
|
import { agentModeDirective, planModeDirective, renderAgentSystemPrompt, renderCompactAgentSystemPrompt, renderRequestEnvironmentContext, scratchDirFor, toolNudge, } from "../prompts/index.js";
|
|
@@ -86,7 +86,7 @@ import { buildRichStopSummary } from "./stop-summary.js";
|
|
|
86
86
|
import { composeAgentSystemPrompt } from "./prompt-composer.js";
|
|
87
87
|
import { createGovernorState, governProgress, } from "./evidence-governor.js";
|
|
88
88
|
import { createTurnState, transitionTurn, } from "./turn-state.js";
|
|
89
|
-
import { deriveOutcomeStatus, inferOutcomeKind, openOutcomeState, recordAnswerEvidence, recordFailedHypothesis, recordToolEvidence, saveOutcomeState, validateCriterionEvidence, } from "./outcomes.js";
|
|
89
|
+
import { deriveOutcomeStatus, inferOutcomeKind, openOutcomeState, recordAnswerEvidence, recordFailedHypothesis, recordToolEvidence, completedOperationObservationDigest, saveOutcomeState, validateCriterionEvidence, } from "./outcomes.js";
|
|
90
90
|
import { createTurnOutcome, normalizeTurnOutcomeInput, renderTurnOutcome, } from "./turn-outcome.js";
|
|
91
91
|
import { beginEngagementAction, finishEngagementAction, recordEngagementCheckpoint, reconcileEngagementJob, openEngagement, saveEngagement, } from "../store/engagement.js";
|
|
92
92
|
export * from "./tool-call-parser.js";
|
|
@@ -856,6 +856,17 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
856
856
|
}
|
|
857
857
|
return message;
|
|
858
858
|
};
|
|
859
|
+
const upsertActionCycleRecovery = (content) => {
|
|
860
|
+
const prefix = "[ACTION CYCLE RECOVERY] ";
|
|
861
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
862
|
+
const message = messages[index];
|
|
863
|
+
if (message.role === "user" && message.internal && message.content.startsWith(prefix)) {
|
|
864
|
+
messages.splice(index, 1);
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
messages.push(recoveryUserMessage(prefix + content));
|
|
869
|
+
};
|
|
859
870
|
const recoveryProse = (content) => {
|
|
860
871
|
const text = textBeforeToolCall(stripSentinelTokens(content)).trim();
|
|
861
872
|
if (!text ||
|
|
@@ -1376,8 +1387,14 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1376
1387
|
...(retryReason ? { retryReason } : {}),
|
|
1377
1388
|
});
|
|
1378
1389
|
if (loopCheck.block) {
|
|
1379
|
-
const
|
|
1390
|
+
const baseReason = loopCheck.reason ??
|
|
1380
1391
|
`${call.name} previously failed with identical arguments. Change the command/args and retry.`;
|
|
1392
|
+
const priorObservation = loopCheck.kind === "unchanged-success"
|
|
1393
|
+
? loopGuard.getPriorObservation(call.name, call.args)
|
|
1394
|
+
: undefined;
|
|
1395
|
+
const reason = priorObservation
|
|
1396
|
+
? `${baseReason}\n\nPrior successful result (reuse this; it is the result of the requested call):\n${priorObservation}`
|
|
1397
|
+
: baseReason;
|
|
1381
1398
|
if (loopCheck.kind === "unchanged-success") {
|
|
1382
1399
|
const result = { ok: true, output: reason, exitCode: 0 };
|
|
1383
1400
|
emitVisibleSyntheticReceipt(result, reason);
|
|
@@ -3884,7 +3901,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3884
3901
|
}
|
|
3885
3902
|
// Exhausted retries — fall through to the normal answer path.
|
|
3886
3903
|
}
|
|
3887
|
-
if (/<\|tool_call(?:s_section)?_begin\|>|<\|tool_call_argument_begin
|
|
3904
|
+
if (/<\|tool_call(?:s_section)?_begin\|>|<\|tool_call_argument_begin\|>|<[||]DSML[||](?:tool_calls|invoke|parameter)\b/i.test(assistantText.visible)) {
|
|
3888
3905
|
writeNotice("warn", "tool call was malformed or cut off — asking the model to retry in JSON form", chalk.yellow(" ⚠ tool call was malformed or cut off — asking the model to retry in JSON form\n"));
|
|
3889
3906
|
commitAssistantRetry(assistantText.visible);
|
|
3890
3907
|
messages.push(recoveryUserMessage(toolsAttached
|
|
@@ -4291,11 +4308,20 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4291
4308
|
writeNotice("warn", reason, chalk.yellow(` ⚠ ${reason}\n`));
|
|
4292
4309
|
const suppressedResults = bound.map((b) => {
|
|
4293
4310
|
const duplicate = runIds.has(b.id);
|
|
4294
|
-
const
|
|
4311
|
+
const priorObservation = duplicate
|
|
4312
|
+
? loopGuard.getPriorObservation(b.call.name, b.call.args)
|
|
4313
|
+
: undefined;
|
|
4314
|
+
const resultReason = duplicate
|
|
4315
|
+
? reason +
|
|
4316
|
+
(priorObservation
|
|
4317
|
+
? `\n\nPrior successful result for ${b.call.name}:\n${priorObservation}`
|
|
4318
|
+
: "")
|
|
4319
|
+
: deferReason;
|
|
4295
4320
|
const result = {
|
|
4296
|
-
ok:
|
|
4321
|
+
ok: duplicate,
|
|
4297
4322
|
output: resultReason,
|
|
4298
|
-
exitCode: duplicate ?
|
|
4323
|
+
exitCode: duplicate ? 0 : 130,
|
|
4324
|
+
...(duplicate ? { suppressedRepeat: true } : {}),
|
|
4299
4325
|
};
|
|
4300
4326
|
return { b, resultReason, result };
|
|
4301
4327
|
});
|
|
@@ -4316,22 +4342,6 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4316
4342
|
writeToolOutput(eventId, output, chalk.dim(` ${output}`));
|
|
4317
4343
|
emitToolResult(eventId, result, resultReason);
|
|
4318
4344
|
}
|
|
4319
|
-
if (historyNativeCalls.length) {
|
|
4320
|
-
appendAssistantWithTools(messages, beforeTool ?? "", historyNativeCalls, completion.reasoningBlock ??
|
|
4321
|
-
(assistantText.hasThinking && assistantText.thinkContent
|
|
4322
|
-
? { text: assistantText.thinkContent }
|
|
4323
|
-
: undefined));
|
|
4324
|
-
for (const { b, resultReason, result } of suppressedResults) {
|
|
4325
|
-
appendToolResult(messages, b.id, `Tool ${b.call.name} result (exit=${result.exitCode}, ok=false):\n${resultReason}`, b.call.name, false);
|
|
4326
|
-
}
|
|
4327
|
-
}
|
|
4328
|
-
else {
|
|
4329
|
-
const standardizedContent = (beforeTool ? beforeTool.trim() + "\n\n" : "") +
|
|
4330
|
-
allCalls
|
|
4331
|
-
.map((candidate) => `\`\`\`tool\n${JSON.stringify(candidate)}\n\`\`\``)
|
|
4332
|
-
.join("\n\n");
|
|
4333
|
-
pushAssistantHistory(standardizedContent);
|
|
4334
|
-
}
|
|
4335
4345
|
if (sequenceDecision.terminal) {
|
|
4336
4346
|
const remainingCriteria = unreadResponderNotificationIds.size > 0
|
|
4337
4347
|
? ["Analyze and acknowledge the delivered Responder result without repeating completed foreground work."]
|
|
@@ -4341,10 +4351,10 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4341
4351
|
moveTurn("partial", "repeated identical action sequence");
|
|
4342
4352
|
return finishTurn("Stopped an identical action cycle before it could execute again.", productiveSteps, "partial", remainingCriteria, "The model repeated an identical action sequence without a new premise or state change.");
|
|
4343
4353
|
}
|
|
4344
|
-
|
|
4354
|
+
upsertActionCycleRecovery(reason +
|
|
4345
4355
|
(unreadResponderNotificationIds.size > 0
|
|
4346
4356
|
? " A delivered Responder result is still unread: analyze the available result, gather only genuinely necessary bounded evidence, then call job.read before returning to foreground work."
|
|
4347
|
-
: " Reassess
|
|
4357
|
+
: " The original successful tool result remains in context. Reassess that evidence and either finish or select a materially different action; do not replay completed work."));
|
|
4348
4358
|
continue;
|
|
4349
4359
|
}
|
|
4350
4360
|
if (sequenceDecision.warn && sequenceDecision.warnMessage) {
|
|
@@ -4408,6 +4418,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4408
4418
|
let planCreatedThisTurn = Boolean(activePlan && activePlan.tasks.length > 0);
|
|
4409
4419
|
let actionSequenceExecuted = 0;
|
|
4410
4420
|
let actionSequenceEligible = allCalls.length > 0;
|
|
4421
|
+
const actionSequenceOutcomes = new Map();
|
|
4411
4422
|
/**
|
|
4412
4423
|
* Record a tool result into history. Failures / user declines are
|
|
4413
4424
|
* always returned to the model — we never cancel later siblings or
|
|
@@ -4418,6 +4429,15 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4418
4429
|
consecutiveModelOnlyRounds = 0;
|
|
4419
4430
|
recordedNativeIds.add(boundCall.id);
|
|
4420
4431
|
actionSequenceExecuted += 1;
|
|
4432
|
+
const sequenceObservation = res.suppressedRepeat
|
|
4433
|
+
? loopGuard.getPriorObservation(res.call.name, res.call.args) ??
|
|
4434
|
+
res.contextOutput
|
|
4435
|
+
: res.result.output ?? res.contextOutput;
|
|
4436
|
+
actionSequenceOutcomes.set(boundCall.id, JSON.stringify({
|
|
4437
|
+
ok: res.ok,
|
|
4438
|
+
exitCode: res.result.exitCode ?? null,
|
|
4439
|
+
digest: completedOperationObservationDigest(res.call.name, sequenceObservation),
|
|
4440
|
+
}));
|
|
4421
4441
|
// A policy-suppressed call is deterministic: replaying it verbatim
|
|
4422
4442
|
// returns the identical receipt. It must therefore keep the sequence
|
|
4423
4443
|
// eligible, otherwise the tool-level suppression and the sequence
|
|
@@ -4757,12 +4777,16 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4757
4777
|
}
|
|
4758
4778
|
fillMissingToolResults(messages, historyNativeCalls, "Cancelled — not executed this turn.");
|
|
4759
4779
|
}
|
|
4780
|
+
const actionSequenceOutcome = createHash("sha256")
|
|
4781
|
+
.update(JSON.stringify(bound.map((entry) => actionSequenceOutcomes.get(entry.id) ?? null)))
|
|
4782
|
+
.digest("hex")
|
|
4783
|
+
.slice(0, 24);
|
|
4760
4784
|
loopGuard.completeActionSequence(actionSequenceCalls, actionSequenceEligible &&
|
|
4761
4785
|
toRun.length === bound.length &&
|
|
4762
4786
|
actionSequenceExecuted === allCalls.length &&
|
|
4763
4787
|
!aborted &&
|
|
4764
4788
|
!awaitingPlanApproval &&
|
|
4765
|
-
!governorPauseReason);
|
|
4789
|
+
!governorPauseReason, actionSequenceOutcome);
|
|
4766
4790
|
// Keep ledger system rows outside the native assistant→tool group so
|
|
4767
4791
|
// protocol repair preserves the real successful job.read body.
|
|
4768
4792
|
for (const notification of deferredResponderLedgerNotifications.splice(0)) {
|