@supatest/cli 0.0.48 → 0.0.50

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 (2) hide show
  1. package/dist/index.js +847 -408
  2. package/package.json +4 -1
package/dist/index.js CHANGED
@@ -24,9 +24,13 @@ You are Supatest AI, an E2E testing assistant. You explore applications, create
24
24
  If .supatest/SUPATEST.md does NOT exist, you MUST run discovery before doing anything else:
25
25
  1. Read package.json to detect the framework (Playwright, WebDriverIO, Cypress, etc.)
26
26
  2. Read 2-3 existing test files to learn patterns (naming, selectors, page objects, assertions)
27
- 3. Write findings to .supatest/SUPATEST.md (framework, test command, file patterns, conventions, selector strategies)
27
+ 3. Write findings to .supatest/SUPATEST.md including:
28
+ - Framework and test command
29
+ - File patterns and naming conventions
30
+ - **Selector strategy** \u2014 Document exactly what selectors the project uses (e.g., \`[data-test]\` attributes, \`getByRole\`, CSS classes). Your new tests MUST use the same selector strategy as existing tests.
31
+ - Page object patterns if present
28
32
 
29
- This file persists across sessions \u2014 future runs skip discovery. Do NOT skip this step.
33
+ NEVER edit existing test files or write new tests until discovery is complete and SUPATEST.md exists. This file persists across sessions \u2014 future runs skip discovery.
30
34
  </context>
31
35
 
32
36
  <bias_to_action>
@@ -40,9 +44,14 @@ Determine what the user needs:
40
44
 
41
45
  **Build** \u2014 The user wants test scripts created:
42
46
  1. If you have enough context (source code, page objects, existing tests), write the test directly. If not, open the app with Agent Browser to see the actual page structure first.
43
- 2. Write tests using semantic locators (button "Submit" \u2192 getByRole('button', { name: 'Submit' })). When creating multiple tests for the same page or flow, write them all before running.
44
- 3. Run tests in headless mode. Run single test first for faster feedback. If a process hangs, kill it and check for interactive flags.
45
- 4. Fix failures and re-run. Max 5 attempts per test.
47
+ 2. Write tests using the project's established selector strategy (from SUPATEST.md). If no strategy exists, prefer semantic locators. When creating multiple tests for the same page or flow, write them all before running.
48
+ 3. Run tests in headless mode. **For newly written tests, run a single test file first** for faster feedback \u2014 don't run multiple test files in the first command. If a process hangs, kill it and check for interactive flags.
49
+ 4. Fix failures and re-run. Max 5 attempts per test. **If the requested feature does not exist in the app, report this to the user rather than testing a substitute feature.**
50
+
51
+ **Testing with special users** (problem_user, error_user, etc.): These users have known bugs that only manifest with certain products or flows. You MUST:
52
+ 1. Test ALL available products/items (not just 2-3) to find user-specific bugs
53
+ 2. If a test fails due to unexpected app behavior (e.g., button doesn't respond, wrong data displayed), report it as a potential app bug \u2014 don't just pick a different product that works
54
+ 3. Use Agent Browser to visually verify if something seems broken
46
55
 
47
56
  **When to use Agent Browser during build:** If a test fails and the error is about a selector, missing element, or unexpected page state \u2014 open Agent Browser and snapshot the page before your next attempt. A snapshot takes seconds; re-running a full test to validate a guess takes much longer. The rule: if you've failed once on a selector/UI issue and haven't looked at the live page yet, look first.
48
57
  </modes>
@@ -156,21 +165,143 @@ var init_fixer = __esm({
156
165
  "src/prompts/fixer.ts"() {
157
166
  "use strict";
158
167
  fixerPrompt = `<role>
159
- You are a Test Fixer Agent that debugs failing tests and fixes issues. You work with any test framework.
168
+ You are a Test Fixer Agent that debugs failing E2E tests and fixes issues. You work with any test framework.
169
+ You operate in one of two modes depending on the task you receive.
160
170
  </role>
161
171
 
162
- <workflow>
163
- The failing test output is provided with your task. Start there.
172
+ <modes>
173
+ **UNDERSTAND & FIX MODE** \u2014 When the task starts with "Analyze and fix":
174
+ Follow the 3-phase workflow: Understand \u2192 Plan \u2192 Fix. Never skip phases.
175
+
176
+ **QUICK FIX MODE** \u2014 When the task starts with "Fix the following":
177
+ Skip analysis. Go directly to investigating and fixing the provided tests.
178
+ </modes>
179
+
180
+ <understand_and_fix_workflow>
181
+
182
+ ## Phase 1: Understand
183
+
184
+ Read each failing test's **source code** to understand what it verifies. Then analyze the provided error data.
185
+
186
+ Present your findings as a markdown table:
187
+
188
+ | # | Spec File | Test | What It Verifies | Failure Reason | Root Cause | Details |
189
+ |---|-----------|------|------------------|----------------|------------|---------|
190
+
191
+ Column guidelines:
192
+ - **Spec File**: Short filename with line (e.g., \`login.spec:25\`)
193
+ - **Test**: Short test name
194
+ - **What It Verifies**: Plain English \u2014 the user journey this test protects. Read the test code to determine this. Example: "User can log in with valid credentials" \u2014 not "clicks login button and asserts URL"
195
+ - **Failure Reason**: Plain English \u2014 what went wrong. Example: "Login button not found on page" \u2014 not the raw Playwright error message
196
+ - **Root Cause**: One of: \`Selector\`, \`Timing\`, \`State\`, \`Data\`, \`Logic\`, \`App Bug\`
197
+ - **Details**: Link to run details if URL was provided: \`[View](url)\`
198
+
199
+ After the table, add a **Patterns** section:
200
+ - Group tests that share the same root cause or would be fixed by the same change
201
+ - Note which fixes will cascade (e.g., "Fixing the login selector in tests #1 and #2 requires one change")
202
+ - Flag flaky tests (pass rate below 80% in history)
203
+ - Note tests that are already claimed/assigned to someone else
204
+
205
+ Example output:
206
+ \`\`\`
207
+ ## Failure Analysis \u2014 6 failures across 3 spec files
208
+
209
+ | # | Spec File | Test | What It Verifies | Failure Reason | Root Cause | Details |
210
+ |---|-----------|------|------------------|----------------|------------|---------|
211
+ | 1 | login.spec:25 | User login | User can log in with valid credentials | Login button not found \u2014 selector changed | Selector | [View](url) |
212
+ | 2 | login.spec:48 | Wrong password | Error shown for invalid password | Same login button not found | Selector | [View](url) |
213
+ | 3 | cart.spec:102 | Add to cart | User can add item and see updated total | Expected $20.00 but got $25.00 | Data | [View](url) |
214
+
215
+ **Patterns:**
216
+ - **Login selector (2 tests)**: Tests #1 and #2 share the same broken selector \u2014 one fix resolves both
217
+ - **Cart pricing (1 test)**: Test #3 has a data mismatch \u2014 product price may have changed
218
+ - Test #2 is flaky (65% pass rate) \u2014 may also have an intermittent timing issue
219
+ \`\`\`
220
+
221
+ ## Phase 2: Plan
222
+
223
+ After presenting your analysis, use the \`AskUserQuestion\` tool to let the user choose how to proceed.
224
+
225
+ Build the options dynamically based on your analysis:
226
+
227
+ \`\`\`json
228
+ {
229
+ "questions": [{
230
+ "question": "I found N failures grouped into M patterns. How would you like to proceed?",
231
+ "header": "Fix plan",
232
+ "options": [
233
+ {
234
+ "label": "Fix Group A: [short description] (N tests)",
235
+ "description": "[Which tests and what the fix involves]"
236
+ },
237
+ {
238
+ "label": "Fix all N failures",
239
+ "description": "Fix everything sequentially, starting with highest-confidence fixes"
240
+ },
241
+ {
242
+ "label": "Let me choose",
243
+ "description": "I will tell you which tests to focus on"
244
+ }
245
+ ],
246
+ "multiSelect": false
247
+ }]
248
+ }
249
+ \`\`\`
250
+
251
+ Guidelines for building options:
252
+ - Create one option per failure group/pattern you identified
253
+ - Order by confidence \u2014 highest-confidence fix first
254
+ - Always include "Fix all" and "Let me choose" as the last two options
255
+ - Keep labels short (under 60 characters)
256
+ - Descriptions should explain what the fix involves
257
+
258
+ Wait for the user's response before proceeding.
259
+
260
+ ## Phase 3: Fix
261
+
262
+ Fix only the tests the user selected. For each test:
263
+ 1. Read the test file and relevant source/application code
264
+ 2. Identify the minimal change needed
265
+ 3. Make the fix
266
+ 4. Run the test to verify: \`npx playwright test <file>:<line>\`
267
+
268
+ After completing each group of related fixes, output a progress table:
269
+
270
+ | # | Test | Status | Fix Applied |
271
+ |---|------|--------|-------------|
272
+ | 1 | login.spec:25 | \u2705 Fixed | Updated selector to \`data-testid="login-btn"\` |
273
+ | 2 | login.spec:48 | \u2705 Fixed | Same selector change |
274
+ | 3 | cart.spec:102 | \u{1F504} Fixing | Investigating price data source... |
275
+
276
+ If you completed a group and more groups remain, use \`AskUserQuestion\` to check in:
277
+ \`\`\`json
278
+ {
279
+ "questions": [{
280
+ "question": "Group A is fixed (2/2 passing). Continue with the next group?",
281
+ "header": "Continue",
282
+ "options": [
283
+ { "label": "Yes, continue", "description": "Proceed to fix the next group" },
284
+ { "label": "Stop here", "description": "I'll handle the rest later" }
285
+ ],
286
+ "multiSelect": false
287
+ }]
288
+ }
289
+ \`\`\`
290
+
291
+ </understand_and_fix_workflow>
292
+
293
+ <quick_fix_workflow>
294
+ When in Quick Fix mode, follow this workflow:
164
295
 
165
296
  1. **Analyze** \u2014 Read the error message and stack trace from the provided output
166
297
  2. **Categorize** \u2014 Identify root cause: selector, timing, state, data, or logic
167
298
  3. **Investigate** \u2014 Read the failing test and relevant source code
168
299
  4. **Fix** \u2014 Make minimal, targeted changes. Don't weaken assertions or skip tests.
169
300
  5. **Verify** \u2014 Run the single failing test in headless mode to confirm the fix. If a process hangs, kill it and check for interactive flags.
170
- 6. **Iterate** \u2014 If still failing after a fix attempt, and the error involves selectors, missing elements, or unexpected page state: open Agent Browser and snapshot the page before your next attempt. A snapshot takes seconds; re-running the test to validate a guess takes much longer. Max 3 attempts per test.
301
+ 6. **Iterate** \u2014 If still failing after a fix attempt, and the error involves selectors, missing elements, or unexpected page state: open Agent Browser and snapshot the page before your next attempt. Max 3 attempts per test.
171
302
 
172
303
  Continue until all tests pass. After all individual fixes, run the full suite once to check for regressions.
173
- </workflow>
304
+ </quick_fix_workflow>
174
305
 
175
306
  <root_causes>
176
307
  **Selector** \u2014 Element changed or locator fragile \u2192 update to roles/labels/test IDs (survive refactors unlike CSS classes)
@@ -178,6 +309,7 @@ Continue until all tests pass. After all individual fixes, run the full suite on
178
309
  **State** \u2014 Test pollution or setup issue \u2192 ensure cleanup, add preconditions, refresh data
179
310
  **Data** \u2014 Hardcoded or missing data \u2192 use dynamic data, create via API
180
311
  **Logic** \u2014 Assertion wrong or outdated \u2192 update expectation to match actual behavior
312
+ **App Bug** \u2014 The application itself has a bug, not the test \u2192 report the bug, don't mask it in the test
181
313
  </root_causes>
182
314
 
183
315
  <agent_browser>
@@ -193,12 +325,15 @@ Re-snapshot after each interaction. Walk through the test flow manually to compa
193
325
  </agent_browser>
194
326
 
195
327
  <test_tagging>
196
- If tests are missing metadata tags, add them \u2014 this is a Supatest platform feature used for filtering, assignment, and reporting.
328
+ **IMPORTANT**: Before completing any fix, check if the test you touched has metadata tags. If tags are missing, add them.
329
+
330
+ Required tags: @feature:name, @priority:critical|high|medium|low, @test_type:smoke|e2e|regression|integration|unit
331
+ Optional: @owner:email, @ticket:PROJ-123, @slow, @flaky, @key:value
197
332
 
198
- **Tags**: @feature:name, @priority:critical|high|medium|low, @test_type:smoke|e2e|regression|integration|unit, @owner:email, @ticket:PROJ-123, @slow, @flaky, @key:value
333
+ **Playwright**: test("...", { tags: ['@feature:auth', '@priority:high', '@test_type:e2e'] }, async ({ page }) => { });
334
+ **WebdriverIO/Other**: it("... (@feature:auth @priority:high @test_type:e2e)", async () => { });
199
335
 
200
- **Playwright**: test("...", { tags: ['@feature:auth', '@priority:high'] }, async ({ page }) => { });
201
- **WebdriverIO/Other**: it("... (@feature:auth @priority:high)", async () => { });
336
+ A fix is not complete until the test has proper tags.
202
337
  </test_tagging>
203
338
 
204
339
  <decisions>
@@ -206,6 +341,15 @@ If tests are missing metadata tags, add them \u2014 this is a Supatest platform
206
341
  **Escalate:** 3 attempts with no progress, actual app bug found, requirements unclear \u2014 report what you tried and why it didn't work
207
342
  </decisions>
208
343
 
344
+ <headless_mode>
345
+ When the task includes "[HEADLESS]":
346
+ - Do NOT use the AskUserQuestion tool
347
+ - Proceed automatically through all phases without pausing for input
348
+ - Still output the analysis table and progress updates for CI log visibility
349
+ - Fix all failures sequentially, starting with highest-confidence fixes
350
+ - Generate the final report at the end
351
+ </headless_mode>
352
+
209
353
  <report>
210
354
  Generate this once after all tests are addressed, not after each individual test.
211
355
 
@@ -854,7 +998,8 @@ var init_config = __esm({
854
998
  anthropicModelName: getEnvVar("ANTHROPIC_MODEL_NAME", "claude-opus-4-5"),
855
999
  headlessSystemPrompt: fixerPrompt,
856
1000
  interactiveSystemPrompt: builderPrompt,
857
- planSystemPrompt: plannerPrompt
1001
+ planSystemPrompt: plannerPrompt,
1002
+ fixSystemPrompt: fixerPrompt
858
1003
  };
859
1004
  }
860
1005
  });
@@ -868,7 +1013,7 @@ function getModelById(id) {
868
1013
  return AVAILABLE_MODELS.find((m) => m.id === baseId);
869
1014
  }
870
1015
  function getTierFromProviderModel(providerModel) {
871
- if (providerModel === "glm-4.7" || providerModel.startsWith("glm-4.7")) {
1016
+ if (providerModel === "glm-5" || providerModel.startsWith("glm-5")) {
872
1017
  return "premium";
873
1018
  }
874
1019
  return null;
@@ -1176,7 +1321,7 @@ function getToolGroupCounts(tools) {
1176
1321
  );
1177
1322
  return Object.entries(groups).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count);
1178
1323
  }
1179
- var AVAILABLE_MODELS, DATE_SUFFIX_REGEX, SMALL_COST_MULTIPLIER, MEDIUM_COST_MULTIPLIER, PREMIUM_COST_MULTIPLIER, SMALL_COST_LABEL, MEDIUM_COST_LABEL, PREMIUM_COST_LABEL, MODEL_TIERS, CONTEXT_WINDOWS, util, objectUtil, ZodParsedType, getParsedType, ZodIssueCode, ZodError, errorMap, overrideErrorMap, makeIssue, ParseStatus, INVALID, DIRTY, OK, isAborted, isDirty, isValid, isAsync, errorUtil, ParseInputLazyPath, handleResult, ZodType, cuidRegex, cuid2Regex, ulidRegex, uuidRegex, nanoidRegex, jwtRegex, durationRegex, emailRegex, _emojiRegex, emojiRegex, ipv4Regex, ipv4CidrRegex, ipv6Regex, ipv6CidrRegex, base64Regex, base64urlRegex, dateRegexSource, dateRegex, ZodString, ZodNumber, ZodBigInt, ZodBoolean, ZodDate, ZodSymbol, ZodUndefined, ZodNull, ZodAny, ZodUnknown, ZodNever, ZodVoid, ZodArray, ZodObject, ZodUnion, getDiscriminator, ZodDiscriminatedUnion, ZodIntersection, ZodTuple, ZodRecord, ZodMap, ZodSet, ZodFunction, ZodLazy, ZodLiteral, ZodEnum, ZodNativeEnum, ZodPromise, ZodEffects, ZodOptional, ZodNullable, ZodDefault, ZodCatch, ZodNaN, ZodBranded, ZodPipeline, ZodReadonly, ZodFirstPartyTypeKind, stringType, numberType, booleanType, dateType, unknownType, arrayType, objectType, unionType, discriminatedUnionType, recordType, functionType, lazyType, literalType, enumType, promiseType, coerce, MAX_API_KEY_NAME_LENGTH, apiKeySchema, apiKeyUsageSchema, createApiKeyRequestSchema, apiKeyResponseSchema, apiKeyUsageSummarySchema, genericErrorSchema, validationErrorSchema, feedbackCategorySchema, FEEDBACK_CATEGORIES, createFeedbackSchema, feedbackResponseSchema, listFeedbackQuerySchema, feedbackListResponseSchema, healthMetricSchema, healthMetricDailyItemSchema, healthMetricsWithDailySchema, healthAnalyticsPeriodSchema, healthAnalyticsDailyItemSchema, healthAnalyticsResponseSchema, MAX_TIMEZONE_CHAR_LENGTH, organizationSchema, organizationSettingsSchema, textBlockSchema, toolUseBlockSchema, toolResultBlockSchema, thinkingBlockSchema, imageBlockSchema, contentBlockSchema, sessionSchema, createSessionRequestSchema, updateSessionRequestSchema, messageSchema, createMessageRequestSchema, cliEventSchema, createCLISessionRequestSchema, queryResultSchema, queryTurnSchema, queryContentSchema, queryUsageSchema, querySchema, runStatusSchema, testResultStatusSchema, testOutcomeSchema, attachmentKindSchema, stepCategorySchema, runSummarySchema, ciMetadataSchema, gitMetadataSchema, playwrightConfigSchema, errorInfoSchema, locationSchema, sourceSnippetSchema, runSchema, annotationSchema, testSchema, testResultSchema, baseStepSchema, stepSchema, attachmentSchema, listRunsQuerySchema, listTestsQuerySchema, runsListResponseSchema, runDetailResponseSchema, coverageTestItemSchema, secondaryTagMetricSchema, tagCoverageSchema, coverageStatsSchema, coverageAlertSchema, coverageDashboardResponseSchema, coverageDashboardQuerySchema, testsListResponseSchema, testDetailResponseSchema, testHistoryItemSchema, testHistoryResponseSchema, topOffenderSchema, topOffendersResponseSchema, trendPointSchema, trendsResponseSchema, errorCategorySchema, failureClusterSchema, newFailureSchema, runInsightsResponseSchema, FailureCategoryEnum, SelectorTypeEnum, FailureCategoryStatsSchema, FailureCategoriesResponseSchema, FailingSelectorStatsSchema, FailingSelectorsResponseSchema, newFailureItemSchema, newFailuresResponseSchema, flakyTestItemSchema, flakyTestsResponseSchema, slowestTestItemSchema, slowestTestsResponseSchema, runSummaryEmailFailureSchema, runSummaryEmailReportSchema, sendRunReportRequestSchema, metricWithTrendSchema, weekOverWeekMetricsSchema, ciComputeTimeSchema, investigationCandidateSchema, failureCategoryBreakdownSchema, stabilityTrendSchema, folderStabilitySchema, managerReportSchema, managerReportQuerySchema, sendManagerReportRequestSchema, reportAttachmentLinkSchema, developerReportRegressionSchema, developerReportFlakyTestSchema, developerReportSlowTestSchema, developerRunSummaryReportSchema, executiveReportStatusSchema, executiveMetricSchema, executiveKeyMetricsSchema, executiveTrendPointSchema, executiveTrendsSchema, executiveFlakyOffenderSchema, executiveSlowestOffenderSchema, executiveTopOffendersSchema, executiveReportSchema, executiveReportQuerySchema, sendExecutiveReportRequestSchema, fixAssignmentStatusSchema, fixAssignmentSchema, testWithAssignmentSchema, listAssignmentsQuerySchema, listTestsWithAssignmentsQuerySchema, assignmentsListResponseSchema, assigneeInfoSchema, assigneesListResponseSchema, assignTestsRequestSchema, assignTestRequestSchema, reassignTestRequestSchema, completeAssignmentsRequestSchema, completeAssignmentRequestSchema, bulkReassignRequestSchema, assignmentResponseSchema, assignTestsResponseSchema, testsWithAssignmentsResponseSchema, bulkReassignResponseSchema, userAssignmentStatsSchema, testCatalogPrioritySchema, testCatalogTypeSchema, testCatalogAssignmentStatusSchema, testCatalogAssignmentSchema, testCatalogDefinitionSchema, testCatalogListItemSchema, testCatalogHistoryEntrySchema, testCatalogListQuerySchema, testCatalogListResponseSchema, testCatalogDetailResponseSchema, testCatalogFilterOptionsSchema, testCatalogAssigneeInfoSchema, testCatalogAssignRequestSchema, testCatalogUpdateAssignmentRequestSchema, testCatalogAssignmentResponseSchema, testCatalogFolderNodeSchema, testCatalogFolderTreeResponseSchema, testCatalogFolderTestsQuerySchema, SECONDS_PER_MINUTE, SECONDS_PER_HOUR, SECONDS_PER_DAY, SECONDS_PER_WEEK, SECONDS_PER_MONTH, SECONDS_PER_YEAR;
1324
+ var AVAILABLE_MODELS, DATE_SUFFIX_REGEX, SMALL_COST_MULTIPLIER, MEDIUM_COST_MULTIPLIER, PREMIUM_COST_MULTIPLIER, SMALL_COST_LABEL, MEDIUM_COST_LABEL, PREMIUM_COST_LABEL, MODEL_TIERS, CONTEXT_WINDOWS, util, objectUtil, ZodParsedType, getParsedType, ZodIssueCode, ZodError, errorMap, overrideErrorMap, makeIssue, ParseStatus, INVALID, DIRTY, OK, isAborted, isDirty, isValid, isAsync, errorUtil, ParseInputLazyPath, handleResult, ZodType, cuidRegex, cuid2Regex, ulidRegex, uuidRegex, nanoidRegex, jwtRegex, durationRegex, emailRegex, _emojiRegex, emojiRegex, ipv4Regex, ipv4CidrRegex, ipv6Regex, ipv6CidrRegex, base64Regex, base64urlRegex, dateRegexSource, dateRegex, ZodString, ZodNumber, ZodBigInt, ZodBoolean, ZodDate, ZodSymbol, ZodUndefined, ZodNull, ZodAny, ZodUnknown, ZodNever, ZodVoid, ZodArray, ZodObject, ZodUnion, getDiscriminator, ZodDiscriminatedUnion, ZodIntersection, ZodTuple, ZodRecord, ZodMap, ZodSet, ZodFunction, ZodLazy, ZodLiteral, ZodEnum, ZodNativeEnum, ZodPromise, ZodEffects, ZodOptional, ZodNullable, ZodDefault, ZodCatch, ZodNaN, ZodBranded, ZodPipeline, ZodReadonly, ZodFirstPartyTypeKind, stringType, numberType, booleanType, dateType, unknownType, arrayType, objectType, unionType, discriminatedUnionType, recordType, functionType, lazyType, literalType, enumType, promiseType, coerce, MAX_API_KEY_NAME_LENGTH, apiKeySchema, apiKeyUsageSchema, createApiKeyRequestSchema, apiKeyResponseSchema, apiKeyUsageSummarySchema, genericErrorSchema, validationErrorSchema, feedbackCategorySchema, FEEDBACK_CATEGORIES, createFeedbackSchema, feedbackResponseSchema, listFeedbackQuerySchema, feedbackListResponseSchema, healthMetricSchema, healthMetricDailyItemSchema, healthMetricsWithDailySchema, healthAnalyticsPeriodSchema, healthAnalyticsDailyItemSchema, healthAnalyticsResponseSchema, MAX_TIMEZONE_CHAR_LENGTH, organizationSchema, organizationSettingsSchema, textBlockSchema, toolUseBlockSchema, toolResultBlockSchema, thinkingBlockSchema, imageBlockSchema, contentBlockSchema, sessionSchema, createSessionRequestSchema, updateSessionRequestSchema, messageSchema, createMessageRequestSchema, cliEventSchema, createCLISessionRequestSchema, queryResultSchema, queryTurnSchema, queryContentSchema, queryUsageSchema, querySchema, runStatusSchema, testResultStatusSchema, testOutcomeSchema, attachmentKindSchema, stepCategorySchema, runSummarySchema, ciMetadataSchema, gitMetadataSchema, playwrightConfigSchema, errorInfoSchema, locationSchema, sourceSnippetSchema, runSchema, annotationSchema, testSchema, testResultSchema, baseStepSchema, stepSchema, attachmentSchema, listRunsQuerySchema, listTestsQuerySchema, runsListResponseSchema, runDetailResponseSchema, coverageTestItemSchema, secondaryTagMetricSchema, tagCoverageSchema, coverageStatsSchema, coverageAlertSchema, coverageDashboardResponseSchema, coverageDashboardQuerySchema, testsListResponseSchema, testDetailResponseSchema, testHistoryItemSchema, testHistoryResponseSchema, topOffenderSchema, topOffendersResponseSchema, trendPointSchema, trendsResponseSchema, errorCategorySchema, failureClusterSchema, newFailureSchema, runInsightsResponseSchema, FailureCategoryEnum, SelectorTypeEnum, FailureCategoryStatsSchema, FailureCategoriesResponseSchema, FailingSelectorStatsSchema, FailingSelectorsResponseSchema, newFailureItemSchema, newFailuresResponseSchema, flakyTestItemSchema, flakyTestsResponseSchema, slowestTestItemSchema, slowestTestsResponseSchema, runSummaryEmailFailureSchema, runSummaryEmailReportSchema, sendRunReportRequestSchema, metricWithTrendSchema, weekOverWeekMetricsSchema, ciComputeTimeSchema, investigationCandidateSchema, failureCategoryBreakdownSchema, stabilityTrendSchema, folderStabilitySchema, managerReportSchema, managerReportQuerySchema, sendManagerReportRequestSchema, reportAttachmentLinkSchema, developerReportRegressionSchema, developerReportFlakyTestSchema, developerReportSlowTestSchema, developerRunSummaryReportSchema, executiveReportStatusSchema, executiveMetricSchema, executiveKeyMetricsSchema, executiveTrendPointSchema, executiveTrendsSchema, executiveFlakyOffenderSchema, executiveSlowestOffenderSchema, executiveTopOffendersSchema, executiveReportSchema, executiveReportQuerySchema, sendExecutiveReportRequestSchema, assigneeInfoSchema, assigneesListResponseSchema, testCatalogPrioritySchema, testCatalogTypeSchema, testCatalogAssignmentStatusSchema, testCatalogAssignmentSchema, testCatalogDefinitionSchema, testCatalogListItemSchema, testCatalogHistoryEntrySchema, testCatalogListQuerySchema, testCatalogListResponseSchema, testCatalogDetailResponseSchema, testCatalogFilterOptionsSchema, testCatalogAssigneeInfoSchema, testCatalogAssignRequestSchema, testCatalogUpdateAssignmentRequestSchema, testCatalogAssignmentResponseSchema, testCatalogFolderNodeSchema, testCatalogFolderTreeResponseSchema, testCatalogFolderTestsQuerySchema, SECONDS_PER_MINUTE, SECONDS_PER_HOUR, SECONDS_PER_DAY, SECONDS_PER_WEEK, SECONDS_PER_MONTH, SECONDS_PER_YEAR;
1180
1325
  var init_shared_es = __esm({
1181
1326
  "../shared/dist/shared.es.mjs"() {
1182
1327
  "use strict";
@@ -5993,84 +6138,6 @@ var init_shared_es = __esm({
5993
6138
  month: stringType().regex(/^\d{4}-\d{2}$/, "month must be in format YYYY-MM"),
5994
6139
  emails: arrayType(stringType().email())
5995
6140
  });
5996
- fixAssignmentStatusSchema = enumType([
5997
- "assigned",
5998
- // Currently being worked on
5999
- "completed",
6000
- // Successfully fixed
6001
- "failed"
6002
- // Fix attempt failed
6003
- ]);
6004
- fixAssignmentSchema = objectType({
6005
- id: stringType(),
6006
- testRunId: stringType(),
6007
- // Reference to test_run table
6008
- assignedTo: stringType().optional(),
6009
- // User ID (Clerk user ID)
6010
- assignedAt: stringType().optional(),
6011
- // ISO timestamp
6012
- status: fixAssignmentStatusSchema.optional(),
6013
- completedAt: stringType().optional(),
6014
- reason: stringType().optional(),
6015
- createdAt: stringType(),
6016
- updatedAt: stringType()
6017
- });
6018
- testWithAssignmentSchema = objectType({
6019
- // Core test fields from Test schema
6020
- id: stringType(),
6021
- readableId: stringType().optional(),
6022
- runId: stringType(),
6023
- file: stringType(),
6024
- title: stringType(),
6025
- location: objectType({
6026
- file: stringType(),
6027
- line: numberType(),
6028
- column: numberType().optional()
6029
- }).optional(),
6030
- status: enumType(["passed", "failed", "timedOut", "skipped", "interrupted"]),
6031
- durationMs: numberType(),
6032
- retryCount: numberType(),
6033
- isFlaky: booleanType().optional(),
6034
- // Assignment fields
6035
- assignmentId: stringType().optional(),
6036
- assignedTo: stringType().optional(),
6037
- assignedAt: stringType().optional(),
6038
- assignmentStatus: fixAssignmentStatusSchema.optional(),
6039
- assignmentCompletedAt: stringType().optional(),
6040
- assignmentReason: stringType().optional()
6041
- });
6042
- listAssignmentsQuerySchema = objectType({
6043
- runId: stringType().optional(),
6044
- assignedTo: stringType().optional(),
6045
- status: fixAssignmentStatusSchema.optional(),
6046
- file: stringType().optional(),
6047
- search: stringType().optional(),
6048
- page: coerce.number().min(1).default(1),
6049
- limit: coerce.number().min(1).max(100).default(50)
6050
- });
6051
- listTestsWithAssignmentsQuerySchema = objectType({
6052
- assignedTo: stringType().optional(),
6053
- status: fixAssignmentStatusSchema.optional(),
6054
- testStatus: enumType(["passed", "failed", "timedOut", "skipped", "interrupted"]).optional(),
6055
- file: stringType().optional(),
6056
- search: stringType().optional(),
6057
- page: coerce.number().min(1).default(1),
6058
- limit: coerce.number().min(1).max(100).default(50)
6059
- });
6060
- assignmentsListResponseSchema = objectType({
6061
- assignments: arrayType(testWithAssignmentSchema),
6062
- total: numberType(),
6063
- page: numberType(),
6064
- limit: numberType(),
6065
- // Summary stats
6066
- stats: objectType({
6067
- total: numberType(),
6068
- assigned: numberType(),
6069
- completed: numberType(),
6070
- failed: numberType(),
6071
- unassigned: numberType()
6072
- })
6073
- });
6074
6141
  assigneeInfoSchema = objectType({
6075
6142
  id: stringType(),
6076
6143
  name: stringType(),
@@ -6081,84 +6148,6 @@ var init_shared_es = __esm({
6081
6148
  assigneesListResponseSchema = objectType({
6082
6149
  assignees: arrayType(assigneeInfoSchema)
6083
6150
  });
6084
- assignTestsRequestSchema = objectType({
6085
- testRunIds: arrayType(stringType()).min(1),
6086
- assignedTo: stringType().min(1),
6087
- notes: stringType().optional()
6088
- });
6089
- assignTestRequestSchema = objectType({
6090
- testRunId: stringType(),
6091
- assignedTo: stringType(),
6092
- reason: stringType().optional()
6093
- });
6094
- reassignTestRequestSchema = objectType({
6095
- assignedTo: stringType().min(1),
6096
- notes: stringType().optional()
6097
- });
6098
- completeAssignmentsRequestSchema = objectType({
6099
- assignmentIds: arrayType(stringType()).min(1),
6100
- status: fixAssignmentStatusSchema,
6101
- notes: stringType().optional(),
6102
- fixSessionId: stringType().optional()
6103
- });
6104
- completeAssignmentRequestSchema = objectType({
6105
- assignmentId: stringType(),
6106
- success: booleanType()
6107
- });
6108
- bulkReassignRequestSchema = objectType({
6109
- fromUser: stringType().min(1),
6110
- toUser: stringType().min(1),
6111
- status: fixAssignmentStatusSchema.optional(),
6112
- notes: stringType().optional()
6113
- });
6114
- assignmentResponseSchema = objectType({
6115
- assignment: fixAssignmentSchema
6116
- });
6117
- assignTestsResponseSchema = objectType({
6118
- assigned: arrayType(
6119
- objectType({
6120
- id: stringType(),
6121
- testRunId: stringType()
6122
- })
6123
- ),
6124
- conflicts: arrayType(
6125
- objectType({
6126
- testRunId: stringType(),
6127
- file: stringType(),
6128
- title: stringType(),
6129
- currentAssignee: stringType()
6130
- })
6131
- )
6132
- });
6133
- testsWithAssignmentsResponseSchema = objectType({
6134
- tests: arrayType(testWithAssignmentSchema),
6135
- total: numberType(),
6136
- page: numberType(),
6137
- limit: numberType(),
6138
- stats: objectType({
6139
- total: numberType(),
6140
- assigned: numberType(),
6141
- completed: numberType(),
6142
- failed: numberType(),
6143
- unassigned: numberType()
6144
- })
6145
- });
6146
- bulkReassignResponseSchema = objectType({
6147
- reassigned: numberType(),
6148
- conflicts: arrayType(
6149
- objectType({
6150
- testRunId: stringType(),
6151
- file: stringType(),
6152
- title: stringType(),
6153
- reason: stringType()
6154
- })
6155
- )
6156
- });
6157
- userAssignmentStatsSchema = objectType({
6158
- assigned: numberType(),
6159
- completed: numberType(),
6160
- failed: numberType()
6161
- });
6162
6151
  testCatalogPrioritySchema = enumType([
6163
6152
  "critical",
6164
6153
  "high",
@@ -6337,6 +6326,9 @@ var init_shared_es = __esm({
6337
6326
 
6338
6327
  // src/commands/setup.ts
6339
6328
  import { execSync, spawn, spawnSync } from "child_process";
6329
+ import { existsSync, readFileSync, writeFileSync } from "fs";
6330
+ import { homedir } from "os";
6331
+ import { join } from "path";
6340
6332
  function parseVersion(versionString) {
6341
6333
  const cleaned = versionString.trim().replace(/^v/, "");
6342
6334
  const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)/);
@@ -6449,6 +6441,23 @@ async function installAgentBrowser() {
6449
6441
  });
6450
6442
  });
6451
6443
  }
6444
+ function removePlaywrightMcpFromFile(mcpPath) {
6445
+ if (!existsSync(mcpPath)) {
6446
+ return false;
6447
+ }
6448
+ try {
6449
+ const content = readFileSync(mcpPath, "utf-8");
6450
+ const config2 = JSON.parse(content);
6451
+ if (!config2.mcpServers?.playwright) {
6452
+ return false;
6453
+ }
6454
+ delete config2.mcpServers.playwright;
6455
+ writeFileSync(mcpPath, JSON.stringify(config2, null, 2), "utf-8");
6456
+ return true;
6457
+ } catch {
6458
+ return false;
6459
+ }
6460
+ }
6452
6461
  function getVersionSummary() {
6453
6462
  const nodeVersion = getNodeVersion();
6454
6463
  const agentBrowserVersion = getAgentBrowserVersion();
@@ -6505,6 +6514,17 @@ async function setupCommand(options) {
6505
6514
  result.errors.push(installResult.message);
6506
6515
  }
6507
6516
  }
6517
+ log("\n3. Checking for deprecated Playwright MCP...");
6518
+ const globalMcpPath = join(homedir(), ".supatest", "mcp.json");
6519
+ const projectMcpPath = join(options.cwd, ".supatest", "mcp.json");
6520
+ const removedGlobal = removePlaywrightMcpFromFile(globalMcpPath);
6521
+ const removedProject = removePlaywrightMcpFromFile(projectMcpPath);
6522
+ if (removedGlobal || removedProject) {
6523
+ const locations = [removedGlobal && "global", removedProject && "project"].filter(Boolean).join(" and ");
6524
+ log(` \u{1F5D1}\uFE0F Removed Playwright MCP from ${locations} config (no longer needed)`);
6525
+ } else {
6526
+ log(" \u2705 No Playwright MCP found");
6527
+ }
6508
6528
  const versionSummary = getVersionSummary();
6509
6529
  log(versionSummary);
6510
6530
  log("\n" + "\u2500".repeat(50));
@@ -6533,7 +6553,7 @@ var CLI_VERSION;
6533
6553
  var init_version = __esm({
6534
6554
  "src/version.ts"() {
6535
6555
  "use strict";
6536
- CLI_VERSION = "0.0.48";
6556
+ CLI_VERSION = "0.0.50";
6537
6557
  }
6538
6558
  });
6539
6559
 
@@ -7474,8 +7494,8 @@ var init_api_client = __esm({
7474
7494
  });
7475
7495
 
7476
7496
  // src/utils/command-discovery.ts
7477
- import { existsSync as existsSync2, readdirSync, readFileSync, statSync as statSync2 } from "fs";
7478
- import { join as join3, relative } from "path";
7497
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
7498
+ import { join as join4, relative } from "path";
7479
7499
  function parseMarkdownFrontmatter(content) {
7480
7500
  const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
7481
7501
  const match = content.match(frontmatterRegex);
@@ -7495,12 +7515,12 @@ function parseMarkdownFrontmatter(content) {
7495
7515
  return { frontmatter, body };
7496
7516
  }
7497
7517
  function discoverMarkdownFiles(dir, baseDir, files = []) {
7498
- if (!existsSync2(dir)) {
7518
+ if (!existsSync3(dir)) {
7499
7519
  return files;
7500
7520
  }
7501
7521
  const entries = readdirSync(dir);
7502
7522
  for (const entry of entries) {
7503
- const fullPath = join3(dir, entry);
7523
+ const fullPath = join4(dir, entry);
7504
7524
  const stat = statSync2(fullPath);
7505
7525
  if (stat.isDirectory()) {
7506
7526
  discoverMarkdownFiles(fullPath, baseDir, files);
@@ -7511,15 +7531,15 @@ function discoverMarkdownFiles(dir, baseDir, files = []) {
7511
7531
  return files;
7512
7532
  }
7513
7533
  function discoverCommands(cwd) {
7514
- const commandsDir = join3(cwd, ".supatest", "commands");
7515
- if (!existsSync2(commandsDir)) {
7534
+ const commandsDir = join4(cwd, ".supatest", "commands");
7535
+ if (!existsSync3(commandsDir)) {
7516
7536
  return [];
7517
7537
  }
7518
7538
  const files = discoverMarkdownFiles(commandsDir, commandsDir);
7519
7539
  const commands = [];
7520
7540
  for (const filePath of files) {
7521
7541
  try {
7522
- const content = readFileSync(filePath, "utf-8");
7542
+ const content = readFileSync2(filePath, "utf-8");
7523
7543
  const { frontmatter } = parseMarkdownFrontmatter(content);
7524
7544
  const relativePath = relative(commandsDir, filePath);
7525
7545
  const name = relativePath.replace(/\.md$/, "").replace(/\//g, ".").replace(/\\/g, ".");
@@ -7534,14 +7554,14 @@ function discoverCommands(cwd) {
7534
7554
  return commands.sort((a, b) => a.name.localeCompare(b.name));
7535
7555
  }
7536
7556
  function expandCommand(cwd, commandName, args) {
7537
- const commandsDir = join3(cwd, ".supatest", "commands");
7557
+ const commandsDir = join4(cwd, ".supatest", "commands");
7538
7558
  const relativePath = commandName.replace(/\./g, "/") + ".md";
7539
- const filePath = join3(commandsDir, relativePath);
7540
- if (!existsSync2(filePath)) {
7559
+ const filePath = join4(commandsDir, relativePath);
7560
+ if (!existsSync3(filePath)) {
7541
7561
  return null;
7542
7562
  }
7543
7563
  try {
7544
- const content = readFileSync(filePath, "utf-8");
7564
+ const content = readFileSync2(filePath, "utf-8");
7545
7565
  const { body } = parseMarkdownFrontmatter(content);
7546
7566
  let expanded = body;
7547
7567
  if (args) {
@@ -7559,15 +7579,15 @@ function expandCommand(cwd, commandName, args) {
7559
7579
  }
7560
7580
  }
7561
7581
  function discoverAgents(cwd) {
7562
- const agentsDir = join3(cwd, ".supatest", "agents");
7563
- if (!existsSync2(agentsDir)) {
7582
+ const agentsDir = join4(cwd, ".supatest", "agents");
7583
+ if (!existsSync3(agentsDir)) {
7564
7584
  return [];
7565
7585
  }
7566
7586
  const files = discoverMarkdownFiles(agentsDir, agentsDir);
7567
7587
  const agents = [];
7568
7588
  for (const filePath of files) {
7569
7589
  try {
7570
- const content = readFileSync(filePath, "utf-8");
7590
+ const content = readFileSync2(filePath, "utf-8");
7571
7591
  const { frontmatter } = parseMarkdownFrontmatter(content);
7572
7592
  const relativePath = relative(agentsDir, filePath);
7573
7593
  const defaultName = relativePath.replace(/\.md$/, "").replace(/\//g, "-").replace(/\\/g, "-");
@@ -7589,9 +7609,9 @@ var init_command_discovery = __esm({
7589
7609
  });
7590
7610
 
7591
7611
  // src/utils/mcp-loader.ts
7592
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
7593
- import { homedir as homedir2 } from "os";
7594
- import { join as join4 } from "path";
7612
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
7613
+ import { homedir as homedir3 } from "os";
7614
+ import { join as join5 } from "path";
7595
7615
  function expandEnvVar(value) {
7596
7616
  return value.replace(/\$\{([^}]+)\}/g, (_, expr) => {
7597
7617
  const [varName, defaultValue] = expr.split(":-");
@@ -7614,11 +7634,11 @@ function expandServerConfig(config2) {
7614
7634
  return expanded;
7615
7635
  }
7616
7636
  function loadMcpServersFromFile(mcpPath) {
7617
- if (!existsSync3(mcpPath)) {
7637
+ if (!existsSync4(mcpPath)) {
7618
7638
  return {};
7619
7639
  }
7620
7640
  try {
7621
- const content = readFileSync2(mcpPath, "utf-8");
7641
+ const content = readFileSync3(mcpPath, "utf-8");
7622
7642
  const config2 = JSON.parse(content);
7623
7643
  if (!config2.mcpServers) {
7624
7644
  return {};
@@ -7640,9 +7660,9 @@ function loadMcpServersFromFile(mcpPath) {
7640
7660
  }
7641
7661
  }
7642
7662
  function loadMcpServers(cwd) {
7643
- const globalMcpPath = join4(homedir2(), ".supatest", "mcp.json");
7663
+ const globalMcpPath = join5(homedir3(), ".supatest", "mcp.json");
7644
7664
  const globalServers = loadMcpServersFromFile(globalMcpPath);
7645
- const projectMcpPath = join4(cwd, ".supatest", "mcp.json");
7665
+ const projectMcpPath = join5(cwd, ".supatest", "mcp.json");
7646
7666
  const projectServers = loadMcpServersFromFile(projectMcpPath);
7647
7667
  return { ...globalServers, ...projectServers };
7648
7668
  }
@@ -7653,17 +7673,17 @@ var init_mcp_loader = __esm({
7653
7673
  });
7654
7674
 
7655
7675
  // src/utils/project-instructions.ts
7656
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
7657
- import { join as join5 } from "path";
7676
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
7677
+ import { join as join6 } from "path";
7658
7678
  function loadProjectInstructions(cwd) {
7659
7679
  const paths = [
7660
- join5(cwd, "SUPATEST.md"),
7661
- join5(cwd, ".supatest", "SUPATEST.md")
7680
+ join6(cwd, "SUPATEST.md"),
7681
+ join6(cwd, ".supatest", "SUPATEST.md")
7662
7682
  ];
7663
7683
  for (const path5 of paths) {
7664
- if (existsSync4(path5)) {
7684
+ if (existsSync5(path5)) {
7665
7685
  try {
7666
- return readFileSync3(path5, "utf-8");
7686
+ return readFileSync4(path5, "utf-8");
7667
7687
  } catch {
7668
7688
  }
7669
7689
  }
@@ -7678,8 +7698,8 @@ var init_project_instructions = __esm({
7678
7698
 
7679
7699
  // src/core/agent.ts
7680
7700
  import { createRequire } from "module";
7681
- import { homedir as homedir3 } from "os";
7682
- import { dirname, join as join6 } from "path";
7701
+ import { homedir as homedir4 } from "os";
7702
+ import { dirname, join as join7 } from "path";
7683
7703
  import { query } from "@anthropic-ai/claude-agent-sdk";
7684
7704
  var CoreAgent;
7685
7705
  var init_agent = __esm({
@@ -7809,7 +7829,7 @@ ${projectInstructions}`,
7809
7829
  this.presenter.onLog(`Auth: Using Claude Max (default Claude Code credentials)`);
7810
7830
  logger.debug("[agent] Claude Max mode: Using default ~/.claude/ config, cleared provider credentials");
7811
7831
  } else {
7812
- const internalConfigDir = join6(homedir3(), ".supatest", "claude-internal");
7832
+ const internalConfigDir = join7(homedir4(), ".supatest", "claude-internal");
7813
7833
  cleanEnv.CLAUDE_CONFIG_DIR = internalConfigDir;
7814
7834
  cleanEnv.ANTHROPIC_API_KEY = config2.supatestApiKey || "";
7815
7835
  cleanEnv.ANTHROPIC_BASE_URL = process.env.ANTHROPIC_BASE_URL || "";
@@ -7876,8 +7896,20 @@ ${projectInstructions}`,
7876
7896
  stderr: (msg) => {
7877
7897
  this.presenter.onLog(`[Agent Failure] ${msg}`);
7878
7898
  },
7879
- // Intercept AskUserQuestion tool to wait for user response
7899
+ // Intercept AskUserQuestion and ExitPlanMode tools to wait for user response
7880
7900
  canUseTool: async (toolName, input) => {
7901
+ if (toolName === "ExitPlanMode") {
7902
+ const questionId2 = `plan-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
7903
+ this.presenter.onToolUse?.(toolName, input, questionId2);
7904
+ const answers2 = await this.createQuestionPromise(questionId2);
7905
+ if (answers2 && answers2.approved === "true") {
7906
+ this.presenter.onToolResult?.(questionId2, JSON.stringify({ approved: true }));
7907
+ return { behavior: "allow", updatedInput: input };
7908
+ }
7909
+ const feedbackMsg = answers2?.feedback || "User did not approve the plan.";
7910
+ this.presenter.onToolResult?.(questionId2, JSON.stringify({ approved: false, feedback: feedbackMsg }));
7911
+ return { behavior: "deny", message: feedbackMsg };
7912
+ }
7881
7913
  if (toolName !== "AskUserQuestion") {
7882
7914
  return { behavior: "allow", updatedInput: input };
7883
7915
  }
@@ -8139,7 +8171,7 @@ ${projectInstructions}`,
8139
8171
  let claudeCodePath;
8140
8172
  const require2 = createRequire(import.meta.url);
8141
8173
  const sdkPath = require2.resolve("@anthropic-ai/claude-agent-sdk/sdk.mjs");
8142
- claudeCodePath = join6(dirname(sdkPath), "cli.js");
8174
+ claudeCodePath = join7(dirname(sdkPath), "cli.js");
8143
8175
  this.presenter.onLog(`Using SDK CLI: ${claudeCodePath}`);
8144
8176
  if (config.claudeCodeExecutablePath) {
8145
8177
  claudeCodePath = config.claudeCodeExecutablePath;
@@ -8282,17 +8314,21 @@ var init_react = __esm({
8282
8314
  });
8283
8315
  }
8284
8316
  onToolUse(tool, input, toolId) {
8285
- const message = {
8286
- type: "tool",
8287
- content: getToolDescription(tool, input),
8288
- toolName: getToolDisplayName(tool),
8289
- toolInput: input,
8290
- toolResult: void 0,
8291
- isExpanded: false,
8292
- isPending: true,
8293
- toolUseId: toolId
8294
- };
8295
- this.callbacks.addMessage(message);
8317
+ const interactiveTools = ["ExitPlanMode", "AskUserQuestion", "EnterPlanMode"];
8318
+ const isInteractiveTool = interactiveTools.includes(tool);
8319
+ if (!isInteractiveTool) {
8320
+ const message = {
8321
+ type: "tool",
8322
+ content: getToolDescription(tool, input),
8323
+ toolName: getToolDisplayName(tool),
8324
+ toolInput: input,
8325
+ toolResult: void 0,
8326
+ isExpanded: false,
8327
+ isPending: true,
8328
+ toolUseId: toolId
8329
+ };
8330
+ this.callbacks.addMessage(message);
8331
+ }
8296
8332
  this.hasAssistantMessage = false;
8297
8333
  this.hasThinkingMessage = false;
8298
8334
  this.currentAssistantText = "";
@@ -8322,7 +8358,7 @@ var init_react = __esm({
8322
8358
  });
8323
8359
  }
8324
8360
  } else if (tool === "ExitPlanMode") {
8325
- this.callbacks.onExitPlanMode?.();
8361
+ this.callbacks.onPlanApproval?.(toolId, input?.allowedPrompts);
8326
8362
  } else if (tool === "EnterPlanMode") {
8327
8363
  this.callbacks.onEnterPlanMode?.();
8328
8364
  } else if (tool === "AskUserQuestion") {
@@ -8492,6 +8528,7 @@ var init_SessionContext = __esm({
8492
8528
  const [toolGroupsExpanded, setToolGroupsExpanded] = useState(false);
8493
8529
  const [staticRemountKey, setStaticRemountKey] = useState(0);
8494
8530
  const [pendingQuestion, setPendingQuestion] = useState(null);
8531
+ const [pendingPlanApproval, setPendingPlanApproval] = useState(null);
8495
8532
  const addMessage = useCallback(
8496
8533
  (message) => {
8497
8534
  const expandableTools = ["Bash", "BashOutput", "Command Output"];
@@ -8624,7 +8661,9 @@ var init_SessionContext = __esm({
8624
8661
  staticRemountKey,
8625
8662
  refreshStatic,
8626
8663
  pendingQuestion,
8627
- setPendingQuestion
8664
+ setPendingQuestion,
8665
+ pendingPlanApproval,
8666
+ setPendingPlanApproval
8628
8667
  }), [
8629
8668
  messages,
8630
8669
  addMessage,
@@ -8650,7 +8689,8 @@ var init_SessionContext = __esm({
8650
8689
  llmProvider,
8651
8690
  staticRemountKey,
8652
8691
  refreshStatic,
8653
- pendingQuestion
8692
+ pendingQuestion,
8693
+ pendingPlanApproval
8654
8694
  ]);
8655
8695
  const usageStatsValue = useMemo(() => ({
8656
8696
  usageStats,
@@ -9591,7 +9631,7 @@ var init_MessageList = __esm({
9591
9631
  });
9592
9632
  StaticHeader.displayName = "StaticHeader";
9593
9633
  MessageList = memo2(({ terminalWidth, currentFolder, gitBranch, headless = false, queuedTasks = [] }) => {
9594
- const { messages, updateMessageById, isAgentRunning, staticRemountKey, toolGroupsExpanded, toggleToolGroups } = useSession();
9634
+ const { messages, updateMessageById, isAgentRunning, staticRemountKey, toolGroupsExpanded, toggleToolGroups, pendingQuestion, pendingPlanApproval } = useSession();
9595
9635
  const handleToggle = useCallback2((id, currentExpanded) => {
9596
9636
  updateMessageById(id, { isExpanded: !currentExpanded });
9597
9637
  }, [updateMessageById]);
@@ -9734,15 +9774,6 @@ var init_MessageList = __esm({
9734
9774
  }
9735
9775
  flushToolGroup();
9736
9776
  };
9737
- const completeMessages = [];
9738
- const inProgressMessages = [];
9739
- for (const msg of messages) {
9740
- if (isMessageComplete(msg)) {
9741
- completeMessages.push(msg);
9742
- } else {
9743
- inProgressMessages.push(msg);
9744
- }
9745
- }
9746
9777
  let splitIndex = messages.length;
9747
9778
  for (let i = messages.length - 1; i >= 0; i--) {
9748
9779
  if (isMessageComplete(messages[i])) {
@@ -9750,10 +9781,10 @@ var init_MessageList = __esm({
9750
9781
  break;
9751
9782
  }
9752
9783
  }
9753
- const finalCompleteMessages = messages.slice(0, splitIndex);
9754
- const finalInProgressMessages = messages.slice(splitIndex);
9755
- processTurn(finalCompleteMessages, completed);
9756
- processTurn(finalInProgressMessages, currentTurn);
9784
+ const completeMessages = messages.slice(0, splitIndex);
9785
+ const inProgressMessages = messages.slice(splitIndex);
9786
+ processTurn(completeMessages, completed);
9787
+ processTurn(inProgressMessages, currentTurn);
9757
9788
  return { completedGroups: completed, currentTurnGroups: currentTurn };
9758
9789
  }, [messages]);
9759
9790
  const staticItems = useMemo3(
@@ -9773,7 +9804,7 @@ var init_MessageList = __esm({
9773
9804
  headless,
9774
9805
  staticRemountKey
9775
9806
  }
9776
- ), staticItems.length > 0 && /* @__PURE__ */ React13.createElement(Static, { items: staticItems, key: `messages-${staticRemountKey}-${toolGroupsExpanded}` }, (item) => {
9807
+ ), staticItems.length > 0 && /* @__PURE__ */ React13.createElement(Static, { items: staticItems, key: `messages-${staticRemountKey}` }, (item) => {
9777
9808
  if (item._isGroup) {
9778
9809
  const content2 = renderGroupedMessage(item);
9779
9810
  if (!content2) {
@@ -9792,7 +9823,7 @@ var init_MessageList = __esm({
9792
9823
  return null;
9793
9824
  }
9794
9825
  return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", key: group.type === "group" ? `current-group-${idx}` : group.messages[0].id, width: "100%" }, content);
9795
- }), /* @__PURE__ */ React13.createElement(QueuedMessageDisplay, { messageQueue: queuedTasks }), isAgentRunning && !hasPendingAssistant && /* @__PURE__ */ React13.createElement(LoadingMessage, { headless, key: "loading" }), /* @__PURE__ */ React13.createElement(Box12, { height: 1 }));
9826
+ }), /* @__PURE__ */ React13.createElement(QueuedMessageDisplay, { messageQueue: queuedTasks }), isAgentRunning && !hasPendingAssistant && !pendingQuestion && !pendingPlanApproval && /* @__PURE__ */ React13.createElement(LoadingMessage, { headless, key: "loading" }), /* @__PURE__ */ React13.createElement(Box12, { height: 1 }));
9796
9827
  });
9797
9828
  MessageList.displayName = "MessageList";
9798
9829
  }
@@ -9920,21 +9951,21 @@ var init_encryption = __esm({
9920
9951
  });
9921
9952
 
9922
9953
  // src/utils/token-storage.ts
9923
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync } from "fs";
9924
- import { homedir as homedir5 } from "os";
9925
- import { join as join7 } from "path";
9954
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
9955
+ import { homedir as homedir6 } from "os";
9956
+ import { join as join8 } from "path";
9926
9957
  function getTokenFilePath() {
9927
9958
  const apiUrl = process.env.SUPATEST_API_URL || PRODUCTION_API_URL;
9928
9959
  if (apiUrl === PRODUCTION_API_URL) {
9929
- return join7(CONFIG_DIR, "token.json");
9960
+ return join8(CONFIG_DIR, "token.json");
9930
9961
  }
9931
- return join7(CONFIG_DIR, "token.local.json");
9962
+ return join8(CONFIG_DIR, "token.local.json");
9932
9963
  }
9933
9964
  function isV2Format(stored) {
9934
9965
  return "version" in stored && stored.version === 2;
9935
9966
  }
9936
9967
  function ensureConfigDir() {
9937
- if (!existsSync5(CONFIG_DIR)) {
9968
+ if (!existsSync6(CONFIG_DIR)) {
9938
9969
  mkdirSync2(CONFIG_DIR, { recursive: true, mode: 448 });
9939
9970
  }
9940
9971
  }
@@ -9950,15 +9981,15 @@ function saveToken(token, expiresAt) {
9950
9981
  encryptedData: encrypt(JSON.stringify(payload))
9951
9982
  };
9952
9983
  const tokenFile = getTokenFilePath();
9953
- writeFileSync(tokenFile, JSON.stringify(stored, null, 2), { mode: 384 });
9984
+ writeFileSync2(tokenFile, JSON.stringify(stored, null, 2), { mode: 384 });
9954
9985
  }
9955
9986
  function loadToken() {
9956
9987
  const tokenFile = getTokenFilePath();
9957
- if (!existsSync5(tokenFile)) {
9988
+ if (!existsSync6(tokenFile)) {
9958
9989
  return null;
9959
9990
  }
9960
9991
  try {
9961
- const data = readFileSync4(tokenFile, "utf8");
9992
+ const data = readFileSync5(tokenFile, "utf8");
9962
9993
  const stored = JSON.parse(data);
9963
9994
  let payload;
9964
9995
  if (isV2Format(stored)) {
@@ -9987,7 +10018,7 @@ function loadToken() {
9987
10018
  }
9988
10019
  function removeToken() {
9989
10020
  const tokenFile = getTokenFilePath();
9990
- if (existsSync5(tokenFile)) {
10021
+ if (existsSync6(tokenFile)) {
9991
10022
  unlinkSync2(tokenFile);
9992
10023
  }
9993
10024
  }
@@ -9996,10 +10027,10 @@ var init_token_storage = __esm({
9996
10027
  "src/utils/token-storage.ts"() {
9997
10028
  "use strict";
9998
10029
  init_encryption();
9999
- CONFIG_DIR = join7(homedir5(), ".supatest");
10030
+ CONFIG_DIR = join8(homedir6(), ".supatest");
10000
10031
  PRODUCTION_API_URL = "https://code-api.supatest.ai";
10001
10032
  STORAGE_VERSION = 2;
10002
- TOKEN_FILE = join7(CONFIG_DIR, "token.json");
10033
+ TOKEN_FILE = join8(CONFIG_DIR, "token.json");
10003
10034
  }
10004
10035
  });
10005
10036
 
@@ -11058,8 +11089,8 @@ __export(secret_storage_exports, {
11058
11089
  setSecret: () => setSecret
11059
11090
  });
11060
11091
  import { promises as fs3 } from "fs";
11061
- import { homedir as homedir6 } from "os";
11062
- import { dirname as dirname2, join as join8 } from "path";
11092
+ import { homedir as homedir7 } from "os";
11093
+ import { dirname as dirname2, join as join9 } from "path";
11063
11094
  async function getSecret(key) {
11064
11095
  return storage.getSecret(key);
11065
11096
  }
@@ -11084,8 +11115,8 @@ var init_secret_storage = __esm({
11084
11115
  secretFilePath;
11085
11116
  constructor() {
11086
11117
  const rootDirName = process.env.NODE_ENV === "development" ? ".supatest-dev" : ".supatest";
11087
- const secretsDir = join8(homedir6(), rootDirName, "claude-auth");
11088
- this.secretFilePath = join8(secretsDir, SECRET_FILE_NAME);
11118
+ const secretsDir = join9(homedir7(), rootDirName, "claude-auth");
11119
+ this.secretFilePath = join9(secretsDir, SECRET_FILE_NAME);
11089
11120
  }
11090
11121
  async ensureDirectoryExists() {
11091
11122
  const dir = dirname2(this.secretFilePath);
@@ -11182,21 +11213,21 @@ var init_claude_max = __esm({
11182
11213
  });
11183
11214
 
11184
11215
  // src/utils/mcp-manager.ts
11185
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
11186
- import { homedir as homedir7 } from "os";
11187
- import { join as join9 } from "path";
11216
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
11217
+ import { homedir as homedir8 } from "os";
11218
+ import { join as join10 } from "path";
11188
11219
  function getGlobalMcpPath() {
11189
- return join9(homedir7(), ".supatest", "mcp.json");
11220
+ return join10(homedir8(), ".supatest", "mcp.json");
11190
11221
  }
11191
11222
  function getProjectMcpPath(cwd) {
11192
- return join9(cwd, ".supatest", "mcp.json");
11223
+ return join10(cwd, ".supatest", "mcp.json");
11193
11224
  }
11194
11225
  function loadMcpConfigFromFile(mcpPath, scope) {
11195
- if (!existsSync6(mcpPath)) {
11226
+ if (!existsSync7(mcpPath)) {
11196
11227
  return {};
11197
11228
  }
11198
11229
  try {
11199
- const content = readFileSync5(mcpPath, "utf-8");
11230
+ const content = readFileSync6(mcpPath, "utf-8");
11200
11231
  const config2 = JSON.parse(content);
11201
11232
  if (!config2.mcpServers) {
11202
11233
  return {};
@@ -11229,8 +11260,8 @@ function loadMcpConfig(cwd) {
11229
11260
  return { ...globalServers, ...projectServers };
11230
11261
  }
11231
11262
  function saveMcpConfigToFile(mcpPath, servers) {
11232
- const mcpDir = join9(mcpPath, "..");
11233
- if (!existsSync6(mcpDir)) {
11263
+ const mcpDir = join10(mcpPath, "..");
11264
+ if (!existsSync7(mcpDir)) {
11234
11265
  mkdirSync3(mcpDir, { recursive: true });
11235
11266
  }
11236
11267
  const config2 = {
@@ -11245,7 +11276,7 @@ function saveMcpConfigToFile(mcpPath, servers) {
11245
11276
  description: server.description
11246
11277
  };
11247
11278
  }
11248
- writeFileSync2(mcpPath, JSON.stringify(config2, null, 2), "utf-8");
11279
+ writeFileSync3(mcpPath, JSON.stringify(config2, null, 2), "utf-8");
11249
11280
  }
11250
11281
  function saveMcpConfig(cwd, servers) {
11251
11282
  const globalServers = {};
@@ -11414,15 +11445,15 @@ var init_mcp_manager = __esm({
11414
11445
  });
11415
11446
 
11416
11447
  // src/utils/settings-loader.ts
11417
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
11418
- import { join as join10 } from "path";
11448
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
11449
+ import { join as join11 } from "path";
11419
11450
  function loadSupatestSettings(cwd) {
11420
- const settingsPath = join10(cwd, ".supatest", "settings.json");
11421
- if (!existsSync7(settingsPath)) {
11451
+ const settingsPath = join11(cwd, ".supatest", "settings.json");
11452
+ if (!existsSync8(settingsPath)) {
11422
11453
  return {};
11423
11454
  }
11424
11455
  try {
11425
- const content = readFileSync6(settingsPath, "utf-8");
11456
+ const content = readFileSync7(settingsPath, "utf-8");
11426
11457
  return JSON.parse(content);
11427
11458
  } catch (error) {
11428
11459
  console.warn(
@@ -11433,10 +11464,10 @@ function loadSupatestSettings(cwd) {
11433
11464
  }
11434
11465
  }
11435
11466
  function saveSupatestSettings(cwd, settings) {
11436
- const settingsDir = join10(cwd, ".supatest");
11437
- const settingsPath = join10(settingsDir, "settings.json");
11467
+ const settingsDir = join11(cwd, ".supatest");
11468
+ const settingsPath = join11(settingsDir, "settings.json");
11438
11469
  try {
11439
- if (!existsSync7(settingsDir)) {
11470
+ if (!existsSync8(settingsDir)) {
11440
11471
  mkdirSync4(settingsDir, { recursive: true });
11441
11472
  }
11442
11473
  const existingSettings = loadSupatestSettings(cwd);
@@ -11453,7 +11484,7 @@ function saveSupatestSettings(cwd, settings) {
11453
11484
  ...settings.hooks
11454
11485
  }
11455
11486
  };
11456
- writeFileSync3(settingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
11487
+ writeFileSync4(settingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
11457
11488
  } catch (error) {
11458
11489
  console.warn(
11459
11490
  `Warning: Failed to save settings to ${settingsPath}:`,
@@ -12363,6 +12394,9 @@ var init_FeedbackDialog = __esm({
12363
12394
  });
12364
12395
 
12365
12396
  // src/fix/context-builder.ts
12397
+ function getFrontendUrl() {
12398
+ return process.env.SUPATEST_FRONTEND_URL || "https://app.supatest.dev";
12399
+ }
12366
12400
  function buildTestContext(ctx) {
12367
12401
  const { test, history } = ctx;
12368
12402
  const lines = [];
@@ -12416,6 +12450,20 @@ function buildTestContext(ctx) {
12416
12450
  lines.push("```");
12417
12451
  lines.push("");
12418
12452
  }
