@supatest/cli 0.0.49 → 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 +746 -338
  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",
@@ -6564,7 +6553,7 @@ var CLI_VERSION;
6564
6553
  var init_version = __esm({
6565
6554
  "src/version.ts"() {
6566
6555
  "use strict";
6567
- CLI_VERSION = "0.0.49";
6556
+ CLI_VERSION = "0.0.50";
6568
6557
  }
6569
6558
  });
6570
6559
 
@@ -7907,8 +7896,20 @@ ${projectInstructions}`,
7907
7896
  stderr: (msg) => {
7908
7897
  this.presenter.onLog(`[Agent Failure] ${msg}`);
7909
7898
  },
7910
- // Intercept AskUserQuestion tool to wait for user response
7899
+ // Intercept AskUserQuestion and ExitPlanMode tools to wait for user response
7911
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
+ }
7912
7913
  if (toolName !== "AskUserQuestion") {
7913
7914
  return { behavior: "allow", updatedInput: input };
7914
7915
  }
@@ -8313,17 +8314,21 @@ var init_react = __esm({
8313
8314
  });
8314
8315
  }
8315
8316
  onToolUse(tool, input, toolId) {
8316
- const message = {
8317
- type: "tool",
8318
- content: getToolDescription(tool, input),
8319
- toolName: getToolDisplayName(tool),
8320
- toolInput: input,
8321
- toolResult: void 0,
8322
- isExpanded: false,
8323
- isPending: true,
8324
- toolUseId: toolId
8325
- };
8326
- 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
+ }
8327
8332
  this.hasAssistantMessage = false;
8328
8333
  this.hasThinkingMessage = false;
8329
8334
  this.currentAssistantText = "";
@@ -8353,7 +8358,7 @@ var init_react = __esm({
8353
8358
  });
8354
8359
  }
8355
8360
  } else if (tool === "ExitPlanMode") {
8356
- this.callbacks.onExitPlanMode?.();
8361
+ this.callbacks.onPlanApproval?.(toolId, input?.allowedPrompts);
8357
8362
  } else if (tool === "EnterPlanMode") {
8358
8363
  this.callbacks.onEnterPlanMode?.();
8359
8364
  } else if (tool === "AskUserQuestion") {
@@ -8523,6 +8528,7 @@ var init_SessionContext = __esm({
8523
8528
  const [toolGroupsExpanded, setToolGroupsExpanded] = useState(false);
8524
8529
  const [staticRemountKey, setStaticRemountKey] = useState(0);
8525
8530
  const [pendingQuestion, setPendingQuestion] = useState(null);
8531
+ const [pendingPlanApproval, setPendingPlanApproval] = useState(null);
8526
8532
  const addMessage = useCallback(
8527
8533
  (message) => {
8528
8534
  const expandableTools = ["Bash", "BashOutput", "Command Output"];
@@ -8655,7 +8661,9 @@ var init_SessionContext = __esm({
8655
8661
  staticRemountKey,
8656
8662
  refreshStatic,
8657
8663
  pendingQuestion,
8658
- setPendingQuestion
8664
+ setPendingQuestion,
8665
+ pendingPlanApproval,
8666
+ setPendingPlanApproval
8659
8667
  }), [
8660
8668
  messages,
8661
8669
  addMessage,
@@ -8681,7 +8689,8 @@ var init_SessionContext = __esm({
8681
8689
  llmProvider,
8682
8690
  staticRemountKey,
8683
8691
  refreshStatic,
8684
- pendingQuestion
8692
+ pendingQuestion,
8693
+ pendingPlanApproval
8685
8694
  ]);
8686
8695
  const usageStatsValue = useMemo(() => ({
8687
8696
  usageStats,
@@ -9622,7 +9631,7 @@ var init_MessageList = __esm({
9622
9631
  });
9623
9632
  StaticHeader.displayName = "StaticHeader";
9624
9633
  MessageList = memo2(({ terminalWidth, currentFolder, gitBranch, headless = false, queuedTasks = [] }) => {
9625
- const { messages, updateMessageById, isAgentRunning, staticRemountKey, toolGroupsExpanded, toggleToolGroups } = useSession();
9634
+ const { messages, updateMessageById, isAgentRunning, staticRemountKey, toolGroupsExpanded, toggleToolGroups, pendingQuestion, pendingPlanApproval } = useSession();
9626
9635
  const handleToggle = useCallback2((id, currentExpanded) => {
9627
9636
  updateMessageById(id, { isExpanded: !currentExpanded });
9628
9637
  }, [updateMessageById]);
@@ -9765,15 +9774,6 @@ var init_MessageList = __esm({
9765
9774
  }
9766
9775
  flushToolGroup();
9767
9776
  };
9768
- const completeMessages = [];
9769
- const inProgressMessages = [];
9770
- for (const msg of messages) {
9771
- if (isMessageComplete(msg)) {
9772
- completeMessages.push(msg);
9773
- } else {
9774
- inProgressMessages.push(msg);
9775
- }
9776
- }
9777
9777
  let splitIndex = messages.length;
9778
9778
  for (let i = messages.length - 1; i >= 0; i--) {
9779
9779
  if (isMessageComplete(messages[i])) {
@@ -9781,10 +9781,10 @@ var init_MessageList = __esm({
9781
9781
  break;
9782
9782
  }
9783
9783
  }
9784
- const finalCompleteMessages = messages.slice(0, splitIndex);
9785
- const finalInProgressMessages = messages.slice(splitIndex);
9786
- processTurn(finalCompleteMessages, completed);
9787
- processTurn(finalInProgressMessages, currentTurn);
9784
+ const completeMessages = messages.slice(0, splitIndex);
9785
+ const inProgressMessages = messages.slice(splitIndex);
9786
+ processTurn(completeMessages, completed);
9787
+ processTurn(inProgressMessages, currentTurn);
9788
9788
  return { completedGroups: completed, currentTurnGroups: currentTurn };
9789
9789
  }, [messages]);
9790
9790
  const staticItems = useMemo3(
@@ -9804,7 +9804,7 @@ var init_MessageList = __esm({
9804
9804
  headless,
9805
9805
  staticRemountKey
9806
9806
  }
9807
- ), 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) => {
9808
9808
  if (item._isGroup) {
9809
9809
  const content2 = renderGroupedMessage(item);
9810
9810
  if (!content2) {
@@ -9823,7 +9823,7 @@ var init_MessageList = __esm({
9823
9823
  return null;
9824
9824
  }
9825
9825
  return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", key: group.type === "group" ? `current-group-${idx}` : group.messages[0].id, width: "100%" }, content);
9826
- }), /* @__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 }));
9827
9827
  });
9828
9828
  MessageList.displayName = "MessageList";
9829
9829
  }
@@ -12394,6 +12394,9 @@ var init_FeedbackDialog = __esm({
12394
12394
  });
12395
12395
 
12396
12396
  // src/fix/context-builder.ts
12397
+ function getFrontendUrl() {
12398
+ return process.env.SUPATEST_FRONTEND_URL || "https://app.supatest.dev";
12399
+ }
12397
12400
  function buildTestContext(ctx) {
12398
12401
  const { test, history } = ctx;
12399
12402
  const lines = [];
@@ -12447,6 +12450,20 @@ function buildTestContext(ctx) {
12447
12450
  lines.push("```");
