@caupulican/pi-adaptative 0.80.88 → 0.80.90

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 (53) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/core/agent-session.d.ts +35 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +279 -0
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/context/brain-curator.d.ts +88 -0
  7. package/dist/core/context/brain-curator.d.ts.map +1 -0
  8. package/dist/core/context/brain-curator.js +192 -0
  9. package/dist/core/context/brain-curator.js.map +1 -0
  10. package/dist/core/context/context-composition.d.ts +122 -0
  11. package/dist/core/context/context-composition.d.ts.map +1 -0
  12. package/dist/core/context/context-composition.js +163 -0
  13. package/dist/core/context/context-composition.js.map +1 -0
  14. package/dist/core/context/context-prompt-enforcement.d.ts +13 -0
  15. package/dist/core/context/context-prompt-enforcement.d.ts.map +1 -1
  16. package/dist/core/context/context-prompt-enforcement.js +17 -2
  17. package/dist/core/context/context-prompt-enforcement.js.map +1 -1
  18. package/dist/core/context-gc.d.ts +13 -0
  19. package/dist/core/context-gc.d.ts.map +1 -1
  20. package/dist/core/context-gc.js +6 -0
  21. package/dist/core/context-gc.js.map +1 -1
  22. package/dist/core/research/model-fitness.d.ts +3 -0
  23. package/dist/core/research/model-fitness.d.ts.map +1 -1
  24. package/dist/core/research/model-fitness.js +54 -3
  25. package/dist/core/research/model-fitness.js.map +1 -1
  26. package/dist/core/settings-manager.d.ts +13 -0
  27. package/dist/core/settings-manager.d.ts.map +1 -1
  28. package/dist/core/settings-manager.js +19 -0
  29. package/dist/core/settings-manager.js.map +1 -1
  30. package/dist/core/slash-commands.d.ts.map +1 -1
  31. package/dist/core/slash-commands.js +6 -1
  32. package/dist/core/slash-commands.js.map +1 -1
  33. package/dist/modes/interactive/components/fitness-role-selector.d.ts +13 -0
  34. package/dist/modes/interactive/components/fitness-role-selector.d.ts.map +1 -0
  35. package/dist/modes/interactive/components/fitness-role-selector.js +65 -0
  36. package/dist/modes/interactive/components/fitness-role-selector.js.map +1 -0
  37. package/dist/modes/interactive/components/settings-selector.d.ts +4 -1
  38. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  39. package/dist/modes/interactive/components/settings-selector.js +84 -0
  40. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  41. package/dist/modes/interactive/interactive-mode.d.ts +5 -0
  42. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  43. package/dist/modes/interactive/interactive-mode.js +91 -0
  44. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  45. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  46. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  47. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  48. package/examples/extensions/sandbox/package-lock.json +2 -2
  49. package/examples/extensions/sandbox/package.json +1 -1
  50. package/examples/extensions/with-deps/package-lock.json +2 -2
  51. package/examples/extensions/with-deps/package.json +1 -1
  52. package/npm-shrinkwrap.json +12 -12
  53. package/package.json +4 -4
@@ -27,8 +27,11 @@ import { appendLaneRecordSnapshot, getLaneRecordSnapshots } from "./autonomy/ses
27
27
  import { composeSubagentSystemPrompt } from "./autonomy/subagent-prompt.js";
28
28
  import { executeBashWithOperations } from "./bash-executor.js";
29
29
  import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
30
+ // (module-scope helper for curation goal extraction defined below the imports)
31
+ import { BrainCurator } from "./context/brain-curator.js";
30
32
  import { createFileArtifactStore } from "./context/context-artifacts.js";
31
33
  import { runContextAudit } from "./context/context-audit.js";
34
+ import { buildContextCompositionReport, formatContextCompositionDashboard, } from "./context/context-composition.js";
32
35
  import { enforcePromptPolicy } from "./context/context-prompt-enforcement.js";
33
36
  import { correlateWithContextGc, planPromptPolicy, } from "./context/context-prompt-policy.js";
34
37
  import { defaultMemoryPromptInclusionReport, sanitizeMemoryRetrievalReportForDiagnostics, } from "./context/memory-diagnostics.js";
@@ -107,6 +110,23 @@ export function parseSkillBlock(text) {
107
110
  }
108
111
  /** customType for spawned-usage roll-up entries (Cost Aggregation, Model A). */