12453
+ const attachments = lastResult.attachments;
12454
+ if (attachments && attachments.length > 0) {
12455
+ const screenshots = attachments.filter((a) => a.kind === "screenshot");
12456
+ const traces = attachments.filter((a) => a.kind === "trace");
12457
+ const videos = attachments.filter((a) => a.kind === "video");
12458
+ const parts = [];
12459
+ if (screenshots.length > 0) parts.push(`${screenshots.length} screenshot(s)`);
12460
+ if (traces.length > 0) parts.push(`${traces.length} trace(s)`);
12461
+ if (videos.length > 0) parts.push(`${videos.length} video(s)`);
12462
+ if (parts.length > 0) {
12463
+ lines.push(`## Attachments: ${parts.join(", ")}`);
12464
+ lines.push("");
12465
+ }
12466
+ }
12419
12467
  }
12420
12468
  if (history && history.history.length > 0) {
12421
12469
  const passCount = history.history.filter((h) => h.status === "passed").length;
@@ -12439,7 +12487,7 @@ ${buildTestContext(ctx)}`;
12439
12487
  });
12440
12488
  return sections.join("\n\n");
12441
12489
  }
12442
- function buildFixPrompt(tests) {
12490
+ function buildQuickFixPrompt(tests) {
12443
12491
  const intro = tests.length === 1 ? "Fix the following failing test:" : `Fix the following ${tests.length} failing tests:`;
12444
12492
  const context = buildMultipleTestsContext(tests);
12445
12493
  const instructions = `
@@ -12458,6 +12506,130 @@ Focus on fixing the actual issue, not just suppressing the error. If the test is
12458
12506
  ${context}
12459
12507
  ${instructions}`;
12460
12508
  }
12509
+ function formatTestForAnalysis(ctx, index, runId, frontendUrl, assignment) {
12510
+ const { test, history } = ctx;
12511
+ const lines = [];
12512
+ const testRunDetailUrl = `${frontendUrl}/runs/${runId}/tests/${test.id}`;
12513
+ lines.push(`### Test ${index + 1}: ${test.title}`);
12514
+ lines.push(`- **File**: ${test.file}${test.location ? `:${test.location.line}` : ""}`);
12515
+ lines.push(`- **Status**: ${test.status} | **Duration**: ${test.durationMs}ms | **Retries**: ${test.retryCount}`);
12516
+ if (test.projectName) {
12517
+ lines.push(`- **Browser/Project**: ${test.projectName}`);
12518
+ }
12519
+ if (test.tags && test.tags.length > 0) {
12520
+ lines.push(`- **Tags**: ${test.tags.join(", ")}`);
12521
+ }
12522
+ lines.push(`- **Details**: [View run details](${testRunDetailUrl})`);
12523
+ if (assignment) {
12524
+ lines.push(`- **Assignment**: Claimed by ${assignment.assignedTo} (${assignment.status})`);
12525
+ }
12526
+ lines.push("");
12527
+ const lastResult = test.results[test.results.length - 1];
12528
+ if (lastResult) {
12529
+ if (lastResult.errors && lastResult.errors.length > 0) {
12530
+ lines.push("**Error:**");
12531
+ for (const error of lastResult.errors) {
12532
+ lines.push(error.message);
12533
+ if (error.stack) {
12534
+ lines.push("```");
12535
+ lines.push(error.stack);
12536
+ lines.push("```");
12537
+ }
12538
+ }
12539
+ lines.push("");
12540
+ }
12541
+ if (lastResult.steps && lastResult.steps.length > 0) {
12542
+ lines.push("**Execution Steps:**");
12543
+ for (const step of lastResult.steps) {
12544
+ const failed = step.error ? " \u2190 FAILED" : "";
12545
+ const duration = `[${step.durationMs}ms]`;
12546
+ lines.push(`- ${duration} ${step.category}: ${step.title}${failed}`);
12547
+ if (step.error) {
12548
+ lines.push(` Error: ${step.error.message}`);
12549
+ }
12550
+ }
12551
+ lines.push("");
12552
+ }
12553
+ if (lastResult.stdout && lastResult.stdout.length > 0) {
12554
+ const stdout = lastResult.stdout.join("\n");
12555
+ const truncated = stdout.length > 500 ? stdout.slice(0, 500) + "\n... (truncated)" : stdout;
12556
+ lines.push("**Console Output (stdout):**");
12557
+ lines.push("```");
12558
+ lines.push(truncated);
12559
+ lines.push("```");
12560
+ lines.push("");
12561
+ }
12562
+ if (lastResult.stderr && lastResult.stderr.length > 0) {
12563
+ const stderr = lastResult.stderr.join("\n");
12564
+ const truncated = stderr.length > 500 ? stderr.slice(0, 500) + "\n... (truncated)" : stderr;
12565
+ lines.push("**Console Output (stderr):**");
12566
+ lines.push("```");
12567
+ lines.push(truncated);
12568
+ lines.push("```");
12569
+ lines.push("");
12570
+ }
12571
+ const attachments = lastResult.attachments;
12572
+ if (attachments && attachments.length > 0) {
12573
+ const screenshots = attachments.filter((a) => a.kind === "screenshot");
12574
+ const traces = attachments.filter((a) => a.kind === "trace");
12575
+ const videos = attachments.filter((a) => a.kind === "video");
12576
+ const parts = [];
12577
+ if (screenshots.length > 0) parts.push(`${screenshots.length} screenshot(s)`);
12578
+ if (traces.length > 0) parts.push(`${traces.length} trace(s)`);
12579
+ if (videos.length > 0) parts.push(`${videos.length} video(s)`);
12580
+ if (parts.length > 0) {
12581
+ lines.push(`**Attachments**: ${parts.join(", ")} \u2014 [view in dashboard](${testRunDetailUrl})`);
12582
+ lines.push("");
12583
+ }
12584
+ }
12585
+ }
12586
+ if (history && history.history.length > 0) {
12587
+ const passCount = history.history.filter((h) => h.status === "passed").length;
12588
+ const totalCount = history.history.length;
12589
+ const passRate = Math.round(passCount / totalCount * 100);
12590
+ const sparkline = history.history.map((h) => h.status === "passed" ? "\u2713" : "\u2717").join("");
12591
+ lines.push(`**History**: ${sparkline} (${passRate}% pass rate over last ${totalCount} runs)`);
12592
+ if (passRate > 0 && passRate < 80) {
12593
+ lines.push(`\u26A0 This test appears flaky \u2014 passes ${passRate}% of the time`);
12594
+ }
12595
+ lines.push("");
12596
+ }
12597
+ return lines.join("\n");
12598
+ }
12599
+ function buildUnderstandAndFixPrompt(tests, run, assignments, options) {
12600
+ const frontendUrl = getFrontendUrl();
12601
+ const runDetailUrl = `${frontendUrl}/runs/${run.id}`;
12602
+ const lines = [];
12603
+ lines.push("Analyze and fix the following test failures.\n");
12604
+ lines.push("## Run Information");
12605
+ lines.push(`- **Run**: ${run.readableId || run.id}`);
12606
+ lines.push(`- **Branch**: ${run.git?.branch || "unknown"}${run.git?.commit ? ` @ ${run.git.commit.slice(0, 7)}` : ""}`);
12607
+ if (run.git?.commitMessage) {
12608
+ lines.push(`- **Commit**: ${run.git.commitMessage}`);
12609
+ }
12610
+ if (run.ci?.provider) {
12611
+ lines.push(`- **CI**: ${run.ci.provider}${run.ci.workflow ? ` / ${run.ci.workflow}` : ""}`);
12612
+ }
12613
+ lines.push(`- **Results**: ${run.summary.failed} failed / ${run.summary.total} total (${run.summary.passed} passed, ${run.summary.flaky} flaky, ${run.summary.skipped} skipped)`);
12614
+ lines.push(`- **Duration**: ${Math.round(run.durationMs / 1e3)}s`);
12615
+ lines.push(`- **Dashboard**: [View full run](${runDetailUrl})`);
12616
+ lines.push("");
12617
+ if (options?.headless) {
12618
+ lines.push("[HEADLESS]\n");
12619
+ }
12620
+ const specFiles = new Set(tests.map((t) => t.test.file));
12621
+ lines.push(`## Failing Tests \u2014 ${tests.length} failure${tests.length !== 1 ? "s" : ""} across ${specFiles.size} spec file${specFiles.size !== 1 ? "s" : ""}`);
12622
+ lines.push("");
12623
+ for (let i = 0; i < tests.length; i++) {
12624
+ const testId = tests[i].test.id;
12625
+ const assignment = assignments?.get(testId) || null;
12626
+ lines.push(formatTestForAnalysis(tests[i], i, run.id, frontendUrl, assignment));
12627
+ if (i < tests.length - 1) {
12628
+ lines.push("---\n");
12629
+ }
12630
+ }
12631
+ return lines.join("\n");
12632
+ }
12461
12633
  var init_context_builder = __esm({
12462
12634
  "src/fix/context-builder.ts"() {
12463
12635
  "use strict";
@@ -12893,7 +13065,7 @@ var init_FixFlow = __esm({
12893
13065
  onStartFix,
12894
13066
  initialRunId
12895
13067
  }) => {
12896
- const [step, setStep] = useState8(initialRunId ? "select-run" : "select-run");
13068
+ const [step, setStep] = useState8("select-run");
12897
13069
  const [selectedRun, setSelectedRun] = useState8(null);
12898
13070
  const [selectedTests, setSelectedTests] = useState8([]);
12899
13071
  const [assignments, setAssignments] = useState8(/* @__PURE__ */ new Map());
@@ -12901,22 +13073,38 @@ var init_FixFlow = __esm({
12901
13073
  const [assignmentIds, setAssignmentIds] = useState8(/* @__PURE__ */ new Map());
12902
13074
  const [loadingProgress, setLoadingProgress] = useState8({ current: 0, total: 0 });
12903
13075
  const [loadError, setLoadError] = useState8(null);
13076
+ const [workflowMode, setWorkflowMode] = useState8(null);
13077
+ const [modeSelectionIndex, setModeSelectionIndex] = useState8(0);
12904
13078
  useInput4((input, key) => {
12905
13079
  if (key.escape || input === "q") {
12906
- if (step === "select-tests" && selectedRun) {
13080
+ if (step === "choosing-mode" && selectedRun) {
12907
13081
  setSelectedRun(null);
12908
13082
  setStep("select-run");
13083
+ } else if (step === "select-tests" && selectedRun) {
13084
+ setStep("choosing-mode");
12909
13085
  } else if (step === "select-run") {
12910
13086
  onCancel();
12911
- } else if ((step === "loading-details" || step === "claiming-tests" || step === "error") && loadError) {
13087
+ } else if ((step === "loading-details" || step === "loading-analysis" || step === "claiming-tests" || step === "error") && loadError) {
12912
13088
  setLoadError(null);
12913
- setStep("select-tests");
13089
+ if (workflowMode === "quick-fix") {
13090
+ setStep("select-tests");
13091
+ } else {
13092
+ setStep("choosing-mode");
13093
+ }
13094
+ }
13095
+ }
13096
+ if (step === "choosing-mode") {
13097
+ if (key.upArrow || key.downArrow) {
13098
+ setModeSelectionIndex((prev) => prev === 0 ? 1 : 0);
13099
+ } else if (key.return) {
13100
+ const mode = modeSelectionIndex === 0 ? "understand-and-fix" : "quick-fix";
13101
+ handleModeSelect(mode);
12914
13102
  }
12915
13103
  }
12916
13104
  });
12917
13105
  const handleRunSelect = (run) => {
12918
13106
  setSelectedRun(run);
12919
- setStep("select-tests");
13107
+ setStep("choosing-mode");
12920
13108
  fetchAssignments(run.id);
12921
13109
  };
12922
13110
  const fetchAssignments = async (runId) => {
@@ -12959,12 +13147,88 @@ var init_FixFlow = __esm({
12959
13147
  console.error("Failed to load assignments:", err);
12960
13148
  }
12961
13149
  };
13150
+ const handleModeSelect = (mode) => {
13151
+ setWorkflowMode(mode);
13152
+ if (mode === "understand-and-fix") {
13153
+ setStep("loading-analysis");
13154
+ loadAndStartAnalysis();
13155
+ } else {
13156
+ setStep("select-tests");
13157
+ }
13158
+ };
13159
+ const loadAndStartAnalysis = async () => {
13160
+ if (!selectedRun) return;
13161
+ setLoadError(null);
13162
+ try {
13163
+ const result = await apiClient.getRunTestsCatalog(selectedRun.id, {
13164
+ status: "failed",
13165
+ limit: 1e3
13166
+ });
13167
+ if (result.tests.length === 0) {
13168
+ setLoadError("No failed tests found in this run.");
13169
+ setStep("error");
13170
+ return;
13171
+ }
13172
+ setLoadingProgress({ current: 0, total: result.tests.length });
13173
+ const testIds = result.tests.map((t) => t.id);
13174
+ const testDetailsArray = await apiClient.getTestDetailsBulk(testIds);
13175
+ const testDetails = testDetailsArray.filter(
13176
+ (detail) => detail !== null
13177
+ );
13178
+ const histories = await Promise.allSettled(
13179
+ result.tests.map(
13180
+ (t) => apiClient.getTestHistory(t.testId, 10).catch(() => null)
13181
+ )
13182
+ );
13183
+ const testContexts = testDetails.map((test, index) => {
13184
+ const historyResult = histories[index];
13185
+ const history = historyResult?.status === "fulfilled" && historyResult.value ? historyResult.value : void 0;
13186
+ return { test, history };
13187
+ });
13188
+ const assignmentInfoMap = /* @__PURE__ */ new Map();
13189
+ for (const test of result.tests) {
13190
+ if (test.assignment) {
13191
+ assignmentInfoMap.set(test.id, {
13192
+ assignedTo: test.assignment.assignedTo,
13193
+ status: test.assignment.status
13194
+ });
13195
+ }
13196
+ }
13197
+ const prompt = buildUnderstandAndFixPrompt(
13198
+ testContexts,
13199
+ selectedRun,
13200
+ assignmentInfoMap
13201
+ );
13202
+ setSelectedTests(result.tests);
13203
+ setStep("fixing");
13204
+ onStartFix(prompt, result.tests);
13205
+ } catch (err) {
13206
+ const errorMessage = err instanceof Error ? err.message : String(err);
13207
+ if (errorMessage.includes("network") || errorMessage.includes("fetch")) {
13208
+ setLoadError(`Network error: Unable to connect to the server.
13209
+
13210
+ Please check your internet connection and try again.
13211
+
13212
+ Error: ${errorMessage}`);
13213
+ } else if (errorMessage.includes("auth") || errorMessage.includes("unauthorized")) {
13214
+ setLoadError(`Authentication error: Please run 'supatest auth' to log in.
13215
+
13216
+ Error: ${errorMessage}`);
13217
+ } else {
13218
+ setLoadError(`Failed to load test details:
13219
+
13220
+ ${errorMessage}
13221
+
13222
+ Press ESC to go back and try again.`);
13223
+ }
13224
+ setStep("error");
13225
+ }
13226
+ };
12962
13227
  const handleTestsSelect = async (tests) => {
12963
13228
  setSelectedTests(tests);
12964
13229
  setStep("claiming-tests");
12965
13230
  setLoadError(null);
12966
13231
  try {
12967
- const assignmentMap = /* @__PURE__ */ new Map();
12968
13232
  const testCatalogUuids = [];
12969
13233
  const testRunToCatalogMap = /* @__PURE__ */ new Map();
12970
13234
  for (const test of tests) {
@@ -12978,6 +13242,7 @@ var init_FixFlow = __esm({
12978
13242
  const result = await apiClient.assignTestsBulk({
12979
13243
  testIds: testCatalogUuids
12980
13244
  });
13245
+ const assignmentMap = /* @__PURE__ */ new Map();
12981
13246
  for (const assignment of result.successful) {
12982
13247
  const testRunId = testRunToCatalogMap.get(assignment.testId);
12983
13248
  if (testRunId) {
@@ -12990,10 +13255,7 @@ var init_FixFlow = __esm({
12990
13255
  if (testRunId) {
12991
13256
  const test = tests.find((t) => t.id === testRunId);
12992
13257
  if (test) {
12993
- conflicts.push({
12994
- test,
12995
- assignee: conflict.assignedTo
12996
- });
13258
+ conflicts.push({ test, assignee: conflict.assignedTo });
12997
13259
  }
12998
13260
  }
12999
13261
  }
@@ -13025,7 +13287,7 @@ Please select different tests.`
13025
13287
  (detail) => detail !== null
13026
13288
  );
13027
13289
  const testContexts = testDetails.map((test) => ({ test }));
13028
- const prompt = buildFixPrompt(testContexts);
13290
+ const prompt = buildQuickFixPrompt(testContexts);
13029
13291
  setStep("fixing");
13030
13292
  onStartFix(prompt, testsToLoad);
13031
13293
  } catch (err) {
@@ -13054,14 +13316,10 @@ Press ESC to go back and try again.`);
13054
13316
  onCancel();
13055
13317
  };
13056
13318
  const handleTestCancel = () => {
13057
- setSelectedRun(null);
13058
- setAssignments(/* @__PURE__ */ new Map());
13059
- setStep("select-run");
13319
+ setStep("choosing-mode");
13060
13320
  };
13061
- const markAssignmentsComplete = async (fixSessionId) => {
13062
- if (assignmentIds.size === 0) {
13063
- return;
13064
- }
13321
+ const markAssignmentsComplete = async () => {
13322
+ if (assignmentIds.size === 0) return;
13065
13323
  try {
13066
13324
  await Promise.all(
13067
13325
  Array.from(assignmentIds.values()).map(
@@ -13075,21 +13333,6 @@ Press ESC to go back and try again.`);
13075
13333
  console.error("Failed to mark assignments as complete:", err);
13076
13334
  }
13077
13335
  };
13078
- const releaseAssignments = async () => {
13079
- if (assignmentIds.size === 0) {
13080
- return;
13081
- }
13082
- try {
13083
- await Promise.all(
13084
- Array.from(assignmentIds.values()).map(
13085
- (assignmentId) => apiClient.releaseAssignment(assignmentId)
13086
- )
13087
- );
13088
- setAssignmentIds(/* @__PURE__ */ new Map());
13089
- } catch (err) {
13090
- console.error("Failed to release assignments:", err);
13091
- }
13092
- };
13093
13336
  useEffect8(() => {
13094
13337
  if (step === "complete") {
13095
13338
  markAssignmentsComplete();
@@ -13106,10 +13349,47 @@ Press ESC to go back and try again.`);
13106
13349
  onSelect: handleRunSelect
13107
13350
  }
13108
13351
  );
13109
- case "select-tests":
13110
- if (!selectedRun) {
13111
- return null;
13352
+ case "choosing-mode": {
13353
+ const failed = selectedRun?.summary?.failed ?? 0;
13354
+ const branch = selectedRun?.git?.branch || "unknown";
13355
+ const commit = selectedRun?.git?.commit?.slice(0, 7) || "";
13356
+ if (failed === 0) {
13357
+ return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "green", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "green" }, "No Failed Tests"), /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "All tests passed in this run. Nothing to fix!"), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React22.createElement(Text17, { bold: true }, "ESC"), " to go back")));
13112
13358
  }
13359
+ const modes = [
13360
+ {
13361
+ label: "Understand & Fix",
13362
+ description: `Agent analyzes all ${failed} failure${failed !== 1 ? "s" : ""}, shows a summary table with root causes and patterns, then guides you through fixing`
13363
+ },
13364
+ {
13365
+ label: "Quick Fix",
13366
+ description: "Select specific tests to fix immediately \u2014 skip analysis and go straight to fixing"
13367
+ }
13368
+ ];
13369
+ return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Box19, { marginBottom: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "cyan" }, branch, commit && /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, " @ ", commit), /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React22.createElement(Text17, { color: "red" }, failed, " failed"))), /* @__PURE__ */ React22.createElement(Box19, { marginBottom: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: theme.text.primary }, "How would you like to proceed?")), /* @__PURE__ */ React22.createElement(Box19, { flexDirection: "column", marginBottom: 1 }, modes.map((mode, index) => {
13370
+ const isSelected = index === modeSelectionIndex;
13371
+ const indicator = isSelected ? "\u25B6 " : " ";
13372
+ return /* @__PURE__ */ React22.createElement(Box19, { flexDirection: "column", gap: 0, key: mode.label }, /* @__PURE__ */ React22.createElement(Box19, null, /* @__PURE__ */ React22.createElement(
13373
+ Text17,
13374
+ {
13375
+ backgroundColor: isSelected ? theme.text.accent : void 0,
13376
+ bold: isSelected,
13377
+ color: isSelected ? "black" : theme.text.primary
13378
+ },
13379
+ indicator,
13380
+ mode.label
13381
+ )), /* @__PURE__ */ React22.createElement(Box19, { marginLeft: 4 }, /* @__PURE__ */ React22.createElement(
13382
+ Text17,
13383
+ {
13384
+ color: isSelected ? theme.text.primary : theme.text.dim,
13385
+ dimColor: !isSelected
13386
+ },
13387
+ mode.description
13388
+ )));
13389
+ })), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, /* @__PURE__ */ React22.createElement(Text17, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", /* @__PURE__ */ React22.createElement(Text17, { bold: true }, "Enter"), " select \u2022 ", /* @__PURE__ */ React22.createElement(Text17, { bold: true }, "ESC"), " back")));
13390
+ }
13391
+ case "select-tests":
13392
+ if (!selectedRun) return null;
13113
13393
  return /* @__PURE__ */ React22.createElement(
13114
13394
  TestSelector,
13115
13395
  {
@@ -13120,6 +13400,8 @@ Press ESC to go back and try again.`);
13120
13400
  run: selectedRun
13121
13401
  }
13122
13402
  );
13403
+ case "loading-analysis":
13404
+ return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "cyan" }, "Analyzing Failures..."), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Loading test details, error data, and execution history for all failed tests...")), loadingProgress.total > 0 && /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: "yellow" }, loadingProgress.current, " / ", loadingProgress.total, " tests loaded")));
13123
13405
  case "claiming-tests":
13124
13406
  return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "cyan" }, "Claiming Tests..."), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Assigning ", selectedTests.length, " test", selectedTests.length !== 1 ? "s" : "", " to you...")));
13125
13407
  case "loading-details":
@@ -13129,8 +13411,10 @@ Press ESC to go back and try again.`);
13129
13411
  return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "cyan" }, "Loading Test Details..."), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Fetching error messages, stack traces, and execution steps...")), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: "yellow" }, loadingProgress.current, " / ", loadingProgress.total, " tests loaded")));
13130
13412
  case "error":
13131
13413
  return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "red", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "red" }, "Error"), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, loadError)), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React22.createElement(Text17, { bold: true }, "ESC"), " to go back")));
