@pentoshi/clai 4.11.4 → 4.11.7

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 (40) hide show
  1. package/dist/agent/compaction-executor.js +12 -0
  2. package/dist/agent/compaction-executor.js.map +1 -1
  3. package/dist/agent/context-manager.js +3 -7
  4. package/dist/agent/context-manager.js.map +1 -1
  5. package/dist/agent/message-slim.d.ts +2 -0
  6. package/dist/agent/message-slim.js +63 -3
  7. package/dist/agent/message-slim.js.map +1 -1
  8. package/dist/agent/request-accounting.js +1 -1
  9. package/dist/agent/request-accounting.js.map +1 -1
  10. package/dist/agent/runner.js +60 -29
  11. package/dist/agent/runner.js.map +1 -1
  12. package/dist/agent/tool-call-parser.js +2 -1
  13. package/dist/agent/tool-call-parser.js.map +1 -1
  14. package/dist/agent/tool-history-projection.d.ts +7 -0
  15. package/dist/agent/tool-history-projection.js +455 -0
  16. package/dist/agent/tool-history-projection.js.map +1 -0
  17. package/dist/agent/tool-history.d.ts +7 -10
  18. package/dist/agent/tool-history.js +155 -151
  19. package/dist/agent/tool-history.js.map +1 -1
  20. package/dist/app/controllers/session-compact-helper.js +14 -7
  21. package/dist/app/controllers/session-compact-helper.js.map +1 -1
  22. package/dist/app/controllers/session-controller.js +3 -2
  23. package/dist/app/controllers/session-controller.js.map +1 -1
  24. package/dist/prompts/embedded.js +1 -1
  25. package/dist/prompts/embedded.js.map +1 -1
  26. package/dist/prompts/index.js +2 -1
  27. package/dist/prompts/index.js.map +1 -1
  28. package/dist/prompts/system.agent.md +2 -2
  29. package/dist/tools/registry.js +6 -2
  30. package/dist/tools/registry.js.map +1 -1
  31. package/dist/tui-v2/bootstrap/resize-repaint.js +7 -1
  32. package/dist/tui-v2/bootstrap/resize-repaint.js.map +1 -1
  33. package/dist/tui-v2/bootstrap/start-tui-v2.js +6 -1
  34. package/dist/tui-v2/bootstrap/start-tui-v2.js.map +1 -1
  35. package/dist/ui-core/bootstrap/theme-mode-cache.d.ts +3 -0
  36. package/dist/ui-core/bootstrap/theme-mode-cache.js +45 -0
  37. package/dist/ui-core/bootstrap/theme-mode-cache.js.map +1 -0
  38. package/dist/version.generated.d.ts +2 -2
  39. package/dist/version.generated.js +2 -2
  40. package/package.json +1 -1
@@ -45,8 +45,8 @@ function safeEngagementActionsForToolCall(call) {
45
45
  }
46
46
  import { availableToolNames, normalizeToolCall, runToolCall, BATCH_SAFE_TOOLS, } from "../tools/registry.js";
47
47
  import { getToolDefinitions, getCompactToolDefinitions, RUNNER_META_TOOL_NAMES, MCP_AGENT_TOOL_NAMES, mcpAgentToolNames, } from "../tools/definitions.js";
48
- import { elidedStubReuseMessage, findElidedStubArg, } from "./message-slim.js";
49
- import { appendAssistantWithTools, ensureUniqueToolCallIds, toolCallIdsInHistory, appendToolResult, assertValidToolProtocol, fillMissingToolResults, repairToolProtocol, } from "./tool-history.js";
48
+ import { elidedStubReuseMessage, findElidedStubArg, stripSupersededElidedArgs, } from "./message-slim.js";
49
+ import { appendAssistantWithTools, ensureUniqueToolCallIds, toolCallIdsInHistory, appendToolResult, assertValidToolProtocol, fillMissingToolResults, projectToolHistory, repairToolProtocol, } from "./tool-history.js";
50
50
  import { legacyReasoningBlockFromArtifacts, reasoningArtifactsForPersistence, } from "../llm/reasoning-artifacts.js";
