@caupulican/pi-adaptative 0.79.0 → 0.80.1

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 (44) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/core/agent-session.d.ts +1 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +34 -2
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/resource-loader.d.ts +1 -0
  7. package/dist/core/resource-loader.d.ts.map +1 -1
  8. package/dist/core/resource-loader.js +26 -1
  9. package/dist/core/resource-loader.js.map +1 -1
  10. package/dist/core/settings-manager.d.ts +12 -0
  11. package/dist/core/settings-manager.d.ts.map +1 -1
  12. package/dist/core/settings-manager.js +16 -0
  13. package/dist/core/settings-manager.js.map +1 -1
  14. package/dist/core/slash-commands.d.ts.map +1 -1
  15. package/dist/core/slash-commands.js +2 -1
  16. package/dist/core/slash-commands.js.map +1 -1
  17. package/dist/modes/interactive/components/settings-selector.d.ts +4 -1
  18. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  19. package/dist/modes/interactive/components/settings-selector.js +98 -3
  20. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  21. package/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  22. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  23. package/dist/modes/interactive/components/tool-execution.js +11 -2
  24. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  25. package/dist/modes/interactive/components/tool-group.d.ts +3 -0
  26. package/dist/modes/interactive/components/tool-group.d.ts.map +1 -1
  27. package/dist/modes/interactive/components/tool-group.js +13 -0
  28. package/dist/modes/interactive/components/tool-group.js.map +1 -1
  29. package/dist/modes/interactive/interactive-mode.d.ts +13 -0
  30. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  31. package/dist/modes/interactive/interactive-mode.js +356 -21
  32. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  33. package/docs/extensions.md +1 -1
  34. package/docs/settings.md +30 -6
  35. package/docs/usage.md +1 -1
  36. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  37. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  38. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  39. package/examples/extensions/sandbox/package-lock.json +2 -2
  40. package/examples/extensions/sandbox/package.json +1 -1
  41. package/examples/extensions/with-deps/package-lock.json +2 -2
  42. package/examples/extensions/with-deps/package.json +1 -1
  43. package/npm-shrinkwrap.json +12 -12
  44. package/package.json +4 -4
@@ -89,6 +89,7 @@ function isDeadTerminalError(error) {
89
89
  }
90
90
  const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage.";
91
91
  const AUTO_LEARN_DEFAULTS = {
92
+ enabled: false,
92
93
  model: "active",
93
94
  longSessionMessages: 32,
94
95
  longSessionContextPercent: 70,
@@ -96,7 +97,53 @@ const AUTO_LEARN_DEFAULTS = {
96
97
  leaseMinutes: 90,
97
98
  maxConcurrentLearners: 2,
98
99
  applyHighConfidence: false,
100
+ reflectionReview: true,
101
+ reflectionMinToolCalls: 5,
102
+ reflectionCooldownMinutes: 60,
99
103
  };
104
+ const AUTONOMY_AUTO_LEARN_PRESETS = {
105
+ off: { ...AUTO_LEARN_DEFAULTS, enabled: false, reflectionReview: false },
106
+ safe: {
107
+ ...AUTO_LEARN_DEFAULTS,
108
+ enabled: true,
109
+ longSessionMessages: 48,
110
+ longSessionContextPercent: 80,
111
+ cooldownMinutes: 180,
112
+ leaseMinutes: 60,
113
+ maxConcurrentLearners: 1,
114
+ applyHighConfidence: false,
115
+ reflectionReview: true,
116
+ reflectionMinToolCalls: 8,
117
+ reflectionCooldownMinutes: 120,
118
+ },
119
+ balanced: {
120
+ ...AUTO_LEARN_DEFAULTS,
121
+ enabled: true,
122
+ longSessionMessages: 32,
123
+ longSessionContextPercent: 70,
124
+ cooldownMinutes: 120,
125
+ leaseMinutes: 90,
126
+ maxConcurrentLearners: 2,
127
+ applyHighConfidence: false,
128
+ reflectionReview: true,
129
+ reflectionMinToolCalls: 5,
130
+ reflectionCooldownMinutes: 60,
131
+ },
132
+ full: {
133
+ ...AUTO_LEARN_DEFAULTS,
134
+ enabled: true,
135
+ longSessionMessages: 8,
136
+ longSessionContextPercent: 50,
137
+ cooldownMinutes: 15,
138
+ leaseMinutes: 90,
139
+ maxConcurrentLearners: 3,
140
+ applyHighConfidence: true,
141
+ reflectionReview: true,
142
+ reflectionMinToolCalls: 1,
143
+ reflectionCooldownMinutes: 0,
144
+ },
145
+ };
146
+ const AUTONOMY_MODES = ["off", "safe", "balanced", "full"];
100
147
  function isAnthropicSubscriptionAuthKey(apiKey) {
101
148
  return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
102
149
  }
@@ -1299,15 +1346,38 @@ export class InteractiveMode {
1299
1346
  }
1300
1347
  this.chatContainer.addChild(component);
1301
1348
  }
