@pentoshi/clai 3.11.31 → 3.11.34
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/compaction-summary.js +1 -1
- package/dist/agent/compaction-summary.js.map +1 -1
- package/dist/agent/continue-orient.js +1 -1
- package/dist/agent/continue-orient.js.map +1 -1
- package/dist/agent/finalize-gate.d.ts +0 -3
- package/dist/agent/finalize-gate.js +2 -10
- package/dist/agent/finalize-gate.js.map +1 -1
- package/dist/agent/must-continue.d.ts +1 -3
- package/dist/agent/must-continue.js +0 -16
- package/dist/agent/must-continue.js.map +1 -1
- package/dist/agent/plan-tool.js +1 -1
- package/dist/agent/plan-tool.js.map +1 -1
- package/dist/agent/runner.js +29 -105
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/task-analyzer.js +1 -1
- package/dist/agent/task-analyzer.js.map +1 -1
- package/dist/agent/tool-call-parser.d.ts +2 -4
- package/dist/agent/tool-call-parser.js +47 -67
- package/dist/agent/tool-call-parser.js.map +1 -1
- package/dist/agent/workspace-orient.js +1 -1
- package/dist/agent/workspace-orient.js.map +1 -1
- package/dist/prompts/embedded.js +1 -1
- package/dist/prompts/embedded.js.map +1 -1
- package/dist/prompts/index.js +13 -6
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/system.agent.md +6 -4
- package/dist/tools/fs.js +17 -5
- package/dist/tools/fs.js.map +1 -1
- package/dist/tools/registry.d.ts +1 -0
- package/dist/tools/registry.js +12 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/tui-v2/rendering/strip-tool-surfaces.js +7 -1
- 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
|
@@ -63,14 +63,14 @@ import { CompactionAttemptLedger, compactionAttemptKey, } from "./compaction-att
|
|
|
63
63
|
import { loadPlan, mutatePlan, markTask, appendPlanTask, readyPlanTasks, foregroundRemaining, responderOpenTasks, isPlanTerminal, isPlanSuccessful, } from "../store/plan.js";
|
|
64
64
|
import { stat } from "node:fs/promises";
|
|
65
65
|
import { isOutsideWorkingDirectory, resolveFsToolPath, } from "../tools/fs.js";
|
|
66
|
-
import { stripSentinelTokens, parseToolCall, recognizeBareToolJson, looksLikeTruncatedToolCall, salvageTruncatedWrite, salvageTruncatedWriteFromNative, countToolFences, parseAllToolCalls, groupToolCallsForExecution, buildTurnHistory, collapseRepeatedText, textBeforeToolCall, formatToolArgs, looksLikePentestTask, looksLikeBuildTask, looksLikeInformationalQuery, looksLikeIdleOrSocialPrompt, looksLikeActionNarration, looksLikeWebActionNarration, localHttpProbeIsFailure, localHttpProbeIsSuccess,
|
|
66
|
+
import { stripSentinelTokens, parseToolCall, recognizeBareToolJson, looksLikeTruncatedToolCall, salvageTruncatedWrite, salvageTruncatedWriteFromNative, countToolFences, parseAllToolCalls, groupToolCallsForExecution, buildTurnHistory, collapseRepeatedText, textBeforeToolCall, formatToolArgs, looksLikePentestTask, looksLikeBuildTask, looksLikeInformationalQuery, looksLikeIdleOrSocialPrompt, looksLikeActionNarration, looksLikeWebActionNarration, localHttpProbeIsFailure, localHttpProbeIsSuccess, buildWorkflowDirective, narrowNmapOperationDirective, pentestWorkflowDirective, pentestNoLocalServerDirective, shouldDimToolChatter, looksLikePromptLeak, } from "./tool-call-parser.js";
|
|
67
67
|
import { createSessionPolicy, isPreApprovalAllowedTool, isPlanModeAllowedShellCommand, isPlanModeAllowedTool, isPlanApprovedByStatus, planHasOpenWork, isAbortError, shouldEnableImageOcr, } from "./session-policy.js";
|
|
68
68
|
import { saveToolOutput, summarizeOutput, formatToolContext, } from "./tool-output-formatting.js";
|
|
69
69
|
import { codingSessionFromContext, isProtocolPlaceholderOutput, progressPauseMode, } from "./progress-pause-policy.js";
|
|
70
70
|
import { renderPlanForTerminal, planContextMessage, upsertPlanContextMessage, handlePlanTool, resolvePlanTaskId, } from "./plan-tool.js";
|
|
71
71
|
import { readDeclaredParentTaskId, resolveResponderParent, isExplicitResponderDelegation, delegationTaskTitle, } from "./responder-parent.js";
|
|
72
72
|
import { readTaskUpdateArgs, distinctAdvancingTaskIds, isSimultaneousTaskAdvance, batchUpdateSignature, buildMultiUpdateReminder, buildMultiOpenRejection, multiUpdateToast, multiOpenToast, openingTaskIds, } from "./task-sync.js";
|
|
73
|
-
import { absorbLooseWorkIntoLedger, applyDestinationCwd, canMarkTaskDone, hasLocalRuntimeProof, hasRemoteWorkProof, isDevServerCall, isEvidenceWorkTool, isFeatureImplementationCall, isPackageInstallCommand, isPlanPreflightTool, isPortListeningOutput, isReadOnlyReconTool, isRemoteActiveTestCall,
|
|
73
|
+
import { absorbLooseWorkIntoLedger, applyDestinationCwd, canMarkTaskDone, hasLocalRuntimeProof, hasRemoteWorkProof, isDevServerCall, isEvidenceWorkTool, isFeatureImplementationCall, isPackageInstallCommand, isPlanPreflightTool, isPortListeningOutput, isReadOnlyReconTool, isRemoteActiveTestCall, isRemoteReconToolCall, isScaffoldCreateCommand, isServerReadyOutput, ledgerFromTaskEvidence, pickPendingTaskForToolCall, recordTaskWorkSuccess, resolveUserDestinationHint, taskEvidenceFromLedger, TOOL_ABORT_GRACE_MS, toolHardBudgetMs, toolStallBudgetMs, userAskedForFeatureApp, } from "./task-evidence.js";
|
|
74
74
|
import { buildSessionStateBlock, inferNextHint, upsertSessionStateMessage, } from "./session-state.js";
|
|
75
75
|
import { buildContinueOrientation, looksLikeContinueOrResumePrompt, } from "./continue-orient.js";
|
|
76
76
|
import { detectPackageManager } from "./workspace-orient.js";
|
|
@@ -442,19 +442,12 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
442
442
|
// brand-new plan (the exact failure where "what do u know till now"
|
|
443
443
|
// triggered explore→plan and created an unrelated "Enhance clai" plan).
|
|
444
444
|
const informationalQuery = looksLikeInformationalQuery(prompt);
|
|
445
|
-
// Greetings / thanks / short acks must never force tools
|
|
446
|
-
// freshness retries — a false "act don't narrate" path burned tokens on
|
|
447
|
-
// web.search recovery loops after a simple "hi".
|
|
445
|
+
// Greetings / thanks / short acks must never force tools or plans.
|
|
448
446
|
const idleOrSocialPrompt = looksLikeIdleOrSocialPrompt(prompt);
|
|
449
447
|
suppressOutcomeDiagnostics =
|
|
450
448
|
informationalQuery ||
|
|
451
449
|
idleOrSocialPrompt ||
|
|
452
450
|
looksLikeContinueOrResumePrompt(prompt);
|
|
453
|
-
const freshWebSearchRequired = !buildLikeTurn &&
|
|
454
|
-
!pentestLikeTurn &&
|
|
455
|
-
!idleOrSocialPrompt &&
|
|
456
|
-
toolNames.includes("web.search") &&
|
|
457
|
-
requiresFreshWebSearch(prompt);
|
|
458
451
|
let provider = initialProvider;
|
|
459
452
|
await ensureProviderConfigured(provider);
|
|
460
453
|
let model = initialModel;
|
|
@@ -608,9 +601,6 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
608
601
|
orientInput.candidateProject = candidate;
|
|
609
602
|
systemSections.push(buildWorkspaceOrientation(orientInput));
|
|
610
603
|
}
|
|
611
|
-
if (freshWebSearchRequired) {
|
|
612
|
-
systemSections.push(freshnessGuardMessage());
|
|
613
|
-
}
|
|
614
604
|
// The live plan is mutable state: it is injected once as a keyed request
|
|
615
605
|
// suffix (upsertPlanContextMessage) instead of being frozen into the stable
|
|
616
606
|
// system prefix, so the model never sees a stale and a fresh plan together.
|
|
@@ -719,7 +709,6 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
719
709
|
content.startsWith("ACTIVE PROJECT ROOT:") ||
|
|
720
710
|
content.startsWith("USER DESTINATION:") ||
|
|
721
711
|
content.startsWith("WORKSPACE STATUS") ||
|
|
722
|
-
content.startsWith("Freshness guard for this turn:") ||
|
|
723
712
|
content.includes("MODE") ||
|
|
724
713
|
content.includes("OUTCOME"),
|
|
725
714
|
}));
|
|
@@ -939,7 +928,6 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
939
928
|
// (e.g. malformed extra/missing braces that are NOT simple truncation). We
|
|
940
929
|
// retry instead of leaking the raw block as the final answer.
|
|
941
930
|
let malformedFenceRetries = 0;
|
|
942
|
-
let sawFreshWebSearch = false;
|
|
943
931
|
const recovery = createRecoveryBudgets();
|
|
944
932
|
let sawServerStart = false;
|
|
945
933
|
let sawPlanCreateOk = false;
|
|
@@ -1011,70 +999,6 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1011
999
|
remoteWorkVerified: planHasVerifiedRemoteWork(plan),
|
|
1012
1000
|
});
|
|
1013
1001
|
};
|
|
1014
|
-
const reconcileOpenTaskBeforeFinalizing = async () => {
|
|
1015
|
-
let plan = await loadPlan(session.sessionId).catch(() => undefined);
|
|
1016
|
-
const open = plan?.tasks.find((task) => task.state === "in_progress" && !task.responderOwned);
|
|
1017
|
-
if (!plan || !open)
|
|
1018
|
-
return plan;
|
|
1019
|
-
const gate = completionGateForTask(plan, open.id);
|
|
1020
|
-
if (!gate.ok)
|
|
1021
|
-
return plan;
|
|
1022
|
-
const reconciledTaskIds = [open.id];
|
|
1023
|
-
const working = plan;
|
|
1024
|
-
markTask(working, open.id, "done", "Completion reconciled from verified task evidence.");
|
|
1025
|
-
while (true) {
|
|
1026
|
-
const observation = readyPlanTasks(working).find((task) => isRuntimeObservationTask(task.title) ||
|
|
1027
|
-
(working.kind === "pentest" && isRemoteObservationTask(task.title)));
|
|
1028
|
-
if (!observation)
|
|
1029
|
-
break;
|
|
1030
|
-
const observationGate = completionGateForTask(working, observation.id);
|
|
1031
|
-
if (!observationGate.ok)
|
|
1032
|
-
break;
|
|
1033
|
-
markTask(working, observation.id, "done", working.kind === "pentest"
|
|
1034
|
-
? "Satisfied by verified remote evidence from the preceding task."
|
|
1035
|
-
: "Satisfied by the verified runtime evidence from the preceding task.");
|
|
1036
|
-
reconciledTaskIds.push(observation.id);
|
|
1037
|
-
}
|
|
1038
|
-
if (working.status === "draft" || working.status === "approved") {
|
|
1039
|
-
working.status = "in_progress";
|
|
1040
|
-
}
|
|
1041
|
-
if (isPlanTerminal(working)) {
|
|
1042
|
-
working.status = isPlanSuccessful(working) ? "completed" : "abandoned";
|
|
1043
|
-
}
|
|
1044
|
-
// Replay the same reconciliation as a reducer so a concurrent
|
|
1045
|
-
// responder settlement is preserved instead of overwritten.
|
|
1046
|
-
const reconciledNotes = new Map(reconciledTaskIds.map((id) => [
|
|
1047
|
-
id,
|
|
1048
|
-
working.tasks.find((task) => task.id === id)?.note,
|
|
1049
|
-
]));
|
|
1050
|
-
const reconcileResult = await mutatePlan(session.sessionId, (draft) => {
|
|
1051
|
-
let changed = false;
|
|
1052
|
-
for (const [id, note] of reconciledNotes) {
|
|
1053
|
-
const task = draft.tasks.find((candidate) => candidate.id === id);
|
|
1054
|
-
if (!task || task.state === "done")
|
|
1055
|
-
continue;
|
|
1056
|
-
task.state = "done";
|
|
1057
|
-
if (note !== undefined)
|
|
1058
|
-
task.note = note;
|
|
1059
|
-
changed = true;
|
|
1060
|
-
}
|
|
1061
|
-
if (draft.status === "draft" || draft.status === "approved") {
|
|
1062
|
-
draft.status = "in_progress";
|
|
1063
|
-
changed = true;
|
|
1064
|
-
}
|
|
1065
|
-
if (isPlanTerminal(draft)) {
|
|
1066
|
-
draft.status = isPlanSuccessful(draft) ? "completed" : "abandoned";
|
|
1067
|
-
changed = true;
|
|
1068
|
-
}
|
|
1069
|
-
return changed;
|
|
1070
|
-
}).catch(() => undefined);
|
|
1071
|
-
if (reconcileResult?.ok && reconcileResult.plan)
|
|
1072
|
-
plan = reconcileResult.plan;
|
|
1073
|
-
writePlanUpdate(plan, renderPlanForTerminal(plan) + "\n");
|
|
1074
|
-
writeNotice("info", `reconciled ${reconciledTaskIds.map((id) => `[${id}]`).join(", ")} from verified evidence`, chalk.dim(` ℹ reconciled ${reconciledTaskIds.map((id) => `[${id}]`).join(", ")} from verified evidence — no duplicate verification\n`));
|
|
1075
|
-
taskWorkLedger = null;
|
|
1076
|
-
return plan;
|
|
1077
|
-
};
|
|
1078
1002
|
async function persistProjectRootOnPlan(root) {
|
|
1079
1003
|
const pm = detectPackageManager(root);
|
|
1080
1004
|
// Metadata patches go through the transactional boundary so a
|
|
@@ -1806,9 +1730,6 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1806
1730
|
};
|
|
1807
1731
|
}
|
|
1808
1732
|
}
|
|
1809
|
-
if (call.name === "web.search") {
|
|
1810
|
-
sawFreshWebSearch = true;
|
|
1811
|
-
}
|
|
1812
1733
|
if (!alreadyPrintedIds.has(toolEventId)) {
|
|
1813
1734
|
const toolCallLine = chalk.cyan(` ▶ ${call.name}`) + chalk.gray(` ${formatToolArgs(call)}`);
|
|
1814
1735
|
writeToolCall(toolEventId, call, styleToolChatter(call, toolCallLine) + "\n");
|
|
@@ -3274,9 +3195,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3274
3195
|
...(toolsAttached
|
|
3275
3196
|
? {
|
|
3276
3197
|
tools: turnTools,
|
|
3277
|
-
toolChoice:
|
|
3278
|
-
? { type: "function", name: "web.search" }
|
|
3279
|
-
: "auto",
|
|
3198
|
+
toolChoice: "auto",
|
|
3280
3199
|
parallelToolCalls: true,
|
|
3281
3200
|
// P2-3: emit tool cards as soon as the function name arrives.
|
|
3282
3201
|
onToolCallDelta: (delta) => {
|
|
@@ -3709,12 +3628,15 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3709
3628
|
}
|
|
3710
3629
|
if (nativeToolCalls.length) {
|
|
3711
3630
|
const first = nativeToolCalls[0];
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3631
|
+
call = first.args?._parseError
|
|
3632
|
+
? {
|
|
3633
|
+
name: first.name || "unknown",
|
|
3634
|
+
args: {
|
|
3635
|
+
__nativeParseError: true,
|
|
3636
|
+
_raw: first.args._raw,
|
|
3637
|
+
},
|
|
3638
|
+
}
|
|
3639
|
+
: normalizeToolCall({ name: first.name, args: first.args });
|
|
3718
3640
|
}
|
|
3719
3641
|
else {
|
|
3720
3642
|
call = parseToolCall(assistantText.visible, {
|
|
@@ -3809,6 +3731,18 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3809
3731
|
}
|
|
3810
3732
|
}
|
|
3811
3733
|
if (!canonicalAssistantVisible.trim() && !call) {
|
|
3734
|
+
const incompleteNativeStream = nativeToolCalls.length === 0 &&
|
|
3735
|
+
deferredToolCalls.some((entry) => entry.shown && entry.call.name !== "…");
|
|
3736
|
+
if (incompleteNativeStream) {
|
|
3737
|
+
const reason = "The provider began this native tool call but never completed it. Nothing ran; reissue a complete call.";
|
|
3738
|
+
for (const deferred of deferredToolCalls) {
|
|
3739
|
+
if (!deferred.shown || deferred.call.name === "…")
|
|
3740
|
+
continue;
|
|
3741
|
+
writeToolBlocked(deferred.eventId, deferred.call.name, reason, chalk.yellow(` ⚠ ${reason}\n`));
|
|
3742
|
+
}
|
|
3743
|
+
markTextOnlyModel(provider, model);
|
|
3744
|
+
writeNotice("warn", "provider abandoned a native tool call — switching this model to the text tool protocol", chalk.yellow(" ⚠ provider abandoned a native tool call — switching this model to the text tool protocol\n"));
|
|
3745
|
+
}
|
|
3812
3746
|
emptyVisibleRetries += 1;
|
|
3813
3747
|
if (emptyVisibleRetries <= 3) {
|
|
3814
3748
|
if (assistantText.hasThinking) {
|
|
@@ -3821,10 +3755,8 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3821
3755
|
retryWithoutThinking = true;
|
|
3822
3756
|
commitAssistantRetry(assistantText.visible);
|
|
3823
3757
|
// Keep nudges SHORT — cheap models lose the key instruction in long text.
|
|
3824
|
-
const buildNudge =
|
|
3825
|
-
?
|
|
3826
|
-
? "No visible output. This is current or scheduled information: call web.search now. Do NOT answer from memory."
|
|
3827
|
-
: "No visible output. This is current or scheduled information: emit exactly one valid ```tool block for web.search now. Do NOT answer from memory or hide the tool call in <think> tags."
|
|
3758
|
+
const buildNudge = incompleteNativeStream
|
|
3759
|
+
? "Your native tool call was incomplete, so nothing ran. Use exactly one complete fenced ```tool block now; do not repeat the incomplete native call."
|
|
3828
3760
|
: isPlanMode && !activePlan
|
|
3829
3761
|
? toolsAttached
|
|
3830
3762
|
? "No visible output. In plan mode: gather context or call plan.create when ready (do not only describe the plan)."
|
|
@@ -4029,15 +3961,12 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4029
3961
|
const cleaned = collapseRepeatedText(stripSentinelTokens(canonicalAssistantVisible));
|
|
4030
3962
|
const narratedAction = looksLikeActionNarration(cleaned);
|
|
4031
3963
|
const narratedWebAction = looksLikeWebActionNarration(cleaned);
|
|
4032
|
-
const
|
|
4033
|
-
const livePlanAtCompletion = reconciledPlanAtCompletion ??
|
|
4034
|
-
(await loadPlan(session.sessionId).catch(() => undefined));
|
|
3964
|
+
const livePlanAtCompletion = await loadPlan(session.sessionId).catch(() => undefined);
|
|
4035
3965
|
const planStatusAtCompletion = livePlanAtCompletion?.status ?? activePlan?.status;
|
|
4036
3966
|
const completedPlanDuringThisTurn = activePlan?.status !== "completed" &&
|
|
4037
3967
|
planStatusAtCompletion === "completed";
|
|
4038
3968
|
const planHasOpenWorkNow = planHasOpenWork(planStatusAtCompletion);
|
|
4039
|
-
const userExpectsWork =
|
|
4040
|
-
(planHasOpenWorkNow && session.planApproved.value) ||
|
|
3969
|
+
const userExpectsWork = (planHasOpenWorkNow && session.planApproved.value) ||
|
|
4041
3970
|
(!informationalQuery &&
|
|
4042
3971
|
!idleOrSocialPrompt &&
|
|
4043
3972
|
(buildLikeTurn || pentestLikeTurn));
|
|
@@ -4105,11 +4034,6 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4105
4034
|
pentestSession,
|
|
4106
4035
|
informationalQuery,
|
|
4107
4036
|
idleOrSocialPrompt,
|
|
4108
|
-
freshWebSearchRequired,
|
|
4109
|
-
freshnessGuardText: freshWebSearchRequired
|
|
4110
|
-
? freshnessGuardMessage()
|
|
4111
|
-
: "",
|
|
4112
|
-
sawFreshWebSearch,
|
|
4113
4037
|
sawPlanCreateOk,
|
|
4114
4038
|
sawFeatureImplWrite,
|
|
4115
4039
|
sawScaffoldOk,
|