@caupulican/pi-adaptative 0.81.8 → 0.81.12

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 (91) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/bundled-resources/skills/tool-call-repair/SKILL.md +148 -0
  3. package/dist/bundled-resources/skills/tool-call-repair/references/failure-grammar.md +128 -0
  4. package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +201 -0
  5. package/dist/bundled-resources/skills/tool-call-repair/references/text-protocol-grammar.md +141 -0
  6. package/dist/core/agent-session.d.ts +18 -0
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +198 -1
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/delegation/worker-actions.d.ts +2 -1
  11. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  12. package/dist/core/delegation/worker-actions.js +34 -2
  13. package/dist/core/delegation/worker-actions.js.map +1 -1
  14. package/dist/core/extensions/types.d.ts +3 -1
  15. package/dist/core/extensions/types.d.ts.map +1 -1
  16. package/dist/core/extensions/types.js.map +1 -1
  17. package/dist/core/failure-corpus.d.ts +27 -1
  18. package/dist/core/failure-corpus.d.ts.map +1 -1
  19. package/dist/core/failure-corpus.js +33 -11
  20. package/dist/core/failure-corpus.js.map +1 -1
  21. package/dist/core/model-registry.d.ts +2 -2
  22. package/dist/core/model-registry.d.ts.map +1 -1
  23. package/dist/core/model-registry.js +6 -4
  24. package/dist/core/model-registry.js.map +1 -1
  25. package/dist/core/models/adaptation-store.d.ts +62 -0
  26. package/dist/core/models/adaptation-store.d.ts.map +1 -0
  27. package/dist/core/models/adaptation-store.js +168 -0
  28. package/dist/core/models/adaptation-store.js.map +1 -0
  29. package/dist/core/session-analytics.d.ts +24 -1
  30. package/dist/core/session-analytics.d.ts.map +1 -1
  31. package/dist/core/session-analytics.js +75 -0
  32. package/dist/core/session-analytics.js.map +1 -1
  33. package/dist/core/settings-manager.d.ts +6 -0
  34. package/dist/core/settings-manager.d.ts.map +1 -1
  35. package/dist/core/settings-manager.js.map +1 -1
  36. package/dist/core/slash-commands.d.ts.map +1 -1
  37. package/dist/core/slash-commands.js +5 -0
  38. package/dist/core/slash-commands.js.map +1 -1
  39. package/dist/core/system-prompt-builder.d.ts +4 -0
  40. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  41. package/dist/core/system-prompt-builder.js +8 -0
  42. package/dist/core/system-prompt-builder.js.map +1 -1
  43. package/dist/core/tool-repair-health.d.ts +3 -0
  44. package/dist/core/tool-repair-health.d.ts.map +1 -0
  45. package/dist/core/tool-repair-health.js +43 -0
  46. package/dist/core/tool-repair-health.js.map +1 -0
  47. package/dist/core/tool-repair-settings.d.ts +11 -0
  48. package/dist/core/tool-repair-settings.d.ts.map +1 -0
  49. package/dist/core/tool-repair-settings.js +18 -0
  50. package/dist/core/tool-repair-settings.js.map +1 -0
  51. package/dist/core/tools/edit.d.ts.map +1 -1
  52. package/dist/core/tools/edit.js +0 -9
  53. package/dist/core/tools/edit.js.map +1 -1
  54. package/dist/core/tools/find.d.ts.map +1 -1
  55. package/dist/core/tools/find.js +10 -7
  56. package/dist/core/tools/find.js.map +1 -1
  57. package/dist/core/tools/grep.d.ts.map +1 -1
  58. package/dist/core/tools/grep.js +15 -9
  59. package/dist/core/tools/grep.js.map +1 -1
  60. package/dist/modes/interactive/components/tool-execution.d.ts +7 -3
  61. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  62. package/dist/modes/interactive/components/tool-execution.js +21 -4
  63. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  64. package/dist/modes/interactive/external-editor.d.ts.map +1 -1
  65. package/dist/modes/interactive/external-editor.js +5 -3
  66. package/dist/modes/interactive/external-editor.js.map +1 -1
  67. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  68. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  69. package/dist/modes/interactive/interactive-mode.js +37 -7
  70. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  71. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  72. package/dist/modes/rpc/rpc-mode.js +8 -0
  73. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  74. package/dist/modes/rpc/rpc-types.d.ts +24 -0
  75. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  76. package/dist/modes/rpc/rpc-types.js.map +1 -1
  77. package/docs/index.md +1 -0
  78. package/docs/rpc.md +36 -0
  79. package/docs/settings.md +20 -0
  80. package/docs/tool-repair.md +75 -0
  81. package/docs/usage.md +2 -0
  82. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  83. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  84. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  85. package/examples/extensions/sandbox/package-lock.json +2 -2
  86. package/examples/extensions/sandbox/package.json +1 -1
  87. package/examples/extensions/with-deps/package-lock.json +2 -2
  88. package/examples/extensions/with-deps/package.json +1 -1
  89. package/npm-shrinkwrap.json +12 -12
  90. package/package.json +6 -4
  91. package/docs/bug-ledger.md +0 -131