1349
+ detachToolExecutionComponent(component) {
1350
+ const children = this.chatContainer.children;
1351
+ const directIndex = children.indexOf(component);
1352
+ if (directIndex !== -1) {
1353
+ children.splice(directIndex, 1);
1354
+ return;
1355
+ }
1356
+ for (let i = 0; i < children.length; i++) {
1357
+ const child = children[i];
1358
+ if (!(child instanceof ToolGroupComponent) || !child.removeTool(component))
1359
+ continue;
1360
+ const remaining = child.getToolCount();
1361
+ if (remaining === 0) {
1362
+ children.splice(i, 1);
1363
+ }
1364
+ else if (remaining === 1) {
1365
+ const onlyTool = child.getOnlyTool();
1366
+ if (onlyTool)
1367
+ children[i] = onlyTool;
1368
+ }
1369
+ return;
1370
+ }
1371
+ }
1302
1372
  attachToolExecutionComponent(toolName, toolCallId, args) {
1303
1373
  const actionKey = getToolPanelActionKey(this.getToolPanelScope(), toolName, args);
1304
1374
  const toolDefinition = this.getRegisteredToolDefinition(toolName);
1305
1375
  const existing = this.toolPanels.getReusable(actionKey);
1306
1376
  if (existing) {
1307
- this.chatContainer.removeChild(existing);
1377
+ this.detachToolExecutionComponent(existing);
1308
1378
  existing.resetInvocation(toolName, toolCallId, args, toolDefinition);
1309
1379
  existing.setExpanded(this.toolOutputExpanded);
1310
- this.chatContainer.addChild(existing);
1380
+ this.appendToolExecutionComponent(existing, true);
1311
1381
  this.toolPanels.register(toolCallId, existing, actionKey);
1312
1382
  return existing;
1313
1383
  }
@@ -1316,7 +1386,7 @@ export class InteractiveMode {
1316
1386
  imageWidthCells: this.settingsManager.getImageWidthCells(),
1317
1387
  }, toolDefinition, this.ui, this.sessionManager.getCwd());
1318
1388
  component.setExpanded(this.toolOutputExpanded);
1319
- this.appendToolExecutionComponent(component, !actionKey);
1389
+ this.appendToolExecutionComponent(component, true);
1320
1390
  this.toolPanels.register(toolCallId, component, actionKey);
1321
1391
  return component;
1322
1392
  }
@@ -2067,6 +2137,11 @@ export class InteractiveMode {
2067
2137
  this.editor.setText("");
2068
2138
  return;
2069
2139
  }
2140
+ if (text === "/autonomy" || text.startsWith("/autonomy ")) {
2141
+ this.handleAutonomyCommand(text);
2142
+ this.editor.setText("");
2143
+ return;
2144
+ }
2070
2145
  if (text === "/scoped-models") {
2071
2146
  this.editor.setText("");
2072
2147
  await this.showModelsSelector();
@@ -2388,7 +2463,9 @@ export class InteractiveMode {
2388
2463
  break;
2389
2464
  }
2390
2465
  case "agent_end":
2391
- this.maybeStartAutoLearn();
2466
+ if (!this.maybeStartAutoLearn()) {
2467
+ this.maybeStartAutonomyReview(event.messages);
2468
+ }
2392
2469
  if (this.settingsManager.getShowTerminalProgress()) {
2393
2470
  this.ui.terminal.setProgress(false);
2394
2471
  }
@@ -3395,17 +3472,25 @@ export class InteractiveMode {
3395
3472
  }
3396
3473
  return { ...state, runs };
3397
3474
  }
