@caupulican/pi-adaptative 0.80.7 → 0.80.9

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 (96) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -1
  3. package/dist/cli/args.d.ts +1 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +16 -4
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +17 -2
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +9 -1
  12. package/dist/config.js.map +1 -1
  13. package/dist/core/auth-storage.d.ts.map +1 -1
  14. package/dist/core/auth-storage.js +4 -3
  15. package/dist/core/auth-storage.js.map +1 -1
  16. package/dist/core/export-html/template.js +19 -6
  17. package/dist/core/package-manager.d.ts +3 -0
  18. package/dist/core/package-manager.d.ts.map +1 -1
  19. package/dist/core/package-manager.js +47 -13
  20. package/dist/core/package-manager.js.map +1 -1
  21. package/dist/core/reload-blockers.d.ts +2 -0
  22. package/dist/core/reload-blockers.d.ts.map +1 -1
  23. package/dist/core/reload-blockers.js +8 -2
  24. package/dist/core/reload-blockers.js.map +1 -1
  25. package/dist/core/resource-loader.d.ts +1 -0
  26. package/dist/core/resource-loader.d.ts.map +1 -1
  27. package/dist/core/resource-loader.js +30 -22
  28. package/dist/core/resource-loader.js.map +1 -1
  29. package/dist/core/sdk.d.ts.map +1 -1
  30. package/dist/core/sdk.js +5 -3
  31. package/dist/core/sdk.js.map +1 -1
  32. package/dist/core/settings-manager.d.ts +10 -2
  33. package/dist/core/settings-manager.d.ts.map +1 -1
  34. package/dist/core/settings-manager.js +71 -30
  35. package/dist/core/settings-manager.js.map +1 -1
  36. package/dist/core/skills.d.ts.map +1 -1
  37. package/dist/core/skills.js +7 -3
  38. package/dist/core/skills.js.map +1 -1
  39. package/dist/core/slash-commands.d.ts.map +1 -1
  40. package/dist/core/slash-commands.js +1 -0
  41. package/dist/core/slash-commands.js.map +1 -1
  42. package/dist/core/system-prompt.d.ts.map +1 -1
  43. package/dist/core/system-prompt.js +3 -0
  44. package/dist/core/system-prompt.js.map +1 -1
  45. package/dist/core/trust-manager.d.ts +9 -0
  46. package/dist/core/trust-manager.d.ts.map +1 -0
  47. package/dist/core/trust-manager.js +134 -0
  48. package/dist/core/trust-manager.js.map +1 -0
  49. package/dist/index.d.ts +2 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +1 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/main.d.ts.map +1 -1
  54. package/dist/main.js +65 -6
  55. package/dist/main.js.map +1 -1
  56. package/dist/modes/interactive/components/index.d.ts +1 -0
  57. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  58. package/dist/modes/interactive/components/index.js +1 -0
  59. package/dist/modes/interactive/components/index.js.map +1 -1
  60. package/dist/modes/interactive/components/login-dialog.d.ts +0 -1
  61. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  62. package/dist/modes/interactive/components/login-dialog.js +3 -12
  63. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  64. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  65. package/dist/modes/interactive/components/settings-selector.js +1 -1
  66. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  67. package/dist/modes/interactive/components/trust-selector.d.ts +20 -0
  68. package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -0
  69. package/dist/modes/interactive/components/trust-selector.js +86 -0
  70. package/dist/modes/interactive/components/trust-selector.js.map +1 -0
  71. package/dist/modes/interactive/interactive-mode.d.ts +5 -0
  72. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  73. package/dist/modes/interactive/interactive-mode.js +127 -26
  74. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  75. package/dist/package-manager-cli.d.ts.map +1 -1
  76. package/dist/package-manager-cli.js +55 -8
  77. package/dist/package-manager-cli.js.map +1 -1
  78. package/dist/utils/git.d.ts.map +1 -1
  79. package/dist/utils/git.js +54 -22
  80. package/dist/utils/git.js.map +1 -1
  81. package/dist/utils/open-browser.d.ts +9 -0
  82. package/dist/utils/open-browser.d.ts.map +1 -0
  83. package/dist/utils/open-browser.js +22 -0
  84. package/dist/utils/open-browser.js.map +1 -0
  85. package/docs/adaptive-extension-shared-state-audit.md +43 -0
  86. package/docs/settings.md +2 -2
  87. package/docs/skills.md +3 -3
  88. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  89. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  90. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  91. package/examples/extensions/sandbox/package-lock.json +2 -2
  92. package/examples/extensions/sandbox/package.json +1 -1
  93. package/examples/extensions/with-deps/package-lock.json +2 -2
  94. package/examples/extensions/with-deps/package.json +1 -1
  95. package/npm-shrinkwrap.json +12 -12
  96. package/package.json +4 -4