12448
12451
  lines.push("");
12449
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
+ }
12450
12467
  }
12451
12468
  if (history && history.history.length > 0) {
12452
12469
  const passCount = history.history.filter((h) => h.status === "passed").length;
@@ -12470,7 +12487,7 @@ ${buildTestContext(ctx)}`;
12470
12487
  });
12471
12488
  return sections.join("\n\n");
12472
12489
  }
12473
- function buildFixPrompt(tests) {
12490
+ function buildQuickFixPrompt(tests) {
12474
12491
  const intro = tests.length === 1 ? "Fix the following failing test:" : `Fix the following ${tests.length} failing tests:`;
12475
12492
  const context = buildMultipleTestsContext(tests);
12476
12493
  const instructions = `
@@ -12489,6 +12506,130 @@ Focus on fixing the actual issue, not just suppressing the error. If the test is
12489
12506
  ${context}
12490
12507
  ${instructions}`;
12491
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
+ }
12492
12633
  var init_context_builder = __esm({
12493
12634
  "src/fix/context-builder.ts"() {
12494
12635
  "use strict";
@@ -12924,7 +13065,7 @@ var init_FixFlow = __esm({
12924
13065
  onStartFix,
12925
13066
  initialRunId
12926
13067
  }) => {
12927
- const [step, setStep] = useState8(initialRunId ? "select-run" : "select-run");
13068
+ const [step, setStep] = useState8("select-run");
12928
13069
  const [selectedRun, setSelectedRun] = useState8(null);
12929
13070
  const [selectedTests, setSelectedTests] = useState8([]);
12930
13071
  const [assignments, setAssignments] = useState8(/* @__PURE__ */ new Map());
@@ -12932,22 +13073,38 @@ var init_FixFlow = __esm({
12932
13073
  const [assignmentIds, setAssignmentIds] = useState8(/* @__PURE__ */ new Map());
12933
13074
  const [loadingProgress, setLoadingProgress] = useState8({ current: 0, total: 0 });
12934
13075
  const [loadError, setLoadError] = useState8(null);
13076
+ const [workflowMode, setWorkflowMode] = useState8(null);
13077
+ const [modeSelectionIndex, setModeSelectionIndex] = useState8(0);
12935
13078
  useInput4((input, key) => {
12936
13079
  if (key.escape || input === "q") {
12937
- if (step === "select-tests" && selectedRun) {
13080
+ if (step === "choosing-mode" && selectedRun) {
12938
13081
  setSelectedRun(null);
12939
13082
  setStep("select-run");
13083
+ } else if (step === "select-tests" && selectedRun) {
13084
+ setStep("choosing-mode");
12940
13085
  } else if (step === "select-run") {
12941
13086
  onCancel();
12942
- } 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) {
12943
13088
  setLoadError(null);
12944
- 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);
12945
13102
  }
12946
13103
  }
12947
13104
  });
12948
13105
  const handleRunSelect = (run) => {
12949
13106
  setSelectedRun(run);
12950
- setStep("select-tests");
13107
+ setStep("choosing-mode");
12951
13108
  fetchAssignments(run.id);
12952
13109
  };
12953
13110
  const fetchAssignments = async (runId) => {
@@ -12990,12 +13147,88 @@ var init_FixFlow = __esm({
12990
13147
  console.error("Failed to load assignments:", err);
12991
13148
  }
12992
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
+ };
12993
13227
  const handleTestsSelect = async (tests) => {
12994
13228
  setSelectedTests(tests);
12995
13229
  setStep("claiming-tests");
12996
13230
  setLoadError(null);
12997
13231
  try {
12998
- const assignmentMap = /* @__PURE__ */ new Map();
12999
13232
  const testCatalogUuids = [];
13000
13233
  const testRunToCatalogMap = /* @__PURE__ */ new Map();
13001
13234
  for (const test of tests) {
@@ -13009,6 +13242,7 @@ var init_FixFlow = __esm({
13009
13242
  const result = await apiClient.assignTestsBulk({
13010
13243
  testIds: testCatalogUuids
13011
13244
  });
13245
+ const assignmentMap = /* @__PURE__ */ new Map();
13012
13246
  for (const assignment of result.successful) {
13013
13247
  const testRunId = testRunToCatalogMap.get(assignment.testId);
13014
13248
  if (testRunId) {
@@ -13021,10 +13255,7 @@ var init_FixFlow = __esm({
13021
13255
  if (testRunId) {
13022
13256
  const test = tests.find((t) => t.id === testRunId);
13023
13257
  if (test) {
13024
- conflicts.push({
13025
- test,
13026
- assignee: conflict.assignedTo
13027
- });
13258
+ conflicts.push({ test, assignee: conflict.assignedTo });
13028
13259
  }
13029
13260
  }
13030
13261
  }
@@ -13056,7 +13287,7 @@ Please select different tests.`
13056
13287
  (detail) => detail !== null
13057
13288
  );