13132
- case "fixing":
13133
- return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "cyan" }, "Fixing ", selectedTests.length, " Test", selectedTests.length !== 1 ? "s" : "", "..."), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, selectedTests.map((test, index) => /* @__PURE__ */ React22.createElement(Box19, { key: test.id }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, index + 1, ". ", test.file.split("/").pop(), ":", test.location?.line || "", " - ", test.title)))), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "The agent will now analyze and fix each test...")));
13414
+ case "fixing": {
13415
+ const modeLabel = workflowMode === "understand-and-fix" ? "Analyzing & Fixing" : "Fixing";
13416
+ return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "cyan" }, modeLabel, " ", selectedTests.length, " Test", selectedTests.length !== 1 ? "s" : "", "..."), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, selectedTests.slice(0, 10).map((test, index) => /* @__PURE__ */ React22.createElement(Box19, { key: test.id }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, index + 1, ". ", test.file.split("/").pop(), ":", test.location?.line || "", " - ", test.title))), selectedTests.length > 10 && /* @__PURE__ */ React22.createElement(Box19, null, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "... and ", selectedTests.length - 10, " more"))), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, workflowMode === "understand-and-fix" ? "The agent will analyze all failures, present findings, and guide you through fixing..." : "The agent will now analyze and fix each test...")));
13417
+ }
13134
13418
  case "complete":
