aiden-runtime 4.0.1 → 4.0.2

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.
@@ -58,6 +58,9 @@ var __importStar = (this && this.__importStar) || (function () {
58
58
  return result;
59
59
  };
60
60
  })();
61
+ var __importDefault = (this && this.__importDefault) || function (mod) {
62
+ return (mod && mod.__esModule) ? mod : { "default": mod };
63
+ };
61
64
  Object.defineProperty(exports, "__esModule", { value: true });
62
65
  exports.getEnvSource = exports.loadAidenEnvFile = void 0;
63
66
  exports.main = main;
@@ -71,6 +74,7 @@ exports.runSkillsSubcommand = runSkillsSubcommand;
71
74
  /* eslint-disable @typescript-eslint/no-explicit-any */
72
75
  const commander_1 = require("commander");
73
76
  const node_fs_1 = require("node:fs");
77
+ const node_path_1 = __importDefault(require("node:path"));
74
78
  const chatSession_1 = require("./chatSession");
75
79
  const aidenTUI_1 = require("./aidenTUI");
76
80
  const display_1 = require("./display");
@@ -106,6 +110,7 @@ const runtimeResolver_1 = require("../../providers/v4/runtimeResolver");
106
110
  const chatCompletionsAdapter_1 = require("../../providers/v4/chatCompletionsAdapter");
107
111
  const providerFallback_1 = require("../../core/v4/providerFallback");
108
112
  const skillBundledRestore_1 = require("../../core/v4/skillBundledRestore");
113
+ const providerDetection_1 = require("../../core/v4/firstRun/providerDetection");
109
114
  const aidenLogger_1 = require("../../core/v4/aidenLogger");
110
115
  const plugins_1 = require("../../core/v4/plugins");
111
116
  const providerAuth_1 = require("../../core/v4/auth/providerAuth");
@@ -351,9 +356,70 @@ async function buildAgentRuntime(cliOpts, opts) {
351
356
  }
352
357
  const config = new config_1.ConfigManager(paths);
353
358
  await config.load();