13058
13289
  const testContexts = testDetails.map((test) => ({ test }));
13059
- const prompt = buildFixPrompt(testContexts);
13290
+ const prompt = buildQuickFixPrompt(testContexts);
13060
13291
  setStep("fixing");
13061
13292
  onStartFix(prompt, testsToLoad);
13062
13293
  } catch (err) {
@@ -13085,14 +13316,10 @@ Press ESC to go back and try again.`);
13085
13316
  onCancel();
13086
13317
  };
13087
13318
  const handleTestCancel = () => {
13088
- setSelectedRun(null);
13089
- setAssignments(/* @__PURE__ */ new Map());
13090
- setStep("select-run");
13319
+ setStep("choosing-mode");
13091
13320
  };
13092
- const markAssignmentsComplete = async (fixSessionId) => {
13093
- if (assignmentIds.size === 0) {
13094
- return;
13095
- }
13321
+ const markAssignmentsComplete = async () => {
13322
+ if (assignmentIds.size === 0) return;
13096
13323
  try {
13097
13324
  await Promise.all(
13098
13325
  Array.from(assignmentIds.values()).map(
@@ -13106,21 +13333,6 @@ Press ESC to go back and try again.`);
13106
13333
  console.error("Failed to mark assignments as complete:", err);
13107
13334
  }
13108
13335
  };
