@caupulican/pi-adaptative 0.80.6 → 0.80.8

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 +14 -0
  2. package/README.md +15 -3
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +4 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/model-resolver.d.ts +7 -1
  7. package/dist/core/model-resolver.d.ts.map +1 -1
  8. package/dist/core/model-resolver.js +85 -16
  9. package/dist/core/model-resolver.js.map +1 -1
  10. package/dist/core/reload-blockers.d.ts +2 -0
  11. package/dist/core/reload-blockers.d.ts.map +1 -1
  12. package/dist/core/reload-blockers.js +8 -2
  13. package/dist/core/reload-blockers.js.map +1 -1
  14. package/dist/core/settings-manager.d.ts.map +1 -1
  15. package/dist/core/settings-manager.js.map +1 -1
  16. package/dist/core/skills.d.ts.map +1 -1
  17. package/dist/core/skills.js +7 -3
  18. package/dist/core/skills.js.map +1 -1
  19. package/dist/core/system-prompt.d.ts.map +1 -1
  20. package/dist/core/system-prompt.js +3 -0
  21. package/dist/core/system-prompt.js.map +1 -1
  22. package/dist/main.d.ts.map +1 -1
  23. package/dist/main.js +14 -8
  24. package/dist/main.js.map +1 -1
  25. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  26. package/dist/modes/interactive/components/settings-selector.js +1 -1
  27. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  28. package/dist/modes/interactive/interactive-mode.d.ts +5 -0
  29. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  30. package/dist/modes/interactive/interactive-mode.js +154 -41
  31. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  32. package/docs/adaptive-extension-shared-state-audit.md +43 -0
  33. package/docs/providers.md +27 -2
  34. package/docs/settings.md +2 -2
  35. package/docs/skills.md +3 -3
  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
@@ -18,7 +18,7 @@ import { FooterDataProvider } from "../../core/footer-data-provider.js";
18
18
  import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
19
19
  import { KeybindingsManager } from "../../core/keybindings.js";
20
20
  import { createCompactionSummaryMessage } from "../../core/messages.js";
21
- import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
21
+ import { cliProviderAliases, defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope, } from "../../core/model-resolver.js";
22
22
  import { DefaultPackageManager } from "../../core/package-manager.js";
23
23
  import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
24
24
  import { getPendingReloadBlockers } from "../../core/reload-blockers.js";
@@ -158,6 +158,14 @@ function definedStringSet(values) {
158
158
  }
159
159
  return set;
160
160
  }
161
+ function sanitizeAutoLearnPathPart(input, fallback) {
162
+ const cleaned = (input || fallback)
163
+ .replace(/[^a-zA-Z0-9._-]+/g, "-")
164
+ .replace(/-+/g, "-")
165
+ .replace(/^-+|-+$/g, "")
166
+ .slice(0, 80);
167
+ return cleaned || fallback;
168
+ }
161
169
  function isOldAutoLearnArtifact(filePath, now, retentionMs) {
162
170
  const stats = fs.lstatSync(filePath);
163
171
  return stats.isFile() && now - stats.mtimeMs > retentionMs;
@@ -171,6 +179,22 @@ function removeOldAutoLearnArtifact(filePath, result, counter) {
171
179
  result.errors++;
172
180
  }
173
181
  }
