testronaut 1.1.2 β†’ 1.2.0

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.
@@ -0,0 +1,194 @@
1
+ # Contributing to πŸ§‘β€πŸš€ Testronaut
2
+
3
+ Thanks for your interest in contributing to πŸ§‘β€πŸš€ **Testronaut**! πŸš€
4
+ This document outlines how to set up your environment, run tests, and write code and documentation that align with the project’s standards.
5
+
6
+ ---
7
+
8
+ ## 🧭 Project Overview
9
+
10
+ πŸ§‘β€πŸš€ **Testronaut** is an open-source CLI for autonomous end-to-end testing.
11
+ It provides mission-driven testing flows, LLM provider integrations (OpenAI, Gemini, etc.), and a growing ecosystem of helpers and automation tools.
12
+
13
+ This guide applies to all contributions β€” whether you're fixing a bug, improving documentation, or adding new features.
14
+
15
+ ---
16
+
17
+ ## 🧩 Local Development Setup
18
+
19
+ 1. **Clone the repository**
20
+ ```bash
21
+ git clone https://github.com/<your-org-or-user>/testronaut.git
22
+ cd testronaut
23
+ ```
24
+ Install dependencies
25
+ ```bash
26
+ npm install
27
+ ```
28
+ Run the CLI locally
29
+
30
+ ```bash
31
+ node ./bin/testronaut.js init
32
+ ```
33
+ Run tests
34
+ ```bash
35
+ npm test
36
+ # or watch mode
37
+ npm run test:watch
38
+ ```
39
+
40
+
41
+ ## πŸ§ͺ Testing Guidelines
42
+ We use [Vitest](https://vitest.dev/) for unit testing.
43
+
44
+ ### Test Structure
45
+ - Unit tests β†’ tests/initHelpersTests/ and similar subfolders
46
+ - Pure, no side effects
47
+ - Target helpers and utilities directly
48
+ - Fast and deterministic
49
+
50
+
51
+
52
+ ### Commands
53
+ ```bash
54
+ # Run all tests
55
+ npm test
56
+
57
+ # Watch mode
58
+ npm run test:watch
59
+
60
+ # Coverage report
61
+ npx vitest run --coverage
62
+ ```
63
+
64
+ ### Principles
65
+ - Each helper or core module should have at least one test file.
66
+ - Tests should verify behavior, not implementation details.
67
+ - Prefer fixtures over mocks when possible.
68
+
69
+ ## πŸ’¬ Comment & Documentation Standards
70
+ Testronaut uses structured, meaningful comments to make the codebase self-documenting.
71
+
72
+ ### 1. File Headers
73
+ Every source file should start with a doc header that summarizes:
74
+
75
+ - Purpose: What the file is responsible for
76
+ - Responsibilities: Major actions or logic areas
77
+ - Side effects: I/O or network operations
78
+ - Related tests: Folder or suite where its tests live
79
+ - Used by: Other modules that import it
80
+
81
+ Example:
82
+ ```js
83
+
84
+ /**
85
+ * ExampleModule.js
86
+ * ----------------
87
+ * Purpose:
88
+ * Handles mission parsing and validation for CLI runs.
89
+ *
90
+ * Design goals:
91
+ * - Pure functions where possible
92
+ * - Unit-tested via Vitest
93
+ *
94
+ * Related tests:
95
+ * Located in `tests/exampleModuleTests/`
96
+ *
97
+ * Used by:
98
+ * ./cli/runMission.js
99
+ */
100
+ ```
101
+
102
+ ### 2. Inline Comments
103
+ - Focus on why rather than what the code is doing.
104
+ - Use NOTE: or TODO: prefixes to highlight rationale or future work.
105
+ - Avoid restating code syntax (β€œ// read file”).
106
+
107
+ Example:
108
+ ```js
109
+ // NOTE: We skip writing .env if it already exists to avoid overwriting secrets.
110
+ ```
111
+
112
+ ### 3. Section Headers
113
+ Use clear dividers for major logical steps in longer scripts (e.g., CLI commands):
114
+ ```js
115
+ // ─────────────────────────────
116
+ // STEP 3: Merge defaults safely
117
+ // ─────────────────────────────
118
+ ```
119
+
120
+ ### 4. Function Documentation (JSDoc)
121
+ Add JSDoc blocks for all exported functions and for internal ones with nontrivial logic:
122
+ ```js
123
+ /**
124
+ * Generates default configuration for new projects.
125
+ *
126
+ * @param {string} rootBasename - The current directory name.
127
+ * @returns {{initialized: boolean, outputDir: string}}
128
+ */
129
+ export function defaultConfig(rootBasename) { ... }
130
+ ```
131
+ These comments improve IDE autocompletion and make code self-descriptive.
132
+
133
+ ### 5. Test References
134
+ If a file has full coverage:
135
+ - Add one Related tests: line at the top (e.g., tests/initHelpersTests/).
136
+ - Don’t link to individual test files unless the logic is unusually complex.
137
+
138
+ ## 🧱 Code Style
139
+ - Language: Modern JavaScript (ES Modules only)
140
+ - Linting: Use ESLint + Prettier (follow repo config)
141
+ - Imports: Order as β†’ Node built-ins β†’ Third-party β†’ Local
142
+ - Async: Prefer async/await over callbacks
143
+ - File Names: Use camelCase for utilities, PascalCase for React components (if applicable)
144
+
145
+ ## 🧰 Folder Conventions
146
+ | Folder | Purpose |
147
+ |---|---|
148
+ /bin/ | CLI logic, utilities, helpers
149
+ /core/ | central functionality, turn loop, and agent management
150
+ /tests/ | All Vitest unit tests
151
+ /runner/ | Mission runner and suite management
152
+ /openAI/ | OpenAI specific helper functions
153
+
154
+ ## βš™οΈ Submitting Changes
155
+ 1. Create a new branch
156
+ ```bash
157
+ git checkout -b feat/new-provider
158
+ ```
159
+
160
+ 2. Make your changes
161
+ - Keep commits focused and descriptive
162
+ - Follow documentation and testing standards
163
+
164
+ 3. Run
165
+ ```bash
166
+ npm test
167
+ ```
168
+
169
+ 4. Open a Pull Request
170
+ - Reference related issues if applicable
171
+ - Summarize what was added, fixed, or refactored
172
+
173
+ ## πŸ§‘β€πŸ’» Example of a β€œGood” PR Description
174
+ > ### Summary
175
+ > Adds Gemini provider support to the CLI init flow.
176
+ > Includes unit tests for new helper functions in initHelpers.js.
177
+ >
178
+ > ### Details
179
+ > - Added new prompt for selecting Gemini model
180
+ > - Updated .env scaffold to include GEMINI_API_KEY
181
+ > - Added tests in tests/initHelpersTests/makeEnvTemplate.test.js
182
+ >
183
+ > ### Checklist
184
+ > - Unit tests pass (npm test)
185
+ > - Comments and headers added
186
+ > - No regressions in CLI behavior
187
+
188
+ ## 🀝 Code of Conduct
189
+ We expect all contributors to follow the Code of Conduct.
190
+ Be respectful, collaborative, and kind β€” we’re all here to build something great together.
191
+
192
+ ## 🧑 Thank You
193
+ Your contributions make πŸ§‘β€πŸš€ Testronaut better for everyone β€” from developers automating their first mission to teams running autonomous test fleets.
194
+ We deeply appreciate your time, effort, and ideas.
package/bin/cli.js CHANGED
@@ -104,7 +104,7 @@ if (args.includes('--init')) {
104
104
 
105
105
  Next steps:
106
106
  1. Get an API key from your AI provider
107
- 2. Add it to your .env file as OPENAI_API_KEY
107
+ 2. Add it to your .env file
108
108
  3. Run your first mission:
109
109
  npx testronaut
110
110
 
package/bin/init.js CHANGED
@@ -1,118 +1,202 @@
1
+ /**
2
+ * Testronaut CLI Initialization
3
+ * -----------------------------
4
+ * Purpose:
5
+ * Bootstraps a new Testronaut project by creating required folders,
6
+ * writing a provider/model-aware config, and scaffolding a .env file.
7
+ *
8
+ * Responsibilities:
9
+ * 1) Ask the user for an LLM provider (OpenAI or Gemini).
10
+ * 2) Ask for a provider-specific model (keeps prior choice when re-run).
11
+ * 3) Write `testronaut-config.json` and an initial `.env` placeholder.
12
+ * 4) Ensure folder structure and create a welcome mission.
13
+ *
14
+ * Side effects:
15
+ * - Writes files to disk: `testronaut-config.json`, `.env`,
16
+ * `missions/`, `missions/mission_reports/`.
17
+ * - Calls `createWelcomeMission()` on first init.
18
+ *
19
+ * Developer notes:
20
+ * - Extend provider support by adding a new provider choice here and
21
+ * updating `initHelpers.js` (model list + template).
22
+ * - Keep this file orchestration-only; pure logic lives in helpers
23
+ * (which are unit-tested).
24
+ */
25
+
1
26
  import fs from 'fs';
2
27
  import path from 'path';
3
28
  import prompts from 'prompts';
4
29
  import { createWelcomeMission } from './createWelcomeMission.js';
5
30
 
31
+ import {
32
+ migrateLegacyConfig,
33
+ defaultConfig,
34
+ makeEnvTemplate,
35
+ openAIModels,
36
+ geminiModels,
37
+ pickInitialIndex
38
+ } from './initHelpers.js';
39
+
6
40
  export async function initializeTestronautProject() {
41
+ // ─────────────────────────────────────────────
42
+ // STEP 0: Resolve important paths
43
+ // ─────────────────────────────────────────────
7
44
  const root = process.cwd();
8
45
  const missionsDir = path.join(root, 'missions');
9
46
  const reportsDir = path.join(missionsDir, 'mission_reports');
10
47
  const configPath = path.join(root, 'testronaut-config.json');
11
48
  const envPath = path.join(root, '.env');
12
49
 
50
+ // ─────────────────────────────────────────────
51
+ // STEP 1: Load existing config (if any) and normalize
52
+ // ─────────────────────────────────────────────
53
+ /** @type {any} */
13
54
  let config = {};
14
55
  if (fs.existsSync(configPath)) {
15
56
  config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
16
-
17
- // --- Back-compat migration: old configs stored provider in `model`
18
- if (!config.provider && config.model === 'openai') {
19
- config.provider = 'openai';
20
- // don't overwrite model yet; we'll re-ask below only if not initialized
21
- }
57
+ // Normalize legacy structures (e.g., provider implied by model).
58
+ config = migrateLegacyConfig(config);
22
59
  }
23
60
 
24
- // If not initialized before, walk through setup
61
+ // ─────────────────────────────────────────────
62
+ // STEP 2: Interactive setup (first run only)
63
+ // ─────────────────────────────────────────────
25
64
  if (!config.initialized) {
26
- // 1) Provider
65
+ // 2a) Provider selection
27
66
  const { llmProvider } = await prompts({
28
67
  type: 'select',
29
68
  name: 'llmProvider',
30
69
  message: 'Which LLM provider would you like to use?',
31
70
  choices: [
32
71
  { title: 'OpenAI (GPT family)', value: 'openai' },
33
- // Add others like Anthropic, Mistral, etc. later
34
- ]
35
- });
36
-
37
- config.provider = llmProvider; // store provider explicitly
38
-
39
- // 2) If OpenAI, ask for model
40
- const { openaiModel } = await prompts({
41
- type: 'select',
42
- name: 'openaiModel',
43
- message: 'Select an OpenAI model for agentic workflows (function/tool calling):',
44
- choices: [
45
- { title: 'GPT-4.1 (general purpose)', value: 'gpt-4.1' },
46
- { title: 'GPT-4.1 mini (faster, cheaper)', value: 'gpt-4.1-mini' },
47
- { title: 'GPT-4o (multimodal, tool use)', value: 'gpt-4o' },
48
- { title: 'GPT-4o mini (speed/cost optimized)', value: 'gpt-4o-mini' },
49
- { title: 'o3 (reasoning w/ native tool use)', value: 'o3' },
50
- { title: 'o4-mini (reasoning, cost-effective)', value: 'o4-mini' },
51
-
52
- // πŸ‘‡ GPT-5 family
53
- { title: 'GPT-5 (latest reasoning model)', value: 'gpt-5' },
54
- { title: 'GPT-5 mini (faster)', value: 'gpt-5-mini' },
55
- { title: 'GPT-5 nano (lightweight)', value: 'gpt-5-nano' },
72
+ { title: 'Google Gemini', value: 'gemini' },
73
+ // Future: add Anthropic, Mistral, etc.
56
74
  ],
57
- initial: 1
75
+ // If user re-runs init before writing config, preselect known value.
76
+ initial: ['openai', 'gemini'].indexOf(config.provider ?? 'openai')
58
77
  });
59
78
 
60
- config.model = openaiModel;
79
+ // Persist provider explicitly to config.
80
+ config.provider = llmProvider;
61
81
 
62
- // Warn about GPT-5 availability
63
- if (openaiModel.startsWith('gpt-5')) {
64
- console.log(`
65
- ⚠️ Warning: GPT-5 support may not be available for all users.
66
- - Access depends on your OpenAI account/region
67
- - Some capabilities may be gated or rate-limited
68
- `);
82
+ // 2b) Provider-specific model selection
83
+ if (llmProvider === 'openai') {
84
+ const models = openAIModels();
85
+ const { openaiModel } = await prompts({
86
+ type: 'select',
87
+ name: 'openaiModel',
88
+ message: 'Select an OpenAI model for agentic workflows (function/tool calling):',
89
+ choices: [
90
+ { title: 'GPT-4.1 (general purpose)', value: 'gpt-4.1' },
91
+ { title: 'GPT-4.1 mini (faster, cheaper)', value: 'gpt-4.1-mini' },
92
+ { title: 'GPT-4o (multimodal, tool use)', value: 'gpt-4o' },
93
+ { title: 'GPT-4o mini (speed/cost optimized)', value: 'gpt-4o-mini' },
94
+ { title: 'o3 (reasoning w/ native tool use)', value: 'o3' },
95
+ { title: 'o4-mini (reasoning, cost-effective)', value: 'o4-mini' },
96
+ // GPT-5 family (availability varies)
97
+ { title: 'GPT-5 (latest reasoning model)', value: 'gpt-5' },
98
+ { title: 'GPT-5 mini (faster)', value: 'gpt-5-mini' },
99
+ { title: 'GPT-5 nano (lightweight)', value: 'gpt-5-nano' },
100
+ ],
101
+ // Keep prior selection highlighted if present; fallback to 4.1-mini.
102
+ initial: pickInitialIndex(models, config.model, 'gpt-4.1-mini')
103
+ });
104
+
105
+ config.model = openaiModel;
106
+
107
+ // Helpful heads-up: GPT-5 access varies by account/region.
108
+ if (openaiModel?.startsWith('gpt-5')) {
109
+ console.log(`
110
+ ⚠️ Warning: GPT-5 support may not be available for all users.
111
+ - Access depends on your OpenAI account/region
112
+ - Some capabilities may be gated or rate-limited
113
+ `);
114
+ }
115
+ } else if (llmProvider === 'gemini') {
116
+ const models = geminiModels();
117
+ const { geminiModel } = await prompts({
118
+ type: 'select',
119
+ name: 'geminiModel',
120
+ message: 'Select a Google Gemini model:',
121
+ choices: [
122
+ { title: 'Gemini 2.5 Pro (general, high quality)', value: 'gemini-2.5-pro' },
123
+ { title: 'Gemini 2.5 Flash (fast, cost-efficient)', value: 'gemini-2.5-flash' },
124
+ { title: 'Gemini 2.5 Flash-8B (lightweight)', value: 'gemini-2.5-flash-8b' },
125
+ ],
126
+ // Keep prior selection if present; default to fast/cost-effective Flash.
127
+ initial: pickInitialIndex(models, config.model, 'gemini-2.5-flash')
128
+ });
129
+
130
+ config.model = geminiModel;
69
131
  }
70
132
 
71
- // Default config fields
72
- config.initialized = true;
73
- config.outputDir = config.outputDir || 'missions/mission_reports';
74
- config.projectName = config.projectName || path.basename(root);
75
- config.maxTurns = config.maxTurns ?? 20;
76
-
133
+ // ─────────────────────────────────────────────
134
+ // STEP 3: Merge defaults without clobbering user-provided fields
135
+ // ─────────────────────────────────────────────
136
+ const defaults = defaultConfig(config.projectName || path.basename(root));
137
+ config.initialized = true; // mark as initialized so subsequent runs skip prompts
138
+ config.outputDir = config.outputDir || defaults.outputDir;
139
+ config.projectName = config.projectName || defaults.projectName;
140
+ config.maxTurns = config.maxTurns ?? defaults.maxTurns;
141
+
142
+ // ─────────────────────────────────────────────
143
+ // STEP 4: Persist config and scaffold .env (first run only)
144
+ // ─────────────────────────────────────────────
77
145
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
78
146
  console.log('πŸ“ Created testronaut-config.json');
79
147
 
80
- if (llmProvider === 'openai') {
81
- // Display .env instructions for OpenAI
82
- if (!fs.existsSync(envPath)) {
83
- const exampleEnv = `# Add your OpenAI API key below
84
- OPENAI_API_KEY=sk-...
85
- `;
148
+ // Only create .env if it doesn't exist to avoid overwriting secrets.
149
+ if (!fs.existsSync(envPath)) {
150
+ const exampleEnv = makeEnvTemplate(config.provider);
151
+ if (exampleEnv) {
86
152
  fs.writeFileSync(envPath, exampleEnv);
87
153
  console.log('πŸ” Created .env file');
88
154
  }
155
+ }
89
156
 
157
+ // Provider-specific post-setup messages: quick path to first run.
158
+ if (config.provider === 'openai') {
90
159
  console.log(`
91
160
  πŸ”§ Setup Complete!
92
161
 
93
- πŸ‘‰ Please ensure your .env file contains your OpenAI API key like this:
162
+ πŸ‘‰ Ensure your .env file contains your OpenAI key:
94
163
 
95
164
  OPENAI_API_KEY=sk-...
96
165
 
97
- πŸ“Œ The key must have permissions to call the OpenAI API.
166
+ βœ… Ready to write missions in \`missions/\`. Try:
167
+
168
+ testronaut welcome.mission.js
169
+ `);
170
+ } else if (config.provider === 'gemini') {
171
+ console.log(`
172
+ πŸ”§ Setup Complete!
173
+
174
+ πŸ‘‰ Ensure your .env file contains your Gemini key:
98
175
 
99
- βœ… You’re now ready to write missions in \`missions/\` and run your first mission using:
176
+ GEMINI_API_KEY=AIza...
177
+
178
+ πŸ“Œ If you plan to use tools/function-calling or images, make sure your account has access to those features.
179
+
180
+ βœ… Ready to write missions in \`missions/\`. Try:
100
181
 
101
182
  testronaut welcome.mission.js
102
183
  `);
103
184
  }
104
185
 
186
+ // Create a small starter mission for a smooth first run.
105
187
  createWelcomeMission();
106
188
  } else {
189
+ // Already initialized: keep init idempotent and non-destructive.
107
190
  console.log('πŸ” Already initialized. Skipping setup.');
108
191
  }
109
192
 
110
- // Create directories if they don't exist
193
+ // ─────────────────────────────────────────────
194
+ // STEP 5: Ensure folder structure exists (safe to call every run)
195
+ // ─────────────────────────────────────────────
111
196
  if (!fs.existsSync(missionsDir)) {
112
197
  fs.mkdirSync(missionsDir);
113
198
  console.log('πŸ“ Created missions/');
114
199
  }
115
-
116
200
  if (!fs.existsSync(reportsDir)) {
117
201
  fs.mkdirSync(reportsDir);
118
202
  console.log('πŸ“ Created missions/mission_reports/');
@@ -0,0 +1,140 @@
1
+ /**
2
+ * initHelpers.js
3
+ * ---------------
4
+ * Purpose:
5
+ * Collection of pure, unit-testable helper functions used by the Testronaut
6
+ * CLI initialization process. These handle config normalization, defaults,
7
+ * provider-specific templates, and list-based selections.
8
+ *
9
+ * Design goals:
10
+ * - No side effects (no file system or prompts)
11
+ * - All logic easily testable via Vitest
12
+ *
13
+ * Related tests:
14
+ * Located in `tests/binTests/`
15
+ * Each helper has corresponding unit tests (e.g., `migrateLegacyConfig.test.js`,
16
+ * `makeEnvTemplate.test.js`, `pickInitialIndex.test.js`) that validate expected
17
+ * behavior and regression safety.
18
+ *
19
+ * Used by:
20
+ * ./init.js
21
+ *
22
+ * Example usage:
23
+ * const defaults = defaultConfig(path.basename(process.cwd()));
24
+ * const envTemplate = makeEnvTemplate('gemini');
25
+ * const models = openAIModels();
26
+ */
27
+
28
+ /**
29
+ * Ensures backward compatibility for older configs where
30
+ * provider information may have been implied by the model field.
31
+ *
32
+ * @param {object} cfg - Parsed testronaut-config.json object
33
+ * @returns {object} - Updated config with normalized provider field
34
+ */
35
+ export function migrateLegacyConfig(cfg) {
36
+ const out = { ...cfg };
37
+ if (!out.provider && out.model === 'openai') {
38
+ out.provider = 'openai';
39
+ }
40
+ return out;
41
+ }
42
+
43
+ /**
44
+ * Generates default configuration values for a new project.
45
+ *
46
+ * @param {string} rootBasename - Name of the current working directory
47
+ * @returns {{initialized: boolean, outputDir: string, projectName: string, maxTurns: number}}
48
+ */
49
+ export function defaultConfig(rootBasename) {
50
+ return {
51
+ initialized: true,
52
+ outputDir: 'missions/mission_reports',
53
+ projectName: rootBasename,
54
+ maxTurns: 20,
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Returns a provider-specific .env template string.
60
+ * This scaffolds a safe placeholder API key for the user to fill in manually.
61
+ *
62
+ * @param {'openai'|'gemini'} provider - The chosen LLM provider
63
+ * @returns {string} A multiline template for the .env file
64
+ */
65
+ export function makeEnvTemplate(provider) {
66
+ if (provider === 'openai') {
67
+ return `# Add your OpenAI API key below
68
+ OPENAI_API_KEY=sk-...
69
+ `;
70
+ }
71
+ if (provider === 'gemini') {
72
+ return `# Add your Google Gemini API key below
73
+ GEMINI_API_KEY=AIza...
74
+ `;
75
+ }
76
+ return '';
77
+ }
78
+
79
+ /**
80
+ * List of supported OpenAI model identifiers.
81
+ *
82
+ * @returns {string[]} Array of OpenAI model names
83
+ */
84
+ export function openAIModels() {
85
+ return [
86
+ 'gpt-4.1',
87
+ 'gpt-4.1-mini',
88
+ 'gpt-4o',
89
+ 'gpt-4o-mini',
90
+ 'o3',
91
+ 'o4-mini',
92
+ 'gpt-5',
93
+ 'gpt-5-mini',
94
+ 'gpt-5-nano',
95
+ ];
96
+ }
97
+
98
+ /**
99
+ * List of supported Google Gemini model identifiers.
100
+ *
101
+ * @returns {string[]} Array of Gemini model names
102
+ */
103
+ export function geminiModels() {
104
+ return [
105
+ 'gemini-2.5-pro', // best reasoning, tool use
106
+ 'gemini-2.5-flash', // fast, cost-effective
107
+ 'gemini-2.5-flash-lite', // lightweight / high throughput
108
+ ];
109
+ }
110
+
111
+ /**
112
+ * Determines whether a given model name belongs to a known provider.
113
+ *
114
+ * @param {'openai'|'gemini'} provider - The LLM provider
115
+ * @param {string} model - The model name to check
116
+ * @returns {boolean} True if the model is recognized for that provider
117
+ */
118
+ export function isKnownModel(provider, model) {
119
+ return provider === 'openai'
120
+ ? openAIModels().includes(model)
121
+ : geminiModels().includes(model);
122
+ }
123
+
124
+ /**
125
+ * Finds the initial selection index for a list-based UI (e.g. prompts.js select).
126
+ * Prefers the current value if found, otherwise falls back to a known default,
127
+ * and finally to the first item if neither is present.
128
+ *
129
+ * @template T
130
+ * @param {T[]} list - Array of available options
131
+ * @param {T} current - Currently selected value (if any)
132
+ * @param {T} fallback - Fallback value if current is missing
133
+ * @returns {number} The index to use as `initial` in a selection prompt
134
+ */
135
+ export function pickInitialIndex(list, current, fallback) {
136
+ const idx = list.indexOf(current);
137
+ if (idx >= 0) return idx;
138
+ const fb = list.indexOf(fallback);
139
+ return fb >= 0 ? fb : 0;
140
+ }
package/core/agent.js CHANGED
@@ -49,7 +49,7 @@ export async function runAgent(goals, missionName, maxTurns = 20, ) {
49
49
  missionName,
50
50
  submissionType: goal.submissionType || goal.label,
51
51
  submissionName: goal.submissionName || null,
52
- status: result.success ? 'passed' : 'failed',
52
+ status: result?.success ? 'passed' : 'failed',
53
53
  steps: JSON.parse(JSON.stringify(steps)), // πŸ‘ˆ deep-clone to avoid refs
54
54
  startTime: Date.now() - 1, // set real values if you have them
55
55
  endTime: Date.now(),