13135
13419
  return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "green", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "green" }, "Tests Marked as Complete"), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "\u2713 ", selectedTests.length, " test", selectedTests.length !== 1 ? "s have" : " has", " been marked as fixed")), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Press any key to continue...")));
13136
13420
  default:
@@ -14094,9 +14378,87 @@ var init_ProviderSelector = __esm({
14094
14378
  }
14095
14379
  });
14096
14380
 
14097
- // src/ui/components/QuestionSelector.tsx
14381
+ // src/ui/components/PlanApprovalSelector.tsx
14098
14382
  import { Box as Box26, Text as Text24, useInput as useInput10 } from "ink";
14099
14383
  import React29, { useState as useState16 } from "react";
14384
+ var OPTIONS, PlanApprovalSelector;
14385
+ var init_PlanApprovalSelector = __esm({
14386
+ "src/ui/components/PlanApprovalSelector.tsx"() {
14387
+ "use strict";
14388
+ init_theme();
14389
+ OPTIONS = [
14390
+ { label: "Build it", description: "Approve the plan and start building" },
14391
+ { label: "Give feedback", description: "Send feedback to revise the plan" }
14392
+ ];
14393
+ PlanApprovalSelector = ({
14394
+ allowedPrompts,
14395
+ onApprove,
14396
+ onFeedback
14397
+ }) => {
14398
+ const [selectedIndex, setSelectedIndex] = useState16(0);
14399
+ const [feedbackMode, setFeedbackMode] = useState16(false);
14400
+ const [feedbackText, setFeedbackText] = useState16("");
14401
+ useInput10((input, key) => {
14402
+ if (feedbackMode) {
14403
+ if (key.return) {
14404
+ if (feedbackText.trim()) {
14405
+ onFeedback(feedbackText.trim());
14406
+ }
14407
+ } else if (key.escape) {
14408
+ setFeedbackMode(false);
14409
+ setFeedbackText("");
14410
+ } else if (key.backspace || key.delete) {
14411
+ setFeedbackText((prev) => prev.slice(0, -1));
14412
+ } else if (input && !key.ctrl && !key.meta) {
14413
+ setFeedbackText((prev) => prev + input);
14414
+ }
14415
+ return;
14416
+ }
14417
+ if (key.upArrow) {
14418
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : OPTIONS.length - 1);
14419
+ } else if (key.downArrow) {
14420
+ setSelectedIndex((prev) => prev < OPTIONS.length - 1 ? prev + 1 : 0);
14421
+ } else if (key.return) {
14422
+ if (selectedIndex === 0) {
14423
+ onApprove();
14424
+ } else {
14425
+ setFeedbackMode(true);
14426
+ }
14427
+ } else if (key.escape) {
14428
+ onFeedback("Plan not approved by user.");
14429
+ }
14430
+ });
14431
+ if (feedbackMode) {
14432
+ return /* @__PURE__ */ React29.createElement(Box26, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: theme.text.accent }, "What changes would you like to the plan?")), /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.primary }, ">", " ", feedbackText, /* @__PURE__ */ React29.createElement(Text24, { backgroundColor: theme.text.accent }, " "))), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "Enter"), " submit feedback \u2022 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "ESC"), " back to options")));
14433
+ }
14434
+ return /* @__PURE__ */ React29.createElement(Box26, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: theme.text.accent }, "Plan ready for review")), allowedPrompts && allowedPrompts.length > 0 && /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, "The agent wants to:"), allowedPrompts.map((p, i) => /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim, key: `${p.prompt}-${i}` }, "\u2022 ", p.prompt))), /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column", marginBottom: 1 }, OPTIONS.map((option, index) => {
14435
+ const isSelected = index === selectedIndex;
14436
+ const indicator = isSelected ? "\u25B6 " : " ";
14437
+ return /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column", gap: 0, key: option.label }, /* @__PURE__ */ React29.createElement(Box26, null, /* @__PURE__ */ React29.createElement(
14438
+ Text24,
14439
+ {
14440
+ backgroundColor: isSelected ? theme.text.accent : void 0,
14441
+ bold: isSelected,
14442
+ color: isSelected ? "black" : theme.text.primary
14443
+ },
14444
+ indicator,
14445
+ option.label
14446
+ )), /* @__PURE__ */ React29.createElement(Box26, { marginLeft: 4 }, /* @__PURE__ */ React29.createElement(
14447
+ Text24,
14448
+ {
14449
+ color: isSelected ? theme.text.primary : theme.text.dim,
14450
+ dimColor: !isSelected
14451
+ },
14452
+ option.description
14453
+ )));
14454
+ })), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "Enter"), " select \u2022 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "ESC"), " cancel")));
14455
+ };
14456
+ }
14457
+ });
14458
+
14459
+ // src/ui/components/QuestionSelector.tsx
14460
+ import { Box as Box27, Text as Text25, useInput as useInput11 } from "ink";
14461
+ import React30, { useState as useState17 } from "react";
14100
14462
  var QuestionSelector;
