@golba98/codexa 1.0.1 → 1.0.2
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 +0 -0
- package/package.json +3 -2
- package/scripts/audit-codexa-capabilities.mjs +0 -0
- package/scripts/gen-build-info.mjs +33 -0
- package/scripts/smoke-terminal-bench.mjs +0 -0
- package/src/app.tsx +103 -2
- package/src/appRenderStability.test.ts +0 -0
- package/src/commands/handler.test.ts +12 -0
- package/src/commands/handler.ts +5 -0
- package/src/config/buildInfo.ts +3 -0
- package/src/config/launchArgs.test.ts +31 -0
- package/src/config/launchArgs.ts +10 -0
- package/src/config/layeredConfig.test.ts +0 -0
- package/src/config/layeredConfig.ts +0 -0
- package/src/config/persistence.test.ts +6 -2
- package/src/config/persistence.ts +41 -0
- package/src/config/runtimeConfig.test.ts +0 -0
- package/src/config/runtimeConfig.ts +4 -0
- package/src/config/settings.test.ts +0 -0
- package/src/config/settings.ts +7 -6
- package/src/config/toml-serialize.ts +0 -0
- package/src/config/trustStore.test.ts +0 -0
- package/src/config/trustStore.ts +0 -0
- package/src/config/updateCheckCache.ts +45 -0
- package/src/core/attachments.test.ts +0 -0
- package/src/core/attachments.ts +0 -0
- package/src/core/auth/codexAuth.test.ts +0 -0
- package/src/core/auth/codexAuth.ts +0 -0
- package/src/core/cleanupFastFail.test.ts +0 -0
- package/src/core/cleanupFastFail.ts +0 -0
- package/src/core/clipboard.ts +0 -0
- package/src/core/codex.ts +0 -0
- package/src/core/codexExecArgs.test.ts +0 -0
- package/src/core/codexExecArgs.ts +0 -0
- package/src/core/codexLaunch.test.ts +0 -0
- package/src/core/codexLaunch.ts +0 -0
- package/src/core/codexPrompt.test.ts +0 -0
- package/src/core/codexPrompt.ts +0 -0
- package/src/core/executables/claudeExecutable.ts +0 -0
- package/src/core/executables/codexExecutable.test.ts +0 -0
- package/src/core/executables/codexExecutable.ts +0 -0
- package/src/core/executables/executableResolver.test.ts +0 -0
- package/src/core/executables/executableResolver.ts +0 -0
- package/src/core/executables/geminiExecutable.test.ts +0 -0
- package/src/core/executables/geminiExecutable.ts +0 -0
- package/src/core/executables/pathSanityScan.test.ts +0 -0
- package/src/core/githubDiagnostics.test.ts +0 -0
- package/src/core/githubDiagnostics.ts +0 -0
- package/src/core/hollowResponseFormat.test.ts +0 -0
- package/src/core/hollowResponseFormat.ts +0 -0
- package/src/core/inputDebug.ts +0 -0
- package/src/core/launchContext.test.ts +0 -0
- package/src/core/launchContext.ts +0 -0
- package/src/core/models/codexCapabilities.test.ts +0 -0
- package/src/core/models/codexCapabilities.ts +0 -0
- package/src/core/models/codexModelCapabilities.test.ts +0 -0
- package/src/core/models/codexModelCapabilities.ts +0 -0
- package/src/core/models/modelSpecs.test.ts +0 -0
- package/src/core/models/modelSpecs.ts +0 -0
- package/src/core/perf/profiler.ts +0 -0
- package/src/core/perf/renderDebug.test.ts +0 -0
- package/src/core/perf/renderDebug.ts +0 -0
- package/src/core/planStorage.test.ts +0 -0
- package/src/core/planStorage.ts +0 -0
- package/src/core/process/CommandRunner.test.ts +0 -0
- package/src/core/process/CommandRunner.ts +0 -0
- package/src/core/process/processValidation.ts +0 -0
- package/src/core/projectInstructions.test.ts +0 -0
- package/src/core/projectInstructions.ts +0 -0
- package/src/core/providerLauncher/launcher.test.ts +0 -0
- package/src/core/providerLauncher/launcher.ts +0 -0
- package/src/core/providerLauncher/registry.test.ts +0 -0
- package/src/core/providerLauncher/registry.ts +0 -0
- package/src/core/providerLauncher/types.ts +0 -0
- package/src/core/providerLauncher/workspaceConfig.test.ts +0 -0
- package/src/core/providerLauncher/workspaceConfig.ts +0 -0
- package/src/core/providerRuntime/anthropic.test.ts +0 -0
- package/src/core/providerRuntime/anthropic.ts +0 -0
- package/src/core/providerRuntime/capabilityProfile.test.ts +0 -0
- package/src/core/providerRuntime/capabilityProfile.ts +0 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +0 -0
- package/src/core/providerRuntime/contextMetadata.test.ts +0 -0
- package/src/core/providerRuntime/contextMetadata.ts +0 -0
- package/src/core/providerRuntime/gemini.test.ts +0 -0
- package/src/core/providerRuntime/gemini.ts +0 -0
- package/src/core/providerRuntime/lmstudio.test.ts +0 -0
- package/src/core/providerRuntime/lmstudio.ts +0 -0
- package/src/core/providerRuntime/local.test.ts +0 -0
- package/src/core/providerRuntime/local.ts +0 -0
- package/src/core/providerRuntime/models.ts +0 -0
- package/src/core/providerRuntime/reasoning.ts +0 -0
- package/src/core/providerRuntime/registry.test.ts +0 -0
- package/src/core/providerRuntime/registry.ts +0 -0
- package/src/core/providerRuntime/types.ts +0 -0
- package/src/core/providers/codexJsonStream.test.ts +0 -0
- package/src/core/providers/codexJsonStream.ts +0 -0
- package/src/core/providers/codexSubprocess.test.ts +0 -0
- package/src/core/providers/codexSubprocess.ts +0 -0
- package/src/core/providers/codexTranscript.test.ts +0 -0
- package/src/core/providers/codexTranscript.ts +0 -0
- package/src/core/providers/openaiNative.ts +0 -0
- package/src/core/providers/registry.ts +0 -0
- package/src/core/providers/types.ts +0 -0
- package/src/core/terminal/startupClear.test.ts +55 -0
- package/src/core/terminal/startupClear.ts +20 -0
- package/src/core/terminal/terminalCapabilities.test.ts +0 -0
- package/src/core/terminal/terminalCapabilities.ts +0 -0
- package/src/core/terminal/terminalControl.test.ts +0 -0
- package/src/core/terminal/terminalControl.ts +0 -0
- package/src/core/terminal/terminalSanitize.test.ts +0 -0
- package/src/core/terminal/terminalSanitize.ts +0 -0
- package/src/core/terminal/terminalSelection.test.ts +0 -0
- package/src/core/terminal/terminalSelection.ts +0 -0
- package/src/core/terminal/terminalTitle.test.ts +0 -0
- package/src/core/terminal/terminalTitle.ts +0 -0
- package/src/core/updateCheck.test.ts +194 -0
- package/src/core/updateCheck.ts +172 -0
- package/src/core/workspaceActivity.test.ts +0 -0
- package/src/core/workspaceActivity.ts +0 -0
- package/src/core/workspaceGuard.test.ts +0 -0
- package/src/core/workspaceGuard.ts +0 -0
- package/src/core/workspaceRoot.test.ts +0 -0
- package/src/core/workspaceRoot.ts +0 -0
- package/src/exec.test.ts +0 -0
- package/src/exec.ts +0 -0
- package/src/headless/execArgs.test.ts +0 -0
- package/src/headless/execArgs.ts +1 -0
- package/src/headless/execRunner.test.ts +8 -6
- package/src/headless/execRunner.ts +0 -0
- package/src/index.test.tsx +5 -3
- package/src/index.tsx +13 -9
- package/src/session/appSession.test.ts +0 -0
- package/src/session/appSession.ts +0 -0
- package/src/session/chatLifecycle.test.ts +0 -0
- package/src/session/chatLifecycle.ts +0 -0
- package/src/session/liveRenderScheduler.test.ts +0 -0
- package/src/session/liveRenderScheduler.ts +0 -0
- package/src/session/planFlow.test.ts +0 -0
- package/src/session/planFlow.ts +0 -0
- package/src/session/planTranscript.test.ts +0 -0
- package/src/session/planTranscript.ts +0 -0
- package/src/session/promptRunSchedule.test.ts +0 -0
- package/src/session/promptRunSchedule.ts +0 -0
- package/src/session/types.ts +0 -0
- package/src/test/runtimeTestUtils.ts +0 -0
- package/src/types/react-dom.d.ts +0 -0
- package/src/ui/ActionRequiredBlock.tsx +0 -0
- package/src/ui/ActivityBars.tsx +0 -0
- package/src/ui/ActivityIndicator.test.tsx +0 -0
- package/src/ui/ActivityIndicator.tsx +0 -0
- package/src/ui/AgentBlock.test.ts +0 -0
- package/src/ui/AgentBlock.tsx +0 -0
- package/src/ui/AnimatedStatusText.test.ts +0 -0
- package/src/ui/AnimatedStatusText.tsx +0 -0
- package/src/ui/AppShell.test.tsx +60 -23
- package/src/ui/AppShell.tsx +28 -5
- package/src/ui/AttachmentImportPanel.test.tsx +0 -0
- package/src/ui/AttachmentImportPanel.tsx +0 -0
- package/src/ui/AuthPanel.tsx +0 -0
- package/src/ui/BackendPicker.tsx +0 -0
- package/src/ui/BottomComposer.test.ts +0 -0
- package/src/ui/BottomComposer.tsx +0 -0
- package/src/ui/CodexLogo.tsx +0 -0
- package/src/ui/DashCard.tsx +0 -0
- package/src/ui/Markdown.test.ts +0 -0
- package/src/ui/Markdown.tsx +0 -0
- package/src/ui/ModePicker.tsx +0 -0
- package/src/ui/ModelPicker.tsx +0 -0
- package/src/ui/ModelPickerProviderScope.test.tsx +0 -0
- package/src/ui/ModelPickerScreen.test.tsx +0 -0
- package/src/ui/ModelPickerScreen.tsx +0 -0
- package/src/ui/ModelPickerState.test.tsx +0 -0
- package/src/ui/ModelReasoningPicker.test.tsx +0 -0
- package/src/ui/ModelReasoningPicker.tsx +0 -0
- package/src/ui/Panel.tsx +0 -0
- package/src/ui/PermissionsPanel.tsx +0 -0
- package/src/ui/PlanActionPicker.tsx +0 -0
- package/src/ui/PlanReviewPanel.test.tsx +0 -0
- package/src/ui/PlanReviewPanel.tsx +0 -0
- package/src/ui/PromptCardBorder.test.tsx +0 -0
- package/src/ui/ProviderPicker.test.tsx +0 -0
- package/src/ui/ProviderPicker.tsx +0 -0
- package/src/ui/ProviderShortcut.test.tsx +0 -0
- package/src/ui/ReasoningPicker.tsx +0 -0
- package/src/ui/RunFooter.tsx +0 -0
- package/src/ui/SelectionPanel.tsx +0 -0
- package/src/ui/SettingsPanel.test.tsx +0 -0
- package/src/ui/SettingsPanel.tsx +0 -0
- package/src/ui/Spinner.tsx +0 -0
- package/src/ui/StaticIntroItem.tsx +0 -0
- package/src/ui/StaticTranscriptItem.tsx +0 -0
- package/src/ui/TextEntryPanel.tsx +0 -0
- package/src/ui/ThemePicker.tsx +0 -0
- package/src/ui/ThinkingBlock.tsx +0 -0
- package/src/ui/Timeline.test.ts +0 -0
- package/src/ui/Timeline.tsx +0 -0
- package/src/ui/TimelineNavigation.test.tsx +0 -0
- package/src/ui/TopHeader.test.tsx +53 -38
- package/src/ui/TopHeader.tsx +51 -15
- package/src/ui/TurnGroup.test.tsx +0 -0
- package/src/ui/TurnGroup.tsx +0 -0
- package/src/ui/busyStatusAnimation.test.ts +0 -0
- package/src/ui/busyStatusAnimation.ts +0 -0
- package/src/ui/commandNormalize.test.ts +0 -0
- package/src/ui/commandNormalize.ts +0 -0
- package/src/ui/diffRenderer.test.ts +0 -0
- package/src/ui/diffRenderer.ts +0 -0
- package/src/ui/focus.ts +0 -0
- package/src/ui/focusFlow.test.tsx +0 -0
- package/src/ui/inputBuffer.test.ts +0 -0
- package/src/ui/inputBuffer.ts +0 -0
- package/src/ui/layout.test.ts +3 -2
- package/src/ui/layout.ts +0 -0
- package/src/ui/modeDisplay.test.ts +0 -0
- package/src/ui/modeDisplay.ts +0 -0
- package/src/ui/outputPipeline.ts +0 -0
- package/src/ui/progressEntries.ts +0 -0
- package/src/ui/runActivityView.test.ts +0 -0
- package/src/ui/runActivityView.ts +0 -0
- package/src/ui/runLifecycleView.test.tsx +0 -0
- package/src/ui/slashCommands.ts +0 -0
- package/src/ui/statusRenderIsolation.test.tsx +0 -0
- package/src/ui/terminalAnswerFormat.test.ts +0 -0
- package/src/ui/terminalAnswerFormat.ts +0 -0
- package/src/ui/textLayout.test.ts +0 -0
- package/src/ui/textLayout.ts +0 -0
- package/src/ui/theme.tsx +0 -0
- package/src/ui/themeFlow.test.ts +0 -0
- package/src/ui/themeFlow.ts +0 -0
- package/src/ui/timelineMeasure.ts +0 -0
- package/src/ui/timelineMeasureCache.test.ts +0 -0
- package/src/ui/useThrottledValue.ts +0 -0
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/core/planStorage.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { performStartupClear } from "./startupClear.js";
|
|
4
|
+
|
|
5
|
+
function makeCapture(): { written: string[]; write: (chunk: string) => boolean } {
|
|
6
|
+
const written: string[] = [];
|
|
7
|
+
return { written, write: (chunk) => { written.push(chunk); return true; } };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const TRANSCRIPT_CLEAR = "\x1b[2J\x1b[3J\x1b[H";
|
|
11
|
+
|
|
12
|
+
test("emits full transcript clear in normal interactive mode", () => {
|
|
13
|
+
const cap = makeCapture();
|
|
14
|
+
performStartupClear({ write: cap.write, noClear: false, env: {} });
|
|
15
|
+
assert.deepEqual(cap.written, [TRANSCRIPT_CLEAR]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("skips clear when noClear flag is true (--no-clear)", () => {
|
|
19
|
+
const cap = makeCapture();
|
|
20
|
+
performStartupClear({ write: cap.write, noClear: true, env: {} });
|
|
21
|
+
assert.equal(cap.written.length, 0);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("skips clear when CODEXA_NO_CLEAR=1 env var is set", () => {
|
|
25
|
+
const cap = makeCapture();
|
|
26
|
+
performStartupClear({ write: cap.write, noClear: false, env: { CODEXA_NO_CLEAR: "1" } });
|
|
27
|
+
assert.equal(cap.written.length, 0);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("noClear flag takes precedence over unset env var", () => {
|
|
31
|
+
const cap = makeCapture();
|
|
32
|
+
performStartupClear({ write: cap.write, noClear: true, env: { CODEXA_NO_CLEAR: "0" } });
|
|
33
|
+
assert.equal(cap.written.length, 0);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("emits clear when CODEXA_NO_CLEAR is not '1'", () => {
|
|
37
|
+
const cap = makeCapture();
|
|
38
|
+
performStartupClear({ write: cap.write, noClear: false, env: { CODEXA_NO_CLEAR: "0" } });
|
|
39
|
+
assert.deepEqual(cap.written, [TRANSCRIPT_CLEAR]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("emits clear when CODEXA_NO_CLEAR is undefined", () => {
|
|
43
|
+
const cap = makeCapture();
|
|
44
|
+
performStartupClear({ write: cap.write, noClear: false, env: { CODEXA_NO_CLEAR: undefined } });
|
|
45
|
+
assert.deepEqual(cap.written, [TRANSCRIPT_CLEAR]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("clear sequence includes viewport clear, scrollback clear, and cursor home", () => {
|
|
49
|
+
const cap = makeCapture();
|
|
50
|
+
performStartupClear({ write: cap.write, noClear: false, env: {} });
|
|
51
|
+
const seq = cap.written[0] ?? "";
|
|
52
|
+
assert.ok(seq.includes("\x1b[2J"), "must include viewport clear");
|
|
53
|
+
assert.ok(seq.includes("\x1b[3J"), "must include scrollback clear");
|
|
54
|
+
assert.ok(seq.includes("\x1b[H"), "must include cursor home");
|
|
55
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface StartupClearOptions {
|
|
2
|
+
write: (chunk: string) => boolean | void;
|
|
3
|
+
/** True when --no-clear was passed on the CLI. */
|
|
4
|
+
noClear: boolean;
|
|
5
|
+
env: Record<string, string | undefined>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Writes a full terminal clear (viewport + scrollback + cursor home) to stdout
|
|
10
|
+
* before the first Ink render. Skipped when --no-clear or CODEXA_NO_CLEAR=1.
|
|
11
|
+
*
|
|
12
|
+
* Only call this from startApp() in TTY mode — never inside render loops.
|
|
13
|
+
*/
|
|
14
|
+
export function performStartupClear(options: StartupClearOptions): void {
|
|
15
|
+
if (options.noClear || options.env["CODEXA_NO_CLEAR"] === "1") return;
|
|
16
|
+
// \x1b[2J: clear visible viewport
|
|
17
|
+
// \x1b[3J: clear scrollback buffer
|
|
18
|
+
// \x1b[H: move cursor to top-left
|
|
19
|
+
options.write("\x1b[2J\x1b[3J\x1b[H");
|
|
20
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
CODEXA_NPM_REGISTRY_URL,
|
|
5
|
+
CODEXA_UPDATE_COMMAND,
|
|
6
|
+
checkForUpdates,
|
|
7
|
+
compareSemver,
|
|
8
|
+
formatUpdateInstructions,
|
|
9
|
+
isNewerVersion,
|
|
10
|
+
type NpmRegistryMetadata,
|
|
11
|
+
} from "./updateCheck.js";
|
|
12
|
+
import { isCacheValid, type UpdateCheckCache } from "../config/updateCheckCache.js";
|
|
13
|
+
|
|
14
|
+
function metadata(version: string): NpmRegistryMetadata {
|
|
15
|
+
return { "dist-tags": { latest: version } };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test("checkForUpdates returns update-available when installed version is lower than npm latest", async () => {
|
|
19
|
+
const result = await checkForUpdates(
|
|
20
|
+
{},
|
|
21
|
+
{
|
|
22
|
+
currentVersion: "1.0.1",
|
|
23
|
+
fetchNpmMetadataFn: async (url) => {
|
|
24
|
+
assert.equal(url, CODEXA_NPM_REGISTRY_URL);
|
|
25
|
+
return metadata("1.0.2");
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
assert.equal(result.status, "update-available");
|
|
31
|
+
assert.equal(result.currentVersion, "1.0.1");
|
|
32
|
+
assert.equal(result.latestVersion, "1.0.2");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("checkForUpdates returns up-to-date when installed version equals npm latest", async () => {
|
|
36
|
+
const result = await checkForUpdates(
|
|
37
|
+
{},
|
|
38
|
+
{
|
|
39
|
+
currentVersion: "1.0.2",
|
|
40
|
+
fetchNpmMetadataFn: async () => metadata("1.0.2"),
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
assert.equal(result.status, "up-to-date");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("checkForUpdates does not show update available when installed version is higher than npm latest", async () => {
|
|
48
|
+
const result = await checkForUpdates(
|
|
49
|
+
{},
|
|
50
|
+
{
|
|
51
|
+
currentVersion: "1.0.3",
|
|
52
|
+
fetchNpmMetadataFn: async () => metadata("1.0.2"),
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
assert.equal(result.status, "up-to-date");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("checkForUpdates returns error status instead of throwing when npm request fails", async () => {
|
|
60
|
+
const result = await checkForUpdates(
|
|
61
|
+
{},
|
|
62
|
+
{
|
|
63
|
+
currentVersion: "1.0.1",
|
|
64
|
+
fetchNpmMetadataFn: async () => {
|
|
65
|
+
throw new Error("network unavailable");
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
assert.equal(result.status, "error");
|
|
71
|
+
assert.match(result.errorMessage ?? "", /network unavailable/);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("checkForUpdates returns error when npm latest is missing", async () => {
|
|
75
|
+
const result = await checkForUpdates(
|
|
76
|
+
{},
|
|
77
|
+
{
|
|
78
|
+
currentVersion: "1.0.1",
|
|
79
|
+
fetchNpmMetadataFn: async () => ({ "dist-tags": {} }),
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
assert.equal(result.status, "error");
|
|
84
|
+
assert.match(result.errorMessage ?? "", /dist-tags\.latest/);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("checkForUpdates returns unknown immediately when enabled=false", async () => {
|
|
88
|
+
let called = false;
|
|
89
|
+
const result = await checkForUpdates(
|
|
90
|
+
{ enabled: false },
|
|
91
|
+
{
|
|
92
|
+
currentVersion: "1.0.1",
|
|
93
|
+
fetchNpmMetadataFn: async () => {
|
|
94
|
+
called = true;
|
|
95
|
+
return metadata("1.0.2");
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
assert.equal(result.status, "unknown");
|
|
101
|
+
assert.equal(called, false, "should not call npm when disabled");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("semver comparison handles numeric and prerelease ordering", () => {
|
|
105
|
+
assert.equal(isNewerVersion("1.0.10", "1.0.2"), true);
|
|
106
|
+
assert.equal(isNewerVersion("1.0.2", "1.0.10"), false);
|
|
107
|
+
assert.equal(compareSemver("1.0.2", "1.0.2"), 0);
|
|
108
|
+
assert.equal(isNewerVersion("1.0.2", "1.0.2-beta.1"), true);
|
|
109
|
+
assert.equal(isNewerVersion("1.0.2-beta.1", "1.0.2"), false);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("isCacheValid returns true when cache is within the interval", () => {
|
|
113
|
+
const cache: UpdateCheckCache = {
|
|
114
|
+
lastChecked: Date.now() - 1000 * 60 * 30,
|
|
115
|
+
currentVersion: "1.0.1",
|
|
116
|
+
latestVersion: "1.0.1",
|
|
117
|
+
updateAvailable: false,
|
|
118
|
+
};
|
|
119
|
+
assert.equal(isCacheValid(cache, 6), true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("isCacheValid returns false when cache is older than the interval", () => {
|
|
123
|
+
const cache: UpdateCheckCache = {
|
|
124
|
+
lastChecked: Date.now() - 1000 * 60 * 60 * 7,
|
|
125
|
+
currentVersion: "1.0.1",
|
|
126
|
+
latestVersion: "1.0.1",
|
|
127
|
+
updateAvailable: false,
|
|
128
|
+
};
|
|
129
|
+
assert.equal(isCacheValid(cache, 6), false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("formatUpdateInstructions formats update-available npm status", () => {
|
|
133
|
+
const result = formatUpdateInstructions({
|
|
134
|
+
status: "update-available",
|
|
135
|
+
currentVersion: "1.0.1",
|
|
136
|
+
latestVersion: "1.0.2",
|
|
137
|
+
checkedAt: Date.now(),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
assert.match(result, /Current installed version: 1\.0\.1/);
|
|
141
|
+
assert.match(result, /npm latest version:\s+1\.0\.2/);
|
|
142
|
+
assert.match(result, /Update available: Codexa 1\.0\.2 is available\. You are using 1\.0\.1\./);
|
|
143
|
+
assert.match(result, new RegExp(`Run: ${CODEXA_UPDATE_COMMAND.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`));
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("formatUpdateInstructions formats up-to-date npm status", () => {
|
|
147
|
+
const result = formatUpdateInstructions({
|
|
148
|
+
status: "up-to-date",
|
|
149
|
+
currentVersion: "1.0.2",
|
|
150
|
+
latestVersion: "1.0.2",
|
|
151
|
+
checkedAt: Date.now(),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
assert.match(result, /Already up to date/);
|
|
155
|
+
assert.match(result, /npm install -g @golba98\/codexa@latest/);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("formatUpdateInstructions formats manual npm errors", () => {
|
|
159
|
+
const result = formatUpdateInstructions({
|
|
160
|
+
status: "error",
|
|
161
|
+
currentVersion: "1.0.1",
|
|
162
|
+
latestVersion: null,
|
|
163
|
+
errorMessage: "Connection timed out",
|
|
164
|
+
checkedAt: Date.now(),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
assert.match(result, /Error checking npm update status: Connection timed out/);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("/update check path bypasses cache by performing a fresh npm check", async () => {
|
|
171
|
+
const validCache: UpdateCheckCache = {
|
|
172
|
+
lastChecked: Date.now(),
|
|
173
|
+
currentVersion: "1.0.1",
|
|
174
|
+
latestVersion: "1.0.1",
|
|
175
|
+
updateAvailable: false,
|
|
176
|
+
};
|
|
177
|
+
assert.equal(isCacheValid(validCache, 6), true);
|
|
178
|
+
|
|
179
|
+
let calls = 0;
|
|
180
|
+
const result = await checkForUpdates(
|
|
181
|
+
{ enabled: true },
|
|
182
|
+
{
|
|
183
|
+
currentVersion: validCache.currentVersion,
|
|
184
|
+
fetchNpmMetadataFn: async () => {
|
|
185
|
+
calls += 1;
|
|
186
|
+
return metadata("1.0.2");
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
assert.equal(calls, 1);
|
|
192
|
+
assert.equal(result.status, "update-available");
|
|
193
|
+
assert.equal(result.latestVersion, "1.0.2");
|
|
194
|
+
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { APP_VERSION } from "../config/settings.js";
|
|
2
|
+
|
|
3
|
+
export type UpdateStatus = "up-to-date" | "update-available" | "unknown" | "error";
|
|
4
|
+
|
|
5
|
+
export interface UpdateCheckResult {
|
|
6
|
+
status: UpdateStatus;
|
|
7
|
+
currentVersion: string;
|
|
8
|
+
latestVersion: string | null;
|
|
9
|
+
errorMessage?: string;
|
|
10
|
+
checkedAt: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface NpmRegistryMetadata {
|
|
14
|
+
"dist-tags"?: {
|
|
15
|
+
latest?: unknown;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UpdateCheckOverrides {
|
|
20
|
+
fetchNpmMetadataFn?: (url: string, init: RequestInit) => Promise<NpmRegistryMetadata>;
|
|
21
|
+
currentVersion?: string;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const CODEXA_NPM_PACKAGE = "@golba98/codexa";
|
|
26
|
+
export const CODEXA_NPM_REGISTRY_URL = "https://registry.npmjs.org/@golba98%2Fcodexa";
|
|
27
|
+
export const CODEXA_UPDATE_COMMAND = "npm install -g @golba98/codexa@latest";
|
|
28
|
+
|
|
29
|
+
const REMOTE_TIMEOUT_MS = 3000;
|
|
30
|
+
|
|
31
|
+
interface ParsedSemver {
|
|
32
|
+
major: number;
|
|
33
|
+
minor: number;
|
|
34
|
+
patch: number;
|
|
35
|
+
prerelease: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseSemver(version: string): ParsedSemver | null {
|
|
39
|
+
const match = version.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
|
|
40
|
+
if (!match) return null;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
major: Number(match[1]),
|
|
44
|
+
minor: Number(match[2]),
|
|
45
|
+
patch: Number(match[3]),
|
|
46
|
+
prerelease: match[4]?.split(".") ?? [],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function comparePrerelease(left: string[], right: string[]): number {
|
|
51
|
+
if (left.length === 0 && right.length === 0) return 0;
|
|
52
|
+
if (left.length === 0) return 1;
|
|
53
|
+
if (right.length === 0) return -1;
|
|
54
|
+
|
|
55
|
+
const max = Math.max(left.length, right.length);
|
|
56
|
+
for (let index = 0; index < max; index += 1) {
|
|
57
|
+
const a = left[index];
|
|
58
|
+
const b = right[index];
|
|
59
|
+
if (a === undefined) return -1;
|
|
60
|
+
if (b === undefined) return 1;
|
|
61
|
+
if (a === b) continue;
|
|
62
|
+
|
|
63
|
+
const aNumeric = /^\d+$/.test(a);
|
|
64
|
+
const bNumeric = /^\d+$/.test(b);
|
|
65
|
+
if (aNumeric && bNumeric) return Number(a) > Number(b) ? 1 : -1;
|
|
66
|
+
if (aNumeric) return -1;
|
|
67
|
+
if (bNumeric) return 1;
|
|
68
|
+
return a > b ? 1 : -1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function compareSemver(leftVersion: string, rightVersion: string): number {
|
|
75
|
+
const left = parseSemver(leftVersion);
|
|
76
|
+
const right = parseSemver(rightVersion);
|
|
77
|
+
if (!left || !right) {
|
|
78
|
+
throw new Error(`Invalid semver version: ${!left ? leftVersion : rightVersion}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const key of ["major", "minor", "patch"] as const) {
|
|
82
|
+
if (left[key] !== right[key]) return left[key] > right[key] ? 1 : -1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return comparePrerelease(left.prerelease, right.prerelease);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function isNewerVersion(latestVersion: string, currentVersion: string): boolean {
|
|
89
|
+
return compareSemver(latestVersion, currentVersion) > 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function fetchNpmMetadata(url: string, init: RequestInit): Promise<NpmRegistryMetadata> {
|
|
93
|
+
const res = await fetch(url, init);
|
|
94
|
+
if (!res.ok) {
|
|
95
|
+
throw new Error(`npm registry responded with HTTP ${res.status}`);
|
|
96
|
+
}
|
|
97
|
+
return await res.json() as NpmRegistryMetadata;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function getNpmLatestVersion(
|
|
101
|
+
fetchMetadata: (url: string, init: RequestInit) => Promise<NpmRegistryMetadata> = fetchNpmMetadata,
|
|
102
|
+
): Promise<string> {
|
|
103
|
+
const controller = new AbortController();
|
|
104
|
+
const timer = setTimeout(() => controller.abort(), REMOTE_TIMEOUT_MS);
|
|
105
|
+
try {
|
|
106
|
+
const data = await fetchMetadata(CODEXA_NPM_REGISTRY_URL, {
|
|
107
|
+
signal: controller.signal,
|
|
108
|
+
headers: { "User-Agent": "Codexa-Update-Checker/1.0" },
|
|
109
|
+
});
|
|
110
|
+
const latest = data?.["dist-tags"]?.latest;
|
|
111
|
+
if (typeof latest !== "string" || !latest.trim()) {
|
|
112
|
+
throw new Error("npm registry response did not include dist-tags.latest");
|
|
113
|
+
}
|
|
114
|
+
return latest.trim();
|
|
115
|
+
} finally {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function checkForUpdates(
|
|
121
|
+
opts?: { enabled?: boolean },
|
|
122
|
+
overrides?: UpdateCheckOverrides,
|
|
123
|
+
): Promise<UpdateCheckResult> {
|
|
124
|
+
const now = overrides?.now ?? Date.now;
|
|
125
|
+
const currentVersion = overrides?.currentVersion ?? APP_VERSION;
|
|
126
|
+
|
|
127
|
+
if (opts?.enabled === false) {
|
|
128
|
+
return { status: "unknown", currentVersion, latestVersion: null, checkedAt: now() };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const latestVersion = await getNpmLatestVersion(overrides?.fetchNpmMetadataFn);
|
|
133
|
+
return {
|
|
134
|
+
status: isNewerVersion(latestVersion, currentVersion) ? "update-available" : "up-to-date",
|
|
135
|
+
currentVersion,
|
|
136
|
+
latestVersion,
|
|
137
|
+
checkedAt: now(),
|
|
138
|
+
};
|
|
139
|
+
} catch (err) {
|
|
140
|
+
return {
|
|
141
|
+
status: "error",
|
|
142
|
+
currentVersion,
|
|
143
|
+
latestVersion: null,
|
|
144
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
145
|
+
checkedAt: now(),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function formatUpdateInstructions(result: UpdateCheckResult | null): string {
|
|
151
|
+
const current = result?.currentVersion ?? APP_VERSION;
|
|
152
|
+
const latest = result?.latestVersion ?? "unknown";
|
|
153
|
+
|
|
154
|
+
let statusLine: string;
|
|
155
|
+
if (result?.status === "update-available" && result.latestVersion) {
|
|
156
|
+
statusLine = `Update available: Codexa ${result.latestVersion} is available. You are using ${current}.`;
|
|
157
|
+
} else if (result?.status === "up-to-date") {
|
|
158
|
+
statusLine = "Already up to date.";
|
|
159
|
+
} else if (result?.status === "error") {
|
|
160
|
+
statusLine = `Error checking npm update status: ${result.errorMessage || "unknown error"}`;
|
|
161
|
+
} else {
|
|
162
|
+
statusLine = "Status unknown - could not read npm latest version.";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return [
|
|
166
|
+
`Current installed version: ${current}`,
|
|
167
|
+
`npm latest version: ${latest}`,
|
|
168
|
+
`Status: ${statusLine}`,
|
|
169
|
+
"",
|
|
170
|
+
`Run: ${CODEXA_UPDATE_COMMAND}`,
|
|
171
|
+
].join("\n");
|
|
172
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/exec.test.ts
CHANGED
|
File without changes
|
package/src/exec.ts
CHANGED
|
File without changes
|
|
File without changes
|
package/src/headless/execArgs.ts
CHANGED
|
@@ -21,6 +21,7 @@ function createLaunchArgs(): LaunchArgs {
|
|
|
21
21
|
configOverrides: [],
|
|
22
22
|
passthroughArgs: [],
|
|
23
23
|
modelOverride: null,
|
|
24
|
+
noClear: false,
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -127,12 +128,13 @@ test("streams assistant deltas to stdout and progress/tool/error diagnostics to
|
|
|
127
128
|
},
|
|
128
129
|
);
|
|
129
130
|
|
|
130
|
-
assert.equal(result.exitCode, 0);
|
|
131
|
-
assert.equal(io.stdoutText(), "Hello world");
|
|
132
|
-
assert.
|
|
133
|
-
assert.match(io.stderrText(), /
|
|
134
|
-
assert.match(io.stderrText(), /
|
|
135
|
-
|
|
131
|
+
assert.equal(result.exitCode, 0);
|
|
132
|
+
assert.equal(io.stdoutText(), "Hello world");
|
|
133
|
+
assert.doesNotMatch(`${io.stdoutText()}\n${io.stderrText()}`, /██████|Codexa v|Workspace:|Provider:|Context:/);
|
|
134
|
+
assert.match(io.stderrText(), /startup:/);
|
|
135
|
+
assert.match(io.stderrText(), /reasoning: thinking/);
|
|
136
|
+
assert.match(io.stderrText(), /tool: running: pwd/);
|
|
137
|
+
});
|
|
136
138
|
|
|
137
139
|
test("emits final_answer_observed timing before provider response settles", async () => {
|
|
138
140
|
const io = createIo();
|
|
File without changes
|