mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Remove unnecessary type assertions across codebase
Add oxlint-tsgolint and configure typescript/no-unnecessary-type-assertion to flag redundant `!` and `as Foo` casts. Type-aware mode is left off by default to keep `npm run lint` fast; the rule sits configured for when we turn type-aware on intentionally. Auto-fix removed ~283 redundant casts; two manual touch-ups: a real tsgolint false positive in split-container.tsx and a stale ChildProcess import after a double-cast collapsed.
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
|
"options": {
|
||||||
|
"typeAware": false
|
||||||
|
},
|
||||||
"plugins": ["react", "react-perf", "unicorn", "typescript", "oxc", "import", "promise"],
|
"plugins": ["react", "react-perf", "unicorn", "typescript", "oxc", "import", "promise"],
|
||||||
"categories": {
|
"categories": {
|
||||||
"correctness": "error",
|
"correctness": "error",
|
||||||
@@ -53,6 +56,7 @@
|
|||||||
"typescript/no-explicit-any": "error",
|
"typescript/no-explicit-any": "error",
|
||||||
"typescript/prefer-as-const": "error",
|
"typescript/prefer-as-const": "error",
|
||||||
"typescript/no-this-alias": "error",
|
"typescript/no-this-alias": "error",
|
||||||
|
"typescript/no-unnecessary-type-assertion": "error",
|
||||||
"typescript/consistent-type-definitions": ["error", "interface"],
|
"typescript/consistent-type-definitions": ["error", "interface"],
|
||||||
|
|
||||||
"import/no-unassigned-import": [
|
"import/no-unassigned-import": [
|
||||||
|
|||||||
103
package-lock.json
generated
103
package-lock.json
generated
@@ -30,6 +30,7 @@
|
|||||||
"lefthook": "^2.1.6",
|
"lefthook": "^2.1.6",
|
||||||
"oxfmt": "0.46.0",
|
"oxfmt": "0.46.0",
|
||||||
"oxlint": "1.61.0",
|
"oxlint": "1.61.0",
|
||||||
|
"oxlint-tsgolint": "^0.22.1",
|
||||||
"patch-package": "^8.0.1",
|
"patch-package": "^8.0.1",
|
||||||
"playwright": "^1.56.1",
|
"playwright": "^1.56.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
@@ -10376,6 +10377,90 @@
|
|||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@oxlint-tsgolint/darwin-arm64": {
|
||||||
|
"version": "0.22.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-0.22.1.tgz",
|
||||||
|
"integrity": "sha512-4150Lpgc1YM09GcjA6GSrra1JoPjC7aOpfywLjWEY4vW0Sd1qKzqHF1WRaiw0/qUZ40OATYdv3aRd7ipPkWQbw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@oxlint-tsgolint/darwin-x64": {
|
||||||
|
"version": "0.22.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-0.22.1.tgz",
|
||||||
|
"integrity": "sha512-vFWcPWYOgZs4HWcgS1EjUZg33NLcNfEYU49KGImmCfZWkflENrmBYV4HN/C0YeAPum6ZZ/goPSvQrB/cOD+NfA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@oxlint-tsgolint/linux-arm64": {
|
||||||
|
"version": "0.22.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-0.22.1.tgz",
|
||||||
|
"integrity": "sha512-6LiUpP0Zir3+29FvBm7Y28q/dBjSHqTZ5MhG1Ckw4fGhI4cAvbcwXaKvbjx1TP7rRmBNOoq/M5xdpHjTb+GAew==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@oxlint-tsgolint/linux-x64": {
|
||||||
|
"version": "0.22.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-0.22.1.tgz",
|
||||||
|
"integrity": "sha512-fuX1hEQfpHauUbXADsfqVhRzrUrGabzGXbj5wsp2vKhV5uk/Rze8Mba9GdjFGECzvXudMGqHqxB4r6jGRdhxVA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@oxlint-tsgolint/win32-arm64": {
|
||||||
|
"version": "0.22.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-0.22.1.tgz",
|
||||||
|
"integrity": "sha512-8SZidAj+jrbZf9ZjBEYW0tiNZ+KasqB2zgW26qdiPpQSF/DzURnPmXz651IeA9YsmbVdHGIooEHUmev6QJdquA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@oxlint-tsgolint/win32-x64": {
|
||||||
|
"version": "0.22.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-0.22.1.tgz",
|
||||||
|
"integrity": "sha512-QweSk9H5lFh5Y+WUf2Kq/OAN88V6+62ZwGhP38gqdRotI90luXSMkruFTj7Q2rYrzH4ZVNaSqx7NY8JpSfIzqg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@oxlint/binding-android-arm-eabi": {
|
"node_modules/@oxlint/binding-android-arm-eabi": {
|
||||||
"version": "1.61.0",
|
"version": "1.61.0",
|
||||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.61.0.tgz",
|
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.61.0.tgz",
|
||||||
@@ -31174,6 +31259,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/oxlint-tsgolint": {
|
||||||
|
"version": "0.22.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-0.22.1.tgz",
|
||||||
|
"integrity": "sha512-YUSGSLUnoolsu8gxISEDio3q1rtsCozwfOzASUn3DT2mR2EeQ93uEEnen7s+6LpF+lyTQFln1pQfqwBh/fsVEg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"tsgolint": "bin/tsgolint.js"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@oxlint-tsgolint/darwin-arm64": "0.22.1",
|
||||||
|
"@oxlint-tsgolint/darwin-x64": "0.22.1",
|
||||||
|
"@oxlint-tsgolint/linux-arm64": "0.22.1",
|
||||||
|
"@oxlint-tsgolint/linux-x64": "0.22.1",
|
||||||
|
"@oxlint-tsgolint/win32-arm64": "0.22.1",
|
||||||
|
"@oxlint-tsgolint/win32-x64": "0.22.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/p-cancelable": {
|
"node_modules/p-cancelable": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
|
||||||
|
|||||||
@@ -101,6 +101,7 @@
|
|||||||
"lefthook": "^2.1.6",
|
"lefthook": "^2.1.6",
|
||||||
"oxfmt": "0.46.0",
|
"oxfmt": "0.46.0",
|
||||||
"oxlint": "1.61.0",
|
"oxlint": "1.61.0",
|
||||||
|
"oxlint-tsgolint": "^0.22.1",
|
||||||
"patch-package": "^8.0.1",
|
"patch-package": "^8.0.1",
|
||||||
"playwright": "^1.56.1",
|
"playwright": "^1.56.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
|
|||||||
@@ -204,8 +204,7 @@ export async function installTerminalRenderProbe(page: Page): Promise<void> {
|
|||||||
if (next?.write && !next.__paseoRenderProbeWriteWrapped) {
|
if (next?.write && !next.__paseoRenderProbeWriteWrapped) {
|
||||||
const originalWrite = next.write.bind(next);
|
const originalWrite = next.write.bind(next);
|
||||||
next.write = (data: string | Uint8Array, callback?: () => void) => {
|
next.write = (data: string | Uint8Array, callback?: () => void) => {
|
||||||
const text =
|
const text = typeof data === "string" ? data : new TextDecoder().decode(data);
|
||||||
typeof data === "string" ? data : new TextDecoder().decode(data as Uint8Array);
|
|
||||||
probe.writeCount += 1;
|
probe.writeCount += 1;
|
||||||
const preview = text
|
const preview = text
|
||||||
.replaceAll("\u001b", "\\x1b")
|
.replaceAll("\u001b", "\\x1b")
|
||||||
@@ -424,7 +423,7 @@ export async function installTerminalKeystrokeStressProbe(page: Page): Promise<v
|
|||||||
const count = Math.min(from.length, to.length);
|
const count = Math.min(from.length, to.length);
|
||||||
const values: number[] = [];
|
const values: number[] = [];
|
||||||
for (let index = 0; index < count; index += 1) {
|
for (let index = 0; index < count; index += 1) {
|
||||||
values.push(to[index]!.at - from[index]!.at);
|
values.push(to[index].at - from[index].at);
|
||||||
}
|
}
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
@@ -459,7 +458,7 @@ export async function installTerminalKeystrokeStressProbe(page: Page): Promise<v
|
|||||||
const keydownToInputFrame = this.keydowns
|
const keydownToInputFrame = this.keydowns
|
||||||
.map((keydown) => firstAtOrAfter(this.inputFrames, keydown.at)?.at ?? null)
|
.map((keydown) => firstAtOrAfter(this.inputFrames, keydown.at)?.at ?? null)
|
||||||
.filter((at): at is number => at !== null)
|
.filter((at): at is number => at !== null)
|
||||||
.map((at, index) => at - this.keydowns[index]!.at);
|
.map((at, index) => at - this.keydowns[index].at);
|
||||||
const inputFrameToOutputFrame = this.inputFrames
|
const inputFrameToOutputFrame = this.inputFrames
|
||||||
.map((input) => {
|
.map((input) => {
|
||||||
const output = firstAtOrAfter(this.outputFrames, input.at);
|
const output = firstAtOrAfter(this.outputFrames, input.at);
|
||||||
@@ -663,8 +662,7 @@ export async function installTerminalKeystrokeStressProbe(page: Page): Promise<v
|
|||||||
if (next?.write && !next.__paseoKeystrokeProbeWriteWrapped) {
|
if (next?.write && !next.__paseoKeystrokeProbeWriteWrapped) {
|
||||||
const originalWrite = next.write.bind(next);
|
const originalWrite = next.write.bind(next);
|
||||||
next.write = (data: string | Uint8Array, callback?: () => void) => {
|
next.write = (data: string | Uint8Array, callback?: () => void) => {
|
||||||
const text =
|
const text = typeof data === "string" ? data : new TextDecoder().decode(data);
|
||||||
typeof data === "string" ? data : new TextDecoder().decode(data as Uint8Array);
|
|
||||||
const event: XtermWriteEvent = {
|
const event: XtermWriteEvent = {
|
||||||
at: performance.now(),
|
at: performance.now(),
|
||||||
committedAt: null,
|
committedAt: null,
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ async function measureDaemonBurstEcho(
|
|||||||
timeoutMs: STRESS_TIMEOUT_MS,
|
timeoutMs: STRESS_TIMEOUT_MS,
|
||||||
});
|
});
|
||||||
|
|
||||||
const latencies = sendTimes.map((sentAt, index) => outputTimesByByte[index]! - sentAt);
|
const latencies = sendTimes.map((sentAt, index) => outputTimesByByte[index] - sentAt);
|
||||||
return {
|
return {
|
||||||
inputTextLength: inputText.length,
|
inputTextLength: inputText.length,
|
||||||
inputFrameCount: sendTimes.length,
|
inputFrameCount: sendTimes.length,
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ function AppContainer({
|
|||||||
const cycleTheme = useCallback(() => {
|
const cycleTheme = useCallback(() => {
|
||||||
const currentIndex = THEME_CYCLE_ORDER.indexOf(settings.theme as ThemeName);
|
const currentIndex = THEME_CYCLE_ORDER.indexOf(settings.theme as ThemeName);
|
||||||
const nextIndex = (currentIndex + 1) % THEME_CYCLE_ORDER.length;
|
const nextIndex = (currentIndex + 1) % THEME_CYCLE_ORDER.length;
|
||||||
void updateSettings({ theme: THEME_CYCLE_ORDER[nextIndex]! });
|
void updateSettings({ theme: THEME_CYCLE_ORDER[nextIndex] });
|
||||||
}, [settings.theme, updateSettings]);
|
}, [settings.theme, updateSettings]);
|
||||||
|
|
||||||
const isCompactLayout = useIsCompactFormFactor();
|
const isCompactLayout = useIsCompactFormFactor();
|
||||||
@@ -785,7 +785,7 @@ function AppWithSidebar({ children }: { children: ReactNode }) {
|
|||||||
if (hosts.some((host) => host.serverId === activeServerId)) {
|
if (hosts.some((host) => host.serverId === activeServerId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.replace(mapPathnameToServer(pathname, hosts[0]!.serverId));
|
router.replace(mapPathnameToServer(pathname, hosts[0].serverId));
|
||||||
}, [activeServerId, hosts, pathname, router]);
|
}, [activeServerId, hosts, pathname, router]);
|
||||||
|
|
||||||
// Parse selectedAgentKey directly from pathname
|
// Parse selectedAgentKey directly from pathname
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ class FakeRequest<T = unknown> {
|
|||||||
class FakeObjectStore {
|
class FakeObjectStore {
|
||||||
constructor(private readonly onPut: (record: unknown) => void) {}
|
constructor(private readonly onPut: (record: unknown) => void) {}
|
||||||
|
|
||||||
put(record: unknown): FakeRequest<unknown> {
|
put(record: unknown): FakeRequest {
|
||||||
this.onPut(record);
|
this.onPut(record);
|
||||||
const request = new FakeRequest<unknown>();
|
const request = new FakeRequest();
|
||||||
queueMicrotask(() => request.emit("success"));
|
queueMicrotask(() => request.emit("success"));
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ function runTx<T>(
|
|||||||
const request = run(store);
|
const request = run(store);
|
||||||
|
|
||||||
request.addEventListener("success", () => {
|
request.addEventListener("success", () => {
|
||||||
resolve(request.result as T);
|
resolve(request.result);
|
||||||
});
|
});
|
||||||
|
|
||||||
request.addEventListener("error", () => {
|
request.addEventListener("error", () => {
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ function buildFallbackAllProviderModels(
|
|||||||
map.set(
|
map.set(
|
||||||
provider,
|
provider,
|
||||||
modelOptions.map((option) => ({
|
modelOptions.map((option) => ({
|
||||||
provider: provider as AgentProvider,
|
provider: provider,
|
||||||
id: option.id,
|
id: option.id,
|
||||||
label: option.label,
|
label: option.label,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -461,7 +461,7 @@ export function BrowserPane({
|
|||||||
};
|
};
|
||||||
const handleFaviconUpdated = (event: Event) => {
|
const handleFaviconUpdated = (event: Event) => {
|
||||||
const favicons = Array.isArray((event as Event & { favicons?: unknown[] }).favicons)
|
const favicons = Array.isArray((event as Event & { favicons?: unknown[] }).favicons)
|
||||||
? (((event as Event & { favicons?: string[] }).favicons as string[] | undefined) ?? [])
|
? ((event as Event & { favicons?: string[] }).favicons ?? [])
|
||||||
: [];
|
: [];
|
||||||
updateBrowserRef.current(browserIdRef.current, { faviconUrl: favicons[0] ?? null });
|
updateBrowserRef.current(browserIdRef.current, { faviconUrl: favicons[0] ?? null });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ describe("combined model selector helpers", () => {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(matchesSearch(rows[0]!, "claude")).toBe(true);
|
expect(matchesSearch(rows[0], "claude")).toBe(true);
|
||||||
expect(matchesSearch(rows[1]!, "gpt-5.4")).toBe(true);
|
expect(matchesSearch(rows[1], "gpt-5.4")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps the selected trigger label model-only", () => {
|
it("keeps the selected trigger label model-only", () => {
|
||||||
|
|||||||
@@ -622,7 +622,7 @@ export function CombinedModelSelector({
|
|||||||
const singleProviderView = useMemo<SelectorView | null>(() => {
|
const singleProviderView = useMemo<SelectorView | null>(() => {
|
||||||
const providers = Array.from(allProviderModels.keys());
|
const providers = Array.from(allProviderModels.keys());
|
||||||
if (providers.length !== 1) return null;
|
if (providers.length !== 1) return null;
|
||||||
const providerId = providers[0]!;
|
const providerId = providers[0];
|
||||||
const label = resolveProviderLabel(providerDefinitions, providerId);
|
const label = resolveProviderLabel(providerDefinitions, providerId);
|
||||||
return { kind: "provider", providerId, providerLabel: label };
|
return { kind: "provider", providerId, providerLabel: label };
|
||||||
}, [allProviderModels, providerDefinitions]);
|
}, [allProviderModels, providerDefinitions]);
|
||||||
@@ -655,7 +655,7 @@ export function CombinedModelSelector({
|
|||||||
|
|
||||||
const handleSelect = useCallback(
|
const handleSelect = useCallback(
|
||||||
(provider: string, modelId: string) => {
|
(provider: string, modelId: string) => {
|
||||||
onSelect(provider as AgentProvider, modelId);
|
onSelect(provider, modelId);
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
setSearchQuery("");
|
setSearchQuery("");
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -160,20 +160,17 @@ function removeAttachmentAtIndex<T extends ComposerAttachment>(prev: T[], index:
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildCancelButtonStyle(isConnected: boolean, isCancellingAgent: boolean): object[] {
|
function buildCancelButtonStyle(isConnected: boolean, isCancellingAgent: boolean): object[] {
|
||||||
const disabled =
|
const disabled = !isConnected || isCancellingAgent ? styles.buttonDisabled : undefined;
|
||||||
!isConnected || isCancellingAgent ? (styles.buttonDisabled as object) : undefined;
|
return [styles.cancelButton, disabled].filter((value): value is object => Boolean(value));
|
||||||
return [styles.cancelButton as object, disabled].filter((value): value is object =>
|
|
||||||
Boolean(value),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildRealtimeVoiceButtonStyle(
|
function buildRealtimeVoiceButtonStyle(
|
||||||
hovered: boolean | undefined,
|
hovered: boolean | undefined,
|
||||||
voiceButtonDisabled: boolean,
|
voiceButtonDisabled: boolean,
|
||||||
): object[] {
|
): object[] {
|
||||||
const hoveredStyle = hovered ? (styles.iconButtonHovered as object) : undefined;
|
const hoveredStyle = hovered ? styles.iconButtonHovered : undefined;
|
||||||
const disabledStyle = voiceButtonDisabled ? (styles.buttonDisabled as object) : undefined;
|
const disabledStyle = voiceButtonDisabled ? styles.buttonDisabled : undefined;
|
||||||
return [styles.realtimeVoiceButton as object, hoveredStyle, disabledStyle].filter(
|
return [styles.realtimeVoiceButton, hoveredStyle, disabledStyle].filter(
|
||||||
(value): value is object => Boolean(value),
|
(value): value is object => Boolean(value),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ function TreeRowItem({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleCopy = useCallback(() => {
|
const handleCopy = useCallback(() => {
|
||||||
void onCopyPath(entry.path);
|
onCopyPath(entry.path);
|
||||||
}, [onCopyPath, entry.path]);
|
}, [onCopyPath, entry.path]);
|
||||||
|
|
||||||
const handleDownload = useCallback(() => {
|
const handleDownload = useCallback(() => {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ interface PathRowProps {
|
|||||||
function PathRow({ path, active, onSelect }: PathRowProps) {
|
function PathRow({ path, active, onSelect }: PathRowProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const handlePress = useCallback(() => {
|
const handlePress = useCallback(() => {
|
||||||
void onSelect(path);
|
onSelect(path);
|
||||||
}, [onSelect, path]);
|
}, [onSelect, path]);
|
||||||
const pressableStyle = useCallback(
|
const pressableStyle = useCallback(
|
||||||
({ hovered = false, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [
|
({ hovered = false, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [
|
||||||
@@ -172,7 +172,7 @@ export function ProjectPickerModal() {
|
|||||||
if (key === "Enter") {
|
if (key === "Enter") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (options.length > 0 && activeIndex < options.length) {
|
if (options.length > 0 && activeIndex < options.length) {
|
||||||
void handleSelectPath(options[activeIndex]!);
|
void handleSelectPath(options[activeIndex]);
|
||||||
} else if (query.trim()) {
|
} else if (query.trim()) {
|
||||||
handleSubmitCustom();
|
handleSubmitCustom();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ function CustomModelsSection(props: {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await refresh([provider as AgentProvider]);
|
await refresh([provider]);
|
||||||
},
|
},
|
||||||
[patchConfig, provider, refresh],
|
[patchConfig, provider, refresh],
|
||||||
);
|
);
|
||||||
@@ -284,7 +284,7 @@ export function ProviderDiagnosticSheet({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await client.getProviderDiagnostic(provider as AgentProvider);
|
const result = await client.getProviderDiagnostic(provider);
|
||||||
setDiagnostic(result.diagnostic);
|
setDiagnostic(result.diagnostic);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setDiagnostic(err instanceof Error ? err.message : "Failed to fetch diagnostic");
|
setDiagnostic(err instanceof Error ? err.message : "Failed to fetch diagnostic");
|
||||||
@@ -308,7 +308,7 @@ export function ProviderDiagnosticSheet({
|
|||||||
if (!provider) {
|
if (!provider) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void Promise.all([refresh([provider as AgentProvider]), fetchDiagnostic()]).catch((err) => {
|
void Promise.all([refresh([provider]), fetchDiagnostic()]).catch((err) => {
|
||||||
setDiagnostic(err instanceof Error ? err.message : "Failed to refresh provider");
|
setDiagnostic(err instanceof Error ? err.message : "Failed to refresh provider");
|
||||||
});
|
});
|
||||||
}, [fetchDiagnostic, provider, refresh]);
|
}, [fetchDiagnostic, provider, refresh]);
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ describe("sidebar workspace render isolation", () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
useSessionStore.getState().mergeWorkspaces(SERVER_ID, [
|
useSessionStore.getState().mergeWorkspaces(SERVER_ID, [
|
||||||
{
|
{
|
||||||
...createWorkspaces()[1]!,
|
...createWorkspaces()[1],
|
||||||
status: "running",
|
status: "running",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ function WorkspaceStatusIndicator({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let KindIcon: typeof ThemedMonitor | typeof ThemedFolderGit2 | null;
|
let KindIcon: typeof ThemedMonitor | null;
|
||||||
if (workspaceKind === "local_checkout") KindIcon = ThemedMonitor;
|
if (workspaceKind === "local_checkout") KindIcon = ThemedMonitor;
|
||||||
else if (workspaceKind === "worktree") KindIcon = ThemedFolderGit2;
|
else if (workspaceKind === "worktree") KindIcon = ThemedFolderGit2;
|
||||||
else KindIcon = null;
|
else KindIcon = null;
|
||||||
|
|||||||
@@ -488,7 +488,7 @@ function handleDesktopEnterKey(input: DesktopKeyHandlerInput) {
|
|||||||
if (input.orderedVisibleOptions.length === 0) return;
|
if (input.orderedVisibleOptions.length === 0) return;
|
||||||
const { activeIndex, orderedVisibleOptions } = input;
|
const { activeIndex, orderedVisibleOptions } = input;
|
||||||
const index = activeIndex >= 0 && activeIndex < orderedVisibleOptions.length ? activeIndex : 0;
|
const index = activeIndex >= 0 && activeIndex < orderedVisibleOptions.length ? activeIndex : 0;
|
||||||
input.handleSelect(orderedVisibleOptions[index]!.id);
|
input.handleSelect(orderedVisibleOptions[index].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FloatingSizeSetters {
|
interface FloatingSizeSetters {
|
||||||
|
|||||||
@@ -140,9 +140,9 @@ export function useWebScrollViewScrollbar(
|
|||||||
const scrollable = scrollableRef.current;
|
const scrollable = scrollableRef.current;
|
||||||
if (!scrollable) return;
|
if (!scrollable) return;
|
||||||
if ("scrollToOffset" in scrollable) {
|
if ("scrollToOffset" in scrollable) {
|
||||||
(scrollable as FlatList).scrollToOffset({ offset, animated: false });
|
scrollable.scrollToOffset({ offset, animated: false });
|
||||||
} else {
|
} else {
|
||||||
(scrollable as ScrollView).scrollTo({ y: offset, animated: false });
|
scrollable.scrollTo({ y: offset, animated: false });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[scrollableRef],
|
[scrollableRef],
|
||||||
|
|||||||
@@ -14,11 +14,7 @@ import {
|
|||||||
type TimelineReducerSideEffect,
|
type TimelineReducerSideEffect,
|
||||||
} from "@/timeline/session-stream-reducers";
|
} from "@/timeline/session-stream-reducers";
|
||||||
import { TIMELINE_FETCH_PAGE_SIZE } from "@/timeline/timeline-fetch-policy";
|
import { TIMELINE_FETCH_PAGE_SIZE } from "@/timeline/timeline-fetch-policy";
|
||||||
import type {
|
import type { AgentAttachment, SessionOutboundMessage } from "@server/shared/messages";
|
||||||
AgentAttachment,
|
|
||||||
AgentStreamEventPayload,
|
|
||||||
SessionOutboundMessage,
|
|
||||||
} from "@server/shared/messages";
|
|
||||||
import { parseServerInfoStatusPayload } from "@server/shared/messages";
|
import { parseServerInfoStatusPayload } from "@server/shared/messages";
|
||||||
import {
|
import {
|
||||||
buildAgentAttentionNotificationPayload,
|
buildAgentAttentionNotificationPayload,
|
||||||
@@ -1201,7 +1197,7 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
|||||||
if (message.type !== "agent_stream") return;
|
if (message.type !== "agent_stream") return;
|
||||||
const { agentId, event, timestamp, seq, epoch } = message.payload;
|
const { agentId, event, timestamp, seq, epoch } = message.payload;
|
||||||
const parsedTimestamp = new Date(timestamp);
|
const parsedTimestamp = new Date(timestamp);
|
||||||
const streamEvent = event as AgentStreamEventPayload;
|
const streamEvent = event;
|
||||||
if (
|
if (
|
||||||
event.type === "turn_started" ||
|
event.type === "turn_started" ||
|
||||||
event.type === "turn_completed" ||
|
event.type === "turn_completed" ||
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export function VoiceProvider({ children }: VoiceProviderProps) {
|
|||||||
runtimeRef.current = runtime;
|
runtimeRef.current = runtime;
|
||||||
}
|
}
|
||||||
|
|
||||||
const engine = engineRef.current!;
|
const engine = engineRef.current;
|
||||||
const runtime = runtimeRef.current!;
|
const runtime = runtimeRef.current!;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function encodeBinaryToBase64(data: Uint8Array | ArrayBuffer): string {
|
|||||||
const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
||||||
let binary = "";
|
let binary = "";
|
||||||
for (let index = 0; index < bytes.length; index += 1) {
|
for (let index = 0; index < bytes.length; index += 1) {
|
||||||
binary += String.fromCharCode(bytes[index]!);
|
binary += String.fromCharCode(bytes[index]);
|
||||||
}
|
}
|
||||||
return globalThis.btoa(binary);
|
return globalThis.btoa(binary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export class DictationStreamSender {
|
|||||||
let sent = 0;
|
let sent = 0;
|
||||||
while (this.sendSeq < this.segments.length) {
|
while (this.sendSeq < this.segments.length) {
|
||||||
const seq = this.sendSeq;
|
const seq = this.sendSeq;
|
||||||
const audio = this.segments[seq]!;
|
const audio = this.segments[seq];
|
||||||
client.sendDictationStreamChunk(dictationId, seq, audio, this.format);
|
client.sendDictationStreamChunk(dictationId, seq, audio, this.format);
|
||||||
this.sendSeq = seq + 1;
|
this.sendSeq = seq + 1;
|
||||||
sent += 1;
|
sent += 1;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const TEST_CLAUDE_DEFINITION: AgentProviderDefinition = {
|
|||||||
function makeProviderMap(
|
function makeProviderMap(
|
||||||
...definitions: AgentProviderDefinition[]
|
...definitions: AgentProviderDefinition[]
|
||||||
): Map<AgentProvider, AgentProviderDefinition> {
|
): Map<AgentProvider, AgentProviderDefinition> {
|
||||||
return new Map(definitions.map((d) => [d.id as AgentProvider, d]));
|
return new Map(definitions.map((d) => [d.id, d]));
|
||||||
}
|
}
|
||||||
|
|
||||||
const codexProviderMap = makeProviderMap(TEST_CODEX_DEFINITION);
|
const codexProviderMap = makeProviderMap(TEST_CODEX_DEFINITION);
|
||||||
|
|||||||
@@ -196,8 +196,8 @@ function resolveProvider(input: {
|
|||||||
if (initialValues?.provider && allowedProviderMap.has(initialValues.provider)) {
|
if (initialValues?.provider && allowedProviderMap.has(initialValues.provider)) {
|
||||||
return initialValues.provider;
|
return initialValues.provider;
|
||||||
}
|
}
|
||||||
if (preferences?.provider && allowedProviderMap.has(preferences.provider as AgentProvider)) {
|
if (preferences?.provider && allowedProviderMap.has(preferences.provider)) {
|
||||||
return preferences.provider as AgentProvider;
|
return preferences.provider;
|
||||||
}
|
}
|
||||||
if (currentProvider && allowedProviderMap.size > 0 && !allowedProviderMap.has(currentProvider)) {
|
if (currentProvider && allowedProviderMap.size > 0 && !allowedProviderMap.has(currentProvider)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -396,7 +396,7 @@ function buildProviderDefinitionMapForStatuses(args: {
|
|||||||
|
|
||||||
const matchingProviders = new Set(
|
const matchingProviders = new Set(
|
||||||
args.snapshotEntries
|
args.snapshotEntries
|
||||||
.filter((entry) => args.statuses.has(entry.status) && entry.enabled !== false)
|
.filter((entry) => args.statuses.has(entry.status) && entry.enabled)
|
||||||
.map((entry) => entry.provider),
|
.map((entry) => entry.provider),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ export function useAudioRecorder(config?: AudioCaptureConfig) {
|
|||||||
return () => {
|
return () => {
|
||||||
attemptGuard.cancel();
|
attemptGuard.cancel();
|
||||||
try {
|
try {
|
||||||
void mediaRecorder.current?.stop();
|
mediaRecorder.current?.stop();
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore stop during unmount.
|
// Ignore stop during unmount.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ export function useCommandCenter() {
|
|||||||
if (currentItems.length === 0) return;
|
if (currentItems.length === 0) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const index = Math.max(0, Math.min(activeIndexRef.current, currentItems.length - 1));
|
const index = Math.max(0, Math.min(activeIndexRef.current, currentItems.length - 1));
|
||||||
handleSelectItemRef.current(currentItems[index]!);
|
handleSelectItemRef.current(currentItems[index]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ const concatInt16 = (a: Int16Array, b: Int16Array): Int16Array => {
|
|||||||
const int16ToFloat32 = (input: Int16Array): Float32Array => {
|
const int16ToFloat32 = (input: Int16Array): Float32Array => {
|
||||||
const out = new Float32Array(input.length);
|
const out = new Float32Array(input.length);
|
||||||
for (let i = 0; i < input.length; i += 1) {
|
for (let i = 0; i < input.length; i += 1) {
|
||||||
out[i] = input[i]! / 32768;
|
out[i] = input[i] / 32768;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
@@ -209,8 +209,8 @@ export function useDictationAudioSource(config: DictationAudioSourceConfig): Dic
|
|||||||
const decodeAudioData = useCallback(
|
const decodeAudioData = useCallback(
|
||||||
async (context: AudioContext, buffer: ArrayBuffer): Promise<AudioBuffer> => {
|
async (context: AudioContext, buffer: ArrayBuffer): Promise<AudioBuffer> => {
|
||||||
const maybePromise = context.decodeAudioData(buffer);
|
const maybePromise = context.decodeAudioData(buffer);
|
||||||
if (maybePromise && typeof (maybePromise as Promise<AudioBuffer>).then === "function") {
|
if (maybePromise && typeof maybePromise.then === "function") {
|
||||||
return maybePromise as Promise<AudioBuffer>;
|
return maybePromise;
|
||||||
}
|
}
|
||||||
return await new Promise<AudioBuffer>((resolve, reject) => {
|
return await new Promise<AudioBuffer>((resolve, reject) => {
|
||||||
context.decodeAudioData(buffer, resolve, reject);
|
context.decodeAudioData(buffer, resolve, reject);
|
||||||
@@ -276,7 +276,7 @@ export function useDictationAudioSource(config: DictationAudioSourceConfig): Dic
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const stream = rawStream as MediaStream;
|
const stream = rawStream;
|
||||||
|
|
||||||
const context = new AudioContextCtor();
|
const context = new AudioContextCtor();
|
||||||
|
|
||||||
@@ -347,7 +347,7 @@ export function useDictationAudioSource(config: DictationAudioSourceConfig): Dic
|
|||||||
|
|
||||||
const recorder = new RecorderCtor(stream, {
|
const recorder = new RecorderCtor(stream, {
|
||||||
mimeType: "audio/webm;codecs=opus",
|
mimeType: "audio/webm;codecs=opus",
|
||||||
} as MediaRecorderOptions) as MediaRecorder;
|
} as MediaRecorderOptions);
|
||||||
|
|
||||||
const recorderRefs: RecorderRefs = {
|
const recorderRefs: RecorderRefs = {
|
||||||
recorder,
|
recorder,
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ export function useKeyboardShortcuts({
|
|||||||
const handleCommandCenterToggle = (event: KeyboardEvent): boolean => {
|
const handleCommandCenterToggle = (event: KeyboardEvent): boolean => {
|
||||||
const store = useKeyboardShortcutsStore.getState();
|
const store = useKeyboardShortcutsStore.getState();
|
||||||
if (!store.commandCenterOpen) {
|
if (!store.commandCenterOpen) {
|
||||||
const target = event.target instanceof Element ? (event.target as Element) : null;
|
const target = event.target instanceof Element ? event.target : null;
|
||||||
const targetEl =
|
const targetEl =
|
||||||
target?.closest?.("textarea, input, [contenteditable='true']") ??
|
target?.closest?.("textarea, input, [contenteditable='true']") ??
|
||||||
(target instanceof HTMLElement ? target : null);
|
(target instanceof HTMLElement ? target : null);
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export function usePrPaneData({
|
|||||||
const checkoutPrStatus = useCheckoutPrStatusQuery({ serverId, cwd, enabled });
|
const checkoutPrStatus = useCheckoutPrStatusQuery({ serverId, cwd, enabled });
|
||||||
const status = checkoutPrStatus.status;
|
const status = checkoutPrStatus.status;
|
||||||
const { prNumber, repoOwner, repoName } = extractPrRepoIdentity(status);
|
const { prNumber, repoOwner, repoName } = extractPrRepoIdentity(status);
|
||||||
const githubFeaturesEnabled = checkoutPrStatus.githubFeaturesEnabled !== false;
|
const githubFeaturesEnabled = checkoutPrStatus.githubFeaturesEnabled;
|
||||||
const unsupportedKey =
|
const unsupportedKey =
|
||||||
prNumber === null ? null : timelineUnsupportedKey({ serverId, cwd, prNumber });
|
prNumber === null ? null : timelineUnsupportedKey({ serverId, cwd, prNumber });
|
||||||
const timelineUnsupported = unsupportedKey ? unsupportedTimelineKeys.has(unsupportedKey) : false;
|
const timelineUnsupported = unsupportedKey ? unsupportedTimelineKeys.has(unsupportedKey) : false;
|
||||||
@@ -100,7 +100,7 @@ export function usePrPaneData({
|
|||||||
timelineUnsupported,
|
timelineUnsupported,
|
||||||
});
|
});
|
||||||
|
|
||||||
const timelineQuery = useQuery<PullRequestTimeline, Error>({
|
const timelineQuery = useQuery<PullRequestTimeline>({
|
||||||
queryKey: prPaneTimelineQueryKey({ serverId, cwd, prNumber }),
|
queryKey: prPaneTimelineQueryKey({ serverId, cwd, prNumber }),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!daemonClient || prNumber === null || repoOwner === null || repoName === null) {
|
if (!daemonClient || prNumber === null || repoOwner === null || repoName === null) {
|
||||||
|
|||||||
@@ -235,16 +235,16 @@ export async function loadSettingsFromStorage(): Promise<Settings> {
|
|||||||
function pickAppSettings(stored: Partial<AppSettings>): Partial<AppSettings> {
|
function pickAppSettings(stored: Partial<AppSettings>): Partial<AppSettings> {
|
||||||
const result: Partial<AppSettings> = {};
|
const result: Partial<AppSettings> = {};
|
||||||
if (typeof stored.theme === "string" && VALID_THEMES.has(stored.theme)) {
|
if (typeof stored.theme === "string" && VALID_THEMES.has(stored.theme)) {
|
||||||
result.theme = stored.theme as AppSettings["theme"];
|
result.theme = stored.theme;
|
||||||
}
|
}
|
||||||
if (stored.sendBehavior === "interrupt" || stored.sendBehavior === "queue") {
|
if (stored.sendBehavior === "interrupt" || stored.sendBehavior === "queue") {
|
||||||
result.sendBehavior = stored.sendBehavior;
|
result.sendBehavior = stored.sendBehavior;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
typeof stored.serviceUrlBehavior === "string" &&
|
typeof stored.serviceUrlBehavior === "string" &&
|
||||||
VALID_SERVICE_URL_BEHAVIORS.has(stored.serviceUrlBehavior as ServiceUrlBehavior)
|
VALID_SERVICE_URL_BEHAVIORS.has(stored.serviceUrlBehavior)
|
||||||
) {
|
) {
|
||||||
result.serviceUrlBehavior = stored.serviceUrlBehavior as ServiceUrlBehavior;
|
result.serviceUrlBehavior = stored.serviceUrlBehavior;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -750,7 +750,7 @@ function ChatAgentContent({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const isInitializing = agentId ? isInitializingFromMap !== false : false;
|
const isInitializing = agentId ? isInitializingFromMap : false;
|
||||||
const isHistorySyncing = useMemo(() => {
|
const isHistorySyncing = useMemo(() => {
|
||||||
if (!agentId || !isInitializing) {
|
if (!agentId || !isInitializing) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ describe("useInlineReviewController", () => {
|
|||||||
body: "first comment",
|
body: "first comment",
|
||||||
});
|
});
|
||||||
|
|
||||||
act(() => result.current.onEditComment(reviewTarget, savedComment!));
|
act(() => result.current.onEditComment(reviewTarget, savedComment));
|
||||||
expect(result.current.editor).toEqual({
|
expect(result.current.editor).toEqual({
|
||||||
target: reviewTarget,
|
target: reviewTarget,
|
||||||
commentId: savedComment?.id,
|
commentId: savedComment?.id,
|
||||||
@@ -208,8 +208,8 @@ describe("useInlineReviewController", () => {
|
|||||||
const updatedComment = useReviewDraftStore.getState().drafts[firstKey]?.[0];
|
const updatedComment = useReviewDraftStore.getState().drafts[firstKey]?.[0];
|
||||||
expect(updatedComment).toMatchObject({ id: savedComment?.id, body: "updated comment" });
|
expect(updatedComment).toMatchObject({ id: savedComment?.id, body: "updated comment" });
|
||||||
|
|
||||||
act(() => result.current.onEditComment(reviewTarget, updatedComment!));
|
act(() => result.current.onEditComment(reviewTarget, updatedComment));
|
||||||
act(() => result.current.onDeleteComment(updatedComment!.id));
|
act(() => result.current.onDeleteComment(updatedComment.id));
|
||||||
expect(useReviewDraftStore.getState().drafts[firstKey]).toEqual([]);
|
expect(useReviewDraftStore.getState().drafts[firstKey]).toEqual([]);
|
||||||
expect(result.current.editor).toBeNull();
|
expect(result.current.editor).toBeNull();
|
||||||
|
|
||||||
|
|||||||
@@ -752,7 +752,7 @@ describe("ProjectSettingsScreen — save flow", () => {
|
|||||||
await flush();
|
await flush();
|
||||||
|
|
||||||
expect(client.writeProjectConfig).toHaveBeenCalledTimes(1);
|
expect(client.writeProjectConfig).toHaveBeenCalledTimes(1);
|
||||||
const callArg = client.writeProjectConfig.mock.calls[0]![0] as {
|
const callArg = client.writeProjectConfig.mock.calls[0][0] as {
|
||||||
repoRoot: string;
|
repoRoot: string;
|
||||||
config: PaseoConfigRaw;
|
config: PaseoConfigRaw;
|
||||||
expectedRevision: PaseoConfigRevision | null;
|
expectedRevision: PaseoConfigRevision | null;
|
||||||
@@ -767,7 +767,7 @@ describe("ProjectSettingsScreen — save flow", () => {
|
|||||||
// Subsequent saves use the freshly-returned revision.
|
// Subsequent saves use the freshly-returned revision.
|
||||||
click(requireById("save-button"));
|
click(requireById("save-button"));
|
||||||
await flush();
|
await flush();
|
||||||
const secondArg = client.writeProjectConfig.mock.calls[1]![0] as {
|
const secondArg = client.writeProjectConfig.mock.calls[1][0] as {
|
||||||
expectedRevision: PaseoConfigRevision;
|
expectedRevision: PaseoConfigRevision;
|
||||||
};
|
};
|
||||||
expect(secondArg.expectedRevision).toEqual(newRevision);
|
expect(secondArg.expectedRevision).toEqual(newRevision);
|
||||||
@@ -869,7 +869,7 @@ describe("ProjectSettingsScreen — round-trip semantics", () => {
|
|||||||
click(requireById("save-button"));
|
click(requireById("save-button"));
|
||||||
await flush();
|
await flush();
|
||||||
|
|
||||||
const savedConfig = client.writeProjectConfig.mock.calls[0]![0]!.config as PaseoConfigRaw;
|
const savedConfig = client.writeProjectConfig.mock.calls[0][0]!.config as PaseoConfigRaw;
|
||||||
// string stays string when edited in place
|
// string stays string when edited in place
|
||||||
expect(savedConfig.worktree?.setup).toBe("npm install\nnpm run prepare");
|
expect(savedConfig.worktree?.setup).toBe("npm install\nnpm run prepare");
|
||||||
});
|
});
|
||||||
@@ -897,7 +897,7 @@ describe("ProjectSettingsScreen — round-trip semantics", () => {
|
|||||||
click(requireById("save-button"));
|
click(requireById("save-button"));
|
||||||
await flush();
|
await flush();
|
||||||
|
|
||||||
const savedConfig = client.writeProjectConfig.mock.calls[0]![0]!.config as PaseoConfigRaw;
|
const savedConfig = client.writeProjectConfig.mock.calls[0][0]!.config as PaseoConfigRaw;
|
||||||
expect(savedConfig.worktree?.teardown).toEqual(["docker compose down", "rm -rf .cache"]);
|
expect(savedConfig.worktree?.teardown).toEqual(["docker compose down", "rm -rf .cache"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -920,7 +920,7 @@ describe("ProjectSettingsScreen — round-trip semantics", () => {
|
|||||||
click(requireById("save-button"));
|
click(requireById("save-button"));
|
||||||
await flush();
|
await flush();
|
||||||
|
|
||||||
const savedConfig = client.writeProjectConfig.mock.calls[0]![0]!.config as PaseoConfigRaw;
|
const savedConfig = client.writeProjectConfig.mock.calls[0][0]!.config as PaseoConfigRaw;
|
||||||
expect(savedConfig.worktree?.setup).toBe("npm install");
|
expect(savedConfig.worktree?.setup).toBe("npm install");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -943,7 +943,7 @@ describe("ProjectSettingsScreen — round-trip semantics", () => {
|
|||||||
click(requireById("save-button"));
|
click(requireById("save-button"));
|
||||||
await flush();
|
await flush();
|
||||||
|
|
||||||
const savedConfig = client.writeProjectConfig.mock.calls[0]![0]!.config as PaseoConfigRaw;
|
const savedConfig = client.writeProjectConfig.mock.calls[0][0]!.config as PaseoConfigRaw;
|
||||||
expect(savedConfig.worktree?.setup).toEqual(["npm install", "npm run prepare"]);
|
expect(savedConfig.worktree?.setup).toEqual(["npm install", "npm run prepare"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -996,7 +996,7 @@ describe("ProjectSettingsScreen — scripts editor", () => {
|
|||||||
click(requireById("save-button"));
|
click(requireById("save-button"));
|
||||||
await flush();
|
await flush();
|
||||||
|
|
||||||
const savedConfig = client.writeProjectConfig.mock.calls[0]![0]!.config as PaseoConfigRaw;
|
const savedConfig = client.writeProjectConfig.mock.calls[0][0]!.config as PaseoConfigRaw;
|
||||||
const scriptKeys = Object.keys((savedConfig as Record<string, unknown>).scripts ?? {});
|
const scriptKeys = Object.keys((savedConfig as Record<string, unknown>).scripts ?? {});
|
||||||
// One of the two scripts was removed.
|
// One of the two scripts was removed.
|
||||||
expect(scriptKeys.length).toBe(1);
|
expect(scriptKeys.length).toBe(1);
|
||||||
@@ -1031,7 +1031,7 @@ describe("ProjectSettingsScreen — scripts editor", () => {
|
|||||||
click(requireById("save-button"));
|
click(requireById("save-button"));
|
||||||
await flush();
|
await flush();
|
||||||
|
|
||||||
const savedConfig = client.writeProjectConfig.mock.calls[0]![0]!.config as PaseoConfigRaw;
|
const savedConfig = client.writeProjectConfig.mock.calls[0][0]!.config as PaseoConfigRaw;
|
||||||
expect((savedConfig as Record<string, unknown>).customTopLevel).toBe("preserved");
|
expect((savedConfig as Record<string, unknown>).customTopLevel).toBe("preserved");
|
||||||
const worktreeRecord = (savedConfig.worktree ?? {}) as Record<string, unknown>;
|
const worktreeRecord = (savedConfig.worktree ?? {}) as Record<string, unknown>;
|
||||||
expect(worktreeRecord.customWorktreeField).toBe("keep");
|
expect(worktreeRecord.customWorktreeField).toBe("keep");
|
||||||
|
|||||||
@@ -1541,7 +1541,7 @@ function WorkspaceScreenContent({
|
|||||||
if (!client || !workspaceDirectory) {
|
if (!client || !workspaceDirectory) {
|
||||||
throw new Error("Host is not connected");
|
throw new Error("Host is not connected");
|
||||||
}
|
}
|
||||||
return (await client.getCheckoutStatus(workspaceDirectory)) as CheckoutStatusPayload;
|
return await client.getCheckoutStatus(workspaceDirectory);
|
||||||
},
|
},
|
||||||
staleTime: Infinity,
|
staleTime: Infinity,
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ describe("computeWorkspaceTabLayout", () => {
|
|||||||
expect(result.closeButtonPolicy).toBe("all");
|
expect(result.closeButtonPolicy).toBe("all");
|
||||||
expect(result.requiresHorizontalScrollFallback).toBe(false);
|
expect(result.requiresHorizontalScrollFallback).toBe(false);
|
||||||
expect(result.items.map((item) => item.width)).toEqual([60, 60, 60, 60]);
|
expect(result.items.map((item) => item.width)).toEqual([60, 60, 60, 60]);
|
||||||
expect(result.items.every((item) => item.showLabel === false)).toBe(true);
|
expect(result.items.every((item) => !item.showLabel)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows horizontal scroll only when icon-only tabs still cannot fit", () => {
|
it("allows horizontal scroll only when icon-only tabs still cannot fit", () => {
|
||||||
@@ -81,7 +81,7 @@ describe("computeWorkspaceTabLayout", () => {
|
|||||||
expect(result.closeButtonPolicy).toBe("all");
|
expect(result.closeButtonPolicy).toBe("all");
|
||||||
expect(result.requiresHorizontalScrollFallback).toBe(true);
|
expect(result.requiresHorizontalScrollFallback).toBe(true);
|
||||||
expect(result.items.map((item) => item.width)).toEqual([60, 60, 60, 60]);
|
expect(result.items.map((item) => item.width)).toEqual([60, 60, 60, 60]);
|
||||||
expect(result.items.every((item) => item.showLabel === false)).toBe(true);
|
expect(result.items.every((item) => !item.showLabel)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns empty layout details when there are no tabs", () => {
|
it("returns empty layout details when there are no tabs", () => {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ describe("deriveWorkspaceTabModel", () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const model = deriveWorkspaceTabModel({
|
const model = deriveWorkspaceTabModel({
|
||||||
tabs: [uiTabs[0]!, uiTabs[2]!, uiTabs[1]!],
|
tabs: [uiTabs[0], uiTabs[2], uiTabs[1]],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(model.tabs.map((tab) => tab.descriptor.tabId)).toEqual([
|
expect(model.tabs.map((tab) => tab.descriptor.tabId)).toEqual([
|
||||||
|
|||||||
@@ -526,7 +526,7 @@ function findNearestSiblingPaneId(root: SplitNodeInternal, paneId: string): stri
|
|||||||
|
|
||||||
for (let depth = path.length - 1; depth >= 0; depth -= 1) {
|
for (let depth = path.length - 1; depth >= 0; depth -= 1) {
|
||||||
const parentPath = path.slice(0, depth);
|
const parentPath = path.slice(0, depth);
|
||||||
const childIndex = path[depth]!;
|
const childIndex = path[depth];
|
||||||
const parentNode = getNodeAtPath(root, parentPath);
|
const parentNode = getNodeAtPath(root, parentPath);
|
||||||
invariant(parentNode.kind === "group", "Expected parent group for pane lookup");
|
invariant(parentNode.kind === "group", "Expected parent group for pane lookup");
|
||||||
|
|
||||||
@@ -667,7 +667,7 @@ function removePaneByPath(root: SplitNodeInternal, path: number[]): SplitNodeInt
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parentPath = path.slice(0, -1);
|
const parentPath = path.slice(0, -1);
|
||||||
const removeIndex = path[path.length - 1]!;
|
const removeIndex = path[path.length - 1];
|
||||||
const parentNode = getNodeAtPath(root, parentPath);
|
const parentNode = getNodeAtPath(root, parentPath);
|
||||||
invariant(parentNode.kind === "group", "Expected parent group while removing pane");
|
invariant(parentNode.kind === "group", "Expected parent group while removing pane");
|
||||||
|
|
||||||
@@ -676,7 +676,7 @@ function removePaneByPath(root: SplitNodeInternal, path: number[]): SplitNodeInt
|
|||||||
|
|
||||||
const nextParentNode =
|
const nextParentNode =
|
||||||
nextParentChildren.length === 1
|
nextParentChildren.length === 1
|
||||||
? nextParentChildren[0]!
|
? nextParentChildren[0]
|
||||||
: createGroupNode({
|
: createGroupNode({
|
||||||
id: parentNode.group.id,
|
id: parentNode.group.id,
|
||||||
direction: parentNode.group.direction,
|
direction: parentNode.group.direction,
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ describe("workspace-layout-store tree transforms", () => {
|
|||||||
|
|
||||||
const nextRoot = insertSplit(root, "right", "tab-c", "right");
|
const nextRoot = insertSplit(root, "right", "tab-c", "right");
|
||||||
const nextGroup = expectGroup(nextRoot);
|
const nextGroup = expectGroup(nextRoot);
|
||||||
const nestedGroup = expectGroup(nextGroup.group.children[1]!);
|
const nestedGroup = expectGroup(nextGroup.group.children[1]);
|
||||||
|
|
||||||
expect(nextGroup.group.direction).toBe("horizontal");
|
expect(nextGroup.group.direction).toBe("horizontal");
|
||||||
expect(nextGroup.group.children).toHaveLength(2);
|
expect(nextGroup.group.children).toHaveLength(2);
|
||||||
@@ -259,7 +259,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
kind: "file",
|
kind: "file",
|
||||||
path: "/repo/worktree/b.ts",
|
path: "/repo/worktree/b.ts",
|
||||||
});
|
});
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
|
|
||||||
expect(firstTabId).toBe("file_/repo/worktree/a.ts");
|
expect(firstTabId).toBe("file_/repo/worktree/a.ts");
|
||||||
expect(secondTabId).toBe("file_/repo/worktree/b.ts");
|
expect(secondTabId).toBe("file_/repo/worktree/b.ts");
|
||||||
@@ -280,7 +280,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
kind: "setup",
|
kind: "setup",
|
||||||
workspaceId: "ws-main",
|
workspaceId: "ws-main",
|
||||||
});
|
});
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
const pane = findPaneById(layout.root, "main")!;
|
const pane = findPaneById(layout.root, "main")!;
|
||||||
|
|
||||||
expect(agentTabId).toBe("agent_agent-1");
|
expect(agentTabId).toBe("agent_agent-1");
|
||||||
@@ -306,7 +306,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
kind: "file",
|
kind: "file",
|
||||||
path: "/repo/worktree/a.ts",
|
path: "/repo/worktree/a.ts",
|
||||||
});
|
});
|
||||||
const layoutAfter = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layoutAfter = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
const pane = findPaneById(layoutAfter.root, "main")!;
|
const pane = findPaneById(layoutAfter.root, "main")!;
|
||||||
|
|
||||||
expect(duplicateTabId).toBe(firstTabId);
|
expect(duplicateTabId).toBe(firstTabId);
|
||||||
@@ -380,7 +380,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
|
|
||||||
const firstTabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-1" });
|
const firstTabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-1" });
|
||||||
const secondTabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-2" });
|
const secondTabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-2" });
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
|
|
||||||
expect(firstTabId).toBe("draft-1");
|
expect(firstTabId).toBe("draft-1");
|
||||||
expect(secondTabId).toBe("draft-2");
|
expect(secondTabId).toBe("draft-2");
|
||||||
@@ -416,14 +416,14 @@ describe("workspace-layout-store actions", () => {
|
|||||||
kind: "draft",
|
kind: "draft",
|
||||||
draftId: "draft-split",
|
draftId: "draft-split",
|
||||||
});
|
});
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
|
|
||||||
expect(newPaneId).toBe("pane_77777777-7777-7777-7777-777777777777");
|
expect(newPaneId).toBe("pane_77777777-7777-7777-7777-777777777777");
|
||||||
expect(draftTabId).toBe("draft-split");
|
expect(draftTabId).toBe("draft-split");
|
||||||
expect(layout.focusedPaneId).toBe(newPaneId);
|
expect(layout.focusedPaneId).toBe(newPaneId);
|
||||||
expect(findPaneById(layout.root, "main")?.tabIds).toEqual(["file_/repo/worktree/a.ts"]);
|
expect(findPaneById(layout.root, "main")?.tabIds).toEqual(["file_/repo/worktree/a.ts"]);
|
||||||
expect(findPaneById(layout.root, newPaneId!)?.tabIds).toEqual([draftTabId!]);
|
expect(findPaneById(layout.root, newPaneId)?.tabIds).toEqual([draftTabId!]);
|
||||||
expect(findPaneById(layout.root, newPaneId!)?.focusedTabId).toBe(draftTabId);
|
expect(findPaneById(layout.root, newPaneId)?.focusedTabId).toBe(draftTabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("focusTab moves workspace focus to the pane containing the tab", () => {
|
it("focusTab moves workspace focus to the pane containing the tab", () => {
|
||||||
@@ -448,14 +448,14 @@ describe("workspace-layout-store actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
store.focusTab(workspaceKey, fileTabId!);
|
store.focusTab(workspaceKey, fileTabId!);
|
||||||
let layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
let layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
expect(layout.focusedPaneId).toBe("main");
|
expect(layout.focusedPaneId).toBe("main");
|
||||||
|
|
||||||
store.focusTab(workspaceKey, terminalTabId!);
|
store.focusTab(workspaceKey, terminalTabId!);
|
||||||
layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
||||||
expect(splitPaneId).toBe("pane_bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb");
|
expect(splitPaneId).toBe("pane_bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb");
|
||||||
expect(layout.focusedPaneId).toBe(splitPaneId);
|
expect(layout.focusedPaneId).toBe(splitPaneId);
|
||||||
expect(findPaneById(layout.root, splitPaneId!)?.focusedTabId).toBe(terminalTabId);
|
expect(findPaneById(layout.root, splitPaneId)?.focusedTabId).toBe(terminalTabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("convertDraftToAgent replaces the draft tab with a canonical agent tab in the same pane", () => {
|
it("convertDraftToAgent replaces the draft tab with a canonical agent tab in the same pane", () => {
|
||||||
@@ -474,8 +474,8 @@ describe("workspace-layout-store actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const nextTabId = store.convertDraftToAgent(workspaceKey, secondTabId!, "agent-1");
|
const nextTabId = store.convertDraftToAgent(workspaceKey, secondTabId!, "agent-1");
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
const splitPane = findPaneById(layout.root, splitPaneId!);
|
const splitPane = findPaneById(layout.root, splitPaneId);
|
||||||
const convertedTab = collectAllTabs(layout.root).find((tab) => tab.tabId === nextTabId);
|
const convertedTab = collectAllTabs(layout.root).find((tab) => tab.tabId === nextTabId);
|
||||||
|
|
||||||
expect(splitPaneId).toBe("pane_12121212-1212-1212-1212-121212121212");
|
expect(splitPaneId).toBe("pane_12121212-1212-1212-1212-121212121212");
|
||||||
@@ -501,7 +501,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
kind: "file",
|
kind: "file",
|
||||||
path: "/repo/worktree/retargeted.ts",
|
path: "/repo/worktree/retargeted.ts",
|
||||||
});
|
});
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
|
|
||||||
expect(draftTabId).toBe("draft-retarget");
|
expect(draftTabId).toBe("draft-retarget");
|
||||||
expect(nextTabId).toBe(draftTabId);
|
expect(nextTabId).toBe(draftTabId);
|
||||||
@@ -541,7 +541,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
kind: "file",
|
kind: "file",
|
||||||
path: "/repo/worktree/existing.ts",
|
path: "/repo/worktree/existing.ts",
|
||||||
});
|
});
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
|
|
||||||
expect(existingFileTabId).toBe("file_/repo/worktree/existing.ts");
|
expect(existingFileTabId).toBe("file_/repo/worktree/existing.ts");
|
||||||
expect(draftTabId).toBe("draft-dup");
|
expect(draftTabId).toBe("draft-dup");
|
||||||
@@ -576,7 +576,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
kind: "agent",
|
kind: "agent",
|
||||||
agentId: "agent-1",
|
agentId: "agent-1",
|
||||||
});
|
});
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
|
|
||||||
expect(firstAgentTabId).toBe(firstDraftTabId);
|
expect(firstAgentTabId).toBe(firstDraftTabId);
|
||||||
expect(nextTabId).toBe(firstDraftTabId);
|
expect(nextTabId).toBe(firstDraftTabId);
|
||||||
@@ -608,7 +608,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
store.reorderTabs(workspaceKey, [thirdTabId!, firstTabId!]);
|
store.reorderTabs(workspaceKey, [thirdTabId!, firstTabId!]);
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
|
|
||||||
expect(findPaneById(layout.root, "main")).toEqual({
|
expect(findPaneById(layout.root, "main")).toEqual({
|
||||||
id: "main",
|
id: "main",
|
||||||
@@ -663,11 +663,11 @@ describe("workspace-layout-store actions", () => {
|
|||||||
store.moveTabToPane(workspaceKey, fourthTabId!, splitPaneId!);
|
store.moveTabToPane(workspaceKey, fourthTabId!, splitPaneId!);
|
||||||
store.focusPane(workspaceKey, "main");
|
store.focusPane(workspaceKey, "main");
|
||||||
store.reorderTabsInPane(workspaceKey, splitPaneId!, [fourthTabId!, thirdTabId!]);
|
store.reorderTabsInPane(workspaceKey, splitPaneId!, [fourthTabId!, thirdTabId!]);
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
|
|
||||||
expect(splitPaneId).toBe("pane_34343434-3434-3434-3434-343434343434");
|
expect(splitPaneId).toBe("pane_34343434-3434-3434-3434-343434343434");
|
||||||
expect(layout.focusedPaneId).toBe("main");
|
expect(layout.focusedPaneId).toBe("main");
|
||||||
expect(findPaneById(layout.root, splitPaneId!)).toEqual({
|
expect(findPaneById(layout.root, splitPaneId)).toEqual({
|
||||||
id: splitPaneId,
|
id: splitPaneId,
|
||||||
tabIds: [fourthTabId!, thirdTabId!],
|
tabIds: [fourthTabId!, thirdTabId!],
|
||||||
focusedTabId: fourthTabId,
|
focusedTabId: fourthTabId,
|
||||||
@@ -705,7 +705,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
store.focusPane(workspaceKey, "main");
|
store.focusPane(workspaceKey, "main");
|
||||||
let layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
let layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
expect(layout.focusedPaneId).toBe("main");
|
expect(layout.focusedPaneId).toBe("main");
|
||||||
|
|
||||||
store.focusPane(workspaceKey, splitPaneId!);
|
store.focusPane(workspaceKey, splitPaneId!);
|
||||||
@@ -734,7 +734,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
store.closeTab(workspaceKey, secondTabId!);
|
store.closeTab(workspaceKey, secondTabId!);
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
|
|
||||||
expect(splitPaneId).toBe("pane_cccccccc-cccc-cccc-cccc-cccccccccccc");
|
expect(splitPaneId).toBe("pane_cccccccc-cccc-cccc-cccc-cccccccccccc");
|
||||||
expect(layout.focusedPaneId).toBe("main");
|
expect(layout.focusedPaneId).toBe("main");
|
||||||
@@ -782,7 +782,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
position: "bottom",
|
position: "bottom",
|
||||||
});
|
});
|
||||||
|
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
expect(pane1).toBe("pane_11111111-1111-1111-1111-111111111111");
|
expect(pane1).toBe("pane_11111111-1111-1111-1111-111111111111");
|
||||||
expect(pane2).toBe("pane_33333333-3333-3333-3333-333333333333");
|
expect(pane2).toBe("pane_33333333-3333-3333-3333-333333333333");
|
||||||
expect(pane3).toBe("pane_55555555-5555-5555-5555-555555555555");
|
expect(pane3).toBe("pane_55555555-5555-5555-5555-555555555555");
|
||||||
@@ -812,11 +812,11 @@ describe("workspace-layout-store actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
store.moveTabToPane(workspaceKey, leftTabId!, splitPaneId!);
|
store.moveTabToPane(workspaceKey, leftTabId!, splitPaneId!);
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
|
|
||||||
expect(layout.focusedPaneId).toBe(splitPaneId);
|
expect(layout.focusedPaneId).toBe(splitPaneId);
|
||||||
expect(collectAllPanes(layout.root).map((pane) => pane.id)).toEqual([splitPaneId!]);
|
expect(collectAllPanes(layout.root).map((pane) => pane.id)).toEqual([splitPaneId!]);
|
||||||
expect(findPaneById(layout.root, splitPaneId!)?.tabIds).toEqual([
|
expect(findPaneById(layout.root, splitPaneId)?.tabIds).toEqual([
|
||||||
"file_/repo/worktree/b.ts",
|
"file_/repo/worktree/b.ts",
|
||||||
"file_/repo/worktree/a.ts",
|
"file_/repo/worktree/a.ts",
|
||||||
]);
|
]);
|
||||||
@@ -852,7 +852,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
store.closeTab(workspaceKey, secondTabId!);
|
store.closeTab(workspaceKey, secondTabId!);
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
const rootGroup = expectGroup(layout.root);
|
const rootGroup = expectGroup(layout.root);
|
||||||
|
|
||||||
expect(paneBId).toBe("pane_78787878-7878-7878-7878-787878787878");
|
expect(paneBId).toBe("pane_78787878-7878-7878-7878-787878787878");
|
||||||
@@ -910,7 +910,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
kind: "file",
|
kind: "file",
|
||||||
path: "/repo/worktree/b.ts",
|
path: "/repo/worktree/b.ts",
|
||||||
});
|
});
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
|
|
||||||
expect(splitPaneId).toBe("pane_abababab-abab-abab-abab-abababababab");
|
expect(splitPaneId).toBe("pane_abababab-abab-abab-abab-abababababab");
|
||||||
expect(duplicateTabId).toBe(secondTabId);
|
expect(duplicateTabId).toBe(secondTabId);
|
||||||
@@ -946,14 +946,14 @@ describe("workspace-layout-store actions", () => {
|
|||||||
position: "right",
|
position: "right",
|
||||||
});
|
});
|
||||||
|
|
||||||
const splitRoot = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!.root;
|
const splitRoot = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey].root;
|
||||||
const splitGroup = expectGroup(splitRoot);
|
const splitGroup = expectGroup(splitRoot);
|
||||||
const nestedGroup = expectGroup(splitGroup.group.children[1]!);
|
const nestedGroup = expectGroup(splitGroup.group.children[1]);
|
||||||
store.resizeSplit(workspaceKey, nestedGroup.group.id, [0.01, 0.99]);
|
store.resizeSplit(workspaceKey, nestedGroup.group.id, [0.01, 0.99]);
|
||||||
|
|
||||||
const resizedRoot = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!.root;
|
const resizedRoot = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey].root;
|
||||||
const resizedGroup = expectGroup(resizedRoot);
|
const resizedGroup = expectGroup(resizedRoot);
|
||||||
const resizedNestedGroup = expectGroup(resizedGroup.group.children[1]!);
|
const resizedNestedGroup = expectGroup(resizedGroup.group.children[1]);
|
||||||
const total = resizedNestedGroup.group.sizes.reduce((sum, size) => sum + size, 0);
|
const total = resizedNestedGroup.group.sizes.reduce((sum, size) => sum + size, 0);
|
||||||
|
|
||||||
expect(rightPaneId).toBe("pane_eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee");
|
expect(rightPaneId).toBe("pane_eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee");
|
||||||
@@ -969,7 +969,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
|
|
||||||
const tabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-1" });
|
const tabId = store.openTabFocused(workspaceKey, { kind: "draft", draftId: "draft-1" });
|
||||||
store.closeTab(workspaceKey, tabId!);
|
store.closeTab(workspaceKey, tabId!);
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
|
|
||||||
expect(layout).toEqual(createDefaultLayout());
|
expect(layout).toEqual(createDefaultLayout());
|
||||||
});
|
});
|
||||||
@@ -1065,7 +1065,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const nextTabId = store.convertDraftToAgent(workspaceKey, draftTabId!, "agent-1");
|
const nextTabId = store.convertDraftToAgent(workspaceKey, draftTabId!, "agent-1");
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
|
|
||||||
expect(splitPaneId).toBe("pane_67676767-6767-6767-6767-676767676767");
|
expect(splitPaneId).toBe("pane_67676767-6767-6767-6767-676767676767");
|
||||||
expect(nextTabId).toBe("agent_agent-1");
|
expect(nextTabId).toBe("agent_agent-1");
|
||||||
@@ -1129,7 +1129,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
hasActivePendingDraftCreate: false,
|
hasActivePendingDraftCreate: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
const tabs = collectAllTabs(layout.root);
|
const tabs = collectAllTabs(layout.root);
|
||||||
|
|
||||||
expect(tabs.map((tab) => tab.tabId)).toEqual([
|
expect(tabs.map((tab) => tab.tabId)).toEqual([
|
||||||
@@ -1189,7 +1189,7 @@ describe("workspace-layout-store actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const tabs = useWorkspaceLayoutStore.getState().getWorkspaceTabs(workspaceKey);
|
const tabs = useWorkspaceLayoutStore.getState().getWorkspaceTabs(workspaceKey);
|
||||||
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
|
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
|
||||||
expect(tabs.map((tab) => tab.tabId)).toEqual(["terminal_term-script", "terminal_term-manual"]);
|
expect(tabs.map((tab) => tab.tabId)).toEqual(["terminal_term-script", "terminal_term-manual"]);
|
||||||
expect(findPaneById(layout.root, layout.focusedPaneId)?.focusedTabId).toBe(scriptTabId);
|
expect(findPaneById(layout.root, layout.focusedPaneId)?.focusedTabId).toBe(scriptTabId);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -134,9 +134,8 @@ export function upsertHostConnectionInProfiles(input: {
|
|||||||
return [...existing, profile];
|
return [...existing, profile];
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchedProfiles = matchingIndexes.map((index) => existing[index]!);
|
const matchedProfiles = matchingIndexes.map((index) => existing[index]);
|
||||||
const prev =
|
const prev = matchedProfiles.find((daemon) => daemon.serverId === serverId) ?? matchedProfiles[0];
|
||||||
matchedProfiles.find((daemon) => daemon.serverId === serverId) ?? matchedProfiles[0]!;
|
|
||||||
const nextConnections = dedupeHostConnections([
|
const nextConnections = dedupeHostConnections([
|
||||||
...matchedProfiles.flatMap((daemon) => daemon.connections),
|
...matchedProfiles.flatMap((daemon) => daemon.connections),
|
||||||
input.connection,
|
input.connection,
|
||||||
@@ -180,7 +179,7 @@ export function upsertHostConnectionInProfiles(input: {
|
|||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
};
|
};
|
||||||
|
|
||||||
const firstIndex = matchingIndexes[0]!;
|
const firstIndex = matchingIndexes[0];
|
||||||
const matchingIndexSet = new Set(matchingIndexes);
|
const matchingIndexSet = new Set(matchingIndexes);
|
||||||
const next = existing.filter((_daemon, index) => !matchingIndexSet.has(index));
|
const next = existing.filter((_daemon, index) => !matchingIndexSet.has(index));
|
||||||
next.splice(firstIndex, 0, nextProfile);
|
next.splice(firstIndex, 0, nextProfile);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { FetchAgentHistoryEntry, FetchAgentsEntry } from "@server/client/daemon-client";
|
import type { FetchAgentsEntry } from "@server/client/daemon-client";
|
||||||
import { type Agent, useSessionStore } from "@/stores/session-store";
|
import { type Agent, useSessionStore } from "@/stores/session-store";
|
||||||
import { derivePendingPermissionKey, normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
import { derivePendingPermissionKey, normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
||||||
import { resolveProjectPlacement } from "@/utils/project-placement";
|
import { resolveProjectPlacement } from "@/utils/project-placement";
|
||||||
|
|
||||||
type AgentDirectoryFetchEntry = FetchAgentsEntry | FetchAgentHistoryEntry;
|
type AgentDirectoryFetchEntry = FetchAgentsEntry;
|
||||||
|
|
||||||
interface PendingPermissionEntry {
|
interface PendingPermissionEntry {
|
||||||
key: string;
|
key: string;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { AgentLifecycleStatus } from "@server/shared/agent-lifecycle";
|
|
||||||
import type { AgentSnapshotPayload } from "@server/shared/messages";
|
import type { AgentSnapshotPayload } from "@server/shared/messages";
|
||||||
import type { AgentPermissionRequest } from "@server/server/agent/agent-sdk-types";
|
import type { AgentPermissionRequest } from "@server/server/agent/agent-sdk-types";
|
||||||
|
|
||||||
@@ -31,7 +30,7 @@ export function normalizeAgentSnapshot(snapshot: AgentSnapshotPayload, serverId:
|
|||||||
serverId,
|
serverId,
|
||||||
id: snapshot.id,
|
id: snapshot.id,
|
||||||
provider: snapshot.provider,
|
provider: snapshot.provider,
|
||||||
status: snapshot.status as AgentLifecycleStatus,
|
status: snapshot.status,
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
lastUserMessageAt,
|
lastUserMessageAt,
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ describe("sendOsNotification", () => {
|
|||||||
clicked.clickListeners[0]?.({} as Event);
|
clicked.clickListeners[0]?.({} as Event);
|
||||||
|
|
||||||
expect(dispatchEvent).toHaveBeenCalledTimes(1);
|
expect(dispatchEvent).toHaveBeenCalledTimes(1);
|
||||||
const event = dispatchEvent.mock.calls[0]?.[0] as unknown as {
|
const event = dispatchEvent.mock.calls[0]?.[0] as {
|
||||||
type?: string;
|
type?: string;
|
||||||
detail?: { data?: Record<string, unknown> };
|
detail?: { data?: Record<string, unknown> };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ function dispatchWebNotificationClick(detail: WebNotificationClickDetail): boole
|
|||||||
cancelable: true,
|
cancelable: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return dispatch(event) === false;
|
return !dispatch(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fallbackNavigateToNotificationTarget(data: Record<string, unknown> | undefined): void {
|
function fallbackNavigateToNotificationTarget(data: Record<string, unknown> | undefined): void {
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ function resamplePcm16(pcm: Uint8Array, fromRate: number, toRate: number): Uint8
|
|||||||
if (i + 1 >= pcm.length) {
|
if (i + 1 >= pcm.length) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const lo = pcm[i]!;
|
const lo = pcm[i];
|
||||||
const hi = pcm[i + 1]!;
|
const hi = pcm[i + 1];
|
||||||
let value = (hi << 8) | lo;
|
let value = (hi << 8) | lo;
|
||||||
if (value & 0x8000) {
|
if (value & 0x8000) {
|
||||||
value = value - 0x10000;
|
value = value - 0x10000;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function resampleToPcm16(input: Float32Array, inputRate: number, outputRate: num
|
|||||||
const i0 = Math.floor(sourceIndex);
|
const i0 = Math.floor(sourceIndex);
|
||||||
const i1 = Math.min(input.length - 1, i0 + 1);
|
const i1 = Math.min(input.length - 1, i0 + 1);
|
||||||
const frac = sourceIndex - i0;
|
const frac = sourceIndex - i0;
|
||||||
const sample = input[i0]! * (1 - frac) + input[i1]! * frac;
|
const sample = input[i0] * (1 - frac) + input[i1] * frac;
|
||||||
output[i] = floatToInt16(sample);
|
output[i] = floatToInt16(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,8 +64,8 @@ function pcm16LeToAudioBuffer(
|
|||||||
const audioBuffer = context.createBuffer(1, sampleCount, sampleRate);
|
const audioBuffer = context.createBuffer(1, sampleCount, sampleRate);
|
||||||
const channel = audioBuffer.getChannelData(0);
|
const channel = audioBuffer.getChannelData(0);
|
||||||
for (let i = 0; i < sampleCount; i += 1) {
|
for (let i = 0; i < sampleCount; i += 1) {
|
||||||
const lo = bytes[i * 2]!;
|
const lo = bytes[i * 2];
|
||||||
const hi = bytes[i * 2 + 1]!;
|
const hi = bytes[i * 2 + 1];
|
||||||
let value = (hi << 8) | lo;
|
let value = (hi << 8) | lo;
|
||||||
if (value & 0x8000) {
|
if (value & 0x8000) {
|
||||||
value -= 0x10000;
|
value -= 0x10000;
|
||||||
@@ -77,8 +77,8 @@ function pcm16LeToAudioBuffer(
|
|||||||
|
|
||||||
async function decodeAudioData(context: AudioContext, buffer: ArrayBuffer): Promise<AudioBuffer> {
|
async function decodeAudioData(context: AudioContext, buffer: ArrayBuffer): Promise<AudioBuffer> {
|
||||||
const maybePromise = context.decodeAudioData(buffer.slice(0));
|
const maybePromise = context.decodeAudioData(buffer.slice(0));
|
||||||
if (maybePromise && typeof (maybePromise as Promise<AudioBuffer>).then === "function") {
|
if (maybePromise && typeof maybePromise.then === "function") {
|
||||||
return maybePromise as Promise<AudioBuffer>;
|
return maybePromise;
|
||||||
}
|
}
|
||||||
return await new Promise<AudioBuffer>((resolve, reject) => {
|
return await new Promise<AudioBuffer>((resolve, reject) => {
|
||||||
context.decodeAudioData(buffer.slice(0), resolve, reject);
|
context.decodeAudioData(buffer.slice(0), resolve, reject);
|
||||||
@@ -323,7 +323,7 @@ export function createAudioEngine(
|
|||||||
const input = event.inputBuffer.getChannelData(0);
|
const input = event.inputBuffer.getChannelData(0);
|
||||||
let sumSquares = 0;
|
let sumSquares = 0;
|
||||||
for (let i = 0; i < input.length; i += 1) {
|
for (let i = 0; i < input.length; i += 1) {
|
||||||
const sample = input[i]!;
|
const sample = input[i];
|
||||||
sumSquares += sample * sample;
|
sumSquares += sample * sample;
|
||||||
}
|
}
|
||||||
const rms = Math.sqrt(sumSquares / Math.max(1, input.length));
|
const rms = Math.sqrt(sumSquares / Math.max(1, input.length));
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime {
|
|||||||
|
|
||||||
function activateNextPlaybackGroup(): void {
|
function activateNextPlaybackGroup(): void {
|
||||||
while (playback.orderedGroupIds.length > 0) {
|
while (playback.orderedGroupIds.length > 0) {
|
||||||
const groupId = playback.orderedGroupIds[0]!;
|
const groupId = playback.orderedGroupIds[0];
|
||||||
if (playback.groups.has(groupId)) {
|
if (playback.groups.has(groupId)) {
|
||||||
playback.activeGroupId = groupId;
|
playback.activeGroupId = groupId;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export async function runArchiveCommand(
|
|||||||
options: AgentArchiveOptions,
|
options: AgentArchiveOptions,
|
||||||
_command: Command,
|
_command: Command,
|
||||||
): Promise<AgentArchiveCommandResult> {
|
): Promise<AgentArchiveCommandResult> {
|
||||||
const host = getDaemonHost({ host: options.host as string | undefined });
|
const host = getDaemonHost({ host: options.host });
|
||||||
|
|
||||||
// Validate arguments
|
// Validate arguments
|
||||||
if (!agentIdArg || agentIdArg.trim().length === 0) {
|
if (!agentIdArg || agentIdArg.trim().length === 0) {
|
||||||
@@ -58,7 +58,7 @@ export async function runArchiveCommand(
|
|||||||
|
|
||||||
let client: DaemonClient;
|
let client: DaemonClient;
|
||||||
try {
|
try {
|
||||||
client = await connectToDaemon({ host: options.host as string | undefined });
|
client = await connectToDaemon({ host: options.host });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
const error: CommandError = {
|
const error: CommandError = {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export async function runAttachCommand(
|
|||||||
options: AgentAttachOptions,
|
options: AgentAttachOptions,
|
||||||
_command: Command,
|
_command: Command,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const host = getDaemonHost({ host: options.host as string | undefined });
|
const host = getDaemonHost({ host: options.host });
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
console.error("Error: Agent ID required");
|
console.error("Error: Agent ID required");
|
||||||
@@ -115,7 +115,7 @@ export async function runAttachCommand(
|
|||||||
|
|
||||||
let client: DaemonClient;
|
let client: DaemonClient;
|
||||||
try {
|
try {
|
||||||
client = await connectToDaemon({ host: options.host as string | undefined });
|
client = await connectToDaemon({ host: options.host });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
console.error(`Error: Cannot connect to daemon at ${host}: ${message}`);
|
console.error(`Error: Cannot connect to daemon at ${host}: ${message}`);
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export async function runImportCommand(
|
|||||||
options: AgentImportOptions,
|
options: AgentImportOptions,
|
||||||
_command: Command,
|
_command: Command,
|
||||||
): Promise<AgentImportCommandResult> {
|
): Promise<AgentImportCommandResult> {
|
||||||
const host = getDaemonHost({ host: options.host as string | undefined });
|
const host = getDaemonHost({ host: options.host });
|
||||||
const sessionId = sessionIdArg.trim();
|
const sessionId = sessionIdArg.trim();
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
throw {
|
throw {
|
||||||
@@ -134,7 +134,7 @@ export async function runImportCommand(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const labels = parseImportLabels(options.label);
|
const labels = parseImportLabels(options.label);
|
||||||
const client = await connectToDaemonOrThrow(options.host as string | undefined, host);
|
const client = await connectToDaemonOrThrow(options.host, host);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const agent = await client.importAgent({
|
const agent = await client.importAgent({
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ export async function runInspectCommand(
|
|||||||
options: AgentInspectOptions,
|
options: AgentInspectOptions,
|
||||||
_command: Command,
|
_command: Command,
|
||||||
): Promise<AgentInspectResult> {
|
): Promise<AgentInspectResult> {
|
||||||
const host = getDaemonHost({ host: options.host as string | undefined });
|
const host = getDaemonHost({ host: options.host });
|
||||||
|
|
||||||
// Validate arguments
|
// Validate arguments
|
||||||
if (!agentIdArg || agentIdArg.trim().length === 0) {
|
if (!agentIdArg || agentIdArg.trim().length === 0) {
|
||||||
@@ -230,7 +230,7 @@ export async function runInspectCommand(
|
|||||||
|
|
||||||
let client;
|
let client;
|
||||||
try {
|
try {
|
||||||
client = await connectToDaemon({ host: options.host as string | undefined });
|
client = await connectToDaemon({ host: options.host });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
const error: CommandError = {
|
const error: CommandError = {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export async function runReloadCommand(
|
|||||||
options: AgentReloadOptions,
|
options: AgentReloadOptions,
|
||||||
_command: Command,
|
_command: Command,
|
||||||
): Promise<AgentReloadCommandResult> {
|
): Promise<AgentReloadCommandResult> {
|
||||||
const host = getDaemonHost({ host: options.host as string | undefined });
|
const host = getDaemonHost({ host: options.host });
|
||||||
|
|
||||||
if (!agentIdArg || agentIdArg.trim().length === 0) {
|
if (!agentIdArg || agentIdArg.trim().length === 0) {
|
||||||
const error: CommandError = {
|
const error: CommandError = {
|
||||||
@@ -53,7 +53,7 @@ export async function runReloadCommand(
|
|||||||
|
|
||||||
let client: DaemonClient;
|
let client: DaemonClient;
|
||||||
try {
|
try {
|
||||||
client = await connectToDaemon({ host: options.host as string | undefined });
|
client = await connectToDaemon({ host: options.host });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
const error: CommandError = {
|
const error: CommandError = {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export async function runUpdateCommand(
|
|||||||
options: AgentUpdateOptions,
|
options: AgentUpdateOptions,
|
||||||
_command: Command,
|
_command: Command,
|
||||||
): Promise<AgentUpdateCommandResult> {
|
): Promise<AgentUpdateCommandResult> {
|
||||||
const host = getDaemonHost({ host: options.host as string | undefined });
|
const host = getDaemonHost({ host: options.host });
|
||||||
|
|
||||||
// Validate arguments
|
// Validate arguments
|
||||||
if (!agentIdArg || agentIdArg.trim().length === 0) {
|
if (!agentIdArg || agentIdArg.trim().length === 0) {
|
||||||
@@ -120,7 +120,7 @@ export async function runUpdateCommand(
|
|||||||
|
|
||||||
let client;
|
let client;
|
||||||
try {
|
try {
|
||||||
client = await connectToDaemon({ host: options.host as string | undefined });
|
client = await connectToDaemon({ host: options.host });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
const error: CommandError = {
|
const error: CommandError = {
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export async function runWaitCommand(
|
|||||||
options: AgentWaitOptions,
|
options: AgentWaitOptions,
|
||||||
_command: Command,
|
_command: Command,
|
||||||
): Promise<SingleResult<AgentWaitResult>> {
|
): Promise<SingleResult<AgentWaitResult>> {
|
||||||
const host = getDaemonHost({ host: options.host as string | undefined });
|
const host = getDaemonHost({ host: options.host });
|
||||||
|
|
||||||
if (!agentIdArg || agentIdArg.trim().length === 0) {
|
if (!agentIdArg || agentIdArg.trim().length === 0) {
|
||||||
throw {
|
throw {
|
||||||
@@ -153,7 +153,7 @@ export async function runWaitCommand(
|
|||||||
|
|
||||||
let client;
|
let client;
|
||||||
try {
|
try {
|
||||||
client = await connectToDaemon({ host: options.host as string | undefined });
|
client = await connectToDaemon({ host: options.host });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
const error: CommandError = {
|
const error: CommandError = {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export async function runPostCommand(
|
|||||||
]);
|
]);
|
||||||
return {
|
return {
|
||||||
type: "single",
|
type: "single",
|
||||||
data: message!,
|
data: message,
|
||||||
schema: chatMessageSchema,
|
schema: chatMessageSchema,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function pairCommand(): Command {
|
|||||||
return addJsonOption(new Command("pair").description("Print the daemon pairing QR code and link"))
|
return addJsonOption(new Command("pair").description("Print the daemon pairing QR code and link"))
|
||||||
.option("--home <path>", "Paseo home directory (default: ~/.paseo)")
|
.option("--home <path>", "Paseo home directory (default: ~/.paseo)")
|
||||||
.action(async (_options: PairOptions, command: Command) => {
|
.action(async (_options: PairOptions, command: Command) => {
|
||||||
await runPairCommand(command.optsWithGlobals() as PairOptions);
|
await runPairCommand(command.optsWithGlobals());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ async function resolveNodePathFromPidWindows(pid: number): Promise<NodePathFromP
|
|||||||
error: errors.join("; ") || "could not resolve executable path from PID",
|
error: errors.join("; ") || "could not resolve executable path from PID",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const probe = probes[index] as (typeof probes)[number];
|
const probe = probes[index];
|
||||||
const result = await runProcessProbe(probe.command, probe.args);
|
const result = await runProcessProbe(probe.command, probe.args);
|
||||||
if (result.resolved) {
|
if (result.resolved) {
|
||||||
const resolved = probe.parseValue ? probe.parseValue(result.resolved) : result.resolved;
|
const resolved = probe.parseValue ? probe.parseValue(result.resolved) : result.resolved;
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export async function runAllowCommand(
|
|||||||
permissionsToAllow = pendingPermissions;
|
permissionsToAllow = pendingPermissions;
|
||||||
} else {
|
} else {
|
||||||
// Find permission by ID prefix
|
// Find permission by ID prefix
|
||||||
const permission = pendingPermissions.find((p) => p.id === reqId || p.id.startsWith(reqId!));
|
const permission = pendingPermissions.find((p) => p.id === reqId || p.id.startsWith(reqId));
|
||||||
if (!permission) {
|
if (!permission) {
|
||||||
await client.close();
|
await client.close();
|
||||||
const error: CommandError = {
|
const error: CommandError = {
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export async function runLsCommand(
|
|||||||
provider: entry.provider,
|
provider: entry.provider,
|
||||||
label: entry.label ?? entry.provider,
|
label: entry.label ?? entry.provider,
|
||||||
status: entry.status === "ready" ? "available" : entry.status,
|
status: entry.status === "ready" ? "available" : entry.status,
|
||||||
enabled: entry.enabled === false ? "Disabled" : "Enabled",
|
enabled: !entry.enabled ? "Disabled" : "Enabled",
|
||||||
defaultMode: entry.defaultModeId ?? "default",
|
defaultMode: entry.defaultModeId ?? "default",
|
||||||
modes: (entry.modes ?? []).map((mode) => mode.label).join(", "),
|
modes: (entry.modes ?? []).map((mode) => mode.label).join(", "),
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ export function renderTable<T>(result: AnyCommandResult<T>, options: OutputOptio
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = schema.columns as ColumnDef<T>[];
|
const columns = schema.columns;
|
||||||
const includeHeaders = !options.noHeaders;
|
const includeHeaders = !options.noHeaders;
|
||||||
const widths = calculateWidths(data, columns, includeHeaders);
|
const widths = calculateWidths(data, columns, includeHeaders);
|
||||||
|
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ export async function connectToDaemon(options?: ConnectOptions): Promise<DaemonC
|
|||||||
if (lastError instanceof Error) throw lastError;
|
if (lastError instanceof Error) throw lastError;
|
||||||
throw new Error(`Unable to connect to Paseo daemon via ${hosts.join(", ")}`);
|
throw new Error(`Unable to connect to Paseo daemon via ${hosts.join(", ")}`);
|
||||||
}
|
}
|
||||||
const host = hosts[index] as string;
|
const host = hosts[index];
|
||||||
const password = resolveDaemonPassword(host);
|
const password = resolveDaemonPassword(host);
|
||||||
const result = await tryConnectHost(host, password, clientId, timeout, nodeWebSocketFactory);
|
const result = await tryConnectHost(host, password, clientId, timeout, nodeWebSocketFactory);
|
||||||
if ("client" in result) {
|
if ("client" in result) {
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ async function runSingleTest(testFile: string): Promise<TestOutcome> {
|
|||||||
|
|
||||||
async function runRemainingTests(index: number): Promise<void> {
|
async function runRemainingTests(index: number): Promise<void> {
|
||||||
if (index >= testFiles.length) return;
|
if (index >= testFiles.length) return;
|
||||||
const testFile = testFiles[index] as string;
|
const testFile = testFiles[index];
|
||||||
const outcome = await runSingleTest(testFile);
|
const outcome = await runSingleTest(testFile);
|
||||||
if (outcome.status === "passed") {
|
if (outcome.status === "passed") {
|
||||||
passed++;
|
passed++;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export interface StopOnQuitDeps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function shouldStopDesktopManagedDaemonOnQuit(settings: QuitLifecycleSettings): boolean {
|
export function shouldStopDesktopManagedDaemonOnQuit(settings: QuitLifecycleSettings): boolean {
|
||||||
return settings.daemon.keepRunningAfterQuit === false;
|
return !settings.daemon.keepRunningAfterQuit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function stopDesktopManagedDaemonOnQuitIfNeeded(
|
export async function stopDesktopManagedDaemonOnQuitIfNeeded(
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export function FlowTest() {
|
|||||||
for (const result of data.results as {
|
for (const result of data.results as {
|
||||||
alternatives: Array<RecognitionAlternative>;
|
alternatives: Array<RecognitionAlternative>;
|
||||||
}[]) {
|
}[]) {
|
||||||
for (const alternative of result.alternatives as Array<RecognitionAlternative>) {
|
for (const alternative of result.alternatives) {
|
||||||
console.log(alternative.content);
|
console.log(alternative.content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ describe("Live relay (relay.paseo.sh) E2E", () => {
|
|||||||
|
|
||||||
const plaintextFromDaemon = "hello-from-daemon";
|
const plaintextFromDaemon = "hello-from-daemon";
|
||||||
const ciphertextFromDaemon = await encrypt(daemonSharedKey, plaintextFromDaemon);
|
const ciphertextFromDaemon = await encrypt(daemonSharedKey, plaintextFromDaemon);
|
||||||
daemonWs!.send(Buffer.from(ciphertextFromDaemon));
|
daemonWs.send(Buffer.from(ciphertextFromDaemon));
|
||||||
|
|
||||||
const clientReceivedCiphertext = await waitForOnceMessage(
|
const clientReceivedCiphertext = await waitForOnceMessage(
|
||||||
clientWs,
|
clientWs,
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ export class DaemonClientRuntimeMetrics {
|
|||||||
|
|
||||||
private pruneBuckets(now: number): void {
|
private pruneBuckets(now: number): void {
|
||||||
const cutoff = now - this.windowMs;
|
const cutoff = now - this.windowMs;
|
||||||
while (this.buckets.length > 0 && this.buckets[0]!.endedAt < cutoff) {
|
while (this.buckets.length > 0 && this.buckets[0].endedAt < cutoff) {
|
||||||
this.buckets.shift();
|
this.buckets.shift();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export function bindWsHandler(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const prop = `on${event}` as "onopen" | "onclose" | "onerror" | "onmessage";
|
const prop = `on${event}`;
|
||||||
const wsRecord = ws as unknown as Record<string, unknown>;
|
const wsRecord = ws as unknown as Record<string, unknown>;
|
||||||
const previous = wsRecord[prop];
|
const previous = wsRecord[prop];
|
||||||
wsRecord[prop] = handler;
|
wsRecord[prop] = handler;
|
||||||
|
|||||||
@@ -1124,7 +1124,7 @@ export class DaemonClient {
|
|||||||
return this.subscribe(arg1);
|
return this.subscribe(arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = arg1 as SessionOutboundMessage["type"];
|
const type = arg1;
|
||||||
const handler = arg2 as (message: SessionOutboundMessage) => void;
|
const handler = arg2 as (message: SessionOutboundMessage) => void;
|
||||||
|
|
||||||
if (!this.messageHandlers.has(type)) {
|
if (!this.messageHandlers.has(type)) {
|
||||||
|
|||||||
@@ -1833,7 +1833,7 @@ test("fetchTimeline returns a bounded reset window when cursor epoch is stale",
|
|||||||
direction: "after",
|
direction: "after",
|
||||||
cursor: {
|
cursor: {
|
||||||
epoch: "stale-epoch",
|
epoch: "stale-epoch",
|
||||||
seq: baseline.rows[baseline.rows.length - 1]!.seq,
|
seq: baseline.rows[baseline.rows.length - 1].seq,
|
||||||
},
|
},
|
||||||
limit: 1,
|
limit: 1,
|
||||||
});
|
});
|
||||||
@@ -2613,7 +2613,7 @@ test("replaceAgentRun does not emit idle or resolve waiters between interrupted
|
|||||||
}, []);
|
}, []);
|
||||||
expect(runningIndexes.length).toBeGreaterThanOrEqual(2);
|
expect(runningIndexes.length).toBeGreaterThanOrEqual(2);
|
||||||
|
|
||||||
const firstReplacementRunningIndex = runningIndexes[1]!;
|
const firstReplacementRunningIndex = runningIndexes[1];
|
||||||
expect(lifecycleUpdates.slice(0, firstReplacementRunningIndex).includes("idle")).toBe(false);
|
expect(lifecycleUpdates.slice(0, firstReplacementRunningIndex).includes("idle")).toBe(false);
|
||||||
|
|
||||||
allowSecondRunToEnd.resolve();
|
allowSecondRunToEnd.resolve();
|
||||||
@@ -4766,11 +4766,10 @@ test("hydrateTimeline suppresses only matching canonical user_message messageId"
|
|||||||
const timeline = manager.getTimeline(snapshot.id);
|
const timeline = manager.getTimeline(snapshot.id);
|
||||||
const userMessages = timeline.filter((item) => item.type === "user_message");
|
const userMessages = timeline.filter((item) => item.type === "user_message");
|
||||||
expect(userMessages).toHaveLength(2);
|
expect(userMessages).toHaveLength(2);
|
||||||
expect(
|
expect(userMessages.map((item) => item.messageId)).toEqual([
|
||||||
userMessages.map(
|
"msg_client_hello",
|
||||||
(item) => (item as Extract<AgentTimelineItem, { type: "user_message" }>).messageId,
|
"msg_provider_distinct",
|
||||||
),
|
]);
|
||||||
).toEqual(["msg_client_hello", "msg_provider_distinct"]);
|
|
||||||
expect(userMessages.map((item) => item.text)).toEqual(["hello from user", "hello from user"]);
|
expect(userMessages.map((item) => item.text)).toEqual(["hello from user", "hello from user"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4815,11 +4814,11 @@ test("recordUserMessage normalizes blank/whitespace messageId to undefined", asy
|
|||||||
|
|
||||||
expect(userMessages).toHaveLength(3);
|
expect(userMessages).toHaveLength(3);
|
||||||
// Empty string → undefined (not empty string)
|
// Empty string → undefined (not empty string)
|
||||||
expect(userMessages[0]!.messageId).toBeUndefined();
|
expect(userMessages[0].messageId).toBeUndefined();
|
||||||
// Whitespace → undefined
|
// Whitespace → undefined
|
||||||
expect(userMessages[1]!.messageId).toBeUndefined();
|
expect(userMessages[1].messageId).toBeUndefined();
|
||||||
// Valid → preserved
|
// Valid → preserved
|
||||||
expect(userMessages[2]!.messageId).toBe("msg_valid_123");
|
expect(userMessages[2].messageId).toBe("msg_valid_123");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("recordUserMessage preserves provided messageId in timeline item and dispatched event", async () => {
|
test("recordUserMessage preserves provided messageId in timeline item and dispatched event", async () => {
|
||||||
@@ -4942,9 +4941,7 @@ test("live provider user_message echo is suppressed when recordUserMessage was c
|
|||||||
// Should be exactly 1 (canonical), not 2 (canonical + provider echo)
|
// Should be exactly 1 (canonical), not 2 (canonical + provider echo)
|
||||||
expect(userMessages).toHaveLength(1);
|
expect(userMessages).toHaveLength(1);
|
||||||
// The canonical one must carry the client messageId for optimistic matching
|
// The canonical one must carry the client messageId for optimistic matching
|
||||||
expect((userMessages[0] as Extract<AgentTimelineItem, { type: "user_message" }>).messageId).toBe(
|
expect(userMessages[0].messageId).toBe("msg_client_echo_1");
|
||||||
"msg_client_echo_1",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Assistant messages from the run should still appear
|
// Assistant messages from the run should still appear
|
||||||
const assistantMessages = timeline.filter((item) => item.type === "assistant_message");
|
const assistantMessages = timeline.filter((item) => item.type === "assistant_message");
|
||||||
@@ -5135,9 +5132,7 @@ test("provider user_message is NOT suppressed when no prior recordUserMessage",
|
|||||||
|
|
||||||
// Provider's user_message should be recorded (no canonical to dedup against)
|
// Provider's user_message should be recorded (no canonical to dedup against)
|
||||||
expect(userMessages).toHaveLength(1);
|
expect(userMessages).toHaveLength(1);
|
||||||
expect((userMessages[0] as Extract<AgentTimelineItem, { type: "user_message" }>).text).toBe(
|
expect(userMessages[0].text).toBe("continuation prompt");
|
||||||
"continuation prompt",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("replaceAgentRun succeeds when foreground turn terminal event is never delivered", async () => {
|
test("replaceAgentRun succeeds when foreground turn terminal event is never delivered", async () => {
|
||||||
|
|||||||
@@ -1301,7 +1301,7 @@ export class AgentManager {
|
|||||||
throw new Error(`Agent ${agentId} already has an active run`);
|
throw new Error(`Agent ${agentId} already has an active run`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const agent = existingAgent as ActiveManagedAgent;
|
const agent = existingAgent;
|
||||||
agent.pendingReplacement = false;
|
agent.pendingReplacement = false;
|
||||||
agent.lastError = undefined;
|
agent.lastError = undefined;
|
||||||
|
|
||||||
@@ -1361,7 +1361,7 @@ export class AgentManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private finalizeForegroundTurn(agent: ActiveManagedAgent, turnId?: string): void {
|
private finalizeForegroundTurn(agent: ActiveManagedAgent, turnId?: string): void {
|
||||||
const mutableAgent = agent as ActiveManagedAgent;
|
const mutableAgent = agent;
|
||||||
if (turnId) {
|
if (turnId) {
|
||||||
this.foregroundRuns.rememberFinalizedTurn(mutableAgent, turnId);
|
this.foregroundRuns.rememberFinalizedTurn(mutableAgent, turnId);
|
||||||
}
|
}
|
||||||
@@ -1430,7 +1430,7 @@ export class AgentManager {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
const latest = this.agents.get(agentId);
|
const latest = this.agents.get(agentId);
|
||||||
if (latest) {
|
if (latest) {
|
||||||
const latestActive = latest as ActiveManagedAgent;
|
const latestActive = latest;
|
||||||
latestActive.pendingReplacement = false;
|
latestActive.pendingReplacement = false;
|
||||||
if (!latestActive.activeForegroundTurnId && latestActive.lifecycle === "running") {
|
if (!latestActive.activeForegroundTurnId && latestActive.lifecycle === "running") {
|
||||||
(latestActive as ActiveManagedAgent).lifecycle = "idle";
|
(latestActive as ActiveManagedAgent).lifecycle = "idle";
|
||||||
@@ -1502,7 +1502,7 @@ export class AgentManager {
|
|||||||
|
|
||||||
if (options?.signal) {
|
if (options?.signal) {
|
||||||
abortHandler = () =>
|
abortHandler = () =>
|
||||||
finishErr(createAbortError(options.signal!, "wait_for_agent_start aborted"));
|
finishErr(createAbortError(options.signal, "wait_for_agent_start aborted"));
|
||||||
options.signal.addEventListener("abort", abortHandler, { once: true });
|
options.signal.addEventListener("abort", abortHandler, { once: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2636,7 +2636,7 @@ export class AgentManager {
|
|||||||
"handleStreamEvent: turn_canceled",
|
"handleStreamEvent: turn_canceled",
|
||||||
);
|
);
|
||||||
if (!isForegroundEvent && !agent.pendingReplacement) {
|
if (!isForegroundEvent && !agent.pendingReplacement) {
|
||||||
(agent as ActiveManagedAgent).lifecycle = "idle";
|
agent.lifecycle = "idle";
|
||||||
}
|
}
|
||||||
agent.lastError = undefined;
|
agent.lastError = undefined;
|
||||||
this.resolvePendingPermissionsForAgent(agent, event.provider, options, "Interrupted");
|
this.resolvePendingPermissionsForAgent(agent, event.provider, options, "Interrupted");
|
||||||
@@ -2661,7 +2661,7 @@ export class AgentManager {
|
|||||||
"handleStreamEvent: turn_started",
|
"handleStreamEvent: turn_started",
|
||||||
);
|
);
|
||||||
if (!isForegroundEvent) {
|
if (!isForegroundEvent) {
|
||||||
(agent as ActiveManagedAgent).lifecycle = "running";
|
agent.lifecycle = "running";
|
||||||
this.emitState(agent);
|
this.emitState(agent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { AGENT_LIFECYCLE_STATUSES, type AgentLifecycleStatus } from "./agent-manager.js";
|
import { AGENT_LIFECYCLE_STATUSES } from "./agent-manager.js";
|
||||||
import { toAgentPayload, toStoredAgentRecord, type ManagedAgent } from "./agent-projections.js";
|
import { toAgentPayload, toStoredAgentRecord, type ManagedAgent } from "./agent-projections.js";
|
||||||
import type { AgentSession } from "./agent-sdk-types.js";
|
import type { AgentSession } from "./agent-sdk-types.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -203,7 +203,7 @@ describe("toStoredAgentRecord", () => {
|
|||||||
|
|
||||||
it("propagates lifecycle status for all states", () => {
|
it("propagates lifecycle status for all states", () => {
|
||||||
for (const status of AGENT_LIFECYCLE_STATUSES) {
|
for (const status of AGENT_LIFECYCLE_STATUSES) {
|
||||||
const agent = createManagedAgent({ lifecycle: status as AgentLifecycleStatus });
|
const agent = createManagedAgent({ lifecycle: status });
|
||||||
const record = toStoredAgentRecord(agent);
|
const record = toStoredAgentRecord(agent);
|
||||||
expect(record.lastStatus).toBe(status);
|
expect(record.lastStatus).toBe(status);
|
||||||
}
|
}
|
||||||
@@ -301,7 +301,7 @@ describe("toAgentPayload", () => {
|
|||||||
|
|
||||||
it("propagates lifecycle status for all states", () => {
|
it("propagates lifecycle status for all states", () => {
|
||||||
for (const status of AGENT_LIFECYCLE_STATUSES) {
|
for (const status of AGENT_LIFECYCLE_STATUSES) {
|
||||||
const agent = createManagedAgent({ lifecycle: status as AgentLifecycleStatus });
|
const agent = createManagedAgent({ lifecycle: status });
|
||||||
const payload = toAgentPayload(agent);
|
const payload = toAgentPayload(agent);
|
||||||
expect(payload.status).toBe(status);
|
expect(payload.status).toBe(status);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,14 +214,14 @@ function tryParseJson(candidate: string): string | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function extractBalancedJsonCandidate(source: string, start: number): string | null {
|
function extractBalancedJsonCandidate(source: string, start: number): string | null {
|
||||||
const open = source[start]!;
|
const open = source[start];
|
||||||
const close = open === "{" ? "}" : "]";
|
const close = open === "{" ? "}" : "]";
|
||||||
let depth = 0;
|
let depth = 0;
|
||||||
let inString = false;
|
let inString = false;
|
||||||
let escaped = false;
|
let escaped = false;
|
||||||
|
|
||||||
for (let i = start; i < source.length; i += 1) {
|
for (let i = start; i < source.length; i += 1) {
|
||||||
const ch = source[i]!;
|
const ch = source[i];
|
||||||
|
|
||||||
if (inString) {
|
if (inString) {
|
||||||
if (escaped) {
|
if (escaped) {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ function fetchTail(ctx: FetchContext): AgentTimelineFetchResult {
|
|||||||
staleCursor: false,
|
staleCursor: false,
|
||||||
gap: false,
|
gap: false,
|
||||||
window,
|
window,
|
||||||
hasOlder: selected.length > 0 && selected[0]!.seq > minSeq,
|
hasOlder: selected.length > 0 && selected[0].seq > minSeq,
|
||||||
hasNewer: false,
|
hasNewer: false,
|
||||||
rows: selected.map(cloneRow),
|
rows: selected.map(cloneRow),
|
||||||
};
|
};
|
||||||
@@ -93,7 +93,7 @@ function fetchAfter(ctx: FetchContext): AgentTimelineFetchResult {
|
|||||||
staleCursor: false,
|
staleCursor: false,
|
||||||
gap: false,
|
gap: false,
|
||||||
window,
|
window,
|
||||||
hasOlder: selected[0]!.seq > minSeq,
|
hasOlder: selected[0].seq > minSeq,
|
||||||
hasNewer: Boolean(lastSelected && lastSelected.seq < maxSeq),
|
hasNewer: Boolean(lastSelected && lastSelected.seq < maxSeq),
|
||||||
rows: selected.map(cloneRow),
|
rows: selected.map(cloneRow),
|
||||||
};
|
};
|
||||||
@@ -115,7 +115,7 @@ function fetchBefore(ctx: FetchContext): AgentTimelineFetchResult {
|
|||||||
staleCursor: false,
|
staleCursor: false,
|
||||||
gap: false,
|
gap: false,
|
||||||
window,
|
window,
|
||||||
hasOlder: selected.length > 0 && selected[0]!.seq > minSeq,
|
hasOlder: selected.length > 0 && selected[0].seq > minSeq,
|
||||||
hasNewer: endExclusive >= 0,
|
hasNewer: endExclusive >= 0,
|
||||||
rows: selected.map(cloneRow),
|
rows: selected.map(cloneRow),
|
||||||
};
|
};
|
||||||
@@ -137,7 +137,7 @@ function fetchReset(
|
|||||||
staleCursor: flags.staleCursor,
|
staleCursor: flags.staleCursor,
|
||||||
gap: flags.gap,
|
gap: flags.gap,
|
||||||
window,
|
window,
|
||||||
hasOlder: rows.length > 0 && rows[0]!.seq > minSeq,
|
hasOlder: rows.length > 0 && rows[0].seq > minSeq,
|
||||||
hasNewer: false,
|
hasNewer: false,
|
||||||
rows,
|
rows,
|
||||||
};
|
};
|
||||||
@@ -155,7 +155,7 @@ export class InMemoryAgentTimelineStore {
|
|||||||
const rows = options?.rows?.length
|
const rows = options?.rows?.length
|
||||||
? options.rows.map(cloneRow)
|
? options.rows.map(cloneRow)
|
||||||
: this.buildRowsFromItems(options?.items ?? [], options?.nextSeq ?? 1, timestamp);
|
: this.buildRowsFromItems(options?.items ?? [], options?.nextSeq ?? 1, timestamp);
|
||||||
const nextSeq = options?.nextSeq ?? (rows.length ? rows[rows.length - 1]!.seq + 1 : 1);
|
const nextSeq = options?.nextSeq ?? (rows.length ? rows[rows.length - 1].seq + 1 : 1);
|
||||||
this.states.set(agentId, {
|
this.states.set(agentId, {
|
||||||
epoch: options?.epoch ?? randomUUID(),
|
epoch: options?.epoch ?? randomUUID(),
|
||||||
rows,
|
rows,
|
||||||
@@ -188,8 +188,8 @@ export class InMemoryAgentTimelineStore {
|
|||||||
? DEFAULT_TIMELINE_FETCH_LIMIT
|
? DEFAULT_TIMELINE_FETCH_LIMIT
|
||||||
: Math.max(0, Math.floor(requestedLimit));
|
: Math.max(0, Math.floor(requestedLimit));
|
||||||
const cursor = options?.cursor;
|
const cursor = options?.cursor;
|
||||||
const minSeq = state.rows.length ? state.rows[0]!.seq : 0;
|
const minSeq = state.rows.length ? state.rows[0].seq : 0;
|
||||||
const maxSeq = state.rows.length ? state.rows[state.rows.length - 1]!.seq : 0;
|
const maxSeq = state.rows.length ? state.rows[state.rows.length - 1].seq : 0;
|
||||||
const selectAll = limit === 0;
|
const selectAll = limit === 0;
|
||||||
|
|
||||||
const window = {
|
const window = {
|
||||||
@@ -265,7 +265,7 @@ export class InMemoryAgentTimelineStore {
|
|||||||
const rows = this.requireState(agentId).rows;
|
const rows = this.requireState(agentId).rows;
|
||||||
const chunks: string[] = [];
|
const chunks: string[] = [];
|
||||||
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
||||||
const item = rows[i]!.item;
|
const item = rows[i].item;
|
||||||
if (item.type !== "assistant_message") {
|
if (item.type !== "assistant_message") {
|
||||||
if (chunks.length > 0) {
|
if (chunks.length > 0) {
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ function createTestDeps(): TestDeps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createProviderDefinition(overrides: Partial<ProviderDefinition>): ProviderDefinition {
|
function createProviderDefinition(overrides: Partial<ProviderDefinition>): ProviderDefinition {
|
||||||
const provider = (overrides.id ?? "claude") as AgentProvider;
|
const provider = overrides.id ?? "claude";
|
||||||
return {
|
return {
|
||||||
id: provider,
|
id: provider,
|
||||||
label: "Claude",
|
label: "Claude",
|
||||||
|
|||||||
@@ -168,9 +168,7 @@ function resolveRegisteredProviderIds(
|
|||||||
agentManager: AgentManager,
|
agentManager: AgentManager,
|
||||||
providerRegistry: Record<AgentProvider, ProviderDefinition> | null | undefined,
|
providerRegistry: Record<AgentProvider, ProviderDefinition> | null | undefined,
|
||||||
): AgentProvider[] {
|
): AgentProvider[] {
|
||||||
return providerRegistry
|
return providerRegistry ? Object.keys(providerRegistry) : agentManager.getRegisteredProviderIds();
|
||||||
? (Object.keys(providerRegistry) as AgentProvider[])
|
|
||||||
: agentManager.getRegisteredProviderIds();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProviderSummary {
|
interface ProviderSummary {
|
||||||
@@ -239,7 +237,7 @@ function resolveScheduleProviderAndModel(params: {
|
|||||||
const providerInput = params.provider?.trim() || params.defaultProvider;
|
const providerInput = params.provider?.trim() || params.defaultProvider;
|
||||||
const slashIndex = providerInput.indexOf("/");
|
const slashIndex = providerInput.indexOf("/");
|
||||||
if (slashIndex === -1) {
|
if (slashIndex === -1) {
|
||||||
return { provider: providerInput as AgentProvider };
|
return { provider: providerInput };
|
||||||
}
|
}
|
||||||
|
|
||||||
const provider = providerInput.slice(0, slashIndex).trim();
|
const provider = providerInput.slice(0, slashIndex).trim();
|
||||||
@@ -249,7 +247,7 @@ function resolveScheduleProviderAndModel(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
provider: provider as AgentProvider,
|
provider: provider,
|
||||||
model,
|
model,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -447,7 +445,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
|||||||
|
|
||||||
const resolvedProviderModel = resolveScheduleProviderAndModel({
|
const resolvedProviderModel = resolveScheduleProviderAndModel({
|
||||||
provider: params?.provider,
|
provider: params?.provider,
|
||||||
defaultProvider: params.provider as AgentProvider,
|
defaultProvider: params.provider,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
type: "new-agent" as const,
|
type: "new-agent" as const,
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export function resolveRequiredProviderModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
provider: provider as AgentProvider,
|
provider: provider,
|
||||||
model,
|
model,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { resolveAgentModel } from "./model-resolver.js";
|
|||||||
|
|
||||||
vi.mock("./provider-registry.js", () => ({
|
vi.mock("./provider-registry.js", () => ({
|
||||||
buildProviderRegistry: vi.fn(),
|
buildProviderRegistry: vi.fn(),
|
||||||
isProviderEnabled: vi.fn((definition: { enabled: boolean }) => definition.enabled === true),
|
isProviderEnabled: vi.fn((definition: { enabled: boolean }) => definition.enabled),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { buildProviderRegistry } from "./provider-registry.js";
|
import { buildProviderRegistry } from "./provider-registry.js";
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ export class Pcm16MonoResampler {
|
|||||||
while (this.pos < maxPos) {
|
while (this.pos < maxPos) {
|
||||||
const i = Math.floor(this.pos);
|
const i = Math.floor(this.pos);
|
||||||
const frac = this.pos - i;
|
const frac = this.pos - i;
|
||||||
const s0 = src[i]!;
|
const s0 = src[i];
|
||||||
const s1 = src[i + 1]!;
|
const s1 = src[i + 1];
|
||||||
const sample = s0 + (s1 - s0) * frac;
|
const sample = s0 + (s1 - s0) * frac;
|
||||||
const clamped = Math.max(-1, Math.min(1, sample));
|
const clamped = Math.max(-1, Math.min(1, sample));
|
||||||
const int16 = Math.round(clamped * 32767);
|
const int16 = Math.round(clamped * 32767);
|
||||||
@@ -61,7 +61,7 @@ export class Pcm16MonoResampler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Keep the last input sample as carry for the next chunk.
|
// Keep the last input sample as carry for the next chunk.
|
||||||
const lastInput = srcChunk[srcChunk.length - 1]!;
|
const lastInput = srcChunk[srcChunk.length - 1];
|
||||||
this.carrySample = lastInput;
|
this.carrySample = lastInput;
|
||||||
|
|
||||||
// Shift position so next chunk (which will include carry sample) continues smoothly.
|
// Shift position so next chunk (which will include carry sample) continues smoothly.
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export async function resolveProviderCommandPrefix(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
command: commandConfig.argv[0]!,
|
command: commandConfig.argv[0],
|
||||||
args: commandConfig.argv.slice(1),
|
args: commandConfig.argv.slice(1),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -389,7 +389,7 @@ export class ProviderSnapshotManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getProviderIds(): AgentProvider[] {
|
private getProviderIds(): AgentProvider[] {
|
||||||
return Object.keys(this.providerRegistry) as AgentProvider[];
|
return Object.keys(this.providerRegistry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveRefreshProviders(providers?: AgentProvider[]): AgentProvider[] | undefined {
|
private resolveRefreshProviders(providers?: AgentProvider[]): AgentProvider[] | undefined {
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ test("Test 4: Autonomous run", async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const autoTurnId = autoStarts[0]!.turnId;
|
const autoTurnId = autoStarts[0].turnId;
|
||||||
expect(fgTurnId).not.toBe(autoTurnId);
|
expect(fgTurnId).not.toBe(autoTurnId);
|
||||||
|
|
||||||
// Autonomous turn reaches terminal
|
// Autonomous turn reaches terminal
|
||||||
|
|||||||
@@ -653,14 +653,14 @@ export class ACPAgentClient implements AgentClient {
|
|||||||
{ logger: this.logger, provider: this.provider },
|
{ logger: this.logger, provider: this.provider },
|
||||||
);
|
);
|
||||||
const connection = new ClientSideConnection(() => this.buildProbeClient(), stream);
|
const connection = new ClientSideConnection(() => this.buildProbeClient(), stream);
|
||||||
const initialize = (await Promise.race([
|
const initialize = await Promise.race([
|
||||||
connection.initialize({
|
connection.initialize({
|
||||||
protocolVersion: PROTOCOL_VERSION,
|
protocolVersion: PROTOCOL_VERSION,
|
||||||
clientCapabilities: ACP_CLIENT_CAPABILITIES,
|
clientCapabilities: ACP_CLIENT_CAPABILITIES,
|
||||||
clientInfo: { name: "Paseo", version: "dev" },
|
clientInfo: { name: "Paseo", version: "dev" },
|
||||||
}),
|
}),
|
||||||
spawnErrorPromise,
|
spawnErrorPromise,
|
||||||
])) as InitializeResponse;
|
]);
|
||||||
|
|
||||||
return { child, connection, initialize };
|
return { child, connection, initialize };
|
||||||
}
|
}
|
||||||
@@ -2093,7 +2093,7 @@ function mergeToolSnapshot(
|
|||||||
): ACPToolSnapshot {
|
): ACPToolSnapshot {
|
||||||
return {
|
return {
|
||||||
toolCallId,
|
toolCallId,
|
||||||
title: (update.title ?? previous?.title ?? toolCallId) as string,
|
title: update.title ?? previous?.title ?? toolCallId,
|
||||||
kind: update.kind ?? previous?.kind ?? null,
|
kind: update.kind ?? previous?.kind ?? null,
|
||||||
status: update.status ?? previous?.status ?? null,
|
status: update.status ?? previous?.status ?? null,
|
||||||
content: coalesceDefined(update.content, previous?.content, null),
|
content: coalesceDefined(update.content, previous?.content, null),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { beforeAll, beforeEach, describe, expect, test } from "vitest";
|
import { beforeAll, beforeEach, describe, expect, test } from "vitest";
|
||||||
import pino from "pino";
|
import pino from "pino";
|
||||||
|
|
||||||
import type { AgentSlashCommand } from "../agent-sdk-types.js";
|
|
||||||
import { isCommandAvailable } from "../../../utils/executable.js";
|
import { isCommandAvailable } from "../../../utils/executable.js";
|
||||||
import { ClaudeAgentClient } from "./claude-agent.js";
|
import { ClaudeAgentClient } from "./claude-agent.js";
|
||||||
|
|
||||||
@@ -40,7 +39,7 @@ describe("claude agent commands contract (real)", () => {
|
|||||||
expect(commands.map((command) => command.name)).toContain("rewind");
|
expect(commands.map((command) => command.name)).toContain("rewind");
|
||||||
|
|
||||||
for (const command of commands) {
|
for (const command of commands) {
|
||||||
const typed = command as AgentSlashCommand;
|
const typed = command;
|
||||||
expect(typeof typed.name).toBe("string");
|
expect(typeof typed.name).toBe("string");
|
||||||
expect(typed.name.length).toBeGreaterThan(0);
|
expect(typed.name.length).toBeGreaterThan(0);
|
||||||
expect(typed.name.startsWith("/")).toBe(false);
|
expect(typed.name.startsWith("/")).toBe(false);
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ function resolveClaudeSpawnCommand(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
command: commandConfig.argv[0]!,
|
command: commandConfig.argv[0],
|
||||||
args: [...commandConfig.argv.slice(1), ...spawnOptions.args],
|
args: [...commandConfig.argv.slice(1), ...spawnOptions.args],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -459,7 +459,7 @@ function normalizeForDeterministicString(value: unknown, seen: WeakSet<object>):
|
|||||||
return value.map((entry) => normalizeForDeterministicString(entry, seen));
|
return value.map((entry) => normalizeForDeterministicString(entry, seen));
|
||||||
}
|
}
|
||||||
if (typeof value === "object") {
|
if (typeof value === "object") {
|
||||||
const objectValue = value as object;
|
const objectValue = value;
|
||||||
if (seen.has(objectValue)) {
|
if (seen.has(objectValue)) {
|
||||||
return "[circular]";
|
return "[circular]";
|
||||||
}
|
}
|
||||||
@@ -1340,7 +1340,7 @@ async function resolveClaudeVersion(
|
|||||||
try {
|
try {
|
||||||
if (command?.mode === "replace") {
|
if (command?.mode === "replace") {
|
||||||
const { stdout } = await execCommand(
|
const { stdout } = await execCommand(
|
||||||
command.argv[0]!,
|
command.argv[0],
|
||||||
[...command.argv.slice(1), "--version"],
|
[...command.argv.slice(1), "--version"],
|
||||||
{ ...envSpec, timeout: 5_000 },
|
{ ...envSpec, timeout: 5_000 },
|
||||||
);
|
);
|
||||||
@@ -1392,7 +1392,7 @@ async function resolveClaudeAuth(
|
|||||||
try {
|
try {
|
||||||
let result: { stdout: string; stderr: string };
|
let result: { stdout: string; stderr: string };
|
||||||
if (command?.mode === "replace") {
|
if (command?.mode === "replace") {
|
||||||
result = await run(command.argv[0]!, [...command.argv.slice(1), "auth", "status"]);
|
result = await run(command.argv[0], [...command.argv.slice(1), "auth", "status"]);
|
||||||
} else {
|
} else {
|
||||||
const executable = await findExecutable("claude");
|
const executable = await findExecutable("claude");
|
||||||
if (!executable) {
|
if (!executable) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ describe("getClaudeModels", () => {
|
|||||||
const models = getClaudeModels();
|
const models = getClaudeModels();
|
||||||
const defaults = models.filter((m) => m.isDefault);
|
const defaults = models.filter((m) => m.isDefault);
|
||||||
expect(defaults).toHaveLength(1);
|
expect(defaults).toHaveLength(1);
|
||||||
expect(defaults[0]!.id).toBe("claude-opus-4-6");
|
expect(defaults[0].id).toBe("claude-opus-4-6");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns fresh copies each call", () => {
|
it("returns fresh copies each call", () => {
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ export function normalizeClaudeRuntimeModelId(value: string | null | undefined):
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const family = runtimeMatch[1]!.toLowerCase();
|
const family = runtimeMatch[1].toLowerCase();
|
||||||
const major = runtimeMatch[2]!;
|
const major = runtimeMatch[2];
|
||||||
const minor = runtimeMatch[3]!;
|
const minor = runtimeMatch[3];
|
||||||
const suffix = runtimeMatch[4] ?? "";
|
const suffix = runtimeMatch[4] ?? "";
|
||||||
return `claude-${family}-${major}-${minor}${suffix}`;
|
return `claude-${family}-${major}-${minor}${suffix}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -289,14 +289,14 @@ function tokenizeCommandArgs(args: string): string[] {
|
|||||||
let current = "";
|
let current = "";
|
||||||
let quote: "'" | '"' | null = null;
|
let quote: "'" | '"' | null = null;
|
||||||
for (let i = 0; i < args.length; i += 1) {
|
for (let i = 0; i < args.length; i += 1) {
|
||||||
const ch = args[i]!;
|
const ch = args[i];
|
||||||
if (quote) {
|
if (quote) {
|
||||||
if (ch === quote) {
|
if (ch === quote) {
|
||||||
quote = null;
|
quote = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ch === "\\" && i + 1 < args.length) {
|
if (ch === "\\" && i + 1 < args.length) {
|
||||||
const next = args[i + 1]!;
|
const next = args[i + 1];
|
||||||
if (next === quote || next === "\\" || next === "n" || next === "t") {
|
if (next === quote || next === "\\" || next === "n" || next === "t") {
|
||||||
i += 1;
|
i += 1;
|
||||||
current += decodeEscapedChar(next);
|
current += decodeEscapedChar(next);
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ function resolveModelProfile(modelId: string | null | undefined): {
|
|||||||
durationMs: number;
|
durationMs: number;
|
||||||
intervalMs: number;
|
intervalMs: number;
|
||||||
} {
|
} {
|
||||||
const model = MODELS.find((entry) => entry.id === modelId) ?? MODELS[0]!;
|
const model = MODELS.find((entry) => entry.id === modelId) ?? MODELS[0];
|
||||||
const metadata = model.metadata ?? {};
|
const metadata = model.metadata ?? {};
|
||||||
return {
|
return {
|
||||||
modelId: model.id,
|
modelId: model.id,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { beforeAll, beforeEach, describe, expect, test } from "vitest";
|
import { beforeAll, beforeEach, describe, expect, test } from "vitest";
|
||||||
import pino from "pino";
|
import pino from "pino";
|
||||||
|
|
||||||
import type { AgentSlashCommand } from "../agent-sdk-types.js";
|
|
||||||
import { isCommandAvailable } from "../../../utils/executable.js";
|
import { isCommandAvailable } from "../../../utils/executable.js";
|
||||||
import { OpenCodeAgentClient } from "./opencode-agent.js";
|
import { OpenCodeAgentClient } from "./opencode-agent.js";
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ describe("opencode agent commands contract (real)", () => {
|
|||||||
expect(commands.length).toBeGreaterThan(0);
|
expect(commands.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
for (const command of commands) {
|
for (const command of commands) {
|
||||||
const typed = command as AgentSlashCommand;
|
const typed = command;
|
||||||
expect(typeof typed.name).toBe("string");
|
expect(typeof typed.name).toBe("string");
|
||||||
expect(typed.name.length).toBeGreaterThan(0);
|
expect(typed.name.length).toBeGreaterThan(0);
|
||||||
expect(typed.name.startsWith("/")).toBe(false);
|
expect(typed.name.startsWith("/")).toBe(false);
|
||||||
@@ -63,7 +62,7 @@ describe("opencode agent commands contract (real)", () => {
|
|||||||
expect(commands.length).toBeGreaterThan(0);
|
expect(commands.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
// Pick the first available command and send it without arguments.
|
// Pick the first available command and send it without arguments.
|
||||||
const command = commands[0]!;
|
const command = commands[0];
|
||||||
const events: Array<{ type: string }> = [];
|
const events: Array<{ type: string }> = [];
|
||||||
session.subscribe((event) => events.push(event));
|
session.subscribe((event) => events.push(event));
|
||||||
|
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ describe("opencode agent error handling (real)", () => {
|
|||||||
const terminal = events.find(isTerminalEvent);
|
const terminal = events.find(isTerminalEvent);
|
||||||
expect(terminal).toBeDefined();
|
expect(terminal).toBeDefined();
|
||||||
expect(terminal!.type).toBe("turn_failed");
|
expect(terminal!.type).toBe("turn_failed");
|
||||||
expect((terminal!.type === "turn_failed" ? terminal!.error : "").toLowerCase()).toMatch(
|
expect((terminal!.type === "turn_failed" ? terminal.error : "").toLowerCase()).toMatch(
|
||||||
/insufficient balance|resource package|recharge/,
|
/insufficient balance|resource package|recharge/,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export async function* streamSession(
|
|||||||
turnId = result.turnId;
|
turnId = result.turnId;
|
||||||
|
|
||||||
for (let idx = queue.length - 1; idx >= 0; idx -= 1) {
|
for (let idx = queue.length - 1; idx >= 0; idx -= 1) {
|
||||||
if (!matchesTurn(queue[idx]!)) {
|
if (!matchesTurn(queue[idx])) {
|
||||||
queue.splice(idx, 1);
|
queue.splice(idx, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -703,7 +703,7 @@ export function toShellToolDetail(
|
|||||||
|
|
||||||
export function toReadToolDetail(
|
export function toReadToolDetail(
|
||||||
input: ParsedToolReadInput | null,
|
input: ParsedToolReadInput | null,
|
||||||
output: ParsedToolReadOutput | ParsedToolReadOutputWithPath | null,
|
output: ParsedToolReadOutput | null,
|
||||||
options?: { normalizePath?: NormalizePathFn },
|
options?: { normalizePath?: NormalizePathFn },
|
||||||
): ToolCallDetail | undefined {
|
): ToolCallDetail | undefined {
|
||||||
const filePath = normalizeDetailPath(input?.filePath ?? output?.filePath, options?.normalizePath);
|
const filePath = normalizeDetailPath(input?.filePath ?? output?.filePath, options?.normalizePath);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function tokenizeToolName(name: string): string[] {
|
|||||||
|
|
||||||
export function getToolLeafName(name: string): string | null {
|
export function getToolLeafName(name: string): string | null {
|
||||||
const tokens = tokenizeToolName(name);
|
const tokens = tokenizeToolName(name);
|
||||||
return tokens.length > 0 ? tokens[tokens.length - 1]! : null;
|
return tokens.length > 0 ? tokens[tokens.length - 1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSpeakToolName(name: string): boolean {
|
export function isSpeakToolName(name: string): boolean {
|
||||||
@@ -33,7 +33,7 @@ export function isLikelyNamespacedToolName(name: string): boolean {
|
|||||||
if (segments.length >= 3) {
|
if (segments.length >= 3) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (segments.length === 2 && segments[1]!.includes("_")) {
|
if (segments.length === 2 && segments[1].includes("_")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -49,11 +49,11 @@ export function isPaseoToolName(name: string): boolean {
|
|||||||
return (
|
return (
|
||||||
segments.length >= 3 &&
|
segments.length >= 3 &&
|
||||||
segments[0] === "mcp" &&
|
segments[0] === "mcp" &&
|
||||||
(segments[1] === "paseo" || segments[1]!.startsWith("paseo_"))
|
(segments[1] === "paseo" || segments[1].startsWith("paseo_"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (normalized.includes(".")) {
|
if (normalized.includes(".")) {
|
||||||
const firstSegment = normalized.split(".")[0]!;
|
const firstSegment = normalized.split(".")[0];
|
||||||
return firstSegment === "paseo" || firstSegment.startsWith("paseo_");
|
return firstSegment === "paseo" || firstSegment.startsWith("paseo_");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -66,14 +66,14 @@ export function getPaseoToolLeafName(name: string): string | null {
|
|||||||
if (
|
if (
|
||||||
segments.length >= 3 &&
|
segments.length >= 3 &&
|
||||||
segments[0] === "mcp" &&
|
segments[0] === "mcp" &&
|
||||||
(segments[1] === "paseo" || segments[1]!.startsWith("paseo_"))
|
(segments[1] === "paseo" || segments[1].startsWith("paseo_"))
|
||||||
) {
|
) {
|
||||||
return segments.slice(2).join("__");
|
return segments.slice(2).join("__");
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (normalized.includes(".")) {
|
if (normalized.includes(".")) {
|
||||||
const firstSegment = normalized.split(".")[0]!;
|
const firstSegment = normalized.split(".")[0];
|
||||||
if (firstSegment === "paseo" || firstSegment.startsWith("paseo_")) {
|
if (firstSegment === "paseo" || firstSegment.startsWith("paseo_")) {
|
||||||
return normalized.split(".").slice(1).join(".");
|
return normalized.split(".").slice(1).join(".");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ describe("TTSManager", () => {
|
|||||||
|
|
||||||
expect(calls.length).toBeGreaterThan(1);
|
expect(calls.length).toBeGreaterThan(1);
|
||||||
expect(calls.every((text) => text.length <= 260)).toBe(true);
|
expect(calls.every((text) => text.length <= 260)).toBe(true);
|
||||||
expect(calls[0]!.length).toBeLessThanOrEqual(120);
|
expect(calls[0].length).toBeLessThanOrEqual(120);
|
||||||
expect(calls.slice(1).some((text) => text.length > calls[0]!.length)).toBe(true);
|
expect(calls.slice(1).some((text) => text.length > calls[0].length)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prefetches the next segment before current playback completes", async () => {
|
it("prefetches the next segment before current playback completes", async () => {
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ export class TTSManager {
|
|||||||
|
|
||||||
const scheduleNextSegments = () => {
|
const scheduleNextSegments = () => {
|
||||||
while (nextSegmentToSchedule < segments.length && inflight.size < TTS_PREFETCH_SEGMENTS) {
|
while (nextSegmentToSchedule < segments.length && inflight.size < TTS_PREFETCH_SEGMENTS) {
|
||||||
const segment = segments[nextSegmentToSchedule]!;
|
const segment = segments[nextSegmentToSchedule];
|
||||||
inflight.set(segment.index, this.scheduleSegmentSynthesis(segment, abortSignal));
|
inflight.set(segment.index, this.scheduleSegmentSynthesis(segment, abortSignal));
|
||||||
nextSegmentToSchedule += 1;
|
nextSegmentToSchedule += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function pickTwoDistinctModels(models: Array<{ id: string }>): [string, string]
|
|||||||
if (ids.length < 2) {
|
if (ids.length < 2) {
|
||||||
throw new Error(`Need at least 2 models to test switching; got ${ids.length}`);
|
throw new Error(`Need at least 2 models to test switching; got ${ids.length}`);
|
||||||
}
|
}
|
||||||
return [ids[0]!, ids[1]!];
|
return [ids[0], ids[1]];
|
||||||
}
|
}
|
||||||
|
|
||||||
function isBinaryInstalled(binary: string): boolean {
|
function isBinaryInstalled(binary: string): boolean {
|
||||||
@@ -127,7 +127,7 @@ test("live thinking switching works for Claude (off -> on)", async () => {
|
|||||||
if (!modelList.models || modelList.models.length === 0) {
|
if (!modelList.models || modelList.models.length === 0) {
|
||||||
throw new Error("No Claude models returned");
|
throw new Error("No Claude models returned");
|
||||||
}
|
}
|
||||||
const modelId = modelList.models[0]!.id;
|
const modelId = modelList.models[0].id;
|
||||||
|
|
||||||
const agent = await ctx.client.createAgent({
|
const agent = await ctx.client.createAgent({
|
||||||
provider: "claude",
|
provider: "claude",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function pickOpenCodeModel(
|
|||||||
const preferred = models.find((model) =>
|
const preferred = models.find((model) =>
|
||||||
preferences.some((fragment) => model.id.includes(fragment)),
|
preferences.some((fragment) => model.id.includes(fragment)),
|
||||||
);
|
);
|
||||||
return preferred?.id ?? models[0]!.id;
|
return preferred?.id ?? models[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createHarness(): Promise<{
|
async function createHarness(): Promise<{
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function pickOpenCodeModel(
|
|||||||
const preferred = models.find((model) =>
|
const preferred = models.find((model) =>
|
||||||
preferences.some((fragment) => model.id.includes(fragment)),
|
preferences.some((fragment) => model.id.includes(fragment)),
|
||||||
);
|
);
|
||||||
return preferred?.id ?? models[0]!.id;
|
return preferred?.id ?? models[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasRunningBashToolCall(messages: SessionOutboundMessage[], agentId: string): boolean {
|
function hasRunningBashToolCall(messages: SessionOutboundMessage[], agentId: string): boolean {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ describe("daemon E2E - permission flow: Claude", () => {
|
|||||||
const permissionState = await ctx.client.waitForFinish(agent.id, 5_000);
|
const permissionState = await ctx.client.waitForFinish(agent.id, 5_000);
|
||||||
expect(permissionState.status).toBe("permission");
|
expect(permissionState.status).toBe("permission");
|
||||||
expect(permissionState.final?.pendingPermissions?.length).toBeGreaterThan(0);
|
expect(permissionState.final?.pendingPermissions?.length).toBeGreaterThan(0);
|
||||||
const permission = permissionState.final!.pendingPermissions[0]!;
|
const permission = permissionState.final!.pendingPermissions[0];
|
||||||
|
|
||||||
await ctx.client.respondToPermission(agent.id, permission.id, { behavior: "allow" });
|
await ctx.client.respondToPermission(agent.id, permission.id, { behavior: "allow" });
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ describe("daemon E2E - permission flow: Claude", () => {
|
|||||||
const permissionState = await ctx.client.waitForFinish(agent.id, 5_000);
|
const permissionState = await ctx.client.waitForFinish(agent.id, 5_000);
|
||||||
expect(permissionState.status).toBe("permission");
|
expect(permissionState.status).toBe("permission");
|
||||||
expect(permissionState.final?.pendingPermissions?.length).toBeGreaterThan(0);
|
expect(permissionState.final?.pendingPermissions?.length).toBeGreaterThan(0);
|
||||||
const permission = permissionState.final!.pendingPermissions[0]!;
|
const permission = permissionState.final!.pendingPermissions[0];
|
||||||
|
|
||||||
await ctx.client.respondToPermission(agent.id, permission.id, {
|
await ctx.client.respondToPermission(agent.id, permission.id, {
|
||||||
behavior: "deny",
|
behavior: "deny",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user