@@ -26,6 +26,7 @@ import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../cor
26
26
  import { isAutoLearnSessionId, SessionManager } from "../../core/session-manager.js";
27
27
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
28
28
  import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
29
+ import { hasProjectTrustInputs, ProjectTrustStore } from "../../core/trust-manager.js";
29
30
  import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
30
31
  import { copyToClipboard } from "../../utils/clipboard.js";
31
32
  import { readClipboardImage } from "../../utils/clipboard-image.js";
@@ -63,6 +64,7 @@ import { ToolExecutionComponent } from "./components/tool-execution.js";
63
64
  import { ToolGroupComponent } from "./components/tool-group.js";
64
65
  import { getToolPanelActionKey, ToolPanelRegistry } from "./components/tool-panel-registry.js";
65
66
  import { TreeSelectorComponent } from "./components/tree-selector.js";
67
+ import { TrustSelectorComponent } from "./components/trust-selector.js";
66
68
  import { UserMessageComponent } from "./components/user-message.js";
67
69
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
68
70
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
@@ -158,6 +160,14 @@ function definedStringSet(values) {
158
160
  }
159
161
  return set;
160
162
  }
163
+ function sanitizeAutoLearnPathPart(input, fallback) {
164
+ const cleaned = (input || fallback)
165
+ .replace(/[^a-zA-Z0-9._-]+/g, "-")
166
+ .replace(/-+/g, "-")
167
+ .replace(/^-+|-+$/g, "")
168
+ .slice(0, 80);
169
+ return cleaned || fallback;
170
+ }
161
171
  function isOldAutoLearnArtifact(filePath, now, retentionMs) {
162
172
  const stats = fs.lstatSync(filePath);
163
173
  return stats.isFile() && now - stats.mtimeMs > retentionMs;
@@ -171,6 +181,22 @@ function removeOldAutoLearnArtifact(filePath, result, counter) {
171
181
  result.errors++;
172
182
  }
173
183
  }