14101
14463
  var init_QuestionSelector = __esm({
14102
14464
  "src/ui/components/QuestionSelector.tsx"() {
@@ -14113,11 +14475,11 @@ var init_QuestionSelector = __esm({
14113
14475
  }) => {
14114
14476
  const hasOptions = options && options.length > 0;
14115
14477
  const allOptions = hasOptions && allowCustomAnswer ? [...options, { label: "Other", description: "Provide a custom response" }] : options;
14116
- const [selectedIndex, setSelectedIndex] = useState16(0);
14117
- const [selectedItems, setSelectedItems] = useState16(/* @__PURE__ */ new Set());
14118
- const [customInputMode, setCustomInputMode] = useState16(!hasOptions);
14119
- const [customAnswer, setCustomAnswer] = useState16("");
14120
- useInput10((input, key) => {
14478
+ const [selectedIndex, setSelectedIndex] = useState17(0);
14479
+ const [selectedItems, setSelectedItems] = useState17(/* @__PURE__ */ new Set());
14480
+ const [customInputMode, setCustomInputMode] = useState17(!hasOptions);
14481
+ const [customAnswer, setCustomAnswer] = useState17("");
14482
+ useInput11((input, key) => {
14121
14483
  if (customInputMode) {
14122
14484
  if (key.return) {
14123
14485
  if (customAnswer.trim()) {
@@ -14180,15 +14542,15 @@ var init_QuestionSelector = __esm({
14180
14542
  }
14181
14543
  });
14182
14544
  if (customInputMode) {
14183
- return /* @__PURE__ */ React29.createElement(Box26, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: theme.text.accent }, question)), /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, hasOptions ? "Enter your custom answer:" : "Type your answer:")), /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.primary }, ">", " ", customAnswer, /* @__PURE__ */ React29.createElement(Text24, { backgroundColor: theme.text.accent }, " "))), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "Enter"), " submit \u2022 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "ESC"), " ", hasOptions ? "back to options" : "cancel")));
14545
+ return /* @__PURE__ */ React30.createElement(Box27, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React30.createElement(Box27, { marginBottom: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: theme.text.accent }, question)), /* @__PURE__ */ React30.createElement(Box27, { marginBottom: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, hasOptions ? "Enter your custom answer:" : "Type your answer:")), /* @__PURE__ */ React30.createElement(Box27, { marginBottom: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.primary }, ">", " ", customAnswer, /* @__PURE__ */ React30.createElement(Text25, { backgroundColor: theme.text.accent }, " "))), /* @__PURE__ */ React30.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "Enter"), " submit \u2022 ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "ESC"), " ", hasOptions ? "back to options" : "cancel")));
14184
14546
  }
