@pentoshi/clai 3.11.28 → 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.d.ts +1 -0
- package/dist/agent/runner.js +64 -29
- 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/app/adapters/current-agent-adapter.js +3 -0
- package/dist/app/adapters/current-agent-adapter.js.map +1 -1
- package/dist/app/controllers/session-context-limits.d.ts +1 -7
- package/dist/app/controllers/session-context-limits.js +16 -18
- package/dist/app/controllers/session-context-limits.js.map +1 -1
- package/dist/app/controllers/session-controller.d.ts +0 -2
- package/dist/app/controllers/session-controller.js +1 -4
- package/dist/app/controllers/session-controller.js.map +1 -1
- package/dist/app/controllers/session-turn-request.d.ts +1 -0
- package/dist/app/controllers/session-turn-request.js +3 -0
- package/dist/app/controllers/session-turn-request.js.map +1 -1
- package/dist/app/ports/agent-port.d.ts +1 -0
- 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/app/App.js +8 -2
- package/dist/tui-v2/app/App.js.map +1 -1
- package/dist/tui-v2/components/status/context-limit-chip.d.ts +1 -1
- package/dist/tui-v2/components/status/context-limit-chip.js +23 -3
- package/dist/tui-v2/components/status/context-limit-chip.js.map +1 -1
- package/dist/tui-v2/components/status/status-line.d.ts +1 -0
- package/dist/tui-v2/components/status/status-line.js +3 -3
- package/dist/tui-v2/components/status/status-line.js.map +1 -1
- package/dist/tui-v2/composer/composer-editor.d.ts +1 -0
- package/dist/tui-v2/composer/composer-editor.js +17 -12
- package/dist/tui-v2/composer/composer-editor.js.map +1 -1
- package/dist/tui-v2/composer/use-draft-actions.d.ts +1 -1
- package/dist/tui-v2/composer/use-draft-actions.js +2 -2
- package/dist/tui-v2/composer/use-draft-actions.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";
|
|
@@ -458,6 +458,9 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
458
458
|
let provider = initialProvider;
|
|
459
459
|
await ensureProviderConfigured(provider);
|
|
460
460
|
let model = initialModel;
|
|
461
|
+
const currentContextLimitTokens = () => options.getContextLimitTokens
|
|
462
|
+
? options.getContextLimitTokens(provider, model)
|
|
463
|
+
: options.contextLimitTokens;
|
|
461
464
|
// Some Groq free-tier models have a per-request/per-minute input budget
|
|
462
465
|
// below the normal agent prompt alone. Select a purpose-built compact
|
|
463
466
|
// instruction set before the request is made, rather than treating the
|
|
@@ -853,6 +856,17 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
853
856
|
}
|
|
854
857
|
return message;
|
|
855
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
|
+
};
|
|
856
870
|
const recoveryProse = (content) => {
|
|
857
871
|
const text = textBeforeToolCall(stripSentinelTokens(content)).trim();
|
|
858
872
|
if (!text ||
|
|
@@ -1373,8 +1387,14 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1373
1387
|
...(retryReason ? { retryReason } : {}),
|
|
1374
1388
|
});
|
|
1375
1389
|
if (loopCheck.block) {
|
|
1376
|
-
const
|
|
1390
|
+
const baseReason = loopCheck.reason ??
|
|
1377
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;
|
|
1378
1398
|
if (loopCheck.kind === "unchanged-success") {
|
|
1379
1399
|
const result = { ok: true, output: reason, exitCode: 0 };
|
|
1380
1400
|
emitVisibleSyntheticReceipt(result, reason);
|
|
@@ -2931,11 +2951,12 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
2931
2951
|
}
|
|
2932
2952
|
async function maybeAutoCompact(reason, force = false) {
|
|
2933
2953
|
const beforeTokens = estimateNextRequestTokens(messages);
|
|
2954
|
+
const contextLimitTokens = currentContextLimitTokens();
|
|
2934
2955
|
const compactTrigger = autoCompactTriggerTokens(getReliabilityPolicy(), {
|
|
2935
2956
|
provider,
|
|
2936
2957
|
model,
|
|
2937
|
-
...(
|
|
2938
|
-
? { contextLimitTokens
|
|
2958
|
+
...(contextLimitTokens !== undefined
|
|
2959
|
+
? { contextLimitTokens }
|
|
2939
2960
|
: {}),
|
|
2940
2961
|
});
|
|
2941
2962
|
if (!force && beforeTokens < compactTrigger)
|
|
@@ -3196,6 +3217,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3196
3217
|
writeNotice("info", notice, chalk.dim(` ℹ ${notice}\n`));
|
|
3197
3218
|
}
|
|
3198
3219
|
}
|
|
3220
|
+
const contextLimitTokens = currentContextLimitTokens();
|
|
3199
3221
|
await auditLog("agent.turn", {
|
|
3200
3222
|
provider,
|
|
3201
3223
|
model,
|
|
@@ -3204,7 +3226,13 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3204
3226
|
step,
|
|
3205
3227
|
// Metadata-only composition metrics (no prompt/tool text).
|
|
3206
3228
|
...contextBreakdownAuditPayload(contextBreakdown),
|
|
3207
|
-
compactTriggerTokens: autoCompactTriggerTokens(),
|
|
3229
|
+
compactTriggerTokens: autoCompactTriggerTokens(getReliabilityPolicy(), {
|
|
3230
|
+
provider,
|
|
3231
|
+
model,
|
|
3232
|
+
...(contextLimitTokens !== undefined
|
|
3233
|
+
? { contextLimitTokens }
|
|
3234
|
+
: {}),
|
|
3235
|
+
}),
|
|
3208
3236
|
maxTokensBudget: resolveStepMaxTokens({
|
|
3209
3237
|
nativeToolsActive,
|
|
3210
3238
|
toolsAttached,
|
|
@@ -3873,7 +3901,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3873
3901
|
}
|
|
3874
3902
|
// Exhausted retries — fall through to the normal answer path.
|
|
3875
3903
|
}
|
|
3876
|
-
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)) {
|
|
3877
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"));
|
|
3878
3906
|
commitAssistantRetry(assistantText.visible);
|
|
3879
3907
|
messages.push(recoveryUserMessage(toolsAttached
|
|
@@ -4280,11 +4308,20 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4280
4308
|
writeNotice("warn", reason, chalk.yellow(` ⚠ ${reason}\n`));
|
|
4281
4309
|
const suppressedResults = bound.map((b) => {
|
|
4282
4310
|
const duplicate = runIds.has(b.id);
|
|
4283
|
-
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;
|
|
4284
4320
|
const result = {
|
|
4285
|
-
ok:
|
|
4321
|
+
ok: duplicate,
|
|
4286
4322
|
output: resultReason,
|
|
4287
|
-
exitCode: duplicate ?
|
|
4323
|
+
exitCode: duplicate ? 0 : 130,
|
|
4324
|
+
...(duplicate ? { suppressedRepeat: true } : {}),
|
|
4288
4325
|
};
|
|
4289
4326
|
return { b, resultReason, result };
|
|
4290
4327
|
});
|
|
@@ -4305,22 +4342,6 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4305
4342
|
writeToolOutput(eventId, output, chalk.dim(` ${output}`));
|
|
4306
4343
|
emitToolResult(eventId, result, resultReason);
|
|
4307
4344
|
}
|
|
4308
|
-
if (historyNativeCalls.length) {
|
|
4309
|
-
appendAssistantWithTools(messages, beforeTool ?? "", historyNativeCalls, completion.reasoningBlock ??
|
|
4310
|
-
(assistantText.hasThinking && assistantText.thinkContent
|
|
4311
|
-
? { text: assistantText.thinkContent }
|
|
4312
|
-
: undefined));
|
|
4313
|
-
for (const { b, resultReason, result } of suppressedResults) {
|
|
4314
|
-
appendToolResult(messages, b.id, `Tool ${b.call.name} result (exit=${result.exitCode}, ok=false):\n${resultReason}`, b.call.name, false);
|
|
4315
|
-
}
|
|
4316
|
-
}
|
|
4317
|
-
else {
|
|
4318
|
-
const standardizedContent = (beforeTool ? beforeTool.trim() + "\n\n" : "") +
|
|
4319
|
-
allCalls
|
|
4320
|
-
.map((candidate) => `\`\`\`tool\n${JSON.stringify(candidate)}\n\`\`\``)
|
|
4321
|
-
.join("\n\n");
|
|
4322
|
-
pushAssistantHistory(standardizedContent);
|
|
4323
|
-
}
|
|
4324
4345
|
if (sequenceDecision.terminal) {
|
|
4325
4346
|
const remainingCriteria = unreadResponderNotificationIds.size > 0
|
|
4326
4347
|
? ["Analyze and acknowledge the delivered Responder result without repeating completed foreground work."]
|
|
@@ -4330,10 +4351,10 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4330
4351
|
moveTurn("partial", "repeated identical action sequence");
|
|
4331
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.");
|
|
4332
4353
|
}
|
|
4333
|
-
|
|
4354
|
+
upsertActionCycleRecovery(reason +
|
|
4334
4355
|
(unreadResponderNotificationIds.size > 0
|
|
4335
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."
|
|
4336
|
-
: " 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."));
|
|
4337
4358
|
continue;
|
|
4338
4359
|
}
|
|
4339
4360
|
if (sequenceDecision.warn && sequenceDecision.warnMessage) {
|
|
@@ -4397,6 +4418,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4397
4418
|
let planCreatedThisTurn = Boolean(activePlan && activePlan.tasks.length > 0);
|
|
4398
4419
|
let actionSequenceExecuted = 0;
|
|
4399
4420
|
let actionSequenceEligible = allCalls.length > 0;
|
|
4421
|
+
const actionSequenceOutcomes = new Map();
|
|
4400
4422
|
/**
|
|
4401
4423
|
* Record a tool result into history. Failures / user declines are
|
|
4402
4424
|
* always returned to the model — we never cancel later siblings or
|
|
@@ -4407,6 +4429,15 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4407
4429
|
consecutiveModelOnlyRounds = 0;
|
|
4408
4430
|
recordedNativeIds.add(boundCall.id);
|
|
4409
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
|
+
}));
|
|
4410
4441
|
// A policy-suppressed call is deterministic: replaying it verbatim
|
|
4411
4442
|
// returns the identical receipt. It must therefore keep the sequence
|
|
4412
4443
|
// eligible, otherwise the tool-level suppression and the sequence
|
|
@@ -4746,12 +4777,16 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4746
4777
|
}
|
|
4747
4778
|
fillMissingToolResults(messages, historyNativeCalls, "Cancelled — not executed this turn.");
|
|
4748
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);
|
|
4749
4784
|
loopGuard.completeActionSequence(actionSequenceCalls, actionSequenceEligible &&
|
|
4750
4785
|
toRun.length === bound.length &&
|
|
4751
4786
|
actionSequenceExecuted === allCalls.length &&
|
|
4752
4787
|
!aborted &&
|
|
4753
4788
|
!awaitingPlanApproval &&
|
|
4754
|
-
!governorPauseReason);
|
|
4789
|
+
!governorPauseReason, actionSequenceOutcome);
|
|
4755
4790
|
// Keep ledger system rows outside the native assistant→tool group so
|
|
4756
4791
|
// protocol repair preserves the real successful job.read body.
|
|
4757
4792
|
for (const notification of deferredResponderLedgerNotifications.splice(0)) {
|