354
- if (await (0, setupWizard_1.isFreshInstall)(paths)) {
355
- process.stdout.write('Aiden is not configured yet. Running setup wizard…\n');
356
- await (0, setupWizard_1.runSetupWizard)({ paths });
359
+ // Phase 30.2 — fresh-user UX. Detection extends the old
360
+ // `isFreshInstall`-only gate so we cover three new failure modes:
361
+ // 1. fresh user with no env / no OAuth / no config → wizard fires
362
+ // (was working under the old gate; still does).
363
+ // 2. user with config.yaml pointing at chatgpt-plus but a stale /
364
+ // missing OAuth token file → wizard fires (was NOT under old
365
+ // code — it saw config.yaml present and proceeded into a
366
+ // broken resolve, which surfaced as a confusing rate-limit
367
+ // error on the user's first chat).
368
+ // 3. user with no config but Ollama running OR an env API key
369
+ // → wizard fires anyway. ConfigManager's DEFAULT_CONFIG points
370
+ // at `anthropic / claude-opus-4-7`, which doesn't match the
371
+ // detected env / Ollama, so skipping the wizard would surface
372
+ // the same confusing "missing ANTHROPIC_API_KEY" error.
373
+ // 4. moat-boot test fixtures that stub `providers.fake.apiKey`
374
+ // inline in config.yaml count as configured — `isFreshInstall`
375
+ // already returns false for them so `wizardNeeded` stays false.
376
+ const detection = await (0, providerDetection_1.detectAvailableProviders)({ paths });
377
+ const configuredProviderBroken = !!detection.configProvider &&
378
+ !detection.configuredProviderHasCredentials;
379
+ const wizardNeeded = !detection.hasAnyProvider ||
380
+ configuredProviderBroken ||
381
+ (await (0, setupWizard_1.isFreshInstall)(paths));
382
+ // Phase 30.2.1: when the wizard returns 'skipped' (explore mode) we
383
+ // boot the REPL with a NullAdapter instead of trying to resolve a
384
+ // real provider. Flagged here, set inside the wizard block, and
385
+ // consumed when building the adapter.
386
+ let exploreMode = false;
387
+ if (wizardNeeded) {
388
+ if (!detection.hasAnyProvider) {
389
+ // Truly empty: no env, no OAuth, no Ollama, no inline config.
390
+ process.stdout.write(`\n${(0, providerDetection_1.summarizeDetection)(detection)}\n`);
391
+ }
392
+ else if (configuredProviderBroken) {
393
+ // Config points at a provider we can't credential-resolve.
394
+ process.stdout.write(`\nConfigured provider '${detection.configProvider}' has no usable credentials ` +
395
+ `at ${node_path_1.default.join(paths.root, 'auth', `${detection.configProvider}.json`)}.\n`);
396
+ }
397
+ else {
398
+ // Detected something (env / oauth / ollama) but config.yaml is
399
+ // missing or empty — DEFAULT_CONFIG would route to anthropic and
400
+ // the resolver would fail. Surface the detection so the user
401
+ // sees what we found, then walk them through proper setup.
402
+ process.stdout.write(`\n${(0, providerDetection_1.summarizeDetection)(detection)}\n`);
403
+ process.stdout.write('config.yaml is empty — let\'s pick a provider that matches.\n');
404
+ }
405
+ process.stdout.write('Launching setup wizard…\n\n');
406
+ const result = await (0, setupWizard_1.runSetupWizard)({ paths });
407
+ // Phase 30.2.1: three exit states.
408
+ if (result.status === 'exited') {
409
+ // Recovery option [5] — clean exit, no REPL.
410
+ process.exit(0);
411
+ }
412
+ if (result.status === 'skipped') {
413
+ // Recovery option [4] "explore mode" OR Ctrl+C cancellation.
414
+ // Boot continues into the REPL with a NullAdapter; chat is
415
+ // intercepted by ChatSession, slash commands work normally.
416
+ // Flagged here and consumed below where the adapter is built.
417
+ exploreMode = true;
418
+ }
419
+ // 'configured' (or 'skipped' — we still want the env/.env reload
420
+ // for slash commands like /providers that read fresh state) →
421
+ // re-load both so the resolver sees what the wizard wrote.
422
+ (0, envSources_1.loadAidenEnvFile)(paths.envFile);
357
423
  await config.load();
358
424
  }
359
425
  const providerId = cliOpts.provider ??
@@ -385,19 +451,32 @@ async function buildAgentRuntime(cliOpts, opts) {
385
451
  const credentialResolver = new credentialResolver_1.CredentialResolver(paths.authJson);
386
452
  const resolver = new runtimeResolver_1.RuntimeResolver(credentialResolver);
387
453
  let adapter;
388
- try {
389
- adapter = await resolver.resolve({ providerId, modelId, config, paths });
454
+ if (exploreMode) {
455
+ // Phase 30.2.1 — wizard skipped. Use a NullAdapter so AidenAgent
456
+ // construction succeeds; ChatSession will intercept chat attempts
457
+ // BEFORE calling the adapter and surface the friendly message.
458
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
459
+ const { NullAdapter } = require('../../providers/v4/nullAdapter');
460
+ adapter = new NullAdapter();
390
461
  }
391
- catch (err) {
392
- display.printError(`Could not resolve provider '${providerId}' / model '${modelId}': ${err.message}`, 'Run `aiden model` to pick a valid provider, or `aiden doctor`.');
393
- process.exit(1);
462
+ else {
463
+ try {
464
+ adapter = await resolver.resolve({ providerId, modelId, config, paths });
465
+ }
466
+ catch (err) {
467
+ display.printError(`Could not resolve provider '${providerId}' / model '${modelId}': ${err.message}`, 'Run `aiden model` to pick a valid provider, or `aiden doctor`.');
468
+ process.exit(1);
469
+ }
394
470
  }
395
471
  // Phase 16b.1: wrap chat_completions providers in a FallbackAdapter so
396
472
  // 429s on Groq slot 1 transparently retry Groq slot 2/3 and Together.
397
473
  // Only activates when there's at least one *additional* slot configured
398
474
  // beyond the primary — otherwise the wrapper would just rethrow.
475
+ // Phase 30.2.1: skip in explore mode — wrapping a NullAdapter in
476
+ // FallbackAdapter would just defer the friendly error one layer.
399
477
  let fallbackAdapter = null;
400
- if (adapter.apiMode === 'chat_completions' &&
478
+ if (!exploreMode &&
479
+ adapter.apiMode === 'chat_completions' &&
401
480
  (providerId === 'groq' || providerId === 'together')) {
402
481
  const slots = buildAgentFallbackSlots(adapter, providerId, modelId);
403
482
  const reachable = slots.filter((s) => s.keyPresent);
@@ -820,6 +899,7 @@ async function buildAgentRuntime(cliOpts, opts) {
820
899
  fallbackAdapter,
821
900
  personalityManager,
822
901
  pluginLoader,
902
+ exploreMode,
823
903
  };
824
904
  }
825
905
  async function runInteractiveChat(cliOpts, opts) {
@@ -846,6 +926,9 @@ async function runInteractiveChat(cliOpts, opts) {
846
926
  paths: runtime.paths,
847
927
  personalityManager: runtime.personalityManager,
848
928
  pluginLoader: runtime.pluginLoader,
929
+ // Phase 30.2.1 — boot card renders "model not configured" and
930
+ // chat attempts get the friendly NotConfiguredError message.
931
+ unconfigured: runtime.exploreMode,
849
932
  };
850
933
  if (cliOpts.tui) {
851
934
  await (0, aidenTUI_1.runTuiMode)({
@@ -212,6 +212,18 @@ class ChatSession {
212
212
  }
213
213
  // ── Inner: a single agent turn ─────────────────────────────────────
214
214
  async runAgentTurn(userInput) {
215
+ // Phase 30.2.1 — explore mode: short-circuit BEFORE building the
216
+ // turn-status spinner / agent call. The wizard skipped, so there's
217
+ // no real provider to talk to. Print a friendly redirect to /setup
218
+ // (or the env-var alternative) and return — REPL stays alive, user
219
+ // can run slash commands or hit /quit.
220
+ if (this.opts.unconfigured) {
221
+ void userInput; // silence unused-arg warning when this branch fires
222
+ this.opts.display.write('\n');
223
+ this.opts.display.printError('No AI provider configured yet.', 'Run /setup to configure a provider, or set an API key environment variable (e.g. GROQ_API_KEY).');
224
+ this.opts.display.write('\n');
225
+ return;
226
+ }
215
227
  // Phase 22 Task 4: status bar reflects the live phase. Set on
216
228
  // entry, cleared in both success and error paths below.
217
229
  this.setStatusState({ kind: 'generating', sinceMs: Date.now() });
@@ -353,11 +365,15 @@ class ChatSession {
353
365
  skillsLoaded = 0;
354
366
  }
355
367
  // PIECE 1 — status pills row.
368
+ // Phase 30.2.1: in explore mode the model pill renders "not
369
+ // configured" instead of the DEFAULT_CONFIG fallback, so a fresh
370
+ // user who skipped the wizard isn't misled by a stale model name.
356
371
  display.write(display.statusPillsRow({
357
372
  coreOnline: true,
358
373
  mode: 'auto',
359
374
  model: this.currentModelId,
360
375
  memoryActive: true,
376
+ providerOk: !this.opts.unconfigured,
361
377
  }) + '\n');
362
378
  display.write(` ${display.rule()}\n`);
363
379
  display.write('\n');
@@ -12,7 +12,7 @@
12
12
  * and registers each on the global CommandRegistry at boot.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.allCommands = exports.cron = exports.doctor = exports.license = exports.auth = exports.plugins = exports.streaming = exports.debugPrompt = exports.identity = exports.providers = exports.quit = exports.clear = exports.verbose = exports.reasoning = exports.reloadMcp = exports.skills = exports.skin = exports.yolo = exports.usage = exports.compress = exports.title = exports.save = exports.personality = exports.model = exports.tools = exports.help = void 0;
15
+ exports.allCommands = exports.setup = exports.cron = exports.doctor = exports.license = exports.auth = exports.plugins = exports.streaming = exports.debugPrompt = exports.identity = exports.providers = exports.quit = exports.clear = exports.verbose = exports.reasoning = exports.reloadMcp = exports.skills = exports.skin = exports.yolo = exports.usage = exports.compress = exports.title = exports.save = exports.personality = exports.model = exports.tools = exports.help = void 0;
16
16
  const help_1 = require("./help");
17
17
  Object.defineProperty(exports, "help", { enumerable: true, get: function () { return help_1.help; } });
18
18
  const tools_1 = require("./tools");
@@ -63,6 +63,8 @@ const doctor_1 = require("./doctor");
63
63
  Object.defineProperty(exports, "doctor", { enumerable: true, get: function () { return doctor_1.doctor; } });
64
64
  const cron_1 = require("./cron");
65
65
  Object.defineProperty(exports, "cron", { enumerable: true, get: function () { return cron_1.cron; } });
66
+ const setup_1 = require("./setup");
67
+ Object.defineProperty(exports, "setup", { enumerable: true, get: function () { return setup_1.setup; } });
66
68
  /** All built-in system commands, in canonical order. */
67
69
  exports.allCommands = [
68
70
  help_1.help,
@@ -85,6 +87,7 @@ exports.allCommands = [
85
87
  license_1.license,
86
88
  doctor_1.doctor,
87
89
  cron_1.cron,
90
+ setup_1.setup,
88
91
  reloadMcp_1.reloadMcp,
89
92
  reasoning_1.reasoning,
90
93
  verbose_1.verbose,
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setup = void 0;
4
+ const setupWizard_1 = require("../setupWizard");
5
+ exports.setup = {
6
+ name: 'setup',
7
+ description: 'Re-run the setup wizard (configure provider + API key).',
8
+ category: 'system',
9
+ icon: '⚙',
10
+ handler: async (ctx) => {
11
+ if (!ctx.paths) {
12
+ ctx.display.printError('Cannot run wizard from this context — no paths available.', 'This is a wiring bug; please report.');
13
+ return;
14
+ }
15
+ const result = await (0, setupWizard_1.runSetupWizard)({
16
+ paths: ctx.paths,
17
+ display: ctx.display,
18
+ force: true,
19
+ });
20
+ if (result.status === 'configured' && result.ran) {
21
+ ctx.display.write('\nProvider configured. ' +
22
+ 'Restart Aiden (`/quit` then re-run `aiden`) to pick up the new provider.\n\n');
23
+ }
24
+ else if (result.status === 'skipped') {
25
+ ctx.display.write('\nStill in explore mode. Run /setup again whenever you\'re ready.\n\n');
26
+ }
27
+ else if (result.status === 'exited') {
28
+ // Wizard explicitly chose to exit — but we're inside a REPL,
29
+ // so just report and stay in the session.
30
+ ctx.display.dim('Wizard exited; continuing existing session.');
31
+ }
32
+ return;
33
+ },
34
+ };
@@ -305,11 +305,13 @@ class Display {
305
305
  const lab = (s) => sk.applyColors(s, 'muted');
306
306
  const val = (s) => sk.applyColors(s, 'agent');
307
307
  const pill = (on, label, value) => `${dot(on)} ${lab(label)} ${val(value)}`;
308
+ const providerOk = args.providerOk !== false;
309
+ const modelValue = providerOk ? args.model : 'not configured';
308
310
  return (' ' +
309
311
  [
310
312
  pill(args.coreOnline, 'core', args.coreOnline ? 'online' : 'starting'),
311
313
  pill(true, 'mode', args.mode),
312
- pill(true, 'model', args.model),
314
+ pill(providerOk, 'model', modelValue),
313
315
  pill(args.memoryActive, 'memory', args.memoryActive ? 'active' : 'off'),
314
316
  ].join(' '));
315
317
  }