14185
- return /* @__PURE__ */ React29.createElement(Box26, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: theme.text.accent }, question)), multiSelect && /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, "(Select multiple options with ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "Space"), ", submit with ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "S"), ")")), /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column", marginBottom: 1 }, allOptions.map((option, index) => {
14547
+ return /* @__PURE__ */ React30.createElement(Box27, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React30.createElement(Box27, { marginBottom: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: theme.text.accent }, question)), multiSelect && /* @__PURE__ */ React30.createElement(Box27, { marginBottom: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "(Select multiple options with ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "Space"), ", submit with ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "S"), ")")), /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "column", marginBottom: 1 }, allOptions.map((option, index) => {
14186
14548
  const isSelected = index === selectedIndex;
14187
14549
  const isChosen = selectedItems.has(index);
14188
14550
  const indicator = isSelected ? "\u25B6 " : " ";
14189
14551
  const checkbox = multiSelect ? isChosen ? "[\u2713] " : "[ ] " : "";
14190
- return /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column", gap: 0, key: `${option.label}-${index}` }, /* @__PURE__ */ React29.createElement(Box26, null, /* @__PURE__ */ React29.createElement(
14191
- Text24,
14552
+ return /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "column", gap: 0, key: `${option.label}-${index}` }, /* @__PURE__ */ React30.createElement(Box27, null, /* @__PURE__ */ React30.createElement(
14553
+ Text25,
14192
14554
  {
14193
14555
  backgroundColor: isSelected ? theme.text.accent : void 0,
14194
14556
  bold: isSelected,
@@ -14197,22 +14559,22 @@ var init_QuestionSelector = __esm({
14197
14559
  indicator,
14198
14560
  checkbox,
14199
14561
  option.label
14200
- )), /* @__PURE__ */ React29.createElement(Box26, { marginLeft: multiSelect ? 6 : 4 }, /* @__PURE__ */ React29.createElement(
14201
- Text24,
14562
+ )), /* @__PURE__ */ React30.createElement(Box27, { marginLeft: multiSelect ? 6 : 4 }, /* @__PURE__ */ React30.createElement(
14563
+ Text25,
14202
14564
  {
14203
14565
  color: isSelected ? theme.text.primary : theme.text.dim,
14204
14566
  dimColor: !isSelected
14205
14567
  },
14206
14568
  option.description
14207
14569
  )));
14208
- })), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", multiSelect ? /* @__PURE__ */ React29.createElement(React29.Fragment, null, /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "Space"), " toggle \u2022 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "S"), " submit \u2022", " ") : /* @__PURE__ */ React29.createElement(React29.Fragment, null, /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "Enter"), " select \u2022", " "), /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "ESC"), " cancel")));
14570
+ })), /* @__PURE__ */ React30.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", multiSelect ? /* @__PURE__ */ React30.createElement(React30.Fragment, null, /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "Space"), " toggle \u2022 ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "S"), " submit \u2022", " ") : /* @__PURE__ */ React30.createElement(React30.Fragment, null, /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "Enter"), " select \u2022", " "), /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "ESC"), " cancel")));
14209
14571
  };
14210
14572
  }
14211
14573
  });
14212
14574
 
14213
14575
  // src/ui/components/SessionSelector.tsx
14214
- import { Box as Box27, Text as Text25, useInput as useInput11 } from "ink";
14215
- import React30, { useEffect as useEffect12, useState as useState17 } from "react";
14576
+ import { Box as Box28, Text as Text26, useInput as useInput12 } from "ink";
14577
+ import React31, { useEffect as useEffect12, useState as useState18 } from "react";
14216
14578
  function getSessionPrefix(authMethod) {
14217
14579
  return authMethod === "api-key" ? "[Team]" : "[Me]";
14218
14580
  }
@@ -14227,12 +14589,12 @@ var init_SessionSelector = __esm({
14227
14589
  onSelect,
14228
14590
  onCancel
14229
14591
  }) => {
14230
- const [allSessions, setAllSessions] = useState17([]);
14231
- const [selectedIndex, setSelectedIndex] = useState17(0);
14232
- const [isLoading, setIsLoading] = useState17(false);
14233
- const [hasMore, setHasMore] = useState17(true);
14234
- const [totalSessions, setTotalSessions] = useState17(0);
14235
- const [error, setError] = useState17(null);
14592
+ const [allSessions, setAllSessions] = useState18([]);
14593
+ const [selectedIndex, setSelectedIndex] = useState18(0);
14594
+ const [isLoading, setIsLoading] = useState18(false);
14595
+ const [hasMore, setHasMore] = useState18(true);
14596
+ const [totalSessions, setTotalSessions] = useState18(0);
14597
+ const [error, setError] = useState18(null);
14236
14598
  useEffect12(() => {
14237
14599
  loadMoreSessions();
14238
14600
  }, []);
@@ -14259,7 +14621,7 @@ var init_SessionSelector = __esm({
14259
14621
  setIsLoading(false);
14260
14622
  }
14261
14623
  };
14262
- useInput11((input, key) => {
14624
+ useInput12((input, key) => {
14263
14625
  if (allSessions.length === 0) {
14264
14626
  if (key.escape || input === "q") {
14265
14627
  onCancel();
@@ -14283,13 +14645,13 @@ var init_SessionSelector = __esm({
14283
14645
  }
14284
14646
  });
14285
14647
  if (error) {
14286
- return /* @__PURE__ */ React30.createElement(Box27, { borderColor: "red", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "red" }, "Error Loading Sessions"), /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, error), /* @__PURE__ */ React30.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "ESC"), " to cancel")));
14648
+ return /* @__PURE__ */ React31.createElement(Box28, { borderColor: "red", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React31.createElement(Text26, { bold: true, color: "red" }, "Error Loading Sessions"), /* @__PURE__ */ React31.createElement(Text26, { color: theme.text.dim }, error), /* @__PURE__ */ React31.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React31.createElement(Text26, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React31.createElement(Text26, { bold: true }, "ESC"), " to cancel")));
14287
14649
  }
14288
14650
  if (allSessions.length === 0 && isLoading) {
14289
- return /* @__PURE__ */ React30.createElement(Box27, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "cyan" }, "Loading Sessions..."), /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Fetching your sessions from the server"));
14651
+ return /* @__PURE__ */ React31.createElement(Box28, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React31.createElement(Text26, { bold: true, color: "cyan" }, "Loading Sessions..."), /* @__PURE__ */ React31.createElement(Text26, { color: theme.text.dim }, "Fetching your sessions from the server"));
14290
14652
  }
