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