51
51
  import { compactMessagesWithSummary, shouldApplyAutoCompact, COMPACTION_MEMORY_PREFIX, PLAN_IMPLEMENT_MEMORY_PREFIX, isCompactionMemoryMessage, } from "./context-manager.js";
52
52
  import { buildContextBreakdown, contextBreakdownAuditPayload, describeDominantContextBlock, toolSchemaHash, } from "./context-breakdown.js";
@@ -1392,13 +1392,18 @@ export async function runAgentTurn(prompt, options = {}) {
1392
1392
  const reason = elidedStubReuseMessage(elidedStub.key);
1393
1393
  return { reason, result: { ok: false, output: reason, exitCode: 1 } };
1394
1394
  }
1395
+ const canonicalizeTurnCall = (rawCall) => {
1396
+ const normalized = normalizeToolCall(rawCall);
1397
+ const canonicalMcpName = mcpRuntime?.canonicalizeToolName(normalized.name);
1398
+ const named = canonicalMcpName && canonicalMcpName !== normalized.name
1399
+ ? { ...normalized, name: canonicalMcpName }
1400
+ : normalized;
1401
+ const args = stripSupersededElidedArgs(named.args);
1402
+ return args === named.args ? named : { ...named, args };
1403
+ };
1395
1404
  async function executeSingleTool(rawCall, toolEventId, parentSignal) {
1396
1405
  const scratchDir = scratchDirFor(safeCwd());
1397
- const normalizedCall = normalizeToolCall(rawCall);
1398
- const canonicalMcpName = mcpRuntime?.canonicalizeToolName(normalizedCall.name);
1399
- let call = canonicalMcpName && canonicalMcpName !== normalizedCall.name
1400
- ? { ...normalizedCall, name: canonicalMcpName }
1401
- : normalizedCall;
1406
+ let call = canonicalizeTurnCall(rawCall);
1402
1407
  const emitVisibleSyntheticReceipt = (result, summary) => {
1403
1408
  if (!alreadyPrintedIds.has(toolEventId)) {
1404
1409
  writeToolCall(toolEventId, call);
@@ -1418,12 +1423,14 @@ export async function runAgentTurn(prompt, options = {}) {
1418
1423
  let engagementRecord;
1419
1424
  const invalid = invalidToolCall(call);
1420
1425
  if (invalid) {
1426
+ loopGuard.recordAttempt(step, call.name, call.args, false, invalid.result.exitCode, invalid.reason);
1421
1427
  emitToolResult(toolEventId, invalid.result, invalid.reason);
1422
1428
  return {
1423
1429
  ok: false,
1424
1430
  call,
1425
1431
  result: invalid.result,
1426
1432
  contextOutput: invalid.reason,
1433
+ suppressedRepeat: true,
1427
1434
  };
1428
1435
  }
1429
1436
  if (call.name === "image.ocr" && !imageOcrEnabled) {
@@ -3011,6 +3018,9 @@ export async function runAgentTurn(prompt, options = {}) {
3011
3018
  });
3012
3019
  }
3013
3020
  async function maybeAutoCompact(reason, force = false) {
3021
+ if (repairToolProtocol(messages) > 0) {
3022
+ lastSuccessfulRequestSnapshot = undefined;
3023
+ }
3014
3024
  const beforeTokens = estimateNextRequestTokens(messages);
3015
3025
  emit({
3016
3026
  type: "context-estimate",
@@ -3053,7 +3063,10 @@ export async function runAgentTurn(prompt, options = {}) {
3053
3063
  // single pass is forced (the raw estimate gate would otherwise reject a
3054
3064
  // request that fits fine); when it does not, compaction falls back to
3055
3065
  // the legacy transcript-rendered requests so it still succeeds.
3056
- const replaySnapshot = lastSuccessfulRequestSnapshot;
3066
+ const replayCandidate = lastSuccessfulRequestSnapshot;
3067
+ const replaySnapshot = replayCandidate && !projectToolHistory(replayCandidate.messages).changed
3068
+ ? replayCandidate
3069
+ : undefined;
3057
3070
  const replayPlan = replaySnapshot
3058
3071
  ? planCompactionReplay({
3059
3072
  baseRequest: replaySnapshot,
@@ -3216,7 +3229,7 @@ export async function runAgentTurn(prompt, options = {}) {
3216
3229
  let canonicalAssistantVisible = "";
3217
3230
  let recoveredFromBareJson = false;
3218
3231
  if (pendingCalls.length > 0) {
3219
- call = pendingCalls.shift();
3232
+ call = canonicalizeTurnCall(pendingCalls.shift());
3220
3233
  assistantText = { visible: "", thinkContent: "", hasThinking: false };
3221
3234
  const batchStatus = ` ↳ continuing batch (${pendingCalls.length} more queued)\n`;
3222
3235
  writeStatus(batchStatus);
@@ -3426,7 +3439,7 @@ export async function runAgentTurn(prompt, options = {}) {
3426
3439
  const parsedCalls = parseAllToolCalls(accumulatedText);
3427
3440
  if (parsedCalls.length > streamedCallsCount) {
3428
3441
  while (streamedCallsCount < parsedCalls.length) {
3429
- const call = normalizeToolCall(parsedCalls[streamedCallsCount]);
3442
+ const call = canonicalizeTurnCall(parsedCalls[streamedCallsCount]);
3430
3443
  const eventId = `tool-${++nextToolEventId}`;
3431
3444
  callIds[streamedCallsCount] = eventId;
3432
3445
  alreadyPrintedIds.add(eventId);
@@ -3766,14 +3779,26 @@ export async function runAgentTurn(prompt, options = {}) {
3766
3779
  lowYieldResumptions = 0;
3767
3780
  };
3768
3781
  // Native-first: prefer structured toolCalls from the provider.
3769
- let nativeToolCalls = completion.toolCalls ?? [];
3782
+ let nativeToolCalls = (completion.toolCalls ?? []).map((toolCall) => {
3783
+ if (toolCall.args?._parseError)
3784
+ return toolCall;
3785
+ const canonical = canonicalizeTurnCall({
3786
+ name: toolCall.name,
3787
+ args: toolCall.args,
3788
+ });
3789
+ return {
3790
+ ...toolCall,
3791
+ name: canonical.name,
3792
+ args: canonical.args,
3793
+ };
3794
+ });
3770
3795
  // Early UI cards: refresh args if stream deltas already opened cards;
3771
3796
  // otherwise create cards now (non-streaming / name-after-done providers).
3772
3797
  if (nativeToolCalls.length) {
3773
3798
  if (deferredToolCalls.length === 0) {
3774
3799
  for (let i = 0; i < nativeToolCalls.length; i += 1) {
3775
3800
  const tc = nativeToolCalls[i];
3776
- const normalized = normalizeToolCall({
3801
+ const normalized = canonicalizeTurnCall({
3777
3802
  name: tc.name,
3778
3803
  args: tc.args,
3779
3804
  });
@@ -3790,7 +3815,7 @@ export async function runAgentTurn(prompt, options = {}) {
3790
3815
  else {
3791
3816
  for (let i = 0; i < nativeToolCalls.length; i++) {
3792
3817
  const tc = nativeToolCalls[i];
3793
- const normalized = normalizeToolCall({
3818
+ const normalized = canonicalizeTurnCall({
3794
3819
  name: tc.name,
3795
3820
  args: tc.args,
3796
3821
  });
@@ -3826,7 +3851,7 @@ export async function runAgentTurn(prompt, options = {}) {
3826
3851
  _raw: first.args._raw,
3827
3852
  },
3828
3853
  }
3829
- : normalizeToolCall({ name: first.name, args: first.args });
3854
+ : canonicalizeTurnCall({ name: first.name, args: first.args });
3830
3855
  }
3831
3856
  else {
3832
3857
  call = parseToolCall(assistantText.visible, {
@@ -3841,6 +3866,9 @@ export async function runAgentTurn(prompt, options = {}) {
3841
3866
  }
3842
3867
  }
3843
3868
  }
3869
+ if (call && !call.args?.__nativeParseError) {
3870
+ call = canonicalizeTurnCall(call);
3871
+ }
3844
3872
  if (looksLikePromptLeak(assistantText.visible)) {
3845
3873
  if (call || nativeToolCalls.length) {
3846
3874
  writeNotice("warn", "suppressed tool call from apparent prompt leak");
@@ -4182,16 +4210,15 @@ export async function runAgentTurn(prompt, options = {}) {
4182
4210
  content: toolsAttached
4183
4211
  ? `Your ${salvagedToolName} tool call was cut off at the token limit, but the system salvaged the partial content and wrote ${lineCount} lines (file is now ${priorBytes} bytes) to ${salvaged.path}. ` +
4184
4212
  `The file ends with: ${JSON.stringify(salvaged.lastLine)}\n\n` +
4185
- `CONTINUE by calling fs.append now with path=${JSON.stringify(salvaged.path)}, expectedPriorBytes=${priorBytes}, and content set to ONLY the remaining content (prefer large chunks). Use the platform tool interface — no markdown fences.`
4213
+ `CONTINUE by calling fs.append now with path=${JSON.stringify(salvaged.path)}, expectedPriorBytes=${priorBytes}, and content set to ONLY the next remaining chunk not already on disk. Keep the chunk under 24,000 characters and wait for its receipt before sending another. Use the platform tool interface — no markdown fences.`
4186
4214
  : `Your ${salvagedToolName} tool call was cut off at the token limit, but the system salvaged the partial content and wrote ${lineCount} lines (file is now ${priorBytes} bytes) to ${salvaged.path}. ` +
4187
4215
  `The file ends with: ${JSON.stringify(salvaged.lastLine)}\n\n` +
4188
- `CONTINUE with ONE large fs.append of the remaining content (prefer hundreds of lines per call do NOT use tiny ~100-line chunks):\n` +
4216
+ `CONTINUE with one fs.append chunk under 24,000 characters, then wait for its receipt before sending another:\n` +
4189
4217
  '```tool\n{"name":"fs.append","args":{"path":' +
4190
4218
  JSON.stringify(salvaged.path) +
4191
4219
  ',"expectedPriorBytes":' +
4192
4220
  priorBytes +
4193
4221
  ',"content":"...ONLY the remaining content not already on disk..."}}\n```\n' +
4194
- `expectedPriorBytes must match the receipt so append cannot double-write. ` +
4195
4222
  `Do NOT re-read the full file; do NOT re-send content already saved.`,
4196
4223
  });
4197
4224
  continue;
@@ -4208,15 +4235,15 @@ export async function runAgentTurn(prompt, options = {}) {
4208
4235
  role: "user",
4209
4236
  content: toolsAttached
4210
4237
  ? "Your previous tool call was cut off before it finished — the JSON was incomplete, so NOTHING ran. " +
4211
- "Prefer ONE complete fs.write when it fits. If the file is too large: (1) fs.write the first large section, " +
4212
- "(2) fs.append the rest with expectedPriorBytes from the write receipt, (3) repeat with large chunks. " +
4238
+ "Prefer ONE complete fs.write when it fits. If the file is too large: (1) fs.write the first section under 24,000 characters, " +
4239
+ "(2) fs.append the rest with expectedPriorBytes from the write receipt, (3) repeat with chunks under 24,000 characters, waiting for each receipt. " +
4213
4240
  "Keep reasoning SHORT and call the tool via the platform interface. Do NOT claim a file was written until a tool call succeeds."
4214
4241
  : "Your previous tool call was cut off before it finished — the JSON was incomplete, so NOTHING ran. " +
4215
- "Prefer ONE complete fs.write when it fits (~32k output tokens is a lot of file content if reasoning stays short). " +
4242
+ "Prefer ONE complete fs.write when it fits. " +
4216
4243
  "If the file is too large for one call:\n" +
4217
- "1. fs.write the first large section (as much as fits — hundreds+ of lines)\n" +
4244
+ "1. fs.write the first section under 24,000 characters\n" +
4218
4245
  "2. fs.append the rest with expectedPriorBytes from the write receipt\n" +
4219
- "3. Repeat append only if still incomplete large chunks, not ~100-line drips\n" +
4246
+ "3. Keep every append under 24,000 characters and wait for each receipt\n" +
4220
4247
  "Keep reasoning SHORT — emit the ```tool block early. Do NOT claim a file was written until a tool call succeeds.",
4221
4248
  });
4222
4249
  continue;
@@ -4242,7 +4269,7 @@ export async function runAgentTurn(prompt, options = {}) {
4242
4269
  content: `The system extracted and wrote ${lineCount} lines to ${salvaged.path} from your malformed tool call. ` +
4243
4270
  `The file content ends at: "${salvaged.lastLine}"\n\n` +
4244
4271
  `If the file is complete, proceed with the next step. ` +
4245
- `If more content is needed, use one large fs.append with expectedPriorBytes from the write receipt (not tiny chunks).`,
4272
+ `If more content is needed, use fs.append chunks under 24,000 characters with expectedPriorBytes from each receipt.`,
4246
4273
  });
4247
4274
  continue;
4248
4275
  }
@@ -4261,13 +4288,13 @@ export async function runAgentTurn(prompt, options = {}) {
4261
4288
  ? "Your previous tool call JSON was INVALID, so NOTHING ran. " +
4262
4289
  "Common causes: unescaped newlines/quotes, unbalanced braces, or content too large. " +
4263
4290
  toolNudge(true) +
4264
- " Prefer ONE complete fs.write when it fits; if cut off, continue with large fs.append + expectedPriorBytes. " +
4291
+ " Prefer ONE complete fs.write when it fits; if cut off, continue with fs.append chunks under 24,000 characters plus expectedPriorBytes. " +
4265
4292
  "Do NOT claim any file was written until a tool call actually succeeds."
4266
4293
  : "Your previous message contained a ```tool block, but its JSON was INVALID, so NOTHING ran. " +
4267
4294
  "Common causes: unescaped newlines or quotes inside a string value, an extra or missing `}` / `]`, or content too large for the output window. " +
4268
4295
  'Re-emit ONE valid ```tool block of the exact form {"name":"<tool>","args":{...}} with balanced braces. ' +
4269
4296
  "IMPORTANT: Prefer ONE complete fs.write when it fits. Keep reasoning SHORT. " +
4270
- "Only if the output window cuts you off, continue with large fs.append chunks + expectedPriorBytes. " +
4297
+ "Only if the output window cuts you off, continue with fs.append chunks under 24,000 characters plus expectedPriorBytes. " +
4271
4298
  "Do NOT claim any file was written until a tool call actually succeeds.",
4272
4299
  });
4273
4300
  continue;
@@ -4446,7 +4473,8 @@ export async function runAgentTurn(prompt, options = {}) {
4446
4473
  let bound = [];
4447
4474
  if (nativeToolCalls.length) {
4448
4475
  bound = nativeToolCalls.map((tc, index) => {
4449
- const call = tc.args?._parseError
4476
+ const parseError = Boolean(tc.args?._parseError);
4477
+ const call = parseError
4450
4478
  ? {
4451
4479
  name: tc.name || "unknown",
4452
4480
  args: {
@@ -4454,8 +4482,11 @@ export async function runAgentTurn(prompt, options = {}) {
4454
4482
  _raw: tc.args._raw,
4455
4483
  },
4456
4484
  }
4457
- : normalizeToolCall({ name: tc.name, args: tc.args });
4458
- return { index, id: tc.id, call, native: tc, wireId: tc.id };
4485
+ : canonicalizeTurnCall({ name: tc.name, args: tc.args });
4486
+ const native = parseError
4487
+ ? tc
4488
+ : { ...tc, name: call.name, args: call.args };
4489
+ return { index, id: tc.id, call, native, wireId: tc.id };
4459
4490
  });
4460
4491
  }
4461
4492
  else {
@@ -4463,7 +4494,7 @@ export async function runAgentTurn(prompt, options = {}) {
4463
4494
  if (parsed.length === 0 && call)
4464
4495
  parsed = [call];
4465
4496
  bound = parsed.map((rawCall, index) => {
4466
- const call = normalizeToolCall(rawCall);
4497
+ const call = canonicalizeTurnCall(rawCall);
4467
4498
  const id = syntheticToolCallId(index);
4468
4499
  return {
4469
4500
  index,