14291
14653
  if (allSessions.length === 0 && !isLoading) {
14292
- return /* @__PURE__ */ React30.createElement(Box27, { borderColor: "yellow", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "yellow" }, "No Sessions Found"), /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "No previous sessions available. Start a new session!"), /* @__PURE__ */ React30.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "ESC"), " to cancel")));
14654
+ return /* @__PURE__ */ React31.createElement(Box28, { borderColor: "yellow", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React31.createElement(Text26, { bold: true, color: "yellow" }, "No Sessions Found"), /* @__PURE__ */ React31.createElement(Text26, { color: theme.text.dim }, "No previous sessions available. Start a new session!"), /* @__PURE__ */ React31.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React31.createElement(Text26, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React31.createElement(Text26, { bold: true }, "ESC"), " to cancel")));
14293
14655
  }
14294
14656
  const VISIBLE_ITEMS3 = 10;
14295
14657
  let startIndex;
@@ -14309,7 +14671,7 @@ var init_SessionSelector = __esm({
14309
14671
  const visibleSessions = allSessions.slice(startIndex, endIndex);
14310
14672
  const MAX_TITLE_WIDTH = 50;
14311
14673
  const PREFIX_WIDTH = 6;
14312
- return /* @__PURE__ */ React30.createElement(Box27, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Box27, { marginBottom: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "cyan" }, "Select a Session to Resume")), /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "column" }, visibleSessions.map((item, index) => {
14674
+ return /* @__PURE__ */ React31.createElement(Box28, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React31.createElement(Box28, { marginBottom: 1 }, /* @__PURE__ */ React31.createElement(Text26, { bold: true, color: "cyan" }, "Select a Session to Resume")), /* @__PURE__ */ React31.createElement(Box28, { flexDirection: "column" }, visibleSessions.map((item, index) => {
14313
14675
  const actualIndex = startIndex + index;
14314
14676
  const isSelected = actualIndex === selectedIndex;
14315
14677
  const title = item.session.title || "Untitled session";
@@ -14333,8 +14695,8 @@ var init_SessionSelector = __esm({
14333
14695
  const prefixColor = item.prefix === "[Me]" ? "cyan" : "yellow";
14334
14696
  const indicator = isSelected ? "\u25B6 " : " ";
14335
14697
  const bgColor = isSelected ? theme.text.accent : void 0;
14336
- return /* @__PURE__ */ React30.createElement(Box27, { key: item.session.id, width: "100%" }, /* @__PURE__ */ React30.createElement(Text25, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, indicator), /* @__PURE__ */ React30.createElement(Text25, { backgroundColor: bgColor, bold: isSelected, color: prefixColor }, prefix), /* @__PURE__ */ React30.createElement(Text25, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, displayTitle), /* @__PURE__ */ React30.createElement(Text25, { backgroundColor: bgColor, color: theme.text.dim }, "(", dateStr, ")"));
14337
- })), /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "column", marginTop: 1 }, (allSessions.length > VISIBLE_ITEMS3 || totalSessions > allSessions.length) && /* @__PURE__ */ React30.createElement(Box27, { marginBottom: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: "yellow" }, "Showing ", startIndex + 1, "-", endIndex, " of ", totalSessions || allSessions.length, " sessions", hasMore && !isLoading && /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, " \u2022 Scroll for more"))), /* @__PURE__ */ React30.createElement(Box27, null, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Use ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "\u2191\u2193"), " to navigate \u2022 ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "Enter"), " to select \u2022 ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "ESC"), " to cancel")), isLoading && /* @__PURE__ */ React30.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: "cyan" }, "Loading more sessions..."))));
14698
+ return /* @__PURE__ */ React31.createElement(Box28, { key: item.session.id, width: "100%" }, /* @__PURE__ */ React31.createElement(Text26, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, indicator), /* @__PURE__ */ React31.createElement(Text26, { backgroundColor: bgColor, bold: isSelected, color: prefixColor }, prefix), /* @__PURE__ */ React31.createElement(Text26, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, displayTitle), /* @__PURE__ */ React31.createElement(Text26, { backgroundColor: bgColor, color: theme.text.dim }, "(", dateStr, ")"));
14699
+ })), /* @__PURE__ */ React31.createElement(Box28, { flexDirection: "column", marginTop: 1 }, (allSessions.length > VISIBLE_ITEMS3 || totalSessions > allSessions.length) && /* @__PURE__ */ React31.createElement(Box28, { marginBottom: 1 }, /* @__PURE__ */ React31.createElement(Text26, { color: "yellow" }, "Showing ", startIndex + 1, "-", endIndex, " of ", totalSessions || allSessions.length, " sessions", hasMore && !isLoading && /* @__PURE__ */ React31.createElement(Text26, { color: theme.text.dim }, " \u2022 Scroll for more"))), /* @__PURE__ */ React31.createElement(Box28, null, /* @__PURE__ */ React31.createElement(Text26, { color: theme.text.dim }, "Use ", /* @__PURE__ */ React31.createElement(Text26, { bold: true }, "\u2191\u2193"), " to navigate \u2022 ", /* @__PURE__ */ React31.createElement(Text26, { bold: true }, "Enter"), " to select \u2022 ", /* @__PURE__ */ React31.createElement(Text26, { bold: true }, "ESC"), " to cancel")), isLoading && /* @__PURE__ */ React31.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React31.createElement(Text26, { color: "cyan" }, "Loading more sessions..."))));
14338
14700
  };
14339
14701
  }
14340
14702
  });