@@ -2,7 +2,8 @@ import { readFileSync } from "node:fs";
2
2
  import { basename, dirname, join } from "node:path";
3
3
  import { classifyFailure, compactToolResultDetailsForRetention, computeRetryDelayMs, createCustomMessage, DEFAULT_RETRY_POLICY, RetryController, sleepAbortable, withStreamIdleWatchdog, } from "@caupulican/pi-agent-core";
4
4
  import { calculateContextTokens, compact, createDeterministicCompaction, estimateContextTokens, getLatestCompactionEntry, prepareCompaction, runCompactionLoop, shouldCompact, } from "@caupulican/pi-agent-core/node";
5
- import { cleanupSessionResources, isContextOverflow, streamSimple } from "@caupulican/pi-ai";
5
+ import { cleanupSessionResources, formatToolRepairStandingRule, generateTextToolProtocolPrimer, isContextOverflow, parseTextToolCalls, streamSimple, } from "@caupulican/pi-ai";
6
+ import { Type } from "typebox";
6
7
  import { getAgentDir } from "../config.js";
7
8
  import { stripFrontmatter } from "../utils/frontmatter.js";
8
9
  import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
@@ -28,6 +29,7 @@ import { MemoryController } from "./memory-controller.js";
28
29
  import { deriveModelCapabilityProfile, filterToolNamesForCapability, } from "./model-capability.js";
29
30
  import { formatModelRouterModel, ModelRouterController } from "./model-router-controller.js";
30
31
  import { ModelSelectionController } from "./model-selection-controller.js";
32
+ import { ModelAdaptationStore } from "./models/adaptation-store.js";
31
33
  import { ProfileFilterController } from "./profile-filter-controller.js";
32
34
  import { expandPromptTemplate } from "./prompt-templates.js";
33
35
  import { ReflectionController } from "./reflection-controller.js";
@@ -39,6 +41,8 @@ import { SessionAnalytics } from "./session-analytics.js";
39
41
  import { SessionTreeNavigator } from "./session-tree-navigator.js";
40
42
  import { SystemPromptBuilder } from "./system-prompt-builder.js";
41
43
  import { ToolGateController } from "./tool-gate-controller.js";
44
+ import { formatToolRepairHealthReport } from "./tool-repair-health.js";
45
+ import { resolveCurrentToolRepairSettings } from "./tool-repair-settings.js";
42
46
  // ============================================================================
43
47
  // Stream-idle watchdog wiring
44
48
  // ============================================================================
@@ -52,6 +56,15 @@ import { ToolGateController } from "./tool-gate-controller.js";
52
56
  * key stable regardless of how many times this module is evaluated.
53
57
  */
54
58
  const RAW_STREAM_MARKER = Symbol.for("pi.rawStreamSimple");
59
+ const MODEL_ADAPTATION_REPAIR_THRESHOLD = 3;
60
+ const TEXT_TOOL_PROTOCOL_VERSION = 1;
61
+ const TEXT_TOOL_PROTOCOL_TRIALS_PER_VARIANT = 2;
62
+ const TEXT_TOOL_PROTOCOL_VARIANTS = ["tool-tag", "tool-call", "fenced-json"];
63
+ const TEXT_TOOL_PROTOCOL_ECHO_TOOL = {
64
+ name: "echo",
65
+ description: "Echo calibration data",
66
+ parameters: Type.Object({ data: Type.String() }),
67
+ };
55
68
  /** Test-only override of the stream-idle bounds. Read per-request by the wiring's resolver. */
56
69
  let streamIdleOptionsOverride;