3475
+ getAutoLearnPresetForAutonomyMode(mode, current = {}) {
3476
+ const preset = AUTONOMY_AUTO_LEARN_PRESETS[mode] ?? AUTONOMY_AUTO_LEARN_PRESETS.off;
3477
+ return { ...preset, model: current.model?.trim() || preset.model };
3478
+ }
3398
3479
  getEffectiveAutoLearnSettings() {
3399
3480
  const settings = this.settingsManager.getAutoLearnSettings();
3481
+ const preset = this.getAutoLearnPresetForAutonomyMode(this.settingsManager.getAutonomySettings().mode, settings);
3400
3482
  return {
3401
- enabled: settings.enabled ?? false,
3402
- model: settings.model?.trim() || AUTO_LEARN_DEFAULTS.model,
3403
- longSessionMessages: settings.longSessionMessages ?? AUTO_LEARN_DEFAULTS.longSessionMessages,
3404
- longSessionContextPercent: settings.longSessionContextPercent ?? AUTO_LEARN_DEFAULTS.longSessionContextPercent,
3405
- cooldownMinutes: settings.cooldownMinutes ?? AUTO_LEARN_DEFAULTS.cooldownMinutes,
3406
- leaseMinutes: settings.leaseMinutes ?? AUTO_LEARN_DEFAULTS.leaseMinutes,
3407
- maxConcurrentLearners: settings.maxConcurrentLearners ?? AUTO_LEARN_DEFAULTS.maxConcurrentLearners,
3408
- applyHighConfidence: settings.applyHighConfidence ?? AUTO_LEARN_DEFAULTS.applyHighConfidence,
3483
+ enabled: settings.enabled ?? preset.enabled,
3484
+ model: settings.model?.trim() || preset.model,
3485
+ longSessionMessages: settings.longSessionMessages ?? preset.longSessionMessages,
3486
+ longSessionContextPercent: settings.longSessionContextPercent ?? preset.longSessionContextPercent,
3487
+ cooldownMinutes: settings.cooldownMinutes ?? preset.cooldownMinutes,
3488
+ leaseMinutes: settings.leaseMinutes ?? preset.leaseMinutes,
3489
+ maxConcurrentLearners: settings.maxConcurrentLearners ?? preset.maxConcurrentLearners,
3490
+ applyHighConfidence: settings.applyHighConfidence ?? preset.applyHighConfidence,
3491
+ reflectionReview: settings.reflectionReview ?? preset.reflectionReview,
3492
+ reflectionMinToolCalls: settings.reflectionMinToolCalls ?? preset.reflectionMinToolCalls,
3493
+ reflectionCooldownMinutes: settings.reflectionCooldownMinutes ?? preset.reflectionCooldownMinutes,
3409
3494
  };
3410
3495
  }
3411
3496
  getAutoLearnTenantKey() {
@@ -3542,10 +3627,41 @@ export class InteractiveMode {
3542
3627
  runningCount,
3543
3628
  };
3544
3629
  }
