@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.
Files changed (36) hide show
  1. package/dist/agent/compaction-summary.js +1 -1
  2. package/dist/agent/compaction-summary.js.map +1 -1
  3. package/dist/agent/continue-orient.js +1 -1
  4. package/dist/agent/continue-orient.js.map +1 -1
  5. package/dist/agent/finalize-gate.d.ts +0 -3
  6. package/dist/agent/finalize-gate.js +2 -10
  7. package/dist/agent/finalize-gate.js.map +1 -1
  8. package/dist/agent/must-continue.d.ts +1 -3
  9. package/dist/agent/must-continue.js +0 -16
  10. package/dist/agent/must-continue.js.map +1 -1
  11. package/dist/agent/plan-tool.js +1 -1
  12. package/dist/agent/plan-tool.js.map +1 -1
  13. package/dist/agent/runner.js +29 -105
  14. package/dist/agent/runner.js.map +1 -1
  15. package/dist/agent/task-analyzer.js +1 -1
  16. package/dist/agent/task-analyzer.js.map +1 -1
  17. package/dist/agent/tool-call-parser.d.ts +2 -4
  18. package/dist/agent/tool-call-parser.js +47 -67
  19. package/dist/agent/tool-call-parser.js.map +1 -1
  20. package/dist/agent/workspace-orient.js +1 -1
  21. package/dist/agent/workspace-orient.js.map +1 -1
  22. package/dist/prompts/embedded.js +1 -1
  23. package/dist/prompts/embedded.js.map +1 -1
  24. package/dist/prompts/index.js +13 -6
  25. package/dist/prompts/index.js.map +1 -1
  26. package/dist/prompts/system.agent.md +6 -4
  27. package/dist/tools/fs.js +17 -5
  28. package/dist/tools/fs.js.map +1 -1
  29. package/dist/tools/registry.d.ts +1 -0
  30. package/dist/tools/registry.js +12 -1
  31. package/dist/tools/registry.js.map +1 -1
  32. package/dist/tui-v2/rendering/strip-tool-surfaces.js +7 -1
  33. package/dist/tui-v2/rendering/strip-tool-surfaces.js.map +1 -1
  34. package/dist/version.generated.d.ts +2 -2
  35. package/dist/version.generated.js +2 -2
  36. package/package.json +1 -1
@@ -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, requiresFreshWebSearch, freshnessGuardMessage, buildWorkflowDirective, narrowNmapOperationDirective, pentestWorkflowDirective, pentestNoLocalServerDirective, shouldDimToolChatter, looksLikePromptLeak, } from "./tool-call-parser.js";
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, isRemoteObservationTask, isRemoteReconToolCall, isRuntimeObservationTask, isScaffoldCreateCommand, isServerReadyOutput, ledgerFromTaskEvidence, pickPendingTaskForToolCall, recordTaskWorkSuccess, resolveUserDestinationHint, taskEvidenceFromLedger, TOOL_ABORT_GRACE_MS, toolHardBudgetMs, toolStallBudgetMs, userAskedForFeatureApp, } from "./task-evidence.js";
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, plans, or
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: freshWebSearchRequired && !sawFreshWebSearch
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
- if (first.args?._parseError) {
3713
- call = undefined;
3714
- }
3715
- else {
3716
- call = normalizeToolCall({ name: first.name, args: first.args });
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 = freshWebSearchRequired && !sawFreshWebSearch
3825
- ? toolsAttached
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 reconciledPlanAtCompletion = await reconcileOpenTaskBeforeFinalizing();
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 = freshWebSearchRequired ||
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,