57
70
  /**
@@ -133,6 +146,8 @@ export class AgentSession {
133
146
  _agentDir;
134
147
  _collectWorkspaceSources;
135
148
  _localRuntimeController;
149
+ _modelAdaptationStore;
150
+ _repairModeSessionCounts = new Map();
136
151
  /** Assembles the session's base system prompt from live session state (see
137
152
  * system-prompt-builder.ts); owns the paired _baseSystemPromptOptions. */
138
153
  _systemPromptBuilder;
@@ -242,6 +257,8 @@ export class AgentSession {
242
257
  this._customTools = config.customTools ?? [];
243
258
  this._cwd = config.cwd;
244
259
  this._agentDir = config.agentDir ?? getAgentDir();
260
+ this._modelAdaptationStore = ModelAdaptationStore.forAgentDir(this._agentDir);
261
+ this._applyToolRepairLayerSettings();
245
262
  this._collectWorkspaceSources = config.collectWorkspaceSources ?? collectWorkspaceSources;
246
263
  this._localRuntimeController = new LocalRuntimeController({
247
264
  agentDir: this._agentDir,
@@ -260,6 +277,7 @@ export class AgentSession {
260
277
  hasTool: (name) => this._runtimeBuilder.hasTool(name),
261
278
  getToolPromptSnippet: (name) => this._runtimeBuilder.getToolPromptSnippet(name),
262
279
  getToolPromptGuidelines: (name) => this._runtimeBuilder.getToolPromptGuidelines(name),
280
+ getModelAdaptationRules: () => this._getModelAdaptationRulesForPrompt(),
263
281
  getActiveExtensions: () => this._extensionRunner.activeExtensions,
264
282
  });
265
283
  this._autonomyTelemetry = new AutonomyTelemetry({
@@ -479,6 +497,14 @@ export class AgentSession {
479
497
  getSettingsManager: () => this.settingsManager,
480
498
  getToolDefinition: (name) => this.getToolDefinition(name),
481
499
  });
500
+ const previousToolArgumentValidation = this.agent.onToolArgumentValidation;
501
+ this.agent.onToolArgumentValidation = (event) => {
502
+ const taggedEvent = this._tagModelAdaptationRuleTeaching(event);
503
+ previousToolArgumentValidation?.(taggedEvent);
504
+ this._analytics.recordToolArgumentValidation(taggedEvent);
505
+ this._recordToolValidationBounce(taggedEvent);
506
+ this._handleModelAdaptationTelemetry(taggedEvent);
507
+ };
482
508
  this._treeNavigator = new SessionTreeNavigator({
483
509
  getSessionManager: () => this.sessionManager,
484
510
  getModel: () => this.model,
@@ -757,6 +783,169 @@ export class AgentSession {
757
783
  tools: this.agent.state.tools.slice(),
758
784
  };
759
785
  }
786
+ _modelAdaptationKeyFor(model) {
787
+ return model ? formatModelRouterModel(model) : undefined;
788
+ }
789
+ _toolRepairSettings() {
790
+ return resolveCurrentToolRepairSettings(this.settingsManager.settings);
791
+ }
792
+ _applyToolRepairLayerSettings() {
793
+ const settings = this._toolRepairSettings();
794
+ this.agent.toolArgumentRepairEnabled = settings.repair;
795
+ this.agent.toolArgumentTeachEnabled = settings.teach;
796
+ }
797
+ _getModelAdaptationRulesForPrompt() {
798
+ if (!this._toolRepairSettings().teach)
799
+ return [];
800
+ const modelKey = this._modelAdaptationKeyFor(this.agent.state.model);
801
+ return modelKey ? this._modelAdaptationStore.get(modelKey).rules : [];
802
+ }
803
+ _textProtocolFlag(model) {
804
+ const override = this._toolRepairSettings().textProtocol;
805
+ return override ?? model?.textToolCallProtocol === true;
806
+ }
807
+ _textProtocolCalibrationContext(variant, token) {
808
+ const primer = generateTextToolProtocolPrimer([TEXT_TOOL_PROTOCOL_ECHO_TOOL], { variant });
809
+ const instruction = `Text tool protocol calibration trial. Using the protocol above, call echo with data exactly "${token}". Output only the tool-call envelope.`;
810
+ return {
811
+ systemPrompt: `${primer}\n\n${instruction}`,
812
+ messages: [{ role: "user", content: [{ type: "text", text: instruction }], timestamp: Date.now() }],
813
+ tools: [TEXT_TOOL_PROTOCOL_ECHO_TOOL],
814
+ };
815
+ }
816
+ async _runTextProtocolTrial(model, variant, token) {
817
+ const stream = await this.agent.streamFn(model, this._textProtocolCalibrationContext(variant, token), {
818
+ textToolCallProtocol: false,
819
+ maxRetries: 0,
820
+ });
821
+ const message = await stream.result();
822
+ if (message.content.some((block) => block.type === "toolCall" && block.name === "echo" && block.arguments.data === token)) {
823
+ return true;
824
+ }
825
+ const text = message.content
826
+ .filter((block) => block.type === "text")
827
+ .map((block) => block.text)
828
+ .join("\n")
829
+ .trim();
830
+ if (!text)
831
+ return false;
832
+ const parsed = parseTextToolCalls(text, [TEXT_TOOL_PROTOCOL_ECHO_TOOL]);
833
+ return parsed.calls.some((call) => call.name === "echo" && call.arguments.data === token);
834
+ }
835
+ async _ensureTextToolProtocolForActiveModel() {
836
+ const model = this.agent.state.model;
837
+ if (!this._textProtocolFlag(model)) {
838
+ this.agent.textToolCallProtocol = undefined;
839
+ return;
840
+ }
841
+ const modelKey = this._modelAdaptationKeyFor(model);
842
+ if (!modelKey) {
843
+ this.agent.textToolCallProtocol = true;
844
+ return;
845
+ }
846
+ const profile = this._modelAdaptationStore.get(modelKey);
847
+ if (profile.protocol?.version === TEXT_TOOL_PROTOCOL_VERSION) {
848
+ this.agent.textToolCallProtocol = { variant: profile.protocol.variant };
849
+ return;
850
+ }
851
+ for (const variant of TEXT_TOOL_PROTOCOL_VARIANTS) {
852
+ let passed = true;
853
+ for (let trial = 0; trial < TEXT_TOOL_PROTOCOL_TRIALS_PER_VARIANT; trial++) {
854
+ const ok = await this._runTextProtocolTrial(model, variant, `pi-calibration-${trial + 1}`);
855
+ if (!ok) {
856
+ passed = false;
857
+ break;
858
+ }
859
+ }
860
+ if (passed) {
861
+ const calibratedAt = new Date().toISOString();
862
+ this._modelAdaptationStore.setProtocol(modelKey, { version: TEXT_TOOL_PROTOCOL_VERSION, variant, calibratedAt }, calibratedAt);
863
+ this.agent.textToolCallProtocol = { variant };
864
+ return;
865
+ }
866
+ }
867
+ this.agent.textToolCallProtocol = undefined;
868
+ throw new Error(`Model ${modelKey} cannot follow the text tool protocol after calibration.`);
869
+ }
870
+ _recordToolValidationBounce(event) {
871
+ if (event.outcome !== "bounced" || !event.failureShape || event.failureShape.length === 0)
872
+ return;
873
+ this._failureCorpus.recordToolValidation({
874
+ provider: event.provider ?? this.agent.state.model?.provider,
875
+ modelId: event.model ?? this.agent.state.model?.id,
876
+ tool: event.tool,
877
+ failureModes: event.failureModes,
878
+ shape: event.failureShape,
879
+ errorKeywords: event.errorKeywords,
880
+ });
881
+ }
882
+ _tagModelAdaptationRuleTeaching(event) {
883
+ if (!this._toolRepairSettings().teach || event.taught !== "none")
884
+ return event;
885
+ const modelKey = event.provider && event.model
886
+ ? `${event.provider}/${event.model}`
887
+ : this._modelAdaptationKeyFor(this.agent.state.model);
888
+ if (!modelKey)
889
+ return event;
890
+ try {
891
+ const rules = this._modelAdaptationStore.get(modelKey).rules;
892
+ const modes = new Set([...event.failureModes, ...event.repairsApplied]);
893
+ if (rules.some((rule) => modes.has(rule.mode))) {
894
+ return { ...event, taught: "rule" };
895
+ }
896
+ }
897
+ catch {
898
+ // Adaptation telemetry tagging is best-effort; leave the original event unchanged.
899
+ }
900
+ return event;
901
+ }
902
+ _repairSessionCount(modelKey, mode) {
903
+ const key = `${modelKey}\0${mode}`;
904
+ const count = (this._repairModeSessionCounts.get(key) ?? 0) + 1;
905
+ this._repairModeSessionCounts.set(key, count);
906
+ return count;
907
+ }
908
+ _handleModelAdaptationTelemetry(event) {
909
+ if (!this._toolRepairSettings().teach || event.outcome !== "repaired" || event.repairsApplied.length === 0)
910
+ return;
911
+ const modelKey = event.provider && event.model
912
+ ? `${event.provider}/${event.model}`
913
+ : this._modelAdaptationKeyFor(this.agent.state.model);
914
+ if (!modelKey)
915
+ return;
916
+ try {
917
+ for (const mode of [...new Set(event.repairsApplied)]) {
918
+ const profile = this._modelAdaptationStore.get(modelKey);
919
+ const stats = profile.teachStats[mode] ?? { taught: 0, recurrenceBefore: 0, recurrenceAfter: 0 };
920
+ if (profile.rules.some((rule) => rule.mode === mode)) {
921
+ this._modelAdaptationStore.markRuleFired(modelKey, mode);
922
+ this._modelAdaptationStore.setTeachStats(modelKey, mode, {
923
+ ...stats,
924
+ recurrenceAfter: stats.recurrenceAfter + 1,
925
+ });
926
+ continue;
927
+ }
928
+ const recurrenceBefore = stats.recurrenceBefore + 1;
929
+ this._modelAdaptationStore.setTeachStats(modelKey, mode, { ...stats, recurrenceBefore });
930
+ const sessionCount = this._repairSessionCount(modelKey, mode);
931
+ if (sessionCount >= MODEL_ADAPTATION_REPAIR_THRESHOLD ||
932
+ recurrenceBefore >= MODEL_ADAPTATION_REPAIR_THRESHOLD) {
933
+ this._modelAdaptationStore.addRule(modelKey, {
934
+ mode,
935
+ text: formatToolRepairStandingRule(mode),
936
+ });
937
+ this._modelAdaptationStore.setTeachStats(modelKey, mode, {
938
+ ...stats,
939
+ taught: stats.taught + 1,
940
+ recurrenceBefore,
941
+ });
942
+ }
943
+ }
944
+ }
945
+ catch {
946
+ // Model adaptation is best-effort; failed telemetry persistence must not affect the turn.
947
+ }
948
+ }
760
949
  /** Tool-build call-site delegation to {@link ContextPipeline.getToolArtifactStore}. */
761
950
  _getToolArtifactStore() {
762
951
  return this._pipeline.getToolArtifactStore();
@@ -896,6 +1085,12 @@ export class AgentSession {
896
1085
  formatContextCompositionDashboard() {
897
1086
  return formatContextCompositionDashboard(this.getContextCompositionReport());
898
1087
  }
1088
+ formatToolRepairHealthReport() {
1089
+ return formatToolRepairHealthReport(this._modelAdaptationStore);
1090
+ }
1091
+ removeToolRepairRule(model, mode) {
1092
+ return this._modelAdaptationStore.removeRule(model, mode);
1093
+ }
899
1094
  /** Curation status for diagnostics/dashboard: settings, live telemetry, last refusal reason. */
900
1095
  /** Curation status for diagnostics/dashboard (delegates to {@link ContextPipeline.getContextCurationStatus}). */
901
1096
  getContextCurationStatus() {
@@ -1492,6 +1687,7 @@ export class AgentSession {
1492
1687
  try {
1493
1688
  const maxGoalLoopRounds = this.settingsManager.getAutonomySettings().maxStallTurns;
1494
1689
  this.agent.maxStallTurns = maxGoalLoopRounds;
1690
+ await this._ensureTextToolProtocolForActiveModel();
1495
1691
  let goalLoopRounds = 1;
1496
1692
  await this.agent.prompt(messages);
1497
1693
  while ((maxGoalLoopRounds === 0 || goalLoopRounds < maxGoalLoopRounds) && (await this._handlePostAgentRun())) {
@@ -1587,6 +1783,7 @@ export class AgentSession {
1587
1783
  return this._promptUnserialized(text, options);
1588
1784
  }
1589
1785
  async _promptUnserialized(text, options) {
1786
+ this._applyToolRepairLayerSettings();
1590
1787
  const expandPromptTemplates = options?.expandPromptTemplates ?? true;
1591
1788
  const processSlashCommands = options?.processSlashCommands ?? expandPromptTemplates;
1592
1789
  const preflightResult = options?.preflightResult;