3545
- buildAutoLearnPrompt(reason, settings) {
3546
- return `You are Pi Auto Learn running as a background learner.\n\nObjective: run one bounded continuous-learning pass for this Pi tenant.\nTrigger: ${reason}.\n\nRequired workflow:\n1. Query existing durable memory/rules first when tools allow it.\n2. Run the available Auto Learn tooling, preferably learning_run_auto, with applyHighConfidence=${settings.applyHighConfidence}.\n3. Keep tooling/core/source changes proposal/approval-gated; do not publish, tag, release, or modify Pi source from this background run.\n4. If the learning tools are unavailable, report BLOCKED with the missing tool names and do not improvise.\n5. Finish with PASS, BLOCKED, or FAIL and concise evidence.`;
3630
+ buildAutonomyAuthorityPrompt() {
3631
+ const autonomy = this.settingsManager.getAutonomySettings();
3632
+ const selfModification = this.settingsManager.getSelfModificationSettings();
3633
+ if (autonomy.mode !== "full") {
3634
+ return [
3635
+ "Authority mode: proposal-gated.",
3636
+ "- You may autonomously query memory and run bounded learning tools.",
3637
+ "- Durable memory writes require the configured high-confidence policy; skills, extensions, source, settings, publishing, tagging, and releases remain proposals unless the foreground user explicitly asks.",
3638
+ ].join("\n");
3639
+ }
3640
+ const sourceAuthority = selfModification.enabled && selfModification.sourcePath?.trim()
3641
+ ? `- Pi source: standing authority to edit the authorized selfModification.sourcePath (${selfModification.sourcePath.trim()}) for self-evolution improvements; inspect git status first, preserve unrelated user changes, run focused validation, and leave a clear rollback/test summary.`
3642
+ : "- Pi source: no standing source-edit authority until selfModification.enabled and selfModification.sourcePath are set.";
3643
+ return [
3644
+ "Authority mode: FULL AUTONOMOUS standing grant.",
3645
+ "- Memory: may apply high-confidence durable Automata memory/corrections after duplicate and corroboration checks.",
3646
+ "- Skills: may create or patch user/project-owned procedural skills and support files; prefer updating existing umbrella skills; do not delete skills without making a recoverable archive/backup.",
3647
+ "- User/project extensions and tools: may create or patch small scoped extensions/tools under Pi user/project roots when they address repeated workflow/tooling gaps; validate and keep changes auditable.",
3648
+ "- Settings: may auto-tune autonomy/autoLearn settings to reduce bottlenecks; do not modify credentials, provider auth, package sources, or unrelated user preferences.",
3649
+ sourceAuthority,
3650
+ "- Hard stops without explicit foreground approval: publish, npm release, git push, tag creation, credential changes, destructive user-data deletion, network-exposed services, or authority expansion beyond this policy.",
3651
+ "- Audit: final output must list changed paths/settings, commands/tests run, evidence, residual risks, and rollback guidance. If no safe validation is possible, leave the change as a proposal instead of applying it.",
3652
+ ].join("\n");
3547
3653
  }
3548
- launchAutoLearn(reason, force = false) {
3654
+ buildAutoLearnPrompt(reason, settings, options = {}) {
3655
+ const authorityBlock = this.buildAutonomyAuthorityPrompt();
3656
+ const reflectionBlock = options.kind === "reflection" && options.turnDigest
3657
+ ? `\n\nLatest completed turn digest (bounded; use only as current-session evidence, not as longitudinal proof):\n<turn_digest>\n${options.turnDigest}\n</turn_digest>`
3658
+ : "";
3659
+ const objective = options.kind === "reflection"
3660
+ ? "review the latest completed turn for durable memory, skill, validation, and tooling-improvement cues, then run one bounded continuous-learning pass if the learning tools are available"
3661
+ : "run one bounded continuous-learning pass for this Pi tenant";
3662
+ return `You are Pi Auto Learn running as a background learner.\n\nObjective: ${objective}.\nTrigger: ${reason}.\n\n${authorityBlock}\n\nRequired workflow:\n1. Query existing durable memory/rules first when tools allow it.\n2. Run the available Auto Learn tooling, preferably learning_run_auto, with applyHighConfidence=${settings.applyHighConfidence}.\n3. Treat the latest-turn digest as current-session evidence only; do not auto-commit one-off cues unless deterministic tooling corroborates them.\n4. In mode=full, apply safe memory/skill/user-extension/authorized-source improvements under the standing grant above; otherwise keep them proposal-gated.\n5. Never cross hard-stop boundaries from the authority policy.\n6. If the learning tools are unavailable, report BLOCKED with the missing tool names and do not improvise.\n7. Finish with PASS, BLOCKED, or FAIL and concise evidence.${reflectionBlock}`;
3663
+ }
3664
+ launchAutoLearn(reason, force = false, options = {}) {
3549
3665
  const settings = this.getEffectiveAutoLearnSettings();
3550
3666
  const decision = this.evaluateAutoLearn(force);
3551
3667
  if (!decision.shouldRun) {
@@ -3563,8 +3679,14 @@ export class InteractiveMode {
3563
3679
  fs.mkdirSync(dir, { recursive: true });
3564
3680
  const runId = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
3565
3681
  const logPath = path.join(dir, `${runId}.log`);
3682
+ const promptPath = path.join(dir, `${runId}.prompt.md`);
3566
3683
  const outFd = fs.openSync(logPath, "a");
3567
- const prompt = this.buildAutoLearnPrompt(reason, settings);
3684
+ const kind = options.promptKind ?? "auto";
3685
+ const prompt = this.buildAutoLearnPrompt(reason, settings, {
3686
+ kind,
3687
+ turnDigest: options.turnDigest,
3688
+ });
3689
+ fs.writeFileSync(promptPath, prompt, "utf-8");
3568
3690
  const args = [
3569
3691
  ...spawnTarget.argsPrefix,
3570
3692
  "--print",
@@ -3584,7 +3706,15 @@ export class InteractiveMode {
3584
3706
  fs.closeSync(outFd);
3585
3707
  const now = Date.now();
3586
3708
  const state = this.pruneAutoLearnState(this.readAutoLearnState(), now);
3587
- state.lastLaunchByTenant = { ...(state.lastLaunchByTenant ?? {}), [this.getAutoLearnTenantKey()]: now };
3709
+ if (options.cooldownKind === "reflection") {
3710
+ state.lastReflectionByTenant = {
3711
+ ...(state.lastReflectionByTenant ?? {}),
3712
+ [this.getAutoLearnTenantKey()]: now,
3713
+ };
3714
+ }
3715
+ else {
3716
+ state.lastLaunchByTenant = { ...(state.lastLaunchByTenant ?? {}), [this.getAutoLearnTenantKey()]: now };
3717
+ }
3588
3718
  state.runs = {
3589
3719
  ...(state.runs ?? {}),
3590
3720
  [runId]: {
@@ -3596,6 +3726,12 @@ export class InteractiveMode {
3596
3726
  expiresAt: now + settings.leaseMinutes * 60 * 1000,
3597
3727
  cwd: this.sessionManager.getCwd(),
3598
3728
  logPath,
3729
+ promptPath,
3730
+ kind,
3731
+ autonomyMode: this.settingsManager.getAutonomySettings().mode,
3732
+ authority: this.settingsManager.getAutonomySettings().mode === "full"
3733
+ ? "standing-full-autonomous"
3734
+ : "proposal-gated",
3599
3735
  },
3600
3736
  };
3601
3737
  this.writeAutoLearnState(state);
@@ -3603,17 +3739,159 @@ export class InteractiveMode {
3603
3739
  this.updateAutoLearnFooter();
3604
3740
  return `Auto Learn started (${reason}) with ${modelPattern}. Log: ${logPath}`;
3605
3741
  }
3742
+ sanitizeAutoLearnDigestText(text) {
3743
+ return text
3744
+ .replace(/-----BEGIN [A-Z ]*(?:PRIVATE|OPENSSH|RSA|DSA|EC) KEY-----[\s\S]*?-----END [A-Z ]*(?:PRIVATE|OPENSSH|RSA|DSA|EC) KEY-----/g, "[redacted-private-key]")
3745
+ .replace(/\b(?:sk|pk)-(?:proj-)?[A-Za-z0-9_-]{12,}/g, "[redacted-api-key]")
3746
+ .replace(/\bsk-ant-[A-Za-z0-9_-]{12,}/g, "[redacted-api-key]")
3747
+ .replace(/\b(?:ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_]{20,}/g, "[redacted-github-token]")
3748
+ .replace(/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g, "[redacted-aws-access-key]")
3749
+ .replace(/(?:Bearer\s+)[A-Za-z0-9._-]{16,}/gi, "Bearer [redacted]")
3750
+ .replace(/([?&](?:key|token|api_key|access_token|secret|password)=)[^&\s]+/gi, "$1[redacted]")
3751
+ .replace(/((?:access|refresh|token|apiKey|api_key|password|secret|authorization|auth)\s*[:=]\s*)[^\s,'"}]{8,}/gi, "$1[redacted]");
3752
+ }
3753
+ capAutoLearnDigestText(text, maxChars) {
3754
+ const compact = this.sanitizeAutoLearnDigestText(text).replace(/\s+/g, " ").trim();
3755
+ if (compact.length <= maxChars)
3756
+ return compact;
3757
+ return `${compact.slice(0, Math.max(0, maxChars - 20)).trimEnd()} …[truncated]`;
3758
+ }
3759
+ getAgentMessagePlainText(message) {
3760
+ const raw = message;
3761
+ const content = raw.content;
3762
+ if (typeof content === "string")
3763
+ return content;
3764
+ if (!Array.isArray(content))
3765
+ return "";
3766
+ const parts = [];
3767
+ for (const block of content) {
3768
+ if (!block || typeof block !== "object")
3769
+ continue;
3770
+ const item = block;
3771
+ if (item.type === "text" && typeof item.text === "string")
3772
+ parts.push(item.text);
3773
+ if (item.type === "toolCall" && typeof item.name === "string")
3774
+ parts.push(`[tool call: ${item.name}]`);
3775
+ }
3776
+ return parts.join("\n");
3777
+ }
3778
+ countAgentToolCalls(messages) {
3779
+ let toolCalls = 0;
3780
+ let toolResults = 0;
3781
+ for (const message of messages) {
3782
+ const raw = message;
3783
+ const role = String(raw.role ?? "");
3784
+ if (role === "toolResult" || role === "bashExecution")
3785
+ toolResults++;
3786
+ const content = raw.content;
3787
+ if (!Array.isArray(content))
3788
+ continue;
3789
+ for (const block of content) {
3790
+ if (block && typeof block === "object" && block.type === "toolCall") {
3791
+ toolCalls++;
3792
+ }
3793
+ }
3794
+ }
3795
+ return Math.max(toolCalls, toolResults);
3796
+ }
3797
+ buildAutonomyReviewDigest(messages) {
3798
+ const lines = [];
3799
+ for (const message of messages.slice(-18)) {
3800
+ const raw = message;
3801
+ const role = String(raw.role ?? "message");
3802
+ const label = role === "toolResult" && typeof raw.toolName === "string" ? `toolResult:${raw.toolName}` : role;
3803
+ const text = this.capAutoLearnDigestText(this.getAgentMessagePlainText(message), 700);
3804
+ if (text)
3805
+ lines.push(`${label}: ${text}`);
3806
+ }
3807
+ const digest = lines.join("\n---\n");
3808
+ return this.capAutoLearnDigestText(digest || "[No textual turn digest available.]", 6000);
3809
+ }
3810
+ evaluateAutonomyReview(messages) {
3811
+ const settings = this.getEffectiveAutoLearnSettings();
3812
+ const autonomy = this.settingsManager.getAutonomySettings();
3813
+ const state = this.pruneAutoLearnState(this.readAutoLearnState());
3814
+ this.writeAutoLearnState(state);
3815
+ const now = Date.now();
3816
+ const tenant = this.getAutoLearnTenantKey();
3817
+ const runningCount = Object.keys(state.runs ?? {}).length;
3818
+ const lastReflection = state.lastReflectionByTenant?.[tenant] ?? 0;
3819
+ const cooldownMs = settings.reflectionCooldownMinutes * 60 * 1000;
3820
+ const cooldownRemainingMs = Math.max(0, lastReflection + cooldownMs - now);
3821
+ const messageCount = this.getAutoLearnMessageCount();
3822
+ const contextPercent = this.session.getContextUsage()?.percent ?? null;
3823
+ const toolCalls = this.countAgentToolCalls(messages);
3824
+ const userText = messages
3825
+ .filter((message) => String(message.role ?? "") === "user")
3826
+ .map((message) => this.getAgentMessagePlainText(message))
3827
+ .join("\n");
3828
+ const correctionSignal = /\b(next time|for future|from now on|remember this|don't|do not|avoid|instead|you should|should have|you forgot|you missed|not what i asked|wrong again)\b/i.test(userText);
3829
+ const base = { messageCount, contextPercent, cooldownRemainingMs, runningCount, toolCalls };
3830
+ if (!settings.enabled)
3831
+ return { ...base, shouldRun: false, reason: "disabled" };
3832
+ if (!settings.reflectionReview)
3833
+ return { ...base, shouldRun: false, reason: "reflection disabled" };
3834
+ if (runningCount >= settings.maxConcurrentLearners) {
3835
+ return {
3836
+ ...base,
3837
+ shouldRun: false,
3838
+ reason: `max learners running (${runningCount}/${settings.maxConcurrentLearners})`,
3839
+ };
3840
+ }
3841
+ if (cooldownRemainingMs > 0)
3842
+ return { ...base, shouldRun: false, reason: "reflection cooldown" };
3843
+ if (correctionSignal) {
3844
+ return {
3845
+ ...base,
3846
+ shouldRun: true,
3847
+ reason: "reflection correction signal",
3848
+ digest: this.buildAutonomyReviewDigest(messages),
3849
+ };
3850
+ }
3851
+ if (autonomy.mode === "full") {
3852
+ return {
3853
+ ...base,
3854
+ shouldRun: true,
3855
+ reason: "full autonomy post-turn review",
3856
+ digest: this.buildAutonomyReviewDigest(messages),
3857
+ };
3858
+ }
3859
+ if (toolCalls >= settings.reflectionMinToolCalls) {
3860
+ return {
3861
+ ...base,
3862
+ shouldRun: true,
3863
+ reason: `reflection tool trigger (${toolCalls}/${settings.reflectionMinToolCalls})`,
3864
+ digest: this.buildAutonomyReviewDigest(messages),
3865
+ };
3866
+ }
3867
+ return { ...base, shouldRun: false, reason: "reflection thresholds not met" };
3868
+ }
3606
3869
  maybeStartAutoLearn() {
3607
3870
  if (process.env.PI_AUTO_LEARN_CHILD === "1")
3608
- return;
3871
+ return false;
3609
3872
  const decision = this.evaluateAutoLearn(false);
3610
3873
  if (!decision.shouldRun) {
3611
3874
  this.autoLearnLastStatus = decision.reason;
3612
3875
  this.updateAutoLearnFooter();
3613
- return;
3876
+ return false;
3614
3877
  }
3615
3878
  const message = this.launchAutoLearn(decision.reason, false);
3616
3879
  this.showStatus(message);
3880
+ return message.startsWith("Auto Learn started");
3881
+ }
3882
+ maybeStartAutonomyReview(messages) {
3883
+ if (process.env.PI_AUTO_LEARN_CHILD === "1")
3884
+ return false;
3885
+ const decision = this.evaluateAutonomyReview(messages);
3886
+ if (!decision.shouldRun)
3887
+ return false;
3888
+ const message = this.launchAutoLearn(decision.reason, true, {
3889
+ cooldownKind: "reflection",
3890
+ promptKind: "reflection",
3891
+ turnDigest: decision.digest,
3892
+ });
3893
+ this.showStatus(message);
3894
+ return message.startsWith("Auto Learn started");
3617
3895
  }
3618
3896
  updateAutoLearnFooter() {
3619
3897
  const settings = this.getEffectiveAutoLearnSettings();
@@ -3633,9 +3911,60 @@ export class InteractiveMode {
3633
3911
  const contextText = decision.contextPercent === null ? "unknown" : `${decision.contextPercent.toFixed(1)}%`;
3634
3912
  const cooldownText = decision.cooldownRemainingMs > 0 ? `${Math.ceil(decision.cooldownRemainingMs / 60000)}m remaining` : "ready";
3635
3913
  const runLines = runs.length
3636
- ? runs.map(([id, run]) => `- ${id}: ${run.model}, pid=${run.pid ?? "?"}, log=${run.logPath}`).join("\n")
3914
+ ? runs
3915
+ .map(([id, run]) => `- ${id}: ${run.model}, kind=${run.kind ?? "auto"}, authority=${run.authority ?? "unknown"}, pid=${run.pid ?? "?"}, log=${run.logPath}`)
3916
+ .join("\n")
3637
3917
  : "- none";
3638
- return `Auto Learn status\nEnabled: ${settings.enabled}\nModel: ${settings.model}\nNext decision: ${decision.shouldRun ? "ready" : decision.reason}\nMessages: ${decision.messageCount}/${settings.longSessionMessages}\nContext: ${contextText}/${settings.longSessionContextPercent}%\nCooldown: ${cooldownText}\nRunning leases: ${runs.length}/${settings.maxConcurrentLearners}\nRuns:\n${runLines}`;
3918
+ const reflectionLast = state.lastReflectionByTenant?.[this.getAutoLearnTenantKey()] ?? 0;
3919
+ const reflectionCooldownRemainingMs = Math.max(0, reflectionLast + settings.reflectionCooldownMinutes * 60 * 1000 - Date.now());
3920
+ const reflectionCooldownText = reflectionCooldownRemainingMs > 0 ? `${Math.ceil(reflectionCooldownRemainingMs / 60000)}m remaining` : "ready";
3921
+ return `Auto Learn status\nEnabled: ${settings.enabled}\nModel: ${settings.model}\nNext decision: ${decision.shouldRun ? "ready" : decision.reason}\nMessages: ${decision.messageCount}/${settings.longSessionMessages}\nContext: ${contextText}/${settings.longSessionContextPercent}%\nCooldown: ${cooldownText}\nReflection review: ${settings.reflectionReview ? "enabled" : "disabled"} (tool trigger ${settings.reflectionMinToolCalls}, cooldown ${reflectionCooldownText})\nRunning leases: ${runs.length}/${settings.maxConcurrentLearners}\nRuns:\n${runLines}`;
3922
+ }
3923
+ formatAutonomyStatus() {
3924
+ const autonomy = this.settingsManager.getAutonomySettings();
3925
+ const settings = this.getEffectiveAutoLearnSettings();
3926
+ const autoLearnState = this.pruneAutoLearnState(this.readAutoLearnState());
3927
+ const running = Object.entries(autoLearnState.runs ?? {});
3928
+ const safety = autonomy.mode === "full"
3929
+ ? "standing grant for memory, skills, user/project extensions, autonomy/autoLearn tuning, and authorized selfModification.sourcePath edits; hard stops still require explicit foreground approval"
3930
+ : "proposal-gated outside configured high-confidence memory policy";
3931
+ const reflectionLine = autonomy.mode === "full"
3932
+ ? `Reflection review: ${settings.reflectionReview ? "enabled" : "disabled"}; post-turn when concurrency allows; cooldown=${settings.reflectionCooldownMinutes}m`
3933
+ : `Reflection review: ${settings.reflectionReview ? "enabled" : "disabled"}; tool trigger=${settings.reflectionMinToolCalls}; cooldown=${settings.reflectionCooldownMinutes}m`;
3934
+ return [
3935
+ "Autonomy status",
3936
+ `Mode: ${autonomy.mode}${autonomy.mode === "full" ? " (standing autonomy)" : ""}`,
3937
+ `Auto Learn: ${settings.enabled ? "enabled" : "disabled"}; model=${settings.model}; applyHighConfidence=${settings.applyHighConfidence}`,
3938
+ `Long-session trigger: ${settings.longSessionMessages} messages or ${settings.longSessionContextPercent}% context; cooldown=${settings.cooldownMinutes}m`,
3939
+ reflectionLine,
3940
+ `Running learners: ${running.length}/${settings.maxConcurrentLearners}`,
3941
+ `Standing authority: ${safety}`,
3942
+ `Audit/log dir: ${this.getAutoLearnDataDir()}`,
3943
+ "Use /autonomy off|safe|balanced|full to switch presets. Advanced overrides remain in /settings → Auto Learn Advanced.",
3944
+ ].join("\n");
3945
+ }
3946
+ applyAutonomyMode(mode, scope = "global") {
3947
+ const currentAutoLearn = this.settingsManager.getAutoLearnSettings();
3948
+ const preset = this.getAutoLearnPresetForAutonomyMode(mode, currentAutoLearn);
3949
+ this.settingsManager.setAutonomySettings({ mode }, scope);
3950
+ this.settingsManager.setAutoLearnSettings(preset, scope);
3951
+ this.updateAutoLearnFooter();
3952
+ }
3953
+ handleAutonomyCommand(text) {
3954
+ const action = text.slice("/autonomy".length).trim() || "status";
3955
+ if (AUTONOMY_MODES.includes(action)) {
3956
+ const mode = action;
3957
+ this.applyAutonomyMode(mode);
3958
+ this.showStatus(`Autonomy mode set to ${mode}${mode === "full" ? " (standing autonomy)" : ""}.`);
3959
+ return;
3960
+ }
3961
+ if (action === "status") {
3962
+ this.chatContainer.addChild(new Spacer(1));
3963
+ this.chatContainer.addChild(new Text(this.formatAutonomyStatus(), 1, 0));
3964
+ this.ui.requestRender();
3965
+ return;
3966
+ }
3967
+ this.showStatus("Usage: /autonomy [status|off|safe|balanced|full]");
3639
3968
  }
3640
3969
  handleAutoLearnCommand(text) {
3641
3970
  const action = text.slice("/auto-learn".length).trim() || "status";
@@ -3683,6 +4012,8 @@ export class InteractiveMode {
3683
4012
  warnings: this.settingsManager.getWarnings(),
3684
4013
  selfModification: this.settingsManager.getSelfModificationSettings(),
3685
4014
  selfModificationScope: projectSettings.selfModification ? "project" : "global",
4015
+ autonomy: this.settingsManager.getAutonomySettings(),
4016
+ autonomyScope: projectSettings.autonomy ? "project" : "global",
3686
4017
  autoLearn: this.settingsManager.getAutoLearnSettings(),
3687
4018
  autoLearnScope: projectSettings.autoLearn ? "project" : "global",
3688
4019
  autoLearnModelOptions: this.getAutoLearnModelOptions(),
@@ -3817,6 +4148,10 @@ export class InteractiveMode {
3817
4148
  }
3818
4149
  this.showStatus(`Self modification settings saved to ${scope}. Start a new session or /reload for system-prompt guardrails to fully refresh.`);
3819
4150
  },
4151
+ onAutonomyChange: (settings, scope) => {
4152
+ this.applyAutonomyMode(settings.mode ?? "off", scope);
4153
+ this.showStatus(`Autonomy mode ${settings.mode ?? "off"} saved to ${scope}. Use /autonomy status.`);
4154
+ },
3820
4155
  onAutoLearnChange: (settings, scope) => {
3821
4156
  this.settingsManager.setAutoLearnSettings(settings, scope);
3822
4157
  const validationMessage = this.validateAutoLearnModelValue(settings.model);