@golba98/codexa 1.0.1
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/README.md +320 -0
- package/bin/codexa.js +445 -0
- package/package.json +45 -0
- package/scripts/audit-codexa-capabilities.mjs +466 -0
- package/scripts/smoke-terminal-bench.mjs +35 -0
- package/src/app.tsx +4561 -0
- package/src/appRenderStability.test.ts +131 -0
- package/src/commands/handler.test.ts +643 -0
- package/src/commands/handler.ts +875 -0
- package/src/config/launchArgs.test.ts +158 -0
- package/src/config/launchArgs.ts +186 -0
- package/src/config/layeredConfig.test.ts +143 -0
- package/src/config/layeredConfig.ts +836 -0
- package/src/config/persistence.test.ts +110 -0
- package/src/config/persistence.ts +311 -0
- package/src/config/runtimeConfig.test.ts +218 -0
- package/src/config/runtimeConfig.ts +554 -0
- package/src/config/settings.test.ts +155 -0
- package/src/config/settings.ts +401 -0
- package/src/config/toml-serialize.ts +98 -0
- package/src/config/trustStore.test.ts +29 -0
- package/src/config/trustStore.ts +68 -0
- package/src/core/attachments.test.ts +155 -0
- package/src/core/attachments.ts +71 -0
- package/src/core/auth/codexAuth.test.ts +68 -0
- package/src/core/auth/codexAuth.ts +359 -0
- package/src/core/cleanupFastFail.test.ts +76 -0
- package/src/core/cleanupFastFail.ts +67 -0
- package/src/core/clipboard.ts +24 -0
- package/src/core/codex.ts +124 -0
- package/src/core/codexExecArgs.test.ts +195 -0
- package/src/core/codexExecArgs.ts +152 -0
- package/src/core/codexLaunch.test.ts +205 -0
- package/src/core/codexLaunch.ts +162 -0
- package/src/core/codexPrompt.test.ts +252 -0
- package/src/core/codexPrompt.ts +428 -0
- package/src/core/executables/claudeExecutable.ts +63 -0
- package/src/core/executables/codexExecutable.test.ts +212 -0
- package/src/core/executables/codexExecutable.ts +159 -0
- package/src/core/executables/executableResolver.test.ts +129 -0
- package/src/core/executables/executableResolver.ts +138 -0
- package/src/core/executables/geminiExecutable.test.ts +116 -0
- package/src/core/executables/geminiExecutable.ts +78 -0
- package/src/core/executables/pathSanityScan.test.ts +47 -0
- package/src/core/githubDiagnostics.test.ts +92 -0
- package/src/core/githubDiagnostics.ts +222 -0
- package/src/core/hollowResponseFormat.test.ts +58 -0
- package/src/core/hollowResponseFormat.ts +39 -0
- package/src/core/inputDebug.ts +51 -0
- package/src/core/launchContext.test.ts +157 -0
- package/src/core/launchContext.ts +266 -0
- package/src/core/models/codexCapabilities.test.ts +45 -0
- package/src/core/models/codexCapabilities.ts +95 -0
- package/src/core/models/codexModelCapabilities.test.ts +246 -0
- package/src/core/models/codexModelCapabilities.ts +571 -0
- package/src/core/models/modelSpecs.test.ts +283 -0
- package/src/core/models/modelSpecs.ts +300 -0
- package/src/core/perf/profiler.ts +125 -0
- package/src/core/perf/renderDebug.test.ts +230 -0
- package/src/core/perf/renderDebug.ts +373 -0
- package/src/core/planStorage.test.ts +143 -0
- package/src/core/planStorage.ts +141 -0
- package/src/core/process/CommandRunner.test.ts +105 -0
- package/src/core/process/CommandRunner.ts +269 -0
- package/src/core/process/processValidation.ts +101 -0
- package/src/core/projectInstructions.test.ts +50 -0
- package/src/core/projectInstructions.ts +54 -0
- package/src/core/providerLauncher/launcher.test.ts +238 -0
- package/src/core/providerLauncher/launcher.ts +203 -0
- package/src/core/providerLauncher/registry.test.ts +324 -0
- package/src/core/providerLauncher/registry.ts +253 -0
- package/src/core/providerLauncher/types.ts +84 -0
- package/src/core/providerLauncher/workspaceConfig.test.ts +638 -0
- package/src/core/providerLauncher/workspaceConfig.ts +407 -0
- package/src/core/providerRuntime/anthropic.test.ts +1120 -0
- package/src/core/providerRuntime/anthropic.ts +576 -0
- package/src/core/providerRuntime/capabilityProfile.test.ts +311 -0
- package/src/core/providerRuntime/capabilityProfile.ts +288 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +446 -0
- package/src/core/providerRuntime/contextMetadata.test.ts +468 -0
- package/src/core/providerRuntime/contextMetadata.ts +409 -0
- package/src/core/providerRuntime/gemini.test.ts +437 -0
- package/src/core/providerRuntime/gemini.ts +784 -0
- package/src/core/providerRuntime/lmstudio.test.ts +168 -0
- package/src/core/providerRuntime/lmstudio.ts +118 -0
- package/src/core/providerRuntime/local.test.ts +787 -0
- package/src/core/providerRuntime/local.ts +754 -0
- package/src/core/providerRuntime/models.ts +150 -0
- package/src/core/providerRuntime/reasoning.ts +17 -0
- package/src/core/providerRuntime/registry.test.ts +233 -0
- package/src/core/providerRuntime/registry.ts +203 -0
- package/src/core/providerRuntime/types.ts +103 -0
- package/src/core/providers/codexJsonStream.test.ts +148 -0
- package/src/core/providers/codexJsonStream.ts +305 -0
- package/src/core/providers/codexSubprocess.test.ts +68 -0
- package/src/core/providers/codexSubprocess.ts +372 -0
- package/src/core/providers/codexTranscript.test.ts +284 -0
- package/src/core/providers/codexTranscript.ts +695 -0
- package/src/core/providers/openaiNative.ts +13 -0
- package/src/core/providers/registry.ts +21 -0
- package/src/core/providers/types.ts +59 -0
- package/src/core/terminal/terminalCapabilities.test.ts +93 -0
- package/src/core/terminal/terminalCapabilities.ts +100 -0
- package/src/core/terminal/terminalControl.test.ts +75 -0
- package/src/core/terminal/terminalControl.ts +147 -0
- package/src/core/terminal/terminalSanitize.test.ts +22 -0
- package/src/core/terminal/terminalSanitize.ts +147 -0
- package/src/core/terminal/terminalSelection.test.ts +42 -0
- package/src/core/terminal/terminalSelection.ts +66 -0
- package/src/core/terminal/terminalTitle.test.ts +328 -0
- package/src/core/terminal/terminalTitle.ts +483 -0
- package/src/core/workspaceActivity.test.ts +163 -0
- package/src/core/workspaceActivity.ts +380 -0
- package/src/core/workspaceGuard.test.ts +151 -0
- package/src/core/workspaceGuard.ts +288 -0
- package/src/core/workspaceRoot.test.ts +23 -0
- package/src/core/workspaceRoot.ts +47 -0
- package/src/exec.test.ts +13 -0
- package/src/exec.ts +72 -0
- package/src/headless/execArgs.test.ts +147 -0
- package/src/headless/execArgs.ts +294 -0
- package/src/headless/execRunner.test.ts +434 -0
- package/src/headless/execRunner.ts +304 -0
- package/src/index.test.tsx +618 -0
- package/src/index.tsx +296 -0
- package/src/session/appSession.test.ts +897 -0
- package/src/session/appSession.ts +761 -0
- package/src/session/chatLifecycle.test.ts +64 -0
- package/src/session/chatLifecycle.ts +951 -0
- package/src/session/liveRenderScheduler.test.ts +201 -0
- package/src/session/liveRenderScheduler.ts +214 -0
- package/src/session/planFlow.test.ts +103 -0
- package/src/session/planFlow.ts +149 -0
- package/src/session/planTranscript.test.ts +65 -0
- package/src/session/planTranscript.ts +15 -0
- package/src/session/promptRunSchedule.test.ts +36 -0
- package/src/session/promptRunSchedule.ts +26 -0
- package/src/session/types.ts +228 -0
- package/src/test/runtimeTestUtils.ts +14 -0
- package/src/types/react-dom.d.ts +3 -0
- package/src/ui/ActionRequiredBlock.tsx +38 -0
- package/src/ui/ActivityBars.tsx +68 -0
- package/src/ui/ActivityIndicator.test.tsx +58 -0
- package/src/ui/ActivityIndicator.tsx +58 -0
- package/src/ui/AgentBlock.test.ts +6 -0
- package/src/ui/AgentBlock.tsx +130 -0
- package/src/ui/AnimatedStatusText.test.ts +16 -0
- package/src/ui/AnimatedStatusText.tsx +69 -0
- package/src/ui/AppShell.test.tsx +1739 -0
- package/src/ui/AppShell.tsx +698 -0
- package/src/ui/AttachmentImportPanel.test.tsx +204 -0
- package/src/ui/AttachmentImportPanel.tsx +98 -0
- package/src/ui/AuthPanel.tsx +113 -0
- package/src/ui/BackendPicker.tsx +28 -0
- package/src/ui/BottomComposer.test.ts +674 -0
- package/src/ui/BottomComposer.tsx +1028 -0
- package/src/ui/CodexLogo.tsx +55 -0
- package/src/ui/DashCard.tsx +82 -0
- package/src/ui/Markdown.test.ts +157 -0
- package/src/ui/Markdown.tsx +310 -0
- package/src/ui/ModePicker.tsx +27 -0
- package/src/ui/ModelPicker.tsx +31 -0
- package/src/ui/ModelPickerProviderScope.test.tsx +411 -0
- package/src/ui/ModelPickerScreen.test.tsx +99 -0
- package/src/ui/ModelPickerScreen.tsx +416 -0
- package/src/ui/ModelPickerState.test.tsx +151 -0
- package/src/ui/ModelReasoningPicker.test.tsx +447 -0
- package/src/ui/ModelReasoningPicker.tsx +458 -0
- package/src/ui/Panel.tsx +51 -0
- package/src/ui/PermissionsPanel.tsx +78 -0
- package/src/ui/PlanActionPicker.tsx +119 -0
- package/src/ui/PlanReviewPanel.test.tsx +267 -0
- package/src/ui/PlanReviewPanel.tsx +212 -0
- package/src/ui/PromptCardBorder.test.tsx +161 -0
- package/src/ui/ProviderPicker.test.tsx +289 -0
- package/src/ui/ProviderPicker.tsx +321 -0
- package/src/ui/ProviderShortcut.test.tsx +143 -0
- package/src/ui/ReasoningPicker.tsx +46 -0
- package/src/ui/RunFooter.tsx +65 -0
- package/src/ui/SelectionPanel.tsx +67 -0
- package/src/ui/SettingsPanel.test.tsx +233 -0
- package/src/ui/SettingsPanel.tsx +156 -0
- package/src/ui/Spinner.tsx +25 -0
- package/src/ui/StaticIntroItem.tsx +54 -0
- package/src/ui/StaticTranscriptItem.tsx +56 -0
- package/src/ui/TextEntryPanel.tsx +139 -0
- package/src/ui/ThemePicker.tsx +31 -0
- package/src/ui/ThinkingBlock.tsx +100 -0
- package/src/ui/Timeline.test.ts +2067 -0
- package/src/ui/Timeline.tsx +1472 -0
- package/src/ui/TimelineNavigation.test.tsx +201 -0
- package/src/ui/TopHeader.test.tsx +239 -0
- package/src/ui/TopHeader.tsx +257 -0
- package/src/ui/TurnGroup.test.tsx +365 -0
- package/src/ui/TurnGroup.tsx +657 -0
- package/src/ui/busyStatusAnimation.test.ts +30 -0
- package/src/ui/busyStatusAnimation.ts +11 -0
- package/src/ui/commandNormalize.test.ts +142 -0
- package/src/ui/commandNormalize.ts +66 -0
- package/src/ui/diffRenderer.test.ts +102 -0
- package/src/ui/diffRenderer.ts +116 -0
- package/src/ui/focus.ts +61 -0
- package/src/ui/focusFlow.test.tsx +1098 -0
- package/src/ui/inputBuffer.test.ts +151 -0
- package/src/ui/inputBuffer.ts +203 -0
- package/src/ui/layout.test.ts +145 -0
- package/src/ui/layout.ts +287 -0
- package/src/ui/modeDisplay.test.ts +42 -0
- package/src/ui/modeDisplay.ts +52 -0
- package/src/ui/outputPipeline.ts +64 -0
- package/src/ui/progressEntries.ts +156 -0
- package/src/ui/runActivityView.test.ts +89 -0
- package/src/ui/runActivityView.ts +37 -0
- package/src/ui/runLifecycleView.test.tsx +237 -0
- package/src/ui/slashCommands.ts +41 -0
- package/src/ui/statusRenderIsolation.test.tsx +654 -0
- package/src/ui/terminalAnswerFormat.test.ts +19 -0
- package/src/ui/terminalAnswerFormat.ts +128 -0
- package/src/ui/textLayout.test.ts +18 -0
- package/src/ui/textLayout.ts +338 -0
- package/src/ui/theme.tsx +395 -0
- package/src/ui/themeFlow.test.ts +53 -0
- package/src/ui/themeFlow.ts +41 -0
- package/src/ui/timelineMeasure.ts +3088 -0
- package/src/ui/timelineMeasureCache.test.ts +986 -0
- package/src/ui/useThrottledValue.ts +31 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import type { AvailableMode } from "../config/settings.js";
|
|
2
|
+
import type { ResolvedRuntimeConfig } from "../config/runtimeConfig.js";
|
|
3
|
+
import type { ProjectInstructions } from "./projectInstructions.js";
|
|
4
|
+
|
|
5
|
+
// ─── Write-intent detection ───────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
const WRITE_INTENT_PATTERNS = [
|
|
8
|
+
/\b(create|make|build|implement|add|generate|write|scaffold|fix|edit|update|refactor|cleanup|clean up|delete|remove|prune|purge)\b/i,
|
|
9
|
+
/\b(file|files|folder|folders|directory|directories|artifact|artifacts|generated|cache|caches|build|dist|coverage|app|component|script|cli|project|test|tests|bug|feature)\b/i,
|
|
10
|
+
/\b(py|python|ts|tsx|js|jsx|json|md|html|css|sql|yaml|yml|toml)\b/i,
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const GENERATED_CLEANUP_ACTION_PATTERN = /\b(cleanup|clean up|delete|remove|prune|purge)\b/i;
|
|
14
|
+
const GENERATED_CLEANUP_SAFE_TARGET_PATTERN =
|
|
15
|
+
/\b(clearly safe|safe generated|generated|cache|caches|cached|build artifacts?|temporary|temp|tmp|dist|coverage|out|\.cache|node_modules)\b/i;
|
|
16
|
+
const BROAD_DESTRUCTIVE_CLEANUP_PATTERN =
|
|
17
|
+
/\b(delete|remove|cleanup|clean up|wipe|purge|prune)\s+(everything|entire|whole|repo|repository|workspace|project)\b/i;
|
|
18
|
+
const BROAD_ALL_FILES_CLEANUP_PATTERN =
|
|
19
|
+
/\b(delete|remove|cleanup|clean up|wipe|purge|prune)\s+all\s+(?!generated|clearly safe|safe generated)(files|folders|directories|contents)\b/i;
|
|
20
|
+
const FORCEFUL_DELETE_PATTERN = /\b(rm\s+-rf|rmdir\s+\/s|del\s+\/[fsq]|format|nuke|wipe)\b/i;
|
|
21
|
+
|
|
22
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
export interface ExecutionModeDecision {
|
|
25
|
+
mode: AvailableMode;
|
|
26
|
+
autoUpgraded: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PlanningPromptParams {
|
|
30
|
+
task: string;
|
|
31
|
+
constraints?: readonly string[];
|
|
32
|
+
currentPlan?: string | null;
|
|
33
|
+
pendingFeedback?: {
|
|
34
|
+
mode: "revise" | "constraints";
|
|
35
|
+
text: string;
|
|
36
|
+
} | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface PlanExecutionPromptParams {
|
|
40
|
+
task: string;
|
|
41
|
+
approvedPlan: string;
|
|
42
|
+
constraints?: readonly string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface CodexPromptOptions {
|
|
46
|
+
projectInstructions?: ProjectInstructions | null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── Prompt utility functions ─────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
const TERMINAL_RESPONSE_INSTRUCTIONS = [
|
|
52
|
+
"Final answer style for this terminal UI:",
|
|
53
|
+
"- Keep answers compact unless the user asks for detail.",
|
|
54
|
+
"- Prefer terminal-native section labels like Purpose:, Main parts:, Result:, and Next:.",
|
|
55
|
+
"- Reference local files with short relative paths such as src/App.tsx, not Markdown links.",
|
|
56
|
+
"- Do not include absolute local paths like C:/Users/... or file://... unless the user explicitly asks for them.",
|
|
57
|
+
"- Preserve useful file references, but avoid web-style local Markdown links in final answers.",
|
|
58
|
+
"- Answer simple questions simply and do not over-explain.",
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export function promptHasWriteIntent(prompt: string): boolean {
|
|
62
|
+
const normalized = prompt.trim();
|
|
63
|
+
if (!normalized) return false;
|
|
64
|
+
|
|
65
|
+
const hits = WRITE_INTENT_PATTERNS.reduce(
|
|
66
|
+
(count, pattern) => (pattern.test(normalized) ? count + 1 : count),
|
|
67
|
+
0,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return hits >= 2;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function isClearlySafeGeneratedCleanupRequest(prompt: string): boolean {
|
|
74
|
+
const normalized = prompt.trim();
|
|
75
|
+
if (!normalized) return false;
|
|
76
|
+
|
|
77
|
+
if (BROAD_DESTRUCTIVE_CLEANUP_PATTERN.test(normalized)) return false;
|
|
78
|
+
if (BROAD_ALL_FILES_CLEANUP_PATTERN.test(normalized)) return false;
|
|
79
|
+
if (FORCEFUL_DELETE_PATTERN.test(normalized)) return false;
|
|
80
|
+
|
|
81
|
+
return GENERATED_CLEANUP_ACTION_PATTERN.test(normalized)
|
|
82
|
+
&& GENERATED_CLEANUP_SAFE_TARGET_PATTERN.test(normalized);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function resolveExecutionMode(
|
|
86
|
+
requestedMode: AvailableMode,
|
|
87
|
+
prompt: string,
|
|
88
|
+
): ExecutionModeDecision {
|
|
89
|
+
if (requestedMode !== "suggest") {
|
|
90
|
+
return { mode: requestedMode, autoUpgraded: false };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (promptHasWriteIntent(prompt)) {
|
|
94
|
+
return { mode: "auto-edit", autoUpgraded: true };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { mode: requestedMode, autoUpgraded: false };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function enrichFileCreationPrompt(prompt: string): string {
|
|
101
|
+
const normalized = prompt.trim();
|
|
102
|
+
if (!normalized) return prompt;
|
|
103
|
+
|
|
104
|
+
const fileCreationMatch = /^(?:make|create|generate|add|write)\s+(?:a\s+|an\s+|new\s+)?(?:text\s+|markdown\s+|js\s+|ts\s+|python\s+)?(?:file|script|document)\s*(?:about|saying|for|called|named|with)?\s*(.*)$/i.exec(normalized);
|
|
105
|
+
|
|
106
|
+
if (!fileCreationMatch) {
|
|
107
|
+
return prompt;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const description = fileCreationMatch[1]?.trim();
|
|
111
|
+
if (!description) {
|
|
112
|
+
return [
|
|
113
|
+
prompt,
|
|
114
|
+
"",
|
|
115
|
+
"System Instructions for File Creation:",
|
|
116
|
+
"- The user requested a new file but didn't specify a name.",
|
|
117
|
+
"- Infer a sensible default filename (like 'untitled.txt' or 'scratch.md') and add content if context implies it. Do not create an empty file without an extension.",
|
|
118
|
+
].join("\n");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const explicitExact = /^(?:called|named|save as)\s+['"]?([a-zA-Z0-9_\-\.]+\.[a-zA-Z0-9]+)['"]?$/i.test(description);
|
|
122
|
+
const hasExtension = /\.[a-zA-Z0-9]{1,5}(\s|$)/.test(description);
|
|
123
|
+
const hasQuotes = /['"]([^'"]+)['"]/.test(description);
|
|
124
|
+
|
|
125
|
+
if (explicitExact || hasExtension || hasQuotes) {
|
|
126
|
+
return [
|
|
127
|
+
prompt,
|
|
128
|
+
"",
|
|
129
|
+
"System Instructions for File Creation:",
|
|
130
|
+
"- The user provided an explicit filename (detected via quotes, extension, or direct naming).",
|
|
131
|
+
"- Create the file exactly as designated.",
|
|
132
|
+
].join("\n");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return [
|
|
136
|
+
prompt,
|
|
137
|
+
"",
|
|
138
|
+
"System Instructions for File Creation:",
|
|
139
|
+
"- The request is a natural-language description, NOT a literal filename.",
|
|
140
|
+
"- DO NOT use the entire sentence as the filename (e.g., do not create 'how much i love rean go').",
|
|
141
|
+
"- Infer intent and generate a short, sensible filename (e.g., 'rean-love-note.txt', 'project-ideas.md').",
|
|
142
|
+
"- Include an appropriate file extension.",
|
|
143
|
+
"- The request implies content, so generate appropriate starter content. DO NOT create an empty file.",
|
|
144
|
+
].join("\n");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function buildPlanningPrompt({
|
|
148
|
+
task,
|
|
149
|
+
constraints = [],
|
|
150
|
+
currentPlan = null,
|
|
151
|
+
pendingFeedback = null,
|
|
152
|
+
}: PlanningPromptParams): string {
|
|
153
|
+
const sections = [
|
|
154
|
+
"Plan-only turn.",
|
|
155
|
+
"Do not implement the task, do not claim to have edited files, and do not describe completed changes.",
|
|
156
|
+
"Produce a concise, repo-aware implementation plan in Markdown.",
|
|
157
|
+
"Include these sections in order: Files, Steps, Assumptions, Risks.",
|
|
158
|
+
"Under Files, list the files you expect to create, modify, or delete when they can be inferred.",
|
|
159
|
+
"Under Steps, give concrete implementation steps.",
|
|
160
|
+
"Under Assumptions, capture reasonable inferences you are making.",
|
|
161
|
+
"Under Risks, note obvious risks, confirmations, or scope boundaries.",
|
|
162
|
+
"Keep the plan focused and actionable. Do not ask for approval inside the plan.",
|
|
163
|
+
"",
|
|
164
|
+
"Task:",
|
|
165
|
+
task.trim(),
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
if (constraints.length > 0) {
|
|
169
|
+
sections.push("", "Active constraints:");
|
|
170
|
+
for (const constraint of constraints) {
|
|
171
|
+
sections.push(`- ${constraint}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (currentPlan?.trim()) {
|
|
176
|
+
sections.push("", "Current approved draft plan:", currentPlan.trim());
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (pendingFeedback?.text.trim()) {
|
|
180
|
+
const label = pendingFeedback.mode === "revise" ? "Revision request" : "Additional constraints";
|
|
181
|
+
sections.push("", `${label}:`, pendingFeedback.text.trim());
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return sections.join("\n");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function buildPlanExecutionPrompt({
|
|
188
|
+
task,
|
|
189
|
+
approvedPlan,
|
|
190
|
+
constraints = [],
|
|
191
|
+
}: PlanExecutionPromptParams): string {
|
|
192
|
+
const sections = [
|
|
193
|
+
"The user approved the following plan. Implement it now.",
|
|
194
|
+
"Do the work in the workspace instead of re-planning.",
|
|
195
|
+
"Only pause if you discover a genuinely blocking issue.",
|
|
196
|
+
"",
|
|
197
|
+
"Original task:",
|
|
198
|
+
task.trim(),
|
|
199
|
+
"",
|
|
200
|
+
"Approved plan:",
|
|
201
|
+
approvedPlan.trim(),
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
if (constraints.length > 0) {
|
|
205
|
+
sections.push("", "Additional constraints:");
|
|
206
|
+
for (const constraint of constraints) {
|
|
207
|
+
sections.push(`- ${constraint}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return sections.join("\n");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── Hollow response detection ───────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
export type HollowResponseKind = "greeting" | "filler" | "clarification" | "short-no-action" | "none";
|
|
217
|
+
|
|
218
|
+
export interface HollowResponseResult {
|
|
219
|
+
isHollow: boolean;
|
|
220
|
+
kind: HollowResponseKind;
|
|
221
|
+
reason: string;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const GREETING_PATTERNS = [
|
|
225
|
+
/^(hello|hi|hey|sure|okay|ok|got it|understood|of course|absolutely|certainly|great|sounds good)[.!]?\s*$/i,
|
|
226
|
+
/^i('m| am) (ready|here|available|happy to help)[.!]?\s*$/i,
|
|
227
|
+
/^how can i (help|assist) (you )?(today|now)?[?!]?\s*$/i,
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
const FILLER_PATTERNS = [
|
|
231
|
+
/^(thanks|thank you|no problem|you're welcome|will do|on it|noted)[.!]?\s*$/i,
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
const CLARIFICATION_PATTERNS = [
|
|
235
|
+
/^(can you|could you|what|which|where|please)\b.+\b(clarify|specify|provide|tell me|explain|mean)\b.*[?.]?\s*$/i,
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
const ACTION_CONFIRMATION_PATTERNS = [
|
|
239
|
+
/\b(created?|wrote|written|updated?|modified?|added?|deleted?|removed?|changed?|fixed?|built?|generated?|scaffolded?|refactored?)\b/i,
|
|
240
|
+
/\b(file|files|function|class|component|module|directory|folder)\b/i,
|
|
241
|
+
/```/,
|
|
242
|
+
/\[QUESTION\]:/,
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
export function detectHollowResponse(prompt: string, response: string): HollowResponseResult {
|
|
246
|
+
const trimmed = response.trim();
|
|
247
|
+
const hasWriteIntent = promptHasWriteIntent(prompt);
|
|
248
|
+
|
|
249
|
+
// Only flag hollow responses when the prompt actually asked the backend to do something.
|
|
250
|
+
// A user saying "Hello" and getting "Hello" back is perfectly normal.
|
|
251
|
+
if (!hasWriteIntent) {
|
|
252
|
+
return { isHollow: false, kind: "none", reason: "" };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!trimmed) {
|
|
256
|
+
return { isHollow: true, kind: "filler", reason: "Empty response" };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
for (const pattern of GREETING_PATTERNS) {
|
|
260
|
+
if (pattern.test(trimmed)) {
|
|
261
|
+
return { isHollow: true, kind: "greeting", reason: "Generic greeting" };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
for (const pattern of FILLER_PATTERNS) {
|
|
266
|
+
if (pattern.test(trimmed)) {
|
|
267
|
+
return { isHollow: true, kind: "filler", reason: "Filler acknowledgment" };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
for (const pattern of CLARIFICATION_PATTERNS) {
|
|
272
|
+
if (pattern.test(trimmed)) {
|
|
273
|
+
return { isHollow: true, kind: "clarification", reason: "Clarification question" };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (trimmed.length < 80) {
|
|
278
|
+
const hasConfirmation = ACTION_CONFIRMATION_PATTERNS.some((p) => p.test(trimmed));
|
|
279
|
+
if (!hasConfirmation) {
|
|
280
|
+
return { isHollow: true, kind: "short-no-action", reason: "Short response with no action confirmation" };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return { isHollow: false, kind: "none", reason: "" };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ─── Prompt builders ─────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
function resolvePromptRuntime(
|
|
290
|
+
modeOrRuntime: AvailableMode | Pick<ResolvedRuntimeConfig, "mode" | "policy" | "planMode">,
|
|
291
|
+
runtimePolicy?: {
|
|
292
|
+
approvalPolicy: string;
|
|
293
|
+
sandboxMode: string;
|
|
294
|
+
planMode?: boolean;
|
|
295
|
+
},
|
|
296
|
+
): { mode: AvailableMode; sandboxMode: string; planMode: boolean } {
|
|
297
|
+
if (typeof modeOrRuntime === "string") {
|
|
298
|
+
return {
|
|
299
|
+
mode: modeOrRuntime,
|
|
300
|
+
sandboxMode: runtimePolicy?.sandboxMode ?? "workspace-write",
|
|
301
|
+
planMode: runtimePolicy?.planMode ?? false,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
mode: modeOrRuntime.mode,
|
|
307
|
+
sandboxMode: modeOrRuntime.policy.sandboxMode,
|
|
308
|
+
planMode: modeOrRuntime.planMode,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function formatProjectInstructionsSection(projectInstructions: ProjectInstructions | null | undefined): string[] {
|
|
313
|
+
const content = projectInstructions?.content.trim();
|
|
314
|
+
if (!content) {
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
const sourcePath = projectInstructions?.path ?? "unknown";
|
|
318
|
+
|
|
319
|
+
return [
|
|
320
|
+
"Project instructions:",
|
|
321
|
+
`Loaded from: ${sourcePath}`,
|
|
322
|
+
content,
|
|
323
|
+
"",
|
|
324
|
+
];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function buildCodexPrompt(
|
|
328
|
+
prompt: string,
|
|
329
|
+
modeOrRuntime: AvailableMode | Pick<ResolvedRuntimeConfig, "mode" | "policy" | "planMode">,
|
|
330
|
+
runtimePolicy?: {
|
|
331
|
+
approvalPolicy: string;
|
|
332
|
+
sandboxMode: string;
|
|
333
|
+
planMode?: boolean;
|
|
334
|
+
},
|
|
335
|
+
options: CodexPromptOptions = {},
|
|
336
|
+
): string {
|
|
337
|
+
const enrichedPrompt = enrichFileCreationPrompt(prompt);
|
|
338
|
+
const { mode, sandboxMode, planMode } = resolvePromptRuntime(modeOrRuntime, runtimePolicy);
|
|
339
|
+
const projectInstructionsSection = formatProjectInstructionsSection(options.projectInstructions);
|
|
340
|
+
const readOnlySandbox = sandboxMode === "read-only";
|
|
341
|
+
const planModeInstructions = planMode
|
|
342
|
+
? [
|
|
343
|
+
"Planning mode is enabled for this session.",
|
|
344
|
+
"Start by giving a concise, repo-aware plan for how you will handle the task.",
|
|
345
|
+
"After the plan, continue the task normally under the current mode and runtime permissions.",
|
|
346
|
+
"Do not treat planning mode as a permission change and do not silently switch execution modes.",
|
|
347
|
+
]
|
|
348
|
+
: [];
|
|
349
|
+
|
|
350
|
+
if (readOnlySandbox) {
|
|
351
|
+
return [
|
|
352
|
+
"The user request below is the task to handle now.",
|
|
353
|
+
"Do not reply with generic readiness or ask what they want changed if the request is already specific.",
|
|
354
|
+
...planModeInstructions,
|
|
355
|
+
"Runtime permissions are read-only for this turn.",
|
|
356
|
+
"Inspect files and answer carefully, but do not claim to have edited files unless you actually could.",
|
|
357
|
+
"Default to best-effort continuation instead of stopping for clarification.",
|
|
358
|
+
"If a detail is missing but non-critical, make the most reasonable assumption and state it briefly.",
|
|
359
|
+
"If multiple paths are possible, choose one sensible path and continue.",
|
|
360
|
+
"Only ask a blocking follow-up question if proceeding would likely use the wrong file, wrong command, destructive behavior, or produce fundamentally incorrect output.",
|
|
361
|
+
"If you are truly blocked on one critical missing fact, end the response with exactly one line in this format: [QUESTION]: <your question>",
|
|
362
|
+
...TERMINAL_RESPONSE_INSTRUCTIONS,
|
|
363
|
+
"",
|
|
364
|
+
...projectInstructionsSection,
|
|
365
|
+
"Task:",
|
|
366
|
+
enrichedPrompt,
|
|
367
|
+
].join("\n");
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (mode === "suggest") {
|
|
371
|
+
return [
|
|
372
|
+
"The user request below is the task to handle now.",
|
|
373
|
+
"Do not reply with generic readiness or ask what they want changed if the request is already specific.",
|
|
374
|
+
...planModeInstructions,
|
|
375
|
+
"The current permissions allow workspace edits, but this turn is still in suggest mode.",
|
|
376
|
+
"Inspect the repo and answer carefully without making file changes in this turn.",
|
|
377
|
+
"Default to best-effort continuation instead of stopping for clarification.",
|
|
378
|
+
"If a detail is missing but non-critical, make the most reasonable assumption and state it briefly.",
|
|
379
|
+
"If multiple paths are possible, choose one sensible path and continue.",
|
|
380
|
+
"Only ask a blocking follow-up question if proceeding would likely use the wrong file, wrong command, destructive behavior, or produce fundamentally incorrect output.",
|
|
381
|
+
"If you are truly blocked on one critical missing fact, end the response with exactly one line in this format: [QUESTION]: <your question>",
|
|
382
|
+
...TERMINAL_RESPONSE_INSTRUCTIONS,
|
|
383
|
+
"",
|
|
384
|
+
...projectInstructionsSection,
|
|
385
|
+
"Task:",
|
|
386
|
+
enrichedPrompt,
|
|
387
|
+
].join("\n");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const autonomyLine =
|
|
391
|
+
mode === "full-auto"
|
|
392
|
+
? "Act with strong autonomy: inspect the repo, create or update files directly, and run checks when helpful."
|
|
393
|
+
: "Act like a coding agent: inspect the repo, create or update files directly, and prefer real workspace edits over large pasted code blocks.";
|
|
394
|
+
const generatedCleanupInstructions = isClearlySafeGeneratedCleanupRequest(prompt)
|
|
395
|
+
? [
|
|
396
|
+
"Fast generated-file cleanup guidance:",
|
|
397
|
+
"- Start with a shallow workspace inspection and act decisively.",
|
|
398
|
+
"- Delete only conventional generated artifacts, caches, temporary folders, dependency installs, and build outputs inside the workspace.",
|
|
399
|
+
"- Skip ambiguous, user-authored, source, config, docs, lock, and project files.",
|
|
400
|
+
"- Attempt each safe cleanup target once.",
|
|
401
|
+
"- If deletion is blocked by access denied, permission denied, a locked/in-use file, EACCES, EPERM, EBUSY, or Git lock metadata, stop immediately and report the blocked path and cause.",
|
|
402
|
+
"- Do not retry, force-delete, change permissions, run setup/bootstrap commands, or continue broad analysis after a clear blocked-delete failure.",
|
|
403
|
+
"- Do not do branch, bootstrap, package install, or repo setup work for this cleanup.",
|
|
404
|
+
"- Summarize exactly what was removed and what was skipped.",
|
|
405
|
+
]
|
|
406
|
+
: [];
|
|
407
|
+
|
|
408
|
+
return [
|
|
409
|
+
"The user request below is the task to handle now.",
|
|
410
|
+
"Do not reply with generic readiness or ask what they want changed if the request is already specific.",
|
|
411
|
+
...planModeInstructions,
|
|
412
|
+
...generatedCleanupInstructions,
|
|
413
|
+
"If the request is actionable, make the change in the workspace before responding.",
|
|
414
|
+
"You are running inside the user's current workspace with write access.",
|
|
415
|
+
autonomyLine,
|
|
416
|
+
"Default to best-effort continuation instead of stopping for clarification.",
|
|
417
|
+
"If a detail is missing but non-critical, make the most reasonable assumption and state it briefly.",
|
|
418
|
+
"If multiple paths are possible, choose one sensible path and continue.",
|
|
419
|
+
"Only ask a blocking follow-up question if proceeding would likely use the wrong file, wrong command, destructive behavior, or produce fundamentally incorrect output.",
|
|
420
|
+
"If you are truly blocked on one critical missing fact, end the response with exactly one line in this format: [QUESTION]: <your question>",
|
|
421
|
+
"After doing the work, summarize what changed.",
|
|
422
|
+
...TERMINAL_RESPONSE_INSTRUCTIONS,
|
|
423
|
+
"",
|
|
424
|
+
...projectInstructionsSection,
|
|
425
|
+
"Task:",
|
|
426
|
+
enrichedPrompt,
|
|
427
|
+
].join("\n");
|
|
428
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { runCommand } from "../process/CommandRunner.js";
|
|
3
|
+
import { buildSpawnSpec, resolveExecutable } from "./executableResolver.js";
|
|
4
|
+
|
|
5
|
+
type CommandRunner = typeof runCommand;
|
|
6
|
+
|
|
7
|
+
let cachedExecutable: string | null = null;
|
|
8
|
+
|
|
9
|
+
export function resetClaudeExecutableCacheForTests(): void {
|
|
10
|
+
cachedExecutable = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns the resolved Claude CLI executable (full path or bare name).
|
|
15
|
+
*
|
|
16
|
+
* Priority:
|
|
17
|
+
* 1. CLAUDE_EXECUTABLE env var (if set)
|
|
18
|
+
* 2. where.exe lookup on Windows — finds the real .exe/.cmd/.bat even when "claude"
|
|
19
|
+
* is shadowed by a PowerShell function (Invoke-Claude @args)
|
|
20
|
+
* 3. Windows known-path fallbacks: %USERPROFILE%\.local\bin and %USERPROFILE%\bin
|
|
21
|
+
* 4. Bare "claude" fallback (works on Unix; Windows fallback if nothing else found)
|
|
22
|
+
*/
|
|
23
|
+
export async function resolveClaudeExecutable(options?: {
|
|
24
|
+
runCommandImpl?: CommandRunner;
|
|
25
|
+
cwd?: string;
|
|
26
|
+
configuredPath?: string | null;
|
|
27
|
+
}): Promise<string> {
|
|
28
|
+
if (!options?.configuredPath && !options?.runCommandImpl && cachedExecutable !== null) {
|
|
29
|
+
return cachedExecutable;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const knownPathDirectories: string[] = [];
|
|
33
|
+
const userProfile = process.env.USERPROFILE;
|
|
34
|
+
if (userProfile) {
|
|
35
|
+
knownPathDirectories.push(join(userProfile, ".local", "bin"));
|
|
36
|
+
knownPathDirectories.push(join(userProfile, "bin"));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const result = await resolveExecutable({
|
|
40
|
+
runCommandImpl: options?.runCommandImpl,
|
|
41
|
+
cwd: options?.cwd,
|
|
42
|
+
configuredPath: options?.configuredPath,
|
|
43
|
+
envOverrides: ["CLAUDE_EXECUTABLE"],
|
|
44
|
+
commandNames: ["claude.exe", "claude.cmd", "claude.bat", "claude"],
|
|
45
|
+
knownPathDirectories,
|
|
46
|
+
label: "claude",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!options?.configuredPath && !options?.runCommandImpl) {
|
|
50
|
+
cachedExecutable = result;
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Builds the spawn spec for a resolved Claude executable.
|
|
57
|
+
*/
|
|
58
|
+
export function buildClaudeSpawnSpec(
|
|
59
|
+
executable: string,
|
|
60
|
+
args: string[],
|
|
61
|
+
): { executable: string; args: string[] } {
|
|
62
|
+
return buildSpawnSpec(executable, args);
|
|
63
|
+
}
|