109
112
  export const SPAWNED_USAGE_CUSTOM_TYPE = "spawned_usage";
113
+ /** Latest user prompt text in the provider-visible array (curation goal line; bounded by caller). */
114
+ function latestUserPromptText(messages) {
115
+ for (let index = messages.length - 1; index >= 0; index--) {
116
+ const message = messages[index];
117
+ if (!message || message.role !== "user")
118
+ continue;
119
+ if (typeof message.content === "string")
120
+ return message.content;
121
+ const text = message.content
122
+ .filter((part) => part.type === "text")
123
+ .map((part) => part.text)
124
+ .join("\n");
125
+ if (text.length > 0)
126
+ return text;
127
+ }
128
+ return "";
129
+ }
110
130
  // ============================================================================
111
131
  // Constants
112
132
  // ============================================================================
@@ -204,6 +224,10 @@ export class AgentSession {
204
224
  _autoCompactionAbortController = undefined;
205
225
  _overflowRecoveryAttempted = false;
206
226
  _latestContextGcReport = undefined;
227
+ /** Brain-curation sidecar (design: brain-context-curation-design.md). Inert unless the
228
+ * contextPolicy.curation setting is enabled AND the model passes the digest fitness gate. */
229
+ _brainCurator = new BrainCurator();
230
+ _lastCurationSkipReason = undefined;
207
231
  _toolArtifactStore = undefined;
208
232
  _latestContextAuditReport = undefined;
209
233
  _latestPromptPolicyReport = undefined;
@@ -424,6 +448,9 @@ export class AgentSession {
424
448
  const gcResult = this._applyContextGc(finalMessages, true);
425
449
  this._correlatePromptPolicyWithContextGc(gcResult.report);
426
450
  const enforcementResult = this._runPromptEnforcement(gcResult.messages, shadowReport);
451
+ this._enqueueRelevanceCuration(gcResult.messages, shadowReport);
452
+ // Fire-and-forget: the local curator overlaps the frontier call; it never blocks a turn.
453
+ this._maybeDrainBrainCuration();
427
454
  // Appended LAST, after gc and enforcement, so the bounded evidence block is
428
455
  // never packed/stubbed/reshaped by either pass and always reflects this turn's
429
456
  // fresh retrieval. Because nothing downstream trims it, memory-prompt-block.ts's
@@ -685,6 +712,7 @@ export class AgentSession {
685
712
  _runPromptEnforcement(messages, shadowReport) {
686
713
  try {
687
714
  const persistedSettings = this.settingsManager.getContextPromptEnforcementSettings();
715
+ const curationEnabled = this.settingsManager.getContextCurationSettings().enabled;
688
716
  const settings = {
689
717
  ...persistedSettings,
690
718
  // Runtime fact, never assumed: artifact_retrieve is a companion affordance
@@ -692,6 +720,7 @@ export class AgentSession {
692
720
  // tools can differ turn to turn -- see context-prompt-enforcement.ts's doc
693
721
  // comment on why this is checked separately from hasAvailableRetrievalPath.
694
722
  retrievalToolAvailable: this.getActiveToolNames().includes("artifact_retrieve"),
723
+ brainRelevance: curationEnabled ? (itemId) => this._brainCurator.getRelevance(itemId) : undefined,
695
724
  };
696
725
  const result = enforcePromptPolicy(messages, shadowReport, settings);
697
726
  this._latestPromptEnforcementReport = result.report;
@@ -703,6 +732,222 @@ export class AgentSession {
703
732
  return { messages, report };
704
733
  }
705
734
  }
735
+ /**
736
+ * Enqueue relevance-scoring jobs for stale, artifact-backed tool outputs the enforcement
737
+ * pilot could act on. Pure queueing — the verdicts only ever take effect through the
738
+ * asymmetric advisory lever inside enforcePromptPolicy. Never throws into a turn.
739
+ */
740
+ _enqueueRelevanceCuration(messages, shadowReport) {
741
+ try {
742
+ const settings = this.settingsManager.getContextCurationSettings();
743
+ if (!settings.enabled)
744
+ return;
745
+ const goal = latestUserPromptText(messages).slice(0, 400);
746
+ for (const item of shadowReport.items) {
747
+ if (!item.hasAvailableRetrievalPath)
748
+ continue;
749
+ const message = messages[item.messageIndex];
750
+ if (!message || message.role !== "toolResult" || message.toolCallId !== item.toolCallId)
751
+ continue;
752
+ if (message.isError)
753
+ continue;
754
+ const details = message.details;
755
+ if (details?.contextGc?.packed === true || details?.promptPolicy?.enforced === true)
756
+ continue;
757
+ const text = message.content
758
+ .filter((part) => part.type === "text")
759
+ .map((part) => part.text)
760
+ .join("\n");
761
+ if (text.length === 0)
762
+ continue;
763
+ this._brainCurator.enqueue({ kind: "relevance", key: item.itemId, content: text.slice(0, 4000), goal });
764
+ }
765
+ }
766
+ catch {
767
+ // curation is a sidecar; it must never disrupt a turn
768
+ }
769
+ }
770
+ /**
771
+ * Drain gate: settings on, model configured+authed, and the model has PASSED the digest
772
+ * fitness probe on THIS host (design: unfit or unprobed models are refused with a visible
773
+ * reason, never silently degraded). Fire-and-forget; never throws into a turn.
774
+ */
775
+ _maybeDrainBrainCuration() {
776
+ try {
777
+ const settings = this.settingsManager.getContextCurationSettings();
778
+ if (!settings.enabled) {
779
+ // Never surface a stale refusal reason for a feature the user has since disabled.
780
+ this._lastCurationSkipReason = undefined;
781
+ return;
782
+ }
783
+ if (!this._brainCurator.hasWork() || this._brainCurator.isDraining)
784
+ return;
785
+ if (!settings.model) {
786
+ this._lastCurationSkipReason = "curation_model_unset";
787
+ return;
788
+ }
789
+ const resolved = resolveCliModel({ cliModel: settings.model, modelRegistry: this._modelRegistry });
790
+ if (!resolved.model || !this._modelRegistry.hasConfiguredAuth(resolved.model)) {
791
+ this._lastCurationSkipReason = "curation_model_unresolved";
792
+ return;
793
+ }
794
+ // Match on the CANONICAL "provider/id" ref — runModelFitness stores reports under it,
795
+ // while settings.model may be a bare id or pattern; comparing raw strings would refuse
796
+ // forever with curation_model_unprobed even after a successful probe.
797
+ const canonicalRef = `${resolved.model.provider}/${resolved.model.id}`;
798
+ const fitness = FitnessStore.forAgentDir(this._agentDir)
799
+ .getForHost()
800
+ .find((entry) => entry.model === canonicalRef);
801
+ const digestScore = fitness?.report.digest;
802
+ if (!digestScore) {
803
+ this._lastCurationSkipReason = "curation_model_unprobed";
804
+ return;
805
+ }
806
+ if (digestScore.succeeded < Math.ceil(digestScore.total * (2 / 3))) {
807
+ this._lastCurationSkipReason = "curation_model_digest_unfit";
808
+ return;
809
+ }
810
+ this._lastCurationSkipReason = undefined;
811
+ void this._drainBrainCuration(resolved.model, settings.maxJobsPerTurn);
812
+ }
813
+ catch {
814
+ // curation is a sidecar; it must never disrupt a turn
815
+ }
816
+ }
817
+ async _drainBrainCuration(model, maxJobs) {
818
+ try {
819
+ // ACCUMULATE across all drained jobs (the drain runs the completer once PER job) —
820
+ // keeping only the last job's usage would under-report every multi-job drain.
821
+ let spentUsage;
822
+ const results = await this._brainCurator.drain({
823
+ maxJobs,
824
+ complete: async ({ systemPrompt, userPrompt, signal }) => {
825
+ const completion = await this.runIsolatedCompletion({
826
+ systemPrompt,
827
+ messages: [{ role: "user", content: [{ type: "text", text: userPrompt }], timestamp: Date.now() }],
828
+ model,
829
+ thinkingLevel: "off",
830
+ maxTokens: 256,
831
+ signal,
832
+ // Both curation system prompts are static — the provider can cache the prefix.
833
+ cacheRetention: "short",
834
+ });
835
+ const usage = completion.usage;
836
+ if (!spentUsage) {
837
+ spentUsage = structuredClone(usage);
838
+ }
839
+ else {
840
+ spentUsage.input += usage.input;
841
+ spentUsage.output += usage.output;
842
+ spentUsage.cacheRead += usage.cacheRead;
843
+ spentUsage.cacheWrite += usage.cacheWrite;
844
+ spentUsage.totalTokens += usage.totalTokens;
845
+ spentUsage.cost.input += usage.cost.input;
846
+ spentUsage.cost.output += usage.cost.output;
847
+ spentUsage.cost.cacheRead += usage.cost.cacheRead;
848
+ spentUsage.cost.cacheWrite += usage.cost.cacheWrite;
849
+ spentUsage.cost.total += usage.cost.total;
850
+ }
851
+ return {
852
+ text: completion.text,
853
+ costUsd: completion.usage.cost.total,
854
+ stopReason: String(completion.stopReason),
855
+ };
856
+ },
857
+ });
858
+ // Honest accounting even for free local models: token visibility is the contract.
859
+ if (spentUsage && (spentUsage.cost.total > 0 || spentUsage.totalTokens > 0)) {
860
+ this.addSpawnedUsage(spentUsage, { label: "context-curator" });
861
+ }
862
+ if (this._disposed || results.length === 0)
863
+ return;
864
+ this.sessionManager.appendCustomEntry("brain-curation", {
865
+ version: 1,
866
+ results: results.map((result) => ({
867
+ key: result.key,
868
+ kind: result.kind,
869
+ ok: result.ok,
870
+ ms: result.ms,
871
+ ...(result.digest !== undefined ? { digest: result.digest } : {}),
872
+ ...(result.relevant !== undefined ? { relevant: result.relevant, confidence: result.confidence } : {}),
873
+ })),
874
+ telemetry: this._brainCurator.telemetry(),
875
+ });
876
+ }
877
+ catch {
878
+ // curation is a sidecar; it must never disrupt a turn
879
+ }
880
+ }
881
+ /**
882
+ * Context composition dashboard data: decomposes the per-request payload (system prompt, tool
883
+ * schemas, extension contributions, message classes incl. GC/policy stubs and recall pages)
884
+ * plus background spend, so users can see exactly what their integrations cost per request.
885
+ * Read-only: uses the GC report path (writePayloads=false), never mutates anything.
886
+ */
887
+ getContextCompositionReport() {
888
+ const rawMessages = this.agent.state.messages.slice();
889
+ const gcResult = this._applyContextGc(rawMessages, false);
890
+ const activeNames = new Set(this.getActiveToolNames());
891
+ const extensions = this._resourceLoader.getExtensions().extensions;
892
+ const extensionToolNames = new Set(extensions.flatMap((extension) => [...extension.tools.keys()]));
893
+ const usage = this.getContextUsage();
894
+ const enforcementItems = this.getPromptEnforcementReport().items;
895
+ const curationStatus = this.getContextCurationStatus();
896
+ const spawned = this.getSpawnedUsage();
897
+ const promptInclusion = this.getMemoryPromptInclusionReport();
898
+ const memoryEvidenceTokens = promptInclusion.status === "included" ? Math.ceil(promptInclusion.blockChars / 4) : 0;
899
+ // Enforcement stubs are applied at SEND time (not persisted), so the message view here
900
+ // still holds raw text for them; subtract what stubbing reclaims per request.
901
+ const enforcementSavedTokens = enforcementItems
902
+ .filter((item) => item.enforced && typeof item.originalChars === "number")
903
+ .reduce((sum, item) => sum + Math.max(0, Math.ceil((item.originalChars ?? 0) / 4) - 50), 0);
904
+ return buildContextCompositionReport({
905
+ systemPrompt: this.systemPrompt ?? "",
906
+ tools: this.getAllTools()
907
+ .filter((tool) => activeNames.has(tool.name))
908
+ .map((tool) => ({
909
+ name: tool.name,
910
+ description: tool.description,
911
+ parameters: tool.parameters,
912
+ source: extensionToolNames.has(tool.name) ? "extension" : "built-in",
913
+ })),
914
+ extensions: extensions.map((extension) => ({
915
+ name: basename(extension.path),
916
+ path: extension.path,
917
+ toolNames: [...extension.tools.keys()],
918
+ commandCount: extension.commands.size,
919
+ })),
920
+ messages: gcResult.messages,
921
+ providerReportedTokens: usage?.tokens ?? null,
922
+ contextWindow: usage?.contextWindow ?? this.model?.contextWindow ?? null,
923
+ gc: { packedCount: gcResult.report.packedCount, savedTokens: gcResult.report.savedTokens },
924
+ enforcement: {
925
+ enforcedCount: enforcementItems.filter((item) => item.enforced).length,
926
+ advisoryEvictions: enforcementItems.filter((item) => item.advisory === "brain_irrelevant").length,
927
+ },
928
+ curation: {
929
+ enabled: curationStatus.enabled,
930
+ telemetry: curationStatus.telemetry,
931
+ lastSkipReason: curationStatus.lastSkipReason,
932
+ },
933
+ spawned: { cost: spawned.cost, reports: spawned.reports },
934
+ adjustments: { memoryEvidenceTokens, enforcementSavedTokens },
935
+ });
936
+ }
937
+ /** Bounded plain-text rendering of {@link getContextCompositionReport} for the /context command. */
938
+ formatContextCompositionDashboard() {
939
+ return formatContextCompositionDashboard(this.getContextCompositionReport());
940
+ }
941
+ /** Curation status for diagnostics/dashboard: settings, live telemetry, last refusal reason. */
942
+ getContextCurationStatus() {
943
+ const settings = this.settingsManager.getContextCurationSettings();
944
+ return {
945
+ enabled: settings.enabled,
946
+ model: settings.model,
947
+ telemetry: this._brainCurator.telemetry(),
948
+ lastSkipReason: this._lastCurationSkipReason,
949
+ };
950
+ }
706
951
  /** Read-only inspection of the latest prompt-enforcement report, for tests/debugging. */
707
952
  getPromptEnforcementReport() {
708
953
  return this._latestPromptEnforcementReport ?? { turnIndex: this._turnIndex, items: [] };
@@ -877,6 +1122,7 @@ export class AgentSession {
877
1122
  // default provider actually emits are never recognized as semantic-memory pages and
878
1123
  // accumulate raw for the life of the session — the exact growth Bug #7 GC exists to stop.
879
1124
  const providerMarkers = this._memoryManager.getContextMarkers();
1125
+ const curationSettings = this.settingsManager.getContextCurationSettings();
880
1126
  const result = applyContextGc(messages, {
881
1127
  ...settings,
882
1128
  semanticMemory: {
@@ -886,6 +1132,22 @@ export class AgentSession {
886
1132
  cwd: this._cwd,
887
1133
  storageDir: this._contextGcStorageDir(),
888
1134
  writePayloads,
1135
+ curation: curationSettings.enabled
1136
+ ? {
1137
+ resolveDigest: (digestKey) => this._brainCurator.getDigest(digestKey),
1138
+ // Only the real per-turn pass enqueues work; the read-only report path
1139
+ // (writePayloads=false) stays side-effect free.
1140
+ onPacked: writePayloads
1141
+ ? (record, originalText) => {
1142
+ this._brainCurator.enqueue({
1143
+ kind: "stub_digest",
1144
+ key: record.key ?? record.toolCallId,
1145
+ content: originalText,
1146
+ });
1147
+ }
1148
+ : undefined,
1149
+ }
1150
+ : undefined,
889
1151
  });
890
1152
  this._latestContextGcReport = result.report;
891
1153
  // Only release/reclaim on the real per-turn pass (writePayloads=true), never on
@@ -3597,6 +3859,23 @@ export class AgentSession {
3597
3859
  }
3598
3860
  }
3599
3861
  }
3862
+ // Strict UAC: the active profile is the COMPLETE grant, so a tool the profile names
3863
+ // explicitly is itself a request for that tool — it must ACTIVATE from the registry even
3864
+ // if the session never requested it. Without this, activation is only ever the requested
3865
+ // defaults ∩ allow-list, and a profile granting non-default tools (a search-only profile's
3866
+ // grep/find) yields an empty or truncated tool set on load and /reload. A blanket "*"
3867
+ // stays grant-only: activation then still derives from the request/defaults above.
3868
+ const explicitAllowPatterns = toolProfileFilter?.allow.filter((pattern) => pattern !== "*") ?? [];
3869
+ if (explicitAllowPatterns.length > 0) {
3870
+ for (const toolName of this._toolRegistry.keys()) {
3871
+ if (!isAllowedTool(toolName))
3872
+ continue;
3873
+ if (matchesResourceProfilePattern(toolName, explicitAllowPatterns)) {
3874
+ nextActiveToolNames.push(toolName);
3875
+ autoActivated.push(toolName);
3876
+ }
3877
+ }
3878
+ }
3600
3879
  // artifact_retrieve companion auto-activation is enforced inside
3601
3880
  // setActiveToolsByName() itself (not duplicated here), so every activation path --
3602
3881
  // including the public, extension-exposed setActiveTools() -- gets the same