182
+ function isPathInside(target, root) {
183
+ const resolvedTarget = path.resolve(target);
184
+ const resolvedRoot = path.resolve(root);
185
+ return resolvedTarget === resolvedRoot || resolvedTarget.startsWith(`${resolvedRoot}${path.sep}`);
186
+ }
187
+ function removeAutoLearnArtifactPath(filePath, root) {
188
+ if (!isPathInside(filePath, root))
189
+ return false;
190
+ try {
191
+ fs.rmSync(filePath, { recursive: true, force: true });
192
+ return true;
193
+ }
194
+ catch {
195
+ return false;
196
+ }
197
+ }
174
198
  function readAutoLearnSessionIdFromFile(filePath) {
175
199
  let fd;
176
200
  try {
@@ -232,24 +256,20 @@ function pruneAutoLearnSessionFiles(dir, activeSessionIds, now, retentionMs, res
232
256
  removeOldAutoLearnArtifact(filePath, result, "sessionFiles");
233
257
  }
234
258
  }
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;
259
+ function pruneAutoLearnRunArtifacts(dir, activeRunIds, now, retentionMs, result) {
244
260
  let entries;
245
261
  try {
246
- entries = fs.readdirSync(dataDir, { withFileTypes: true });
262
+ entries = fs.readdirSync(dir, { withFileTypes: true });
247
263
  }
248
264
  catch {
249
- result.errors++;
250
- return result;
265
+ return;
251
266
  }
252
267
  for (const entry of entries) {
268
+ const filePath = path.join(dir, entry.name);
269
+ if (entry.isDirectory()) {
270
+ pruneAutoLearnRunArtifacts(filePath, activeRunIds, now, retentionMs, result);
271
+ continue;
272
+ }
253
273
  if (!entry.isFile())
254
274
  continue;
255
275
  const promptRunId = entry.name.endsWith(".prompt.md") ? entry.name.slice(0, -".prompt.md".length) : undefined;
@@ -257,7 +277,6 @@ export function pruneAutoLearnConversationHistory(options) {
257
277
  const runId = promptRunId ?? logRunId;
258
278
  if (!runId || activeRunIds.has(runId))
259
279
  continue;
260
- const filePath = path.join(dataDir, entry.name);
261
280
  let shouldPrune = false;
262
281
  try {
263
282
  shouldPrune = isOldAutoLearnArtifact(filePath, now, retentionMs);
@@ -270,7 +289,18 @@ export function pruneAutoLearnConversationHistory(options) {
270
289
  continue;
271
290
  removeOldAutoLearnArtifact(filePath, result, promptRunId ? "promptFiles" : "logFiles");
272
291
  }
273
- pruneAutoLearnSessionFiles(path.join(dataDir, "sessions"), activeSessionIds, now, retentionMs, result);
292
+ }
293
+ export function pruneAutoLearnConversationHistory(options) {
294
+ const result = { promptFiles: 0, logFiles: 0, sessionFiles: 0, errors: 0 };
295
+ const dataDir = path.resolve(options.dataDir);
296
+ const now = options.now ?? Date.now();
297
+ const retentionMs = options.retentionMs ?? AUTO_LEARN_HISTORY_RETENTION_MS;
298
+ const activeRunIds = definedStringSet(options.activeRunIds);
299
+ const activeSessionIds = definedStringSet(options.activeSessionIds);
300
+ if (retentionMs <= 0 || !fs.existsSync(dataDir))
301
+ return result;
302
+ pruneAutoLearnRunArtifacts(dataDir, activeRunIds, now, retentionMs, result);
303
+ pruneAutoLearnSessionFiles(dataDir, activeSessionIds, now, retentionMs, result);
274
304
  return result;
275
305
  }
276
306
  export function buildAutoLearnSpawnArgs(spawnTarget, options) {
@@ -2389,13 +2419,13 @@ export class InteractiveMode {
2389
2419
  this.editor.setText("");
2390
2420
  return;
2391
2421
  }
2392
- if (text === "/login") {
2393
- this.showOAuthSelector("login");
2422
+ if (text === "/login" || text.startsWith("/login ")) {
2423
+ await this.showOAuthSelector("login", text.slice("/login".length).trim() || undefined);
2394
2424
  this.editor.setText("");
2395
2425
  return;
2396
2426
  }
2397
- if (text === "/logout") {
2398
- this.showOAuthSelector("logout");
2427
+ if (text === "/logout" || text.startsWith("/logout ")) {
2428
+ await this.showOAuthSelector("logout", text.slice("/logout".length).trim() || undefined);
2399
2429
  this.editor.setText("");
2400
2430
  return;
2401
2431
  }
@@ -3759,13 +3789,21 @@ export class InteractiveMode {
3759
3789
  getAutoLearnTenantKey() {
3760
3790
  return `${this.sessionManager.getCwd()}::${this.session.sessionId}`;
3761
3791
  }
3792
+ getAutoLearnTenantId() {
3793
+ const cwdHash = crypto.createHash("sha256").update(this.sessionManager.getCwd()).digest("hex").slice(0, 8);
3794
+ const sessionPart = sanitizeAutoLearnPathPart(this.session.sessionId, "session");
3795
+ return `${sessionPart}-${cwdHash}`;
3796
+ }
3797
+ getAutoLearnTenantDataDir() {
3798
+ return path.join(this.getAutoLearnDataDir(), "tenants", this.getAutoLearnTenantId());
3799
+ }
3762
3800
  getAutoLearnMessageCount() {
3763
3801
  return this.sessionManager.getBranch().filter((entry) => entry.type === "message").length;
3764
3802
  }
3765
3803
  buildAutoLearnDecisionFromState(state, settings, force = false) {
3766
3804
  const now = Date.now();
3767
3805
  const tenant = this.getAutoLearnTenantKey();
3768
- const runningCount = Object.keys(state.runs ?? {}).length;
3806
+ const runningCount = Object.values(state.runs ?? {}).filter((run) => run.tenant === tenant).length;
3769
3807
  const lastLaunch = state.lastLaunchByTenant?.[tenant] ?? 0;
3770
3808
  const cooldownMs = settings.cooldownMinutes * 60 * 1000;
3771
3809
  const cooldownRemainingMs = Math.max(0, lastLaunch + cooldownMs - now);
@@ -3784,7 +3822,7 @@ export class InteractiveMode {
3784
3822
  if (runningCount >= settings.maxConcurrentLearners) {
3785
3823
  return {
3786
3824
  shouldRun: false,
3787
- reason: `max learners running (${runningCount}/${settings.maxConcurrentLearners})`,
3825
+ reason: `max tenant learners running (${runningCount}/${settings.maxConcurrentLearners})`,
3788
3826
  messageCount,
3789
3827
  contextPercent,
3790
3828
  cooldownRemainingMs,
@@ -3926,7 +3964,7 @@ export class InteractiveMode {
3926
3964
  const objective = options.kind === "reflection"
3927
3965
  ? "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
3966
  : "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}`;
3967
+ 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
3968
  }
3931
3969
  reserveAutoLearnRun(params) {
3932
3970
  return this.withAutoLearnStateLock((current) => {
@@ -3993,6 +4031,20 @@ export class InteractiveMode {
3993
4031
  return { result: undefined, next };
3994
4032
  });
3995
4033
  }
4034
+ cleanupCompletedAutoLearnRun(runId, artifactPaths) {
4035
+ const dataDir = this.getAutoLearnDataDir();
4036
+ const removed = artifactPaths.filter((filePath) => removeAutoLearnArtifactPath(filePath, dataDir)).length;
4037
+ this.withAutoLearnStateLock((current) => {
4038
+ const state = this.pruneAutoLearnState(current);
4039
+ const runs = { ...(state.runs ?? {}) };
4040
+ delete runs[runId];
4041
+ return { result: undefined, next: { ...state, runs } };
4042
+ });
4043
+ if (removed > 0) {
4044
+ this.autoLearnLastStatus = `cleaned ${removed} artifact(s)`;
4045
+ this.updateAutoLearnFooter();
4046
+ }
4047
+ }
3996
4048
  markAutoLearnReservationRunning(reservation, pid, settings) {
3997
4049
  this.withAutoLearnStateLock((current) => {
3998
4050
  const now = Date.now();
@@ -4028,14 +4080,14 @@ export class InteractiveMode {
4028
4080
  if (!spawnTarget) {
4029
4081
  return "Auto Learn not started: could not resolve current pi CLI path.";
4030
4082
  }
4031
- const dir = this.getAutoLearnDataDir();
4083
+ const dir = this.getAutoLearnTenantDataDir();
4032
4084
  fs.mkdirSync(dir, { recursive: true });
4033
4085
  const runId = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
4034
4086
  const logPath = path.join(dir, `${runId}.log`);
4035
4087
  const promptPath = path.join(dir, `${runId}.prompt.md`);
4036
4088
  const kind = options.promptKind ?? "auto";
4037
- const sessionDir = path.join(dir, "sessions");
4038
- const sessionId = `auto-learn-${kind}-${runId}`;
4089
+ const sessionDir = path.join(dir, "sessions", runId);
4090
+ const sessionId = `auto-learn-${kind}-${this.getAutoLearnTenantId()}-${runId}`;
4039
4091
  fs.mkdirSync(sessionDir, { recursive: true });
4040
4092
  const prompt = this.buildAutoLearnPrompt(reason, settings, {
4041
4093
  kind,
@@ -4129,6 +4181,10 @@ export class InteractiveMode {
4129
4181
  return `Auto Learn not started: failed to spawn background learner. Log: ${logPath}`;
4130
4182
  }
4131
4183
  const childPid = child.pid;
4184
+ child.once("exit", (code) => {
4185
+ if (code === 0)
4186
+ this.cleanupCompletedAutoLearnRun(reservation.runId, [promptPath, logPath, sessionDir]);
4187
+ });
4132
4188
  child.unref();
4133
4189
  this.markAutoLearnReservationRunning(reservation, childPid, settings);
4134
4190
  this.autoLearnLastStatus = `running ${modelPattern}`;
@@ -4212,7 +4268,7 @@ export class InteractiveMode {
4212
4268
  });
4213
4269
  const now = Date.now();
4214
4270
  const tenant = this.getAutoLearnTenantKey();
4215
- const runningCount = Object.keys(state.runs ?? {}).length;
4271
+ const runningCount = Object.values(state.runs ?? {}).filter((run) => run.tenant === tenant).length;
4216
4272
  const lastReflection = state.lastReflectionByTenant?.[tenant] ?? 0;
4217
4273
  const cooldownMs = settings.reflectionCooldownMinutes * 60 * 1000;
4218
4274
  const cooldownRemainingMs = Math.max(0, lastReflection + cooldownMs - now);
@@ -4233,7 +4289,7 @@ export class InteractiveMode {
4233
4289
  return {
4234
4290
  ...base,
4235
4291
  shouldRun: false,
4236
- reason: `max learners running (${runningCount}/${settings.maxConcurrentLearners})`,
4292
+ reason: `max tenant learners running (${runningCount}/${settings.maxConcurrentLearners})`,
4237
4293
  };
4238
4294
  }
4239
4295
  if (cooldownRemainingMs > 0)
@@ -4305,7 +4361,9 @@ export class InteractiveMode {
4305
4361
  const settings = this.getEffectiveAutoLearnSettings();
4306
4362
  const decision = this.evaluateAutoLearn(false);
4307
4363
  const state = this.getPrunedAutoLearnState();
4308
- const runs = Object.entries(state.runs ?? {});
4364
+ const tenant = this.getAutoLearnTenantKey();
4365
+ const runs = Object.entries(state.runs ?? {}).filter(([, run]) => run.tenant === tenant);
4366
+ const otherTenantRuns = Object.values(state.runs ?? {}).filter((run) => run.tenant !== tenant).length;
4309
4367
  const contextText = decision.contextPercent === null ? "unknown" : `${decision.contextPercent.toFixed(1)}%`;
4310
4368
  const cooldownText = decision.cooldownRemainingMs > 0 ? `${Math.ceil(decision.cooldownRemainingMs / 60000)}m remaining` : "ready";
4311
4369
  const runLines = runs.length
@@ -4333,13 +4391,15 @@ export class InteractiveMode {
4333
4391
  const reflectionLast = state.lastReflectionByTenant?.[this.getAutoLearnTenantKey()] ?? 0;
4334
4392
  const reflectionCooldownRemainingMs = Math.max(0, reflectionLast + settings.reflectionCooldownMinutes * 60 * 1000 - Date.now());
4335
4393
  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}`;
4394
+ 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
4395
  }
4338
4396
  formatAutonomyStatus() {
4339
4397
  const autonomy = this.settingsManager.getAutonomySettings();
4340
4398
  const settings = this.getEffectiveAutoLearnSettings();
4341
4399
  const autoLearnState = this.getPrunedAutoLearnState();
4342
- const running = Object.entries(autoLearnState.runs ?? {});
4400
+ const tenant = this.getAutoLearnTenantKey();
4401
+ const running = Object.entries(autoLearnState.runs ?? {}).filter(([, run]) => run.tenant === tenant);
4402
+ const otherTenantRunning = Object.values(autoLearnState.runs ?? {}).filter((run) => run.tenant !== tenant).length;
4343
4403
  const safety = autonomy.mode === "full"
4344
4404
  ? "standing grant for memory, skills, user/project extensions, autonomy/autoLearn tuning, and authorized selfModification.sourcePath edits; hard stops still require explicit foreground approval"
4345
4405
  : "proposal-gated outside configured high-confidence memory policy";
@@ -4352,10 +4412,12 @@ export class InteractiveMode {
4352
4412
  `Auto Learn: ${settings.enabled ? "enabled" : "disabled"}; model=${settings.model}; applyHighConfidence=${settings.applyHighConfidence}`,
4353
4413
  `Long-session trigger: ${settings.longSessionMessages} messages or ${settings.longSessionContextPercent}% context; cooldown=${settings.cooldownMinutes}m`,
4354
4414
  reflectionLine,
4355
- `Running learners: ${running.length}/${settings.maxConcurrentLearners}`,
4415
+ `Running tenant learners: ${running.length}/${settings.maxConcurrentLearners}`,
4416
+ `Other tenant learners: ${otherTenantRunning}`,
4356
4417
  "History retention: 7 days for internal Auto Learn prompts/logs/sessions",
4357
4418
  `Standing authority: ${safety}`,
4358
4419
  `Audit/log dir: ${this.getAutoLearnDataDir()}`,
4420
+ `Tenant artifact dir: ${this.getAutoLearnTenantDataDir()}`,
4359
4421
  "Use /autonomy off|safe|balanced|full to switch presets. Advanced overrides remain in /settings → Auto Learn Advanced.",
4360
4422
  ].join("\n");
4361
4423
  }
@@ -5021,6 +5083,35 @@ export class InteractiveMode {
5021
5083
  }
5022
5084
  return options.sort((a, b) => a.name.localeCompare(b.name));
5023
5085
  }
5086
+ resolveAuthProviderOption(providerReference, providerOptions) {
5087
+ const normalized = providerReference.trim().toLowerCase();
5088
+ if (!normalized)
5089
+ return undefined;
5090
+ const exactMatch = providerOptions.find((provider) => {
5091
+ const id = provider.id.toLowerCase();
5092
+ const name = provider.name.toLowerCase();
5093
+ return id === normalized || name === normalized;
5094
+ });
5095
+ if (exactMatch)
5096
+ return exactMatch;
5097
+ const aliasTarget = cliProviderAliases[normalized] ?? normalized;
5098
+ return providerOptions.find((provider) => {
5099
+ const id = provider.id.toLowerCase();
5100
+ const name = provider.name.toLowerCase();
5101
+ return id === aliasTarget || name === aliasTarget;
5102
+ });
5103
+ }
5104
+ async startProviderLogin(providerOption) {
5105
+ if (providerOption.authType === "oauth") {
5106
+ await this.showLoginDialog(providerOption.id, providerOption.name);
5107
+ }
5108
+ else if (providerOption.id === BEDROCK_PROVIDER_ID) {
5109
+ this.showBedrockSetupDialog(providerOption.id, providerOption.name);
5110
+ }
5111
+ else {
5112
+ await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
5113
+ }
5114
+ }
5024
5115
  showLoginAuthTypeSelector() {
5025
5116
  const subscriptionLabel = "Use a subscription";
5026
5117
  const apiKeyLabel = "Use an API key";
@@ -5049,15 +5140,7 @@ export class InteractiveMode {
5049
5140
  if (!providerOption) {
5050
5141
  return;
5051
5142
  }
5052
- if (providerOption.authType === "oauth") {
5053
- await this.showLoginDialog(providerOption.id, providerOption.name);
5054
- }
5055
- else if (providerOption.id === BEDROCK_PROVIDER_ID) {
5056
- this.showBedrockSetupDialog(providerOption.id, providerOption.name);
5057
- }
5058
- else {
5059
- await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
5060
- }
5143
+ await this.startProviderLogin(providerOption);
5061
5144
  }, () => {
5062
5145
  done();
5063
5146
  this.showLoginAuthTypeSelector();
@@ -5065,8 +5148,18 @@ export class InteractiveMode {
5065
5148
  return { component: selector, focus: selector };
5066
5149
  });
5067
5150
  }
5068
- async showOAuthSelector(mode) {
5151
+ async showOAuthSelector(mode, providerReference) {
5069
5152
  if (mode === "login") {
5153
+ if (providerReference) {
5154
+ const providerOptions = this.getLoginProviderOptions();
5155
+ const providerOption = this.resolveAuthProviderOption(providerReference, providerOptions);
5156
+ if (!providerOption) {
5157
+ this.showError(`Unknown login provider "${providerReference}". Use /login to select from available providers.`);
5158
+ return;
5159
+ }
5160
+ await this.startProviderLogin(providerOption);
5161
+ return;
5162
+ }
5070
5163
  this.showLoginAuthTypeSelector();
5071
5164
  return;
5072
5165
  }
@@ -5075,6 +5168,26 @@ export class InteractiveMode {
5075
5168
  this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
5076
5169
  return;
5077
5170
  }
5171
+ if (providerReference) {
5172
+ const providerOption = this.resolveAuthProviderOption(providerReference, providerOptions);
5173
+ if (!providerOption) {
5174
+ this.showError(`No stored credentials found for "${providerReference}". Use /logout to select a saved provider.`);
5175
+ return;
5176
+ }
5177
+ try {
5178
+ this.session.modelRegistry.authStorage.logout(providerOption.id);
5179
+ this.session.modelRegistry.refresh();
5180
+ await this.updateAvailableProviderCount();
5181
+ const message = providerOption.authType === "oauth"
5182
+ ? `Logged out of ${providerOption.name}`
5183
+ : `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;
5184
+ this.showStatus(message);
5185
+ }
5186
+ catch (error) {
5187
+ this.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);
5188
+ }
5189
+ return;
5190
+ }
5078
5191
  this.showSelector((done) => {
5079
5192
  const selector = new OAuthSelectorComponent(mode, this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
5080
5193
  done();