13109
- const releaseAssignments = async () => {
13110
- if (assignmentIds.size === 0) {
13111
- return;
13112
- }
13113
- try {
13114
- await Promise.all(
13115
- Array.from(assignmentIds.values()).map(
13116
- (assignmentId) => apiClient.releaseAssignment(assignmentId)
13117
- )
13118
- );
13119
- setAssignmentIds(/* @__PURE__ */ new Map());
13120
- } catch (err) {
13121
- console.error("Failed to release assignments:", err);
13122
- }
13123
- };
13124
13336
  useEffect8(() => {
13125
13337
  if (step === "complete") {
13126
13338
  markAssignmentsComplete();
@@ -13137,10 +13349,47 @@ Press ESC to go back and try again.`);
13137
13349
  onSelect: handleRunSelect
13138
13350
  }
13139
13351
  );
13140
- case "select-tests":
13141
- if (!selectedRun) {
13142
- 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")));
13143
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;
13144
13393
  return /* @__PURE__ */ React22.createElement(
13145
13394
  TestSelector,
13146
13395
  {
@@ -13151,6 +13400,8 @@ Press ESC to go back and try again.`);
13151
13400
  run: selectedRun
13152
13401
  }
13153
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")));
13154
13405
  case "claiming-tests":
13155
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...")));
13156
13407
  case "loading-details":
@@ -13160,8 +13411,10 @@ Press ESC to go back and try again.`);
13160
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")));
13161
13412
  case "error":
13162
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")));
13163
- case "fixing":
13164
- 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
+ }
13165
13418
  case "complete":
13166
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...")));
13167
13420
  default:
@@ -14125,9 +14378,87 @@ var init_ProviderSelector = __esm({
14125
14378
  }
14126
14379
  });
14127
14380
 
14128
- // src/ui/components/QuestionSelector.tsx
14381
+ // src/ui/components/PlanApprovalSelector.tsx
14129
14382
  import { Box as Box26, Text as Text24, useInput as useInput10 } from "ink";
14130
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";
14131
14462
  var QuestionSelector;
14132
14463
  var init_QuestionSelector = __esm({
14133
14464
  "src/ui/components/QuestionSelector.tsx"() {
@@ -14144,11 +14475,11 @@ var init_QuestionSelector = __esm({
14144
14475
  }) => {
14145
14476
  const hasOptions = options && options.length > 0;
14146
14477
  const allOptions = hasOptions && allowCustomAnswer ? [...options, { label: "Other", description: "Provide a custom response" }] : options;
14147
- const [selectedIndex, setSelectedIndex] = useState16(0);
14148
- const [selectedItems, setSelectedItems] = useState16(/* @__PURE__ */ new Set());
14149
- const [customInputMode, setCustomInputMode] = useState16(!hasOptions);
14150
- const [customAnswer, setCustomAnswer] = useState16("");
14151
- 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) => {
14152
14483
  if (customInputMode) {
14153
14484
  if (key.return) {
14154
14485
  if (customAnswer.trim()) {
@@ -14211,15 +14542,15 @@ var init_QuestionSelector = __esm({
14211
14542
  }
14212
14543
  });
14213
14544
  if (customInputMode) {
14214
- 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")));
14215
14546
  }
14216
- 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) => {
14217
14548
  const isSelected = index === selectedIndex;
14218
14549
  const isChosen = selectedItems.has(index);
14219
14550
  const indicator = isSelected ? "\u25B6 " : " ";
14220
14551
  const checkbox = multiSelect ? isChosen ? "[\u2713] " : "[ ] " : "";
14221
- return /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column", gap: 0, key: `${option.label}-${index}` }, /* @__PURE__ */ React29.createElement(Box26, null, /* @__PURE__ */ React29.createElement(
14222
- 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,
14223
14554
  {
14224
14555
  backgroundColor: isSelected ? theme.text.accent : void 0,
14225
14556
  bold: isSelected,
@@ -14228,22 +14559,22 @@ var init_QuestionSelector = __esm({
14228
14559
  indicator,
14229
14560
  checkbox,
14230
14561
  option.label
14231
- )), /* @__PURE__ */ React29.createElement(Box26, { marginLeft: multiSelect ? 6 : 4 }, /* @__PURE__ */ React29.createElement(
14232
- Text24,
14562
+ )), /* @__PURE__ */ React30.createElement(Box27, { marginLeft: multiSelect ? 6 : 4 }, /* @__PURE__ */ React30.createElement(
14563
+ Text25,
14233
14564
  {
14234
14565
  color: isSelected ? theme.text.primary : theme.text.dim,
14235
14566
  dimColor: !isSelected
14236
14567
  },
14237
14568
  option.description
14238
14569
  )));
14239
- })), /* @__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")));
14240
14571
  };
14241
14572
  }
14242
14573
  });
14243
14574
 
14244
14575
  // src/ui/components/SessionSelector.tsx
14245
- import { Box as Box27, Text as Text25, useInput as useInput11 } from "ink";
14246
- 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";
14247
14578
  function getSessionPrefix(authMethod) {
14248
14579
  return authMethod === "api-key" ? "[Team]" : "[Me]";
14249
14580
  }
@@ -14258,12 +14589,12 @@ var init_SessionSelector = __esm({
14258
14589
  onSelect,
14259
14590
  onCancel
14260
14591
  }) => {
14261
- const [allSessions, setAllSessions] = useState17([]);
14262
- const [selectedIndex, setSelectedIndex] = useState17(0);
14263
- const [isLoading, setIsLoading] = useState17(false);
14264
- const [hasMore, setHasMore] = useState17(true);
14265
- const [totalSessions, setTotalSessions] = useState17(0);
14266
- 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);
14267
14598
  useEffect12(() => {
14268
14599
  loadMoreSessions();
14269
14600
  }, []);
@@ -14290,7 +14621,7 @@ var init_SessionSelector = __esm({
14290
14621
  setIsLoading(false);
14291
14622
  }
14292
14623
  };
14293
- useInput11((input, key) => {
14624
+ useInput12((input, key) => {
14294
14625
  if (allSessions.length === 0) {
14295
14626
  if (key.escape || input === "q") {
14296
14627
  onCancel();
@@ -14314,13 +14645,13 @@ var init_SessionSelector = __esm({
14314
14645
  }
14315
14646
  });
14316
14647
  if (error) {
14317
- 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")));
14318
14649
  }
14319
14650
  if (allSessions.length === 0 && isLoading) {
14320
- 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"));
14321
14652
  }
14322
14653
  if (allSessions.length === 0 && !isLoading) {
14323
- 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")));
14324
14655
  }
14325
14656
  const VISIBLE_ITEMS3 = 10;
14326
14657
  let startIndex;
@@ -14340,7 +14671,7 @@ var init_SessionSelector = __esm({
14340
14671
  const visibleSessions = allSessions.slice(startIndex, endIndex);
14341
14672
  const MAX_TITLE_WIDTH = 50;
14342
14673
  const PREFIX_WIDTH = 6;
14343
- 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) => {
14344
14675
  const actualIndex = startIndex + index;
14345
14676
  const isSelected = actualIndex === selectedIndex;
14346
14677
  const title = item.session.title || "Untitled session";
@@ -14364,8 +14695,8 @@ var init_SessionSelector = __esm({
14364
14695
  const prefixColor = item.prefix === "[Me]" ? "cyan" : "yellow";
14365
14696
  const indicator = isSelected ? "\u25B6 " : " ";
14366
14697
  const bgColor = isSelected ? theme.text.accent : void 0;
14367
- 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, ")"));
14368
- })), /* @__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..."))));
14369
14700
  };
14370
14701
  }
14371
14702
  });
@@ -14416,9 +14747,9 @@ var init_useOverlayEscapeGuard = __esm({
14416
14747
  // src/ui/App.tsx
14417
14748
  import { execSync as execSync5 } from "child_process";
14418
14749
  import { homedir as homedir9 } from "os";
14419
- import { Box as Box28, Text as Text26, useApp as useApp2, useStdout as useStdout2 } from "ink";
14750
+ import { Box as Box29, Text as Text27, useApp as useApp2, useStdout as useStdout2 } from "ink";
14420
14751
  import Spinner4 from "ink-spinner";
14421
- 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";
14422
14753
  var getGitBranch2, getCurrentFolder2, AppContent, App;
14423
14754
  var init_App = __esm({
14424
14755
  "src/ui/App.tsx"() {
@@ -14445,6 +14776,7 @@ var init_App = __esm({
14445
14776
  init_MessageList();
14446
14777
  init_ModelSelector();
14447
14778
  init_ProviderSelector();
14779
+ init_PlanApprovalSelector();
14448
14780
  init_QuestionSelector();
14449
14781
  init_SessionSelector();
14450
14782
  init_SessionContext();
@@ -14468,37 +14800,37 @@ var init_App = __esm({
14468
14800
  }
14469
14801
  return cwd;
14470
14802
  };
14471
- 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 }) => {
14472
14804
  const { exit } = useApp2();
14473
14805
  const { stdout } = useStdout2();
14474
- 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();
14475
14807
  useModeToggle();
14476
- const [terminalWidth, setTerminalWidth] = useState18(process.stdout.columns || 80);
14477
- const [showInput, setShowInput] = useState18(true);
14478
- const [gitBranch] = useState18(() => getGitBranch2());
14479
- 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));
14480
14812
  const hasInputContentRef = useRef8(false);
14481
- const [exitWarning, setExitWarning] = useState18(null);
14813
+ const [exitWarning, setExitWarning] = useState19(null);
14482
14814
  const inputPromptRef = useRef8(null);
14483
- const [showSessionSelector, setShowSessionSelector] = useState18(false);
14484
- const [showModelSelector, setShowModelSelector] = useState18(false);
14485
- const [showProviderSelector, setShowProviderSelector] = useState18(false);
14486
- const [claudeMaxAvailable, setClaudeMaxAvailable] = useState18(false);
14487
- const [showFeedbackDialog, setShowFeedbackDialog] = useState18(false);
14488
- const [isLoadingSession, setIsLoadingSession] = useState18(false);
14489
- const [showFixFlow, setShowFixFlow] = useState18(false);
14490
- const [fixRunId, setFixRunId] = useState18(void 0);
14491
- const [showMcpServers, setShowMcpServers] = useState18(false);
14492
- const [showMcpAdd, setShowMcpAdd] = useState18(false);
14493
- const [showMcpSelector, setShowMcpSelector] = useState18(false);
14494
- 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(
14495
14827
  "remove"
14496
14828
  );
14497
- const [mcpServers, setMcpServers] = useState18([]);
14498
- const [authState, setAuthState] = useState18(
14829
+ const [mcpServers, setMcpServers] = useState19([]);
14830
+ const [authState, setAuthState] = useState19(
14499
14831
  () => config2.supatestApiKey ? "authenticated" /* Authenticated */ : "unauthenticated" /* Unauthenticated */
14500
14832
  );
14501
- const [showAuthDialog, setShowAuthDialog] = useState18(false);
14833
+ const [showAuthDialog, setShowAuthDialog] = useState19(false);
14502
14834
  const { isOverlayOpen, isCancelSuppressed, markOverlayClosed } = useOverlayEscapeGuard({
14503
14835
  overlays: [
14504
14836
  showSessionSelector,
@@ -14510,7 +14842,8 @@ var init_App = __esm({
14510
14842
  showMcpServers,
14511
14843
  showMcpAdd,
14512
14844
  showMcpSelector,
14513
- !!pendingQuestion
14845
+ !!pendingQuestion,
14846
+ !!pendingPlanApproval
14514
14847
  ]
14515
14848
  });
14516
14849
  useEffect14(() => {
@@ -14526,6 +14859,11 @@ var init_App = __esm({
14526
14859
  setWebUrl(webUrl);
14527
14860
  }
14528
14861
  }, [sessionId, webUrl, setSessionId, setWebUrl]);
14862
+ useEffect14(() => {
14863
+ if (!isAgentRunning && agentMode === "fix") {
14864
+ setAgentMode("build");
14865
+ }
14866
+ }, [isAgentRunning, agentMode, setAgentMode]);
14529
14867
  const handleLogin = async () => {
14530
14868
  setShowAuthDialog(false);
14531
14869
  setAuthState("authenticating" /* Authenticating */);
@@ -14895,6 +15233,31 @@ var init_App = __esm({
14895
15233
  setPendingQuestion(null);
14896
15234
  markOverlayClosed();
14897
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
+ };
14898
15261
  const handleFixFlowCancel = () => {
14899
15262
  markOverlayClosed();
14900
15263
  setShowFixFlow(false);
@@ -14920,6 +15283,7 @@ var init_App = __esm({
14920
15283
  });
14921
15284
  return;
14922
15285
  }
15286
+ setAgentMode("fix");
14923
15287
  addMessage({
14924
15288
  type: "user",
14925
15289
  content: prompt
@@ -15114,14 +15478,14 @@ var init_App = __esm({
15114
15478
  });
15115
15479
  }
15116
15480
  }, []);
15117
- return /* @__PURE__ */ React31.createElement(
15118
- Box28,
15481
+ return /* @__PURE__ */ React32.createElement(
15482
+ Box29,
15119
15483
  {
15120
15484
  flexDirection: "column",
15121
15485
  paddingX: 1
15122
15486
  },
15123
- /* @__PURE__ */ React31.createElement(MessageList, { currentFolder, gitBranch, queuedTasks, terminalWidth }),
15124
- showSessionSelector && apiClient && /* @__PURE__ */ React31.createElement(
15487
+ /* @__PURE__ */ React32.createElement(MessageList, { currentFolder, gitBranch, queuedTasks, terminalWidth }),
15488
+ showSessionSelector && apiClient && /* @__PURE__ */ React32.createElement(
15125
15489
  SessionSelector,
15126
15490
  {
15127
15491
  apiClient,
@@ -15129,8 +15493,8 @@ var init_App = __esm({
15129
15493
  onSelect: handleSessionSelect
15130
15494
  }
15131
15495
  ),
15132
- 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")),
15133
- 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(
15134
15498
  ModelSelector,
15135
15499
  {
15136
15500
  currentModel: selectedModel,
@@ -15139,7 +15503,7 @@ var init_App = __esm({
15139
15503
  onSelect: handleModelSelect
15140
15504
  }
15141
15505
  ),
15142
- showProviderSelector && /* @__PURE__ */ React31.createElement(
15506
+ showProviderSelector && /* @__PURE__ */ React32.createElement(
15143
15507
  ProviderSelector,
15144
15508
  {
15145
15509
  currentProvider: llmProvider,
@@ -15147,20 +15511,20 @@ var init_App = __esm({
15147
15511
  onSelect: handleProviderSelect
15148
15512
  }
15149
15513
  ),
15150
- showAuthDialog && /* @__PURE__ */ React31.createElement(
15514
+ showAuthDialog && /* @__PURE__ */ React32.createElement(
15151
15515
  AuthDialog,
15152
15516
  {
15153
15517
  onLogin: handleLogin
15154
15518
  }
15155
15519
  ),
15156
- showFeedbackDialog && /* @__PURE__ */ React31.createElement(
15520
+ showFeedbackDialog && /* @__PURE__ */ React32.createElement(
15157
15521
  FeedbackDialog,
15158
15522
  {
15159
15523
  onCancel: handleFeedbackCancel,
15160
15524
  onSubmit: handleFeedbackSubmit
15161
15525
  }
15162
15526
  ),
15163
- showFixFlow && apiClient && /* @__PURE__ */ React31.createElement(
15527
+ showFixFlow && apiClient && /* @__PURE__ */ React32.createElement(
15164
15528
  FixFlow,
15165
15529
  {
15166
15530
  apiClient,
@@ -15170,7 +15534,7 @@ var init_App = __esm({
15170
15534
  onStartFix: handleFixStart
15171
15535
  }
15172
15536
  ),
15173
- showMcpServers && /* @__PURE__ */ React31.createElement(
15537
+ showMcpServers && /* @__PURE__ */ React32.createElement(
15174
15538
  McpServersDisplay,
15175
15539
  {
15176
15540
  cwd: config2.cwd,
@@ -15180,8 +15544,8 @@ var init_App = __esm({
15180
15544
  onTest: handleMcpTest
15181
15545
  }
15182
15546
  ),
15183
- showMcpAdd && /* @__PURE__ */ React31.createElement(McpAddDialog, { onCancel: handleMcpAddCancel, onConfirm: handleMcpAddConfirm }),
15184
- showMcpSelector && /* @__PURE__ */ React31.createElement(
15547
+ showMcpAdd && /* @__PURE__ */ React32.createElement(McpAddDialog, { onCancel: handleMcpAddCancel, onConfirm: handleMcpAddConfirm }),
15548
+ showMcpSelector && /* @__PURE__ */ React32.createElement(
15185
15549
  McpServerSelector,
15186
15550
  {
15187
15551
  action: mcpSelectorAction,
@@ -15190,7 +15554,7 @@ var init_App = __esm({
15190
15554
  servers: mcpServers
15191
15555
  }
15192
15556
  ),
15193
- pendingQuestion && /* @__PURE__ */ React31.createElement(
15557
+ /* @__PURE__ */ React32.createElement(Box29, { flexDirection: "column" }, !showAuthDialog && /* @__PURE__ */ React32.createElement(AuthBanner, { authState }), pendingQuestion && /* @__PURE__ */ React32.createElement(
15194
15558
  QuestionSelector,
15195
15559
  {
15196
15560
  multiSelect: pendingQuestion.multiSelect,
@@ -15199,8 +15563,14 @@ var init_App = __esm({
15199
15563
  options: pendingQuestion.options || [],
15200
15564
  question: pendingQuestion.question
15201
15565
  }
15202
- ),
15203
- /* @__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(
15204
15574
  InputPrompt,
15205
15575
  {
15206
15576
  currentFolder,
@@ -15216,7 +15586,7 @@ var init_App = __esm({
15216
15586
  );
15217
15587
  };
15218
15588
  App = (props) => {
15219
- return /* @__PURE__ */ React31.createElement(AppContent, { ...props });
15589
+ return /* @__PURE__ */ React32.createElement(AppContent, { ...props });
15220
15590
  };
15221
15591
  }
15222
15592
  });
@@ -15246,13 +15616,32 @@ var init_useBracketedPaste = __esm({
15246
15616
  }
15247
15617
  });
15248
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
+
15249
15638
  // src/modes/interactive.tsx
15250
15639
  var interactive_exports = {};
15251
15640
  __export(interactive_exports, {
15252
15641
  runInteractive: () => runInteractive
15253
15642
  });
15254
15643
  import { render as render2 } from "ink";
15255
- import React32, { useEffect as useEffect16, useRef as useRef9 } from "react";
15644
+ import React33, { useEffect as useEffect16, useRef as useRef9 } from "react";
15256
15645
  function getToolDescription2(toolName, input) {
15257
15646
  switch (toolName) {
15258
15647
  case "Read":
@@ -15385,7 +15774,7 @@ async function runInteractive(config2) {
15385
15774
  webUrl = session.webUrl;
15386
15775
  }
15387
15776
  const { unmount, waitUntilExit } = render2(
15388
- /* @__PURE__ */ React32.createElement(
15777
+ /* @__PURE__ */ React33.createElement(
15389
15778
  InteractiveApp,
15390
15779
  {
15391
15780
  apiClient,
@@ -15435,7 +15824,6 @@ var AgentRunner, InteractiveAppContent, InteractiveApp;
15435
15824
  var init_interactive = __esm({
15436
15825
  async "src/modes/interactive.tsx"() {
15437
15826
  "use strict";
15438
- await init_config();
15439
15827
  await init_agent();
15440
15828
  init_message_bridge();
15441
15829
  init_react();
@@ -15447,6 +15835,7 @@ var init_interactive = __esm({
15447
15835
  init_mouse();
15448
15836
  init_claude_max();
15449
15837
  init_logger();
15838
+ await init_prompt_routing();
15450
15839
  init_settings_loader();
15451
15840
  init_stdio();
15452
15841
  init_version();
@@ -15465,7 +15854,8 @@ var init_interactive = __esm({
15465
15854
  planFilePath,
15466
15855
  selectedModel,
15467
15856
  llmProvider,
15468
- setPendingQuestion
15857
+ setPendingQuestion,
15858
+ setPendingPlanApproval
15469
15859
  } = useSession();
15470
15860
  const { setUsageStats } = useUsageStats();
15471
15861
  const agentRef = useRef9(null);
@@ -15515,6 +15905,14 @@ var init_interactive = __esm({
15515
15905
  onEnterPlanMode: () => {
15516
15906
  if (isMounted) setAgentMode("plan");
15517
15907
  },
15908
+ onPlanApproval: (toolId, allowedPrompts) => {
15909
+ if (isMounted) {
15910
+ setPendingPlanApproval({
15911
+ toolUseId: toolId,
15912
+ allowedPrompts
15913
+ });
15914
+ }
15915
+ },
15518
15916
  onTurnComplete: () => {
15519
15917
  if (isMounted) onTurnComplete?.();
15520
15918
  },
@@ -15558,7 +15956,7 @@ var init_interactive = __esm({
15558
15956
  planFilePath,
15559
15957
  selectedModel,
15560
15958
  oauthToken,
15561
- systemPromptAppend: agentMode === "plan" ? config.planSystemPrompt : config2.systemPromptAppend
15959
+ systemPromptAppend: getSystemPromptForMode(agentMode)
15562
15960
  };
15563
15961
  const agent2 = new CoreAgent(presenter, messageBridge);
15564
15962
  agentRef.current = agent2;
@@ -15605,18 +16003,18 @@ var init_interactive = __esm({
15605
16003
  setIsAgentRunning
15606
16004
  } = useSession();
15607
16005
  const { setUsageStats } = useUsageStats();
15608
- const [sessionId, setSessionId] = React32.useState(initialSessionId);
15609
- const [currentTask, setCurrentTask] = React32.useState(config2.task);
15610
- const [taskId, setTaskId] = React32.useState(0);
15611
- const [shouldRunAgent, setShouldRunAgent] = React32.useState(!!config2.task);
15612
- const [taskQueue, setTaskQueue] = React32.useState([]);
15613
- const [providerSessionId, setProviderSessionId] = React32.useState();
15614
- const messageBridgeRef = React32.useRef(null);
15615
- const agentRef = React32.useRef(null);
15616
- const lastSubmitRef = React32.useRef(null);
15617
- const [pendingInjected, setPendingInjected] = React32.useState([]);
15618
- const pendingInjectedRef = React32.useRef([]);
15619
- 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(() => {
15620
16018
  pendingInjectedRef.current = pendingInjected;
15621
16019
  }, [pendingInjected]);
15622
16020
  const handleSubmitTask = async (task) => {
@@ -15680,7 +16078,7 @@ var init_interactive = __esm({
15680
16078
  if (shouldRunAgent && !messageBridgeRef.current) {
15681
16079
  messageBridgeRef.current = new MessageBridge(providerSessionId || "");
15682
16080
  }
15683
- React32.useEffect(() => {
16081
+ React33.useEffect(() => {
15684
16082
  if (!shouldRunAgent && taskQueue.length > 0) {
15685
16083
  const [nextTask, ...remaining] = taskQueue;
15686
16084
  setTaskQueue(remaining);
@@ -15694,26 +16092,35 @@ var init_interactive = __esm({
15694
16092
  setShouldRunAgent(true);
15695
16093
  }
15696
16094
  }, [shouldRunAgent, taskQueue, addMessage, providerSessionId]);
15697
- const handleClearSession = React32.useCallback(() => {
16095
+ const handleClearSession = React33.useCallback(() => {
15698
16096
  setSessionId(void 0);
15699
16097
  setContextSessionId(void 0);
15700
16098
  setProviderSessionId(void 0);
15701
16099
  setTaskQueue([]);
15702
16100
  setPendingInjected([]);
15703
16101
  }, [setContextSessionId]);
15704
- const handleQuestionAnswer = React32.useCallback((questionId, answers) => {
16102
+ const handleQuestionAnswer = React33.useCallback((questionId, answers) => {
15705
16103
  if (agentRef.current) {
15706
16104
  return agentRef.current.submitAnswer(questionId, answers);
15707
16105
  }
15708
16106
  return false;
15709
16107
  }, []);
15710
- 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(
15711
16117
  App,
15712
16118
  {
15713
16119
  apiClient,
15714
16120
  config: { ...config2, task: currentTask },
15715
16121
  onClearSession: handleClearSession,
15716
16122
  onExit,
16123
+ onPlanAnswer: handlePlanAnswer,
15717
16124
  onQuestionAnswer: handleQuestionAnswer,
15718
16125
  onResumeSession: async (session) => {
15719
16126
  try {
@@ -15773,7 +16180,7 @@ var init_interactive = __esm({
15773
16180
  sessionId,
15774
16181
  webUrl
15775
16182
  }
15776
- ), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */ React32.createElement(
16183
+ ), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */ React33.createElement(
15777
16184
  AgentRunner,
15778
16185
  {
15779
16186
  apiClient,
@@ -15801,7 +16208,7 @@ var init_interactive = __esm({
15801
16208
  useBracketedPaste();
15802
16209
  const settings = loadSupatestSettings(props.config.cwd || process.cwd());
15803
16210
  const initialProvider = settings.llmProvider || "supatest-managed";
15804
- 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 })));
15805
16212
  };
15806
16213
  }
15807
16214
  });
@@ -16392,8 +16799,9 @@ program.name("supatest").description(
16392
16799
  plan: config.planSystemPrompt
16393
16800
  };
16394
16801
  logger.raw(getBanner());
16802
+ const headlessTask = headlessMode === "fix" ? `[HEADLESS] ${prompt}` : prompt;
16395
16803
  const result = await runAgent({
16396
- task: prompt,
16804
+ task: headlessTask,
16397
16805
  logs,
16398
16806
  supatestApiKey,
16399
16807
  supatestApiUrl,