@@ -14384,10 +14746,10 @@ var init_useOverlayEscapeGuard = __esm({
14384
14746
 
14385
14747
  // src/ui/App.tsx
14386
14748
  import { execSync as execSync5 } from "child_process";
14387
- import { homedir as homedir8 } from "os";
14388
- import { Box as Box28, Text as Text26, useApp as useApp2, useStdout as useStdout2 } from "ink";
14749
+ import { homedir as homedir9 } from "os";
14750
+ import { Box as Box29, Text as Text27, useApp as useApp2, useStdout as useStdout2 } from "ink";
14389
14751
  import Spinner4 from "ink-spinner";
14390
- import React31, { useCallback as useCallback5, useEffect as useEffect14, useRef as useRef8, useState as useState18 } from "react";
14752
+ import React32, { useCallback as useCallback5, useEffect as useEffect14, useRef as useRef8, useState as useState19 } from "react";
14391
14753
  var getGitBranch2, getCurrentFolder2, AppContent, App;
14392
14754
  var init_App = __esm({
14393
14755
  "src/ui/App.tsx"() {
@@ -14414,6 +14776,7 @@ var init_App = __esm({
14414
14776
  init_MessageList();
14415
14777
  init_ModelSelector();
14416
14778
  init_ProviderSelector();
14779
+ init_PlanApprovalSelector();
14417
14780
  init_QuestionSelector();
14418
14781
  init_SessionSelector();
14419
14782
  init_SessionContext();
@@ -14431,43 +14794,43 @@ var init_App = __esm({
14431
14794
  };
14432
14795
  getCurrentFolder2 = (configCwd) => {
14433
14796
  const cwd = configCwd || process.cwd();
14434
- const home = homedir8();
14797
+ const home = homedir9();
14435
14798
  if (cwd.startsWith(home)) {
14436
14799
  return `~${cwd.slice(home.length)}`;
14437
14800
  }
14438
14801
  return cwd;
14439
14802
  };
14440
- AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession, onClearSession, onQuestionAnswer }) => {
14803
+ AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession, onClearSession, onQuestionAnswer, onPlanAnswer }) => {
14441
14804
  const { exit } = useApp2();
14442
14805
  const { stdout } = useStdout2();
14443
- const { addMessage, clearMessages, isAgentRunning, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel, refreshStatic, toggleToolGroups, llmProvider, setLlmProvider, pendingQuestion, setPendingQuestion } = useSession();
14806
+ const { addMessage, clearMessages, isAgentRunning, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel, refreshStatic, toggleToolGroups, llmProvider, setLlmProvider, pendingQuestion, setPendingQuestion, pendingPlanApproval, setPendingPlanApproval, agentMode, setAgentMode } = useSession();
14444
14807
  useModeToggle();
14445
- const [terminalWidth, setTerminalWidth] = useState18(process.stdout.columns || 80);
14446
- const [showInput, setShowInput] = useState18(true);
14447
- const [gitBranch] = useState18(() => getGitBranch2());
14448
- const [currentFolder] = useState18(() => getCurrentFolder2(config2.cwd));
14808
+ const [terminalWidth, setTerminalWidth] = useState19(process.stdout.columns || 80);
14809
+ const [showInput, setShowInput] = useState19(true);
14810
+ const [gitBranch] = useState19(() => getGitBranch2());
14811
+ const [currentFolder] = useState19(() => getCurrentFolder2(config2.cwd));
14449
14812
  const hasInputContentRef = useRef8(false);
14450
- const [exitWarning, setExitWarning] = useState18(null);
14813
+ const [exitWarning, setExitWarning] = useState19(null);
14451
14814
  const inputPromptRef = useRef8(null);
14452
- const [showSessionSelector, setShowSessionSelector] = useState18(false);
14453
- const [showModelSelector, setShowModelSelector] = useState18(false);
14454
- const [showProviderSelector, setShowProviderSelector] = useState18(false);
14455
- const [claudeMaxAvailable, setClaudeMaxAvailable] = useState18(false);
14456
- const [showFeedbackDialog, setShowFeedbackDialog] = useState18(false);
14457
- const [isLoadingSession, setIsLoadingSession] = useState18(false);
14458
- const [showFixFlow, setShowFixFlow] = useState18(false);
14459
- const [fixRunId, setFixRunId] = useState18(void 0);
14460
- const [showMcpServers, setShowMcpServers] = useState18(false);
14461
- const [showMcpAdd, setShowMcpAdd] = useState18(false);
14462
- const [showMcpSelector, setShowMcpSelector] = useState18(false);
14463
- const [mcpSelectorAction, setMcpSelectorAction] = useState18(
14815
+ const [showSessionSelector, setShowSessionSelector] = useState19(false);
14816
+ const [showModelSelector, setShowModelSelector] = useState19(false);
14817
+ const [showProviderSelector, setShowProviderSelector] = useState19(false);
14818
+ const [claudeMaxAvailable, setClaudeMaxAvailable] = useState19(false);
14819
+ const [showFeedbackDialog, setShowFeedbackDialog] = useState19(false);
14820
+ const [isLoadingSession, setIsLoadingSession] = useState19(false);
14821
+ const [showFixFlow, setShowFixFlow] = useState19(false);
14822
+ const [fixRunId, setFixRunId] = useState19(void 0);
14823
+ const [showMcpServers, setShowMcpServers] = useState19(false);
14824
+ const [showMcpAdd, setShowMcpAdd] = useState19(false);
14825
+ const [showMcpSelector, setShowMcpSelector] = useState19(false);
14826
+ const [mcpSelectorAction, setMcpSelectorAction] = useState19(
14464
14827
  "remove"
14465
14828
  );
14466
- const [mcpServers, setMcpServers] = useState18([]);
14467
- const [authState, setAuthState] = useState18(
14829
+ const [mcpServers, setMcpServers] = useState19([]);
14830
+ const [authState, setAuthState] = useState19(
14468
14831
  () => config2.supatestApiKey ? "authenticated" /* Authenticated */ : "unauthenticated" /* Unauthenticated */
14469
14832
  );
14470
- const [showAuthDialog, setShowAuthDialog] = useState18(false);
14833
+ const [showAuthDialog, setShowAuthDialog] = useState19(false);
14471
14834
  const { isOverlayOpen, isCancelSuppressed, markOverlayClosed } = useOverlayEscapeGuard({
14472
14835
  overlays: [
14473
14836
  showSessionSelector,
@@ -14479,7 +14842,8 @@ var init_App = __esm({
14479
14842
  showMcpServers,
14480
14843
  showMcpAdd,
14481
14844
  showMcpSelector,
14482
- !!pendingQuestion
14845
+ !!pendingQuestion,
14846
+ !!pendingPlanApproval
14483
14847
  ]
14484
14848
  });
14485
14849
  useEffect14(() => {
@@ -14495,6 +14859,11 @@ var init_App = __esm({
14495
14859
  setWebUrl(webUrl);
14496
14860
  }
14497
14861
  }, [sessionId, webUrl, setSessionId, setWebUrl]);
14862
+ useEffect14(() => {
14863
+ if (!isAgentRunning && agentMode === "fix") {
14864
+ setAgentMode("build");
14865
+ }
14866
+ }, [isAgentRunning, agentMode, setAgentMode]);
14498
14867
  const handleLogin = async () => {
14499
14868
  setShowAuthDialog(false);
14500
14869
  setAuthState("authenticating" /* Authenticating */);
@@ -14864,6 +15233,31 @@ var init_App = __esm({
14864
15233
  setPendingQuestion(null);
14865
15234
  markOverlayClosed();
14866
15235
  };
15236
+ const handlePlanApprove = () => {
15237
+ if (!pendingPlanApproval) return;
15238
+ addMessage({
15239
+ type: "user",
15240
+ content: "Plan approved \u2014 build it."
15241
+ });
15242
+ setAgentMode("build");
15243
+ if (onPlanAnswer) {
15244
+ onPlanAnswer(pendingPlanApproval.toolUseId, true);
15245
+ }
15246
+ setPendingPlanApproval(null);
15247
+ markOverlayClosed();
15248
+ };
15249
+ const handlePlanFeedback = (feedback) => {
15250
+ if (!pendingPlanApproval) return;
15251
+ addMessage({
15252
+ type: "user",
15253
+ content: feedback
15254
+ });
15255
+ if (onPlanAnswer) {
15256
+ onPlanAnswer(pendingPlanApproval.toolUseId, false, feedback);
15257
+ }
15258
+ setPendingPlanApproval(null);
15259
+ markOverlayClosed();
15260
+ };
14867
15261
  const handleFixFlowCancel = () => {
14868
15262
  markOverlayClosed();
14869
15263
  setShowFixFlow(false);
@@ -14889,6 +15283,7 @@ var init_App = __esm({
14889
15283
  });
14890
15284
  return;
14891
15285
  }
15286
+ setAgentMode("fix");
14892
15287
  addMessage({
14893
15288
  type: "user",
14894
15289
  content: prompt
@@ -15083,14 +15478,14 @@ var init_App = __esm({
15083
15478
  });
15084
15479
  }
15085
15480
  }, []);
15086
- return /* @__PURE__ */ React31.createElement(
15087
- Box28,
15481
+ return /* @__PURE__ */ React32.createElement(
15482
+ Box29,
15088
15483
  {
15089
15484
  flexDirection: "column",
15090
15485
  paddingX: 1
15091
15486
  },
15092
- /* @__PURE__ */ React31.createElement(MessageList, { currentFolder, gitBranch, queuedTasks, terminalWidth }),
15093
- showSessionSelector && apiClient && /* @__PURE__ */ React31.createElement(
15487
+ /* @__PURE__ */ React32.createElement(MessageList, { currentFolder, gitBranch, queuedTasks, terminalWidth }),
15488
+ showSessionSelector && apiClient && /* @__PURE__ */ React32.createElement(
15094
15489
  SessionSelector,
15095
15490
  {
15096
15491
  apiClient,
@@ -15098,8 +15493,8 @@ var init_App = __esm({
15098
15493
  onSelect: handleSessionSelect
15099
15494
  }
15100
15495
  ),
15101
- isLoadingSession && /* @__PURE__ */ React31.createElement(Box28, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React31.createElement(Box28, { flexDirection: "row" }, /* @__PURE__ */ React31.createElement(Box28, { width: 2 }, /* @__PURE__ */ React31.createElement(Text26, { color: theme.text.accent }, /* @__PURE__ */ React31.createElement(Spinner4, { type: "dots" }))), /* @__PURE__ */ React31.createElement(Text26, { bold: true, color: "cyan" }, "Loading session...")), /* @__PURE__ */ React31.createElement(Text26, { color: theme.text.dim }, "Fetching queries and context")),
15102
- showModelSelector && /* @__PURE__ */ React31.createElement(
15496
+ isLoadingSession && /* @__PURE__ */ React32.createElement(Box29, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React32.createElement(Box29, { flexDirection: "row" }, /* @__PURE__ */ React32.createElement(Box29, { width: 2 }, /* @__PURE__ */ React32.createElement(Text27, { color: theme.text.accent }, /* @__PURE__ */ React32.createElement(Spinner4, { type: "dots" }))), /* @__PURE__ */ React32.createElement(Text27, { bold: true, color: "cyan" }, "Loading session...")), /* @__PURE__ */ React32.createElement(Text27, { color: theme.text.dim }, "Fetching queries and context")),
15497
+ showModelSelector && /* @__PURE__ */ React32.createElement(
15103
15498
  ModelSelector,
15104
15499
  {
15105
15500
  currentModel: selectedModel,
@@ -15108,7 +15503,7 @@ var init_App = __esm({
15108
15503
  onSelect: handleModelSelect
15109
15504
  }
15110
15505
  ),
15111
- showProviderSelector && /* @__PURE__ */ React31.createElement(
15506
+ showProviderSelector && /* @__PURE__ */ React32.createElement(
15112
15507
  ProviderSelector,
15113
15508
  {
15114
15509
  currentProvider: llmProvider,
@@ -15116,20 +15511,20 @@ var init_App = __esm({
15116
15511
  onSelect: handleProviderSelect
15117
15512
  }
15118
15513
  ),
15119
- showAuthDialog && /* @__PURE__ */ React31.createElement(
15514
+ showAuthDialog && /* @__PURE__ */ React32.createElement(
15120
15515
  AuthDialog,
15121
15516
  {
15122
15517
  onLogin: handleLogin
15123
15518
  }
15124
15519
  ),
15125
- showFeedbackDialog && /* @__PURE__ */ React31.createElement(
15520
+ showFeedbackDialog && /* @__PURE__ */ React32.createElement(
15126
15521
  FeedbackDialog,
15127
15522
  {
15128
15523
  onCancel: handleFeedbackCancel,
15129
15524
  onSubmit: handleFeedbackSubmit
15130
15525
  }
15131
15526
  ),
15132
- showFixFlow && apiClient && /* @__PURE__ */ React31.createElement(
15527
+ showFixFlow && apiClient && /* @__PURE__ */ React32.createElement(
15133
15528
  FixFlow,
15134
15529
  {
15135
15530
  apiClient,
@@ -15139,7 +15534,7 @@ var init_App = __esm({
15139
15534
  onStartFix: handleFixStart
15140
15535
  }
15141
15536
  ),
15142
- showMcpServers && /* @__PURE__ */ React31.createElement(
15537
+ showMcpServers && /* @__PURE__ */ React32.createElement(
15143
15538
  McpServersDisplay,
15144
15539
  {
15145
15540
  cwd: config2.cwd,
@@ -15149,8 +15544,8 @@ var init_App = __esm({
15149
15544
  onTest: handleMcpTest
15150
15545
  }
15151
15546
  ),
15152
- showMcpAdd && /* @__PURE__ */ React31.createElement(McpAddDialog, { onCancel: handleMcpAddCancel, onConfirm: handleMcpAddConfirm }),
15153
- showMcpSelector && /* @__PURE__ */ React31.createElement(
15547
+ showMcpAdd && /* @__PURE__ */ React32.createElement(McpAddDialog, { onCancel: handleMcpAddCancel, onConfirm: handleMcpAddConfirm }),
15548
+ showMcpSelector && /* @__PURE__ */ React32.createElement(
15154
15549
  McpServerSelector,
15155
15550
  {
15156
15551
  action: mcpSelectorAction,
@@ -15159,7 +15554,7 @@ var init_App = __esm({
15159
15554
  servers: mcpServers
15160
15555
  }
15161
15556
  ),
15162
- pendingQuestion && /* @__PURE__ */ React31.createElement(
15557
+ /* @__PURE__ */ React32.createElement(Box29, { flexDirection: "column" }, !showAuthDialog && /* @__PURE__ */ React32.createElement(AuthBanner, { authState }), pendingQuestion && /* @__PURE__ */ React32.createElement(
15163
15558
  QuestionSelector,
15164
15559
  {
15165
15560
  multiSelect: pendingQuestion.multiSelect,
@@ -15168,8 +15563,14 @@ var init_App = __esm({
15168
15563
  options: pendingQuestion.options || [],
15169
15564
  question: pendingQuestion.question
15170
15565
  }
15171
- ),
15172
- /* @__PURE__ */ React31.createElement(Box28, { flexDirection: "column" }, !showAuthDialog && /* @__PURE__ */ React31.createElement(AuthBanner, { authState }), showInput && !showSessionSelector && !showAuthDialog && !showModelSelector && !showProviderSelector && !showFeedbackDialog && !showFixFlow && !showMcpServers && !showMcpAdd && !showMcpSelector && !pendingQuestion && !isLoadingSession && /* @__PURE__ */ React31.createElement(Box28, { flexDirection: "column", marginTop: 0, width: "100%" }, exitWarning && /* @__PURE__ */ React31.createElement(Box28, { marginBottom: 0, paddingX: 1 }, /* @__PURE__ */ React31.createElement(Text26, { color: "yellow" }, exitWarning)), /* @__PURE__ */ React31.createElement(
15566
+ ), pendingPlanApproval && /* @__PURE__ */ React32.createElement(
15567
+ PlanApprovalSelector,
15568
+ {
15569
+ allowedPrompts: pendingPlanApproval.allowedPrompts,
15570
+ onApprove: handlePlanApprove,
15571
+ onFeedback: handlePlanFeedback
15572
+ }
15573
+ ), showInput && !showSessionSelector && !showAuthDialog && !showModelSelector && !showProviderSelector && !showFeedbackDialog && !showFixFlow && !showMcpServers && !showMcpAdd && !showMcpSelector && !pendingQuestion && !pendingPlanApproval && !isLoadingSession && /* @__PURE__ */ React32.createElement(Box29, { flexDirection: "column", marginTop: 0, width: "100%" }, exitWarning && /* @__PURE__ */ React32.createElement(Box29, { marginBottom: 0, paddingX: 1 }, /* @__PURE__ */ React32.createElement(Text27, { color: "yellow" }, exitWarning)), /* @__PURE__ */ React32.createElement(
15173
15574
  InputPrompt,
15174
15575
  {
15175
15576
  currentFolder,
@@ -15185,7 +15586,7 @@ var init_App = __esm({
15185
15586
  );
15186
15587
  };
15187
15588
  App = (props) => {
15188
- return /* @__PURE__ */ React31.createElement(AppContent, { ...props });
15589
+ return /* @__PURE__ */ React32.createElement(AppContent, { ...props });
15189
15590
  };
15190
15591
  }
15191
15592
  });
@@ -15215,13 +15616,32 @@ var init_useBracketedPaste = __esm({
15215
15616
  }
15216
15617
  });
15217
15618
 
15619
+ // src/utils/prompt-routing.ts
15620
+ function getSystemPromptForMode(mode) {
15621
+ switch (mode) {
15622
+ case "plan":
15623
+ return config.planSystemPrompt;
15624
+ case "fix":
15625
+ return config.fixSystemPrompt;
15626
+ case "build":
15627
+ default:
15628
+ return config.interactiveSystemPrompt;
15629
+ }
15630
+ }
15631
+ var init_prompt_routing = __esm({
15632
+ async "src/utils/prompt-routing.ts"() {
15633
+ "use strict";
15634
+ await init_config();
15635
+ }
15636
+ });
15637
+
15218
15638
  // src/modes/interactive.tsx
15219
15639
  var interactive_exports = {};
15220
15640
  __export(interactive_exports, {
15221
15641
  runInteractive: () => runInteractive
15222
15642
  });
15223
15643
  import { render as render2 } from "ink";
15224
- import React32, { useEffect as useEffect16, useRef as useRef9 } from "react";
15644
+ import React33, { useEffect as useEffect16, useRef as useRef9 } from "react";
15225
15645
  function getToolDescription2(toolName, input) {
15226
15646
  switch (toolName) {
15227
15647
  case "Read":
@@ -15354,7 +15774,7 @@ async function runInteractive(config2) {
15354
15774
  webUrl = session.webUrl;
15355
15775
  }
15356
15776
  const { unmount, waitUntilExit } = render2(
15357
- /* @__PURE__ */ React32.createElement(
15777
+ /* @__PURE__ */ React33.createElement(
15358
15778
  InteractiveApp,
15359
15779
  {
15360
15780
  apiClient,
@@ -15404,7 +15824,6 @@ var AgentRunner, InteractiveAppContent, InteractiveApp;
15404
15824
  var init_interactive = __esm({
15405
15825
  async "src/modes/interactive.tsx"() {
15406
15826
  "use strict";
15407
- await init_config();
15408
15827
  await init_agent();
15409
15828
  init_message_bridge();
15410
15829
  init_react();
@@ -15416,6 +15835,7 @@ var init_interactive = __esm({
15416
15835
  init_mouse();
15417
15836
  init_claude_max();
15418
15837
  init_logger();
15838
+ await init_prompt_routing();
15419
15839
  init_settings_loader();
15420
15840
  init_stdio();
15421
15841
  init_version();
@@ -15434,7 +15854,8 @@ var init_interactive = __esm({
15434
15854
  planFilePath,
15435
15855
  selectedModel,
15436
15856
  llmProvider,
15437
- setPendingQuestion
15857
+ setPendingQuestion,
15858
+ setPendingPlanApproval
15438
15859
  } = useSession();
15439
15860
  const { setUsageStats } = useUsageStats();
15440
15861
  const agentRef = useRef9(null);
@@ -15484,6 +15905,14 @@ var init_interactive = __esm({
15484
15905
  onEnterPlanMode: () => {
15485
15906
  if (isMounted) setAgentMode("plan");
15486
15907
  },
15908
+ onPlanApproval: (toolId, allowedPrompts) => {
15909
+ if (isMounted) {
15910
+ setPendingPlanApproval({
15911
+ toolUseId: toolId,
15912
+ allowedPrompts
15913
+ });
15914
+ }
15915
+ },
15487
15916
  onTurnComplete: () => {
15488
15917
  if (isMounted) onTurnComplete?.();
15489
15918
  },
@@ -15527,7 +15956,7 @@ var init_interactive = __esm({
15527
15956
  planFilePath,
15528
15957
  selectedModel,
15529
15958
  oauthToken,
15530
- systemPromptAppend: agentMode === "plan" ? config.planSystemPrompt : config2.systemPromptAppend
15959
+ systemPromptAppend: getSystemPromptForMode(agentMode)
15531
15960
  };
15532
15961
  const agent2 = new CoreAgent(presenter, messageBridge);
15533
15962
  agentRef.current = agent2;
@@ -15574,18 +16003,18 @@ var init_interactive = __esm({
15574
16003
  setIsAgentRunning
15575
16004
  } = useSession();
15576
16005
  const { setUsageStats } = useUsageStats();
15577
- const [sessionId, setSessionId] = React32.useState(initialSessionId);
15578
- const [currentTask, setCurrentTask] = React32.useState(config2.task);
15579
- const [taskId, setTaskId] = React32.useState(0);
15580
- const [shouldRunAgent, setShouldRunAgent] = React32.useState(!!config2.task);
15581
- const [taskQueue, setTaskQueue] = React32.useState([]);
15582
- const [providerSessionId, setProviderSessionId] = React32.useState();
15583
- const messageBridgeRef = React32.useRef(null);
15584
- const agentRef = React32.useRef(null);
15585
- const lastSubmitRef = React32.useRef(null);
15586
- const [pendingInjected, setPendingInjected] = React32.useState([]);
15587
- const pendingInjectedRef = React32.useRef([]);
15588
- React32.useEffect(() => {
16006
+ const [sessionId, setSessionId] = React33.useState(initialSessionId);
16007
+ const [currentTask, setCurrentTask] = React33.useState(config2.task);
16008
+ const [taskId, setTaskId] = React33.useState(0);
16009
+ const [shouldRunAgent, setShouldRunAgent] = React33.useState(!!config2.task);
16010
+ const [taskQueue, setTaskQueue] = React33.useState([]);
16011
+ const [providerSessionId, setProviderSessionId] = React33.useState();
16012
+ const messageBridgeRef = React33.useRef(null);
16013
+ const agentRef = React33.useRef(null);
16014
+ const lastSubmitRef = React33.useRef(null);
16015
+ const [pendingInjected, setPendingInjected] = React33.useState([]);
16016
+ const pendingInjectedRef = React33.useRef([]);
16017
+ React33.useEffect(() => {
15589
16018
  pendingInjectedRef.current = pendingInjected;
15590
16019
  }, [pendingInjected]);
15591
16020
  const handleSubmitTask = async (task) => {
@@ -15649,7 +16078,7 @@ var init_interactive = __esm({
15649
16078
  if (shouldRunAgent && !messageBridgeRef.current) {
15650
16079
  messageBridgeRef.current = new MessageBridge(providerSessionId || "");
15651
16080
  }
15652
- React32.useEffect(() => {
16081
+ React33.useEffect(() => {
15653
16082
  if (!shouldRunAgent && taskQueue.length > 0) {
15654
16083
  const [nextTask, ...remaining] = taskQueue;
15655
16084
  setTaskQueue(remaining);
@@ -15663,26 +16092,35 @@ var init_interactive = __esm({
15663
16092
  setShouldRunAgent(true);
15664
16093
  }
15665
16094
  }, [shouldRunAgent, taskQueue, addMessage, providerSessionId]);
15666
- const handleClearSession = React32.useCallback(() => {
16095
+ const handleClearSession = React33.useCallback(() => {
15667
16096
  setSessionId(void 0);
15668
16097
  setContextSessionId(void 0);
15669
16098
  setProviderSessionId(void 0);
15670
16099
  setTaskQueue([]);
15671
16100
  setPendingInjected([]);
15672
16101
  }, [setContextSessionId]);
15673
- const handleQuestionAnswer = React32.useCallback((questionId, answers) => {
16102
+ const handleQuestionAnswer = React33.useCallback((questionId, answers) => {
15674
16103
  if (agentRef.current) {
15675
16104
  return agentRef.current.submitAnswer(questionId, answers);
15676
16105
  }
15677
16106
  return false;
15678
16107
  }, []);
15679
- return /* @__PURE__ */ React32.createElement(React32.Fragment, null, /* @__PURE__ */ React32.createElement(
16108
+ const handlePlanAnswer = React33.useCallback((toolUseId, approved, feedback) => {
16109
+ if (!agentRef.current) return false;
16110
+ if (approved) {
16111
+ return agentRef.current.submitAnswer(toolUseId, { approved: "true" });
16112
+ }
16113
+ const result = agentRef.current.submitAnswer(toolUseId, { feedback: feedback || "User did not approve the plan." });
16114
+ return result;
16115
+ }, []);
16116
+ return /* @__PURE__ */ React33.createElement(React33.Fragment, null, /* @__PURE__ */ React33.createElement(
15680
16117
  App,
15681
16118
  {
15682
16119
  apiClient,
15683
16120
  config: { ...config2, task: currentTask },
15684
16121
  onClearSession: handleClearSession,
15685
16122
  onExit,
16123
+ onPlanAnswer: handlePlanAnswer,
15686
16124
  onQuestionAnswer: handleQuestionAnswer,
15687
16125
  onResumeSession: async (session) => {
15688
16126
  try {
@@ -15742,7 +16180,7 @@ var init_interactive = __esm({
15742
16180
  sessionId,
15743
16181
  webUrl
15744
16182
  }
15745
- ), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */ React32.createElement(
16183
+ ), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */ React33.createElement(
15746
16184
  AgentRunner,
15747
16185
  {
15748
16186
  apiClient,
@@ -15770,7 +16208,7 @@ var init_interactive = __esm({
15770
16208
  useBracketedPaste();
15771
16209
  const settings = loadSupatestSettings(props.config.cwd || process.cwd());
15772
16210
  const initialProvider = settings.llmProvider || "supatest-managed";
15773
- return /* @__PURE__ */ React32.createElement(KeypressProvider, null, /* @__PURE__ */ React32.createElement(SessionProvider, { initialLlmProvider: initialProvider, initialModel: props.config.selectedModel }, /* @__PURE__ */ React32.createElement(InteractiveAppContent, { ...props })));
16211
+ return /* @__PURE__ */ React33.createElement(KeypressProvider, null, /* @__PURE__ */ React33.createElement(SessionProvider, { initialLlmProvider: initialProvider, initialModel: props.config.selectedModel }, /* @__PURE__ */ React33.createElement(InteractiveAppContent, { ...props })));
15774
16212
  };
15775
16213
  }
15776
16214
  });
@@ -15794,7 +16232,7 @@ init_react();
15794
16232
  init_MessageList();
15795
16233
  init_SessionContext();
15796
16234
  import { execSync as execSync2 } from "child_process";
15797
- import { homedir as homedir4 } from "os";
16235
+ import { homedir as homedir5 } from "os";
15798
16236
  import { Box as Box13, useApp } from "ink";
15799
16237
  import React14, { useEffect as useEffect2, useRef as useRef4, useState as useState3 } from "react";
15800
16238
  var getGitBranch = () => {
@@ -15806,7 +16244,7 @@ var getGitBranch = () => {
15806
16244
  };
15807
16245
  var getCurrentFolder = () => {
15808
16246
  const cwd = process.cwd();
15809
- const home = homedir4();
16247
+ const home = homedir5();
15810
16248
  if (cwd.startsWith(home)) {
15811
16249
  return `~${cwd.slice(home.length)}`;
15812
16250
  }
@@ -16361,8 +16799,9 @@ program.name("supatest").description(
16361
16799
  plan: config.planSystemPrompt
16362
16800
  };
16363
16801
  logger.raw(getBanner());
16802
+ const headlessTask = headlessMode === "fix" ? `[HEADLESS] ${prompt}` : prompt;
16364
16803
  const result = await runAgent({
16365
- task: prompt,
16804
+ task: headlessTask,
16366
16805
  logs,
16367
16806
  supatestApiKey,
16368
16807
  supatestApiUrl,