184
+ function isPathInside(target, root) {
185
+ const resolvedTarget = path.resolve(target);
186
+ const resolvedRoot = path.resolve(root);
187
+ return resolvedTarget === resolvedRoot || resolvedTarget.startsWith(`${resolvedRoot}${path.sep}`);
188
+ }
189
+ function removeAutoLearnArtifactPath(filePath, root) {
190
+ if (!isPathInside(filePath, root))
191
+ return false;
192
+ try {
193
+ fs.rmSync(filePath, { recursive: true, force: true });
194
+ return true;
195
+ }
196
+ catch {
197
+ return false;
198
+ }
199
+ }
174
200
  function readAutoLearnSessionIdFromFile(filePath) {
175
201
  let fd;
176
202
  try {
@@ -232,24 +258,20 @@ function pruneAutoLearnSessionFiles(dir, activeSessionIds, now, retentionMs, res
232
258
  removeOldAutoLearnArtifact(filePath, result, "sessionFiles");
233
259
  }
234
260
  }
235
- export function pruneAutoLearnConversationHistory(options) {
236
- const result = { promptFiles: 0, logFiles: 0, sessionFiles: 0, errors: 0 };
237
- const dataDir = path.resolve(options.dataDir);
238
- const now = options.now ?? Date.now();
239
- const retentionMs = options.retentionMs ?? AUTO_LEARN_HISTORY_RETENTION_MS;
240
- const activeRunIds = definedStringSet(options.activeRunIds);
241
- const activeSessionIds = definedStringSet(options.activeSessionIds);
242
- if (retentionMs <= 0 || !fs.existsSync(dataDir))
243
- return result;
261
+ function pruneAutoLearnRunArtifacts(dir, activeRunIds, now, retentionMs, result) {
244
262
  let entries;
245
263
  try {
246
- entries = fs.readdirSync(dataDir, { withFileTypes: true });
264
+ entries = fs.readdirSync(dir, { withFileTypes: true });
247
265
  }
248
266
  catch {
249
- result.errors++;
250
- return result;
267
+ return;
251
268
  }
252
269
  for (const entry of entries) {
270
+ const filePath = path.join(dir, entry.name);
271
+ if (entry.isDirectory()) {
272
+ pruneAutoLearnRunArtifacts(filePath, activeRunIds, now, retentionMs, result);
273
+ continue;
274
+ }
253
275
  if (!entry.isFile())
254
276
  continue;
255
277
  const promptRunId = entry.name.endsWith(".prompt.md") ? entry.name.slice(0, -".prompt.md".length) : undefined;
@@ -257,7 +279,6 @@ export function pruneAutoLearnConversationHistory(options) {
257
279
  const runId = promptRunId ?? logRunId;
258
280
  if (!runId || activeRunIds.has(runId))
259
281
  continue;
260
- const filePath = path.join(dataDir, entry.name);
261
282
  let shouldPrune = false;
262
283
  try {
263
284
  shouldPrune = isOldAutoLearnArtifact(filePath, now, retentionMs);
@@ -270,7 +291,18 @@ export function pruneAutoLearnConversationHistory(options) {
270
291
  continue;
271
292
  removeOldAutoLearnArtifact(filePath, result, promptRunId ? "promptFiles" : "logFiles");
272
293
  }
273
- pruneAutoLearnSessionFiles(path.join(dataDir, "sessions"), activeSessionIds, now, retentionMs, result);
294
+ }
295
+ export function pruneAutoLearnConversationHistory(options) {
296
+ const result = { promptFiles: 0, logFiles: 0, sessionFiles: 0, errors: 0 };
297
+ const dataDir = path.resolve(options.dataDir);
298
+ const now = options.now ?? Date.now();
299
+ const retentionMs = options.retentionMs ?? AUTO_LEARN_HISTORY_RETENTION_MS;
300
+ const activeRunIds = definedStringSet(options.activeRunIds);
301
+ const activeSessionIds = definedStringSet(options.activeSessionIds);
302
+ if (retentionMs <= 0 || !fs.existsSync(dataDir))
303
+ return result;
304
+ pruneAutoLearnRunArtifacts(dataDir, activeRunIds, now, retentionMs, result);
305
+ pruneAutoLearnSessionFiles(dataDir, activeSessionIds, now, retentionMs, result);
274
306
  return result;
275
307
  }
276
308
  export function buildAutoLearnSpawnArgs(spawnTarget, options) {
@@ -704,6 +736,7 @@ export class InteractiveMode {
704
736
  await this.rebindCurrentSession();
705
737
  // Render initial messages AFTER showing loaded resources
706
738
  this.renderInitialMessages();
739
+ this.renderProjectTrustWarningIfNeeded();
707
740
  // Set up theme file watcher
708
741
  onThemeChange(() => {
709
742
  this.ui.invalidate();
@@ -718,6 +751,15 @@ export class InteractiveMode {
718
751
  await this.updateAvailableProviderCount();
719
752
  this.updateAutoLearnFooter();
720
753
  }
754
+ renderProjectTrustWarningIfNeeded() {
755
+ if (this.settingsManager.isProjectTrusted() || !hasProjectTrustInputs(this.sessionManager.getCwd())) {
756
+ return;
757
+ }
758
+ if (this.chatContainer.children.length > 0) {
759
+ this.chatContainer.addChild(new Spacer(1));
760
+ }
761
+ this.chatContainer.addChild(new Text(theme.fg("warning", "This project is not trusted. Project instructions (AGENTS.md/CLAUDE.md/GEMINI.md), .pi resources, and project packages are ignored. Use /trust to save a trust decision, then restart pi."), 1, 0));
762
+ }
721
763
  /**
722
764
  * Update terminal title with session name and cwd.
723
765
  */
@@ -2389,6 +2431,11 @@ export class InteractiveMode {
2389
2431
  this.editor.setText("");
2390
2432
  return;
2391
2433
  }
2434
+ if (text === "/trust") {
2435
+ this.showTrustSelector();
2436
+ this.editor.setText("");
2437
+ return;
2438
+ }
2392
2439
  if (text === "/login" || text.startsWith("/login ")) {
2393
2440
  await this.showOAuthSelector("login", text.slice("/login".length).trim() || undefined);
2394
2441
  this.editor.setText("");
@@ -3759,13 +3806,21 @@ export class InteractiveMode {
3759
3806
  getAutoLearnTenantKey() {
3760
3807
  return `${this.sessionManager.getCwd()}::${this.session.sessionId}`;
3761
3808
  }
3809
+ getAutoLearnTenantId() {
3810
+ const cwdHash = crypto.createHash("sha256").update(this.sessionManager.getCwd()).digest("hex").slice(0, 8);
3811
+ const sessionPart = sanitizeAutoLearnPathPart(this.session.sessionId, "session");
3812
+ return `${sessionPart}-${cwdHash}`;
3813
+ }
3814
+ getAutoLearnTenantDataDir() {
3815
+ return path.join(this.getAutoLearnDataDir(), "tenants", this.getAutoLearnTenantId());
3816
+ }
3762
3817
  getAutoLearnMessageCount() {
3763
3818
  return this.sessionManager.getBranch().filter((entry) => entry.type === "message").length;
3764
3819
  }
3765
3820
  buildAutoLearnDecisionFromState(state, settings, force = false) {
3766
3821
  const now = Date.now();
3767
3822
  const tenant = this.getAutoLearnTenantKey();
3768
- const runningCount = Object.keys(state.runs ?? {}).length;
3823
+ const runningCount = Object.values(state.runs ?? {}).filter((run) => run.tenant === tenant).length;
3769
3824
  const lastLaunch = state.lastLaunchByTenant?.[tenant] ?? 0;
3770
3825
  const cooldownMs = settings.cooldownMinutes * 60 * 1000;
3771
3826
  const cooldownRemainingMs = Math.max(0, lastLaunch + cooldownMs - now);
@@ -3784,7 +3839,7 @@ export class InteractiveMode {
3784
3839
  if (runningCount >= settings.maxConcurrentLearners) {
3785
3840
  return {
3786
3841
  shouldRun: false,
3787
- reason: `max learners running (${runningCount}/${settings.maxConcurrentLearners})`,
3842
+ reason: `max tenant learners running (${runningCount}/${settings.maxConcurrentLearners})`,
3788
3843
  messageCount,
3789
3844
  contextPercent,
3790
3845
  cooldownRemainingMs,
@@ -3926,7 +3981,7 @@ export class InteractiveMode {
3926
3981
  const objective = options.kind === "reflection"
3927
3982
  ? "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"
3928
3983
  : "run one bounded continuous-learning pass for this Pi tenant";
3929
- 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}`;
3984
+ 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. Memory confrontation is mandatory before accepting, merging, upgrading, or rejecting learning candidates.\n2. Run the available Auto Learn tooling, preferably learning_run_auto, with applyHighConfidence=${settings.applyHighConfidence}. Process candidate validation in vectorized chunks/batches; avoid scalar per-candidate memory queries except for final selected writes.\n3. Apply the learning validation tree to each candidate chunk: (a) Why is this good for the user? (b) Is it unique, or similar to existing memory/skills/agents so it should merge or upgrade existing knowledge? (c) Will this make Pi a better agent? Candidates that cannot answer all three are noise.\n4. Treat the latest-turn digest as current-session evidence only; do not auto-commit one-off cues unless deterministic tooling and memory confrontation corroborate them.\n5. In mode=full, apply safe memory/skill/user-extension/authorized-source improvements under the standing grant above; otherwise keep them proposal-gated.\n6. Never cross hard-stop boundaries from the authority policy.\n7. If the learning tools are unavailable, report BLOCKED with the missing tool names and do not improvise.\n8. Finish with PASS, BLOCKED, or FAIL and concise evidence, including chunk counts, merge/upgrade decisions, and cleanup/purge status.${reflectionBlock}`;
3930
3985
  }
3931
3986
  reserveAutoLearnRun(params) {
3932
3987
  return this.withAutoLearnStateLock((current) => {
@@ -3993,6 +4048,20 @@ export class InteractiveMode {
3993
4048
  return { result: undefined, next };
3994
4049
  });
3995
4050
  }
4051
+ cleanupCompletedAutoLearnRun(runId, artifactPaths) {
4052
+ const dataDir = this.getAutoLearnDataDir();
4053
+ const removed = artifactPaths.filter((filePath) => removeAutoLearnArtifactPath(filePath, dataDir)).length;
4054
+ this.withAutoLearnStateLock((current) => {
4055
+ const state = this.pruneAutoLearnState(current);
4056
+ const runs = { ...(state.runs ?? {}) };
4057
+ delete runs[runId];
4058
+ return { result: undefined, next: { ...state, runs } };
4059
+ });
4060
+ if (removed > 0) {
4061
+ this.autoLearnLastStatus = `cleaned ${removed} artifact(s)`;
4062
+ this.updateAutoLearnFooter();
4063
+ }
4064
+ }
3996
4065
  markAutoLearnReservationRunning(reservation, pid, settings) {
3997
4066
  this.withAutoLearnStateLock((current) => {
3998
4067
  const now = Date.now();
@@ -4028,14 +4097,14 @@ export class InteractiveMode {
4028
4097
  if (!spawnTarget) {
4029
4098
  return "Auto Learn not started: could not resolve current pi CLI path.";
4030
4099
  }
4031
- const dir = this.getAutoLearnDataDir();
4100
+ const dir = this.getAutoLearnTenantDataDir();
4032
4101
  fs.mkdirSync(dir, { recursive: true });
4033
4102
  const runId = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
4034
4103
  const logPath = path.join(dir, `${runId}.log`);
4035
4104
  const promptPath = path.join(dir, `${runId}.prompt.md`);
4036
4105
  const kind = options.promptKind ?? "auto";
4037
- const sessionDir = path.join(dir, "sessions");
4038
- const sessionId = `auto-learn-${kind}-${runId}`;
4106
+ const sessionDir = path.join(dir, "sessions", runId);
4107
+ const sessionId = `auto-learn-${kind}-${this.getAutoLearnTenantId()}-${runId}`;
4039
4108
  fs.mkdirSync(sessionDir, { recursive: true });
4040
4109
  const prompt = this.buildAutoLearnPrompt(reason, settings, {
4041
4110
  kind,
@@ -4129,6 +4198,10 @@ export class InteractiveMode {
4129
4198
  return `Auto Learn not started: failed to spawn background learner. Log: ${logPath}`;
4130
4199
  }
4131
4200
  const childPid = child.pid;
4201
+ child.once("exit", (code) => {
4202
+ if (code === 0)
4203
+ this.cleanupCompletedAutoLearnRun(reservation.runId, [promptPath, logPath, sessionDir]);
4204
+ });
4132
4205
  child.unref();
4133
4206
  this.markAutoLearnReservationRunning(reservation, childPid, settings);
4134
4207
  this.autoLearnLastStatus = `running ${modelPattern}`;
@@ -4212,7 +4285,7 @@ export class InteractiveMode {
4212
4285
  });
4213
4286
  const now = Date.now();
4214
4287
  const tenant = this.getAutoLearnTenantKey();
4215
- const runningCount = Object.keys(state.runs ?? {}).length;
4288
+ const runningCount = Object.values(state.runs ?? {}).filter((run) => run.tenant === tenant).length;
4216
4289
  const lastReflection = state.lastReflectionByTenant?.[tenant] ?? 0;
4217
4290
  const cooldownMs = settings.reflectionCooldownMinutes * 60 * 1000;
4218
4291
  const cooldownRemainingMs = Math.max(0, lastReflection + cooldownMs - now);
@@ -4233,7 +4306,7 @@ export class InteractiveMode {
4233
4306
  return {
4234
4307
  ...base,
4235
4308
  shouldRun: false,
4236
- reason: `max learners running (${runningCount}/${settings.maxConcurrentLearners})`,
4309
+ reason: `max tenant learners running (${runningCount}/${settings.maxConcurrentLearners})`,
4237
4310
  };
4238
4311
  }
4239
4312
  if (cooldownRemainingMs > 0)
@@ -4305,7 +4378,9 @@ export class InteractiveMode {
4305
4378
  const settings = this.getEffectiveAutoLearnSettings();
4306
4379
  const decision = this.evaluateAutoLearn(false);
4307
4380
  const state = this.getPrunedAutoLearnState();
4308
- const runs = Object.entries(state.runs ?? {});
4381
+ const tenant = this.getAutoLearnTenantKey();
4382
+ const runs = Object.entries(state.runs ?? {}).filter(([, run]) => run.tenant === tenant);
4383
+ const otherTenantRuns = Object.values(state.runs ?? {}).filter((run) => run.tenant !== tenant).length;
4309
4384
  const contextText = decision.contextPercent === null ? "unknown" : `${decision.contextPercent.toFixed(1)}%`;
4310
4385
  const cooldownText = decision.cooldownRemainingMs > 0 ? `${Math.ceil(decision.cooldownRemainingMs / 60000)}m remaining` : "ready";
4311
4386
  const runLines = runs.length
@@ -4333,13 +4408,15 @@ export class InteractiveMode {
4333
4408
  const reflectionLast = state.lastReflectionByTenant?.[this.getAutoLearnTenantKey()] ?? 0;
4334
4409
  const reflectionCooldownRemainingMs = Math.max(0, reflectionLast + settings.reflectionCooldownMinutes * 60 * 1000 - Date.now());
4335
4410
  const reflectionCooldownText = reflectionCooldownRemainingMs > 0 ? `${Math.ceil(reflectionCooldownRemainingMs / 60000)}m remaining` : "ready";
4336
- 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})\nHistory retention: 7 days for internal Auto Learn prompts/logs/sessions\nRunning leases: ${runs.length}/${settings.maxConcurrentLearners}\nPi auto-reload blockers: ${reloadBlockers.pending ? reloadBlockers.reason : "none"}\n${reloadBlockerLines}\nRuns:\n${runLines}`;
4411
+ 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})\nHistory retention: 7 days for internal Auto Learn prompts/logs/sessions\nRunning tenant leases: ${runs.length}/${settings.maxConcurrentLearners}\nOther tenant leases: ${otherTenantRuns}\nTenant artifact dir: ${this.getAutoLearnTenantDataDir()}\nPi auto-reload blockers: ${reloadBlockers.pending ? reloadBlockers.reason : "none"}\n${reloadBlockerLines}\nRuns:\n${runLines}`;
4337
4412
  }
4338
4413
  formatAutonomyStatus() {
4339
4414
  const autonomy = this.settingsManager.getAutonomySettings();
4340
4415
  const settings = this.getEffectiveAutoLearnSettings();
4341
4416
  const autoLearnState = this.getPrunedAutoLearnState();
4342
- const running = Object.entries(autoLearnState.runs ?? {});
4417
+ const tenant = this.getAutoLearnTenantKey();
4418
+ const running = Object.entries(autoLearnState.runs ?? {}).filter(([, run]) => run.tenant === tenant);
4419
+ const otherTenantRunning = Object.values(autoLearnState.runs ?? {}).filter((run) => run.tenant !== tenant).length;
4343
4420
  const safety = autonomy.mode === "full"
4344
4421
  ? "standing grant for memory, skills, user/project extensions, autonomy/autoLearn tuning, and authorized selfModification.sourcePath edits; hard stops still require explicit foreground approval"
4345
4422
  : "proposal-gated outside configured high-confidence memory policy";
@@ -4352,10 +4429,12 @@ export class InteractiveMode {
4352
4429
  `Auto Learn: ${settings.enabled ? "enabled" : "disabled"}; model=${settings.model}; applyHighConfidence=${settings.applyHighConfidence}`,
4353
4430
  `Long-session trigger: ${settings.longSessionMessages} messages or ${settings.longSessionContextPercent}% context; cooldown=${settings.cooldownMinutes}m`,
4354
4431
  reflectionLine,
4355
- `Running learners: ${running.length}/${settings.maxConcurrentLearners}`,
4432
+ `Running tenant learners: ${running.length}/${settings.maxConcurrentLearners}`,
4433
+ `Other tenant learners: ${otherTenantRunning}`,
4356
4434
  "History retention: 7 days for internal Auto Learn prompts/logs/sessions",
4357
4435
  `Standing authority: ${safety}`,
4358
4436
  `Audit/log dir: ${this.getAutoLearnDataDir()}`,
4437
+ `Tenant artifact dir: ${this.getAutoLearnTenantDataDir()}`,
4359
4438
  "Use /autonomy off|safe|balanced|full to switch presets. Advanced overrides remain in /settings → Auto Learn Advanced.",
4360
4439
  ].join("\n");
4361
4440
  }
@@ -4918,6 +4997,28 @@ export class InteractiveMode {
4918
4997
  return { component: selector, focus: selector };
4919
4998
  });
4920
4999
  }
5000
+ showTrustSelector() {
5001
+ const cwd = this.sessionManager.getCwd();
5002
+ const trustStore = new ProjectTrustStore(this.runtimeHost.services.agentDir);
5003
+ const savedDecision = trustStore.get(cwd);
5004
+ this.showSelector((done) => {
5005
+ const selector = new TrustSelectorComponent({
5006
+ cwd,
5007
+ savedDecision,
5008
+ projectTrusted: this.settingsManager.isProjectTrusted(),
5009
+ onSelect: (trusted) => {
5010
+ trustStore.set(cwd, trusted);
5011
+ done();
5012
+ this.showStatus(`Saved trust decision: ${trusted ? "trusted" : "untrusted"}. Restart pi for this to take effect.`);
5013
+ },
5014
+ onCancel: () => {
5015
+ done();
5016
+ this.ui.requestRender();
5017
+ },
5018
+ });
5019
+ return { component: selector, focus: selector };
5020
+ });
5021
+ }
4921
5022
  showSessionSelector() {
4922
5023
  this.showSelector((done) => {
4923
5024
  const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress), (onProgress) => this.sessionManager.usesDefaultSessionDir()