authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-09 01:11:20-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-02-09 01:11:20-08:00
log70a9a043eecc0da4d11f30416add232b4bf6e0f1
tree4ae28914083e29f210d790175b572d26ffdecee3
parenteef57db955614e346091769bc6d4e80e0068e9b6
signature Signed by PGP key B5690EEEBB952194

oxc stack (#1)

* oxc stacak * kewl * unused * unused

37 files changed, 217 insertions(+), 407 deletions(-)

.oxfmtrc.json created+12
......@@ -0,0 +1,12 @@
1{
2 "$schema": "./node_modules/oxfmt/configuration_schema.json",
3 "ignorePatterns": [
4 ".plans",
5 "dist",
6 "dist-electron",
7 "node_modules",
8 "bun.lock",
9 "*.tsbuildinfo"
10 ],
11 "experimentalSortPackageJson": {}
12}
.oxlintrc.json created+13
......@@ -0,0 +1,13 @@
1{
2 "$schema": "./node_modules/oxlint/configuration_schema.json",
3 "ignorePatterns": ["dist", "dist-electron", "node_modules", "bun.lock", "*.tsbuildinfo"],
4 "plugins": ["eslint", "oxc", "react", "unicorn", "typescript"],
5 "categories": {
6 "correctness": "warn",
7 "suspicious": "warn",
8 "perf": "warn"
9 },
10 "rules": {
11 "react-in-jsx-scope": "off"
12 }
13}
.vscode/extensions.json created+3
......@@ -0,0 +1,3 @@
1{
2 "recommendations": ["oxc.oxc-vscode"]
3}
.vscode/settings.json created+8
......@@ -0,0 +1,8 @@
1{
2 "editor.formatOnSave": true,
3 "editor.defaultFormatter": "oxc.oxc-vscode",
4 "editor.codeActionsOnSave": {
5 "source.fixAll.oxc": "always"
6 },
7 "oxc.unusedDisableDirectives": "warn"
8}
README.md+1
......@@ -49,6 +49,7 @@ Mode changes apply across all threads. Existing live sessions are restarted so o
4949- `.github/workflows/ci.yml` runs `bun run lint`, `bun run typecheck`, and `bun run test` on pull requests and pushes to `main`.
5050
5151Optional:
52
5253- `ELECTRON_RENDERER_PORT=5180 bun run dev` if `5173` is already in use.
5354
5455## Provider architecture
apps/desktop/package.json+1-3
......@@ -4,14 +4,13 @@
44 "private": true,
55 "main": "dist-electron/main.js",
66 "scripts": {
7 "dev": "concurrently -k -n BUNDLE,ELECTRON \"bun run dev:bundle\" \"bun run dev:electron\"",
7 "dev": "bun run --parallel dev:bundle dev:electron",
88 "dev:bundle": "tsup --watch",
99 "dev:electron": "bun run scripts/dev-electron.mjs",
1010 "build": "tsup",
1111 "start": "electron dist-electron/main.js",
1212 "postinstall": "electron-rebuild",
1313 "typecheck": "tsc --noEmit",
14 "lint": "biome check src/",
1514 "test": "vitest run",
1615 "smoke-test": "node scripts/smoke-test.mjs"
1716 },
......@@ -23,7 +22,6 @@
2322 "devDependencies": {
2423 "@electron/rebuild": "^3.7.0",
2524 "@types/node": "^22.10.2",
26 "concurrently": "^9.1.2",
2725 "electronmon": "^2.0.2",
2826 "tsup": "^8.3.5",
2927 "typescript": "^5.7.3",
apps/desktop/scripts/dev-electron.mjs+2-7
......@@ -6,15 +6,10 @@ const port = Number(process.env.ELECTRON_RENDERER_PORT ?? 5173);
66const devServerUrl = `http://localhost:${port}`;
77
88await waitOn({
9 resources: [
10 `tcp:${port}`,
11 "file:dist-electron/main.js",
12 "file:dist-electron/preload.js",
13 ],
9 resources: [`tcp:${port}`, "file:dist-electron/main.js", "file:dist-electron/preload.js"],
1410});
1511
16const command =
17 process.platform === "win32" ? "electronmon.cmd" : "electronmon";
12const command = process.platform === "win32" ? "electronmon.cmd" : "electronmon";
1813const child = spawn(command, ["dist-electron/main.js"], {
1914 stdio: "inherit",
2015 env: {
apps/desktop/src/codexAppServerManager.test.ts+3-9
......@@ -1,9 +1,6 @@
11import { describe, expect, it } from "vitest";
22
3import {
4 classifyCodexStderrLine,
5 normalizeCodexModelSlug,
6} from "./codexAppServerManager";
3import { classifyCodexStderrLine, normalizeCodexModelSlug } from "./codexAppServerManager";
74
85describe("classifyCodexStderrLine", () => {
96 it("ignores empty lines", () => {
......@@ -23,8 +20,7 @@ describe("classifyCodexStderrLine", () => {
2320 });
2421
2522 it("keeps unknown structured errors", () => {
26 const line =
27 "2026-02-08T04:24:20.085687Z ERROR codex_core::runtime: unrecoverable failure";
23 const line = "2026-02-08T04:24:20.085687Z ERROR codex_core::runtime: unrecoverable failure";
2824 expect(classifyCodexStderrLine(line)).toEqual({
2925 message: line,
3026 });
......@@ -45,9 +41,7 @@ describe("normalizeCodexModelSlug", () => {
4541 });
4642
4743 it("prefers codex id when model differs", () => {
48 expect(normalizeCodexModelSlug("gpt-5.3", "gpt-5.3-codex")).toBe(
49 "gpt-5.3-codex",
50 );
44 expect(normalizeCodexModelSlug("gpt-5.3", "gpt-5.3-codex")).toBe("gpt-5.3-codex");
5145 });
5246
5347 it("keeps non-aliased models as-is", () => {
apps/desktop/src/codexAppServerManager.ts+24-89
......@@ -91,9 +91,7 @@ export function normalizeCodexModelSlug(
9191 return normalized;
9292}
9393
94export function classifyCodexStderrLine(
95 rawLine: string,
96): { message: string } | null {
94export function classifyCodexStderrLine(rawLine: string): { message: string } | null {
9795 const line = rawLine.replaceAll(ANSI_ESCAPE_REGEX, "").trim();
9896 if (!line) {
9997 return null;
......@@ -106,9 +104,7 @@ export function classifyCodexStderrLine(
106104 return null;
107105 }
108106
109 const isBenignError = BENIGN_ERROR_LOG_SNIPPETS.some((snippet) =>
110 line.includes(snippet),
111 );
107 const isBenignError = BENIGN_ERROR_LOG_SNIPPETS.some((snippet) => line.includes(snippet));
112108 if (isBenignError) {
113109 return null;
114110 }
......@@ -124,9 +120,7 @@ export interface CodexAppServerManagerEvents {
124120export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEvents> {
125121 private readonly sessions = new Map<string, CodexSessionContext>();
126122
127 async startSession(
128 input: ProviderSessionStartInput,
129 ): Promise<ProviderSession> {
123 async startSession(input: ProviderSessionStartInput): Promise<ProviderSession> {
130124 const sessionId = randomUUID();
131125 const now = new Date().toISOString();
132126
......@@ -160,11 +154,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
160154 this.sessions.set(sessionId, context);
161155 this.attachProcessListeners(context);
162156
163 this.emitLifecycleEvent(
164 context,
165 "session/connecting",
166 "Starting codex app-server",
167 );
157 this.emitLifecycleEvent(context, "session/connecting", "Starting codex app-server");
168158
169159 try {
170160 await this.sendRequest(context, "initialize", {
......@@ -188,10 +178,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
188178 experimentalRawEvents: false,
189179 });
190180
191 const threadId = this.readString(
192 this.readObject(threadStart)?.thread,
193 "id",
194 );
181 const threadId = this.readString(this.readObject(threadStart)?.thread, "id");
195182 if (!threadId) {
196183 throw new Error("thread/start response did not include a thread id.");
197184 }
......@@ -200,30 +187,21 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
200187 status: "ready",
201188 threadId,
202189 });
203 this.emitLifecycleEvent(
204 context,
205 "session/ready",
206 `Connected to thread ${threadId}`,
207 );
190 this.emitLifecycleEvent(context, "session/ready", `Connected to thread ${threadId}`);
208191 return { ...context.session };
209192 } catch (error) {
210 const message =
211 error instanceof Error
212 ? error.message
213 : "Failed to start Codex session.";
193 const message = error instanceof Error ? error.message : "Failed to start Codex session.";
214194 this.updateSession(context, {
215195 status: "error",
216196 lastError: message,
217197 });
218198 this.emitErrorEvent(context, "session/startFailed", message);
219199 this.stopSession(sessionId);
220 throw new Error(message);
200 throw new Error(message, { cause: error });
221201 }
222202 }
223203
224 async sendTurn(
225 input: ProviderSendTurnInput,
226 ): Promise<ProviderTurnStartResult> {
204 async sendTurn(input: ProviderSendTurnInput): Promise<ProviderTurnStartResult> {
227205 const context = this.requireSession(input.sessionId);
228206 if (!context.session.threadId) {
229207 throw new Error("Session is missing a thread id.");
......@@ -252,11 +230,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
252230 turnStartParams.effort = input.effort;
253231 }
254232
255 const response = await this.sendRequest(
256 context,
257 "turn/start",
258 turnStartParams,
259 );
233 const response = await this.sendRequest(context, "turn/start", turnStartParams);
260234
261235 const turn = this.readObject(this.readObject(response), "turn");
262236 const turnId = this.readString(turn, "id");
......@@ -498,10 +472,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
498472 });
499473
500474 if (notification.method === "thread/started") {
501 const threadId = this.readString(
502 this.readObject(notification.params)?.thread,
503 "id",
504 );
475 const threadId = this.readString(this.readObject(notification.params)?.thread, "id");
505476 if (threadId) {
506477 this.updateSession(context, { threadId });
507478 }
......@@ -509,10 +480,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
509480 }
510481
511482 if (notification.method === "turn/started") {
512 const turnId = this.readString(
513 this.readObject(notification.params)?.turn,
514 "id",
515 );
483 const turnId = this.readString(this.readObject(notification.params)?.turn, "id");
516484 this.updateSession(context, {
517485 status: "running",
518486 activeTurnId: turnId,
......@@ -523,10 +491,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
523491 if (notification.method === "turn/completed") {
524492 const turn = this.readObject(notification.params, "turn");
525493 const status = this.readString(turn, "status");
526 const errorMessage = this.readString(
527 this.readObject(turn, "error"),
528 "message",
529 );
494 const errorMessage = this.readString(this.readObject(turn, "error"), "message");
530495 this.updateSession(context, {
531496 status: status === "failed" ? "error" : "ready",
532497 activeTurnId: undefined,
......@@ -536,10 +501,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
536501 }
537502
538503 if (notification.method === "error") {
539 const message = this.readString(
540 this.readObject(notification.params)?.error,
541 "message",
542 );
504 const message = this.readString(this.readObject(notification.params)?.error, "message");
543505 const willRetry = this.readBoolean(notification.params, "willRetry");
544506
545507 this.updateSession(context, {
......@@ -549,10 +511,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
549511 }
550512 }
551513
552 private handleServerRequest(
553 context: CodexSessionContext,
554 request: JsonRpcRequest,
555 ): void {
514 private handleServerRequest(context: CodexSessionContext, request: JsonRpcRequest): void {
556515 const route = this.readRouteFields(request.params);
557516 const requestKind = this.requestKindForMethod(request.method);
558517 let requestId: string | undefined;
......@@ -609,10 +568,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
609568 });
610569 }
611570
612 private handleResponse(
613 context: CodexSessionContext,
614 response: JsonRpcResponse,
615 ): void {
571 private handleResponse(context: CodexSessionContext, response: JsonRpcResponse): void {
616572 const key = String(response.id);
617573 const pending = context.pending.get(key);
618574 if (!pending) {
......@@ -623,11 +579,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
623579 context.pending.delete(key);
624580
625581 if (response.error?.message) {
626 pending.reject(
627 new Error(
628 `${pending.method} failed: ${String(response.error.message)}`,
629 ),
630 );
582 pending.reject(new Error(`${pending.method} failed: ${String(response.error.message)}`));
631583 return;
632584 }
633585
......@@ -674,11 +626,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
674626 context.child.stdin.write(`${encoded}\n`);
675627 }
676628
677 private emitLifecycleEvent(
678 context: CodexSessionContext,
679 method: string,
680 message: string,
681 ): void {
629 private emitLifecycleEvent(context: CodexSessionContext, method: string, message: string): void {
682630 this.emitEvent({
683631 id: randomUUID(),
684632 kind: "session",
......@@ -690,11 +638,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
690638 });
691639 }
692640
693 private emitErrorEvent(
694 context: CodexSessionContext,
695 method: string,
696 message: string,
697 ): void {
641 private emitErrorEvent(context: CodexSessionContext, method: string, message: string): void {
698642 this.emitEvent({
699643 id: randomUUID(),
700644 kind: "error",
......@@ -710,10 +654,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
710654 this.emit("event", event);
711655 }
712656
713 private updateSession(
714 context: CodexSessionContext,
715 updates: Partial<ProviderSession>,
716 ): void {
657 private updateSession(context: CodexSessionContext, updates: Partial<ProviderSession>): void {
717658 context.session = {
718659 ...context.session,
719660 ...updates,
......@@ -762,8 +703,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
762703 }
763704
764705 const candidate = value as Record<string, unknown>;
765 const hasId =
766 typeof candidate.id === "string" || typeof candidate.id === "number";
706 const hasId = typeof candidate.id === "string" || typeof candidate.id === "number";
767707 const hasMethod = typeof candidate.method === "string";
768708 return hasId && !hasMethod;
769709 }
......@@ -783,11 +723,9 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
783723 this.readString(params, "threadId") ??
784724 this.readString(this.readObject(params, "thread"), "id");
785725 const turnId =
786 this.readString(params, "turnId") ??
787 this.readString(this.readObject(params, "turn"), "id");
726 this.readString(params, "turnId") ?? this.readString(this.readObject(params, "turn"), "id");
788727 const itemId =
789 this.readString(params, "itemId") ??
790 this.readString(this.readObject(params, "item"), "id");
728 this.readString(params, "itemId") ?? this.readString(this.readObject(params, "item"), "id");
791729
792730 if (threadId) {
793731 route.threadId = threadId;
......@@ -804,10 +742,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
804742 return route;
805743 }
806744
807 private readObject(
808 value: unknown,
809 key?: string,
810 ): Record<string, unknown> | undefined {
745 private readObject(value: unknown, key?: string): Record<string, unknown> | undefined {
811746 const target =
812747 key === undefined
813748 ? value
apps/desktop/src/ipcHelpers.test.ts+3-5
......@@ -55,11 +55,9 @@ describe("withParsedPayload", () => {
5555
5656describe("withParsedArgs", () => {
5757 it("parses tuple arguments before invoking handler", () => {
58 const handler = vi.fn(
59 (_event: unknown, sessionId: string, data: string) => {
60 return `${sessionId}:${data}`;
61 },
62 );
58 const handler = vi.fn((_event: unknown, sessionId: string, data: string) => {
59 return `${sessionId}:${data}`;
60 });
6361 const wrapped = withParsedArgs(
6462 {
6563 parse(args: unknown[]): [string, string] {
apps/desktop/src/main.ts+12-26
......@@ -105,8 +105,7 @@ function registerIpcHandlers(): void {
105105 );
106106
107107 ipcMain.handle(IPC_CHANNELS.dialogPickFolder, async () => {
108 const owner =
109 BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0];
108 const owner = BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0];
110109 const result = owner
111110 ? await dialog.showOpenDialog(owner, {
112111 properties: ["openDirectory", "createDirectory"],
......@@ -175,12 +174,9 @@ function registerIpcHandlers(): void {
175174 // Provider handlers
176175 ipcMain.handle(
177176 IPC_CHANNELS.providerSessionStart,
178 withParsedPayload(
179 providerSessionStartInputSchema,
180 async (_event, payload) => {
181 return providerManager.startSession(payload);
182 },
183 ),
177 withParsedPayload(providerSessionStartInputSchema, async (_event, payload) => {
178 return providerManager.startSession(payload);
179 }),
184180 );
185181
186182 ipcMain.handle(
......@@ -192,12 +188,9 @@ function registerIpcHandlers(): void {
192188
193189 ipcMain.handle(
194190 IPC_CHANNELS.providerTurnInterrupt,
195 withParsedPayload(
196 providerInterruptTurnInputSchema,
197 async (_event, payload) => {
198 await providerManager.interruptTurn(payload);
199 },
200 ),
191 withParsedPayload(providerInterruptTurnInputSchema, async (_event, payload) => {
192 await providerManager.interruptTurn(payload);
193 }),
201194 );
202195
203196 ipcMain.handle(
......@@ -212,12 +205,9 @@ function registerIpcHandlers(): void {
212205
213206 ipcMain.handle(
214207 IPC_CHANNELS.providerSessionStop,
215 withParsedPayload(
216 providerStopSessionInputSchema,
217 async (_event, payload) => {
218 providerManager.stopSession(payload);
219 },
220 ),
208 withParsedPayload(providerStopSessionInputSchema, async (_event, payload) => {
209 providerManager.stopSession(payload);
210 }),
221211 );
222212
223213 ipcMain.handle(IPC_CHANNELS.providerSessionList, async () => {
......@@ -225,18 +215,14 @@ function registerIpcHandlers(): void {
225215 });
226216}
227217
228async function runTerminalCommand(
229 input: TerminalCommandInput,
230): Promise<TerminalCommandResult> {
218async function runTerminalCommand(input: TerminalCommandInput): Promise<TerminalCommandResult> {
231219 const shellPath =
232220 process.platform === "win32"
233221 ? (process.env.ComSpec ?? "cmd.exe")
234222 : (process.env.SHELL ?? "/bin/sh");
235223
236224 const args =
237 process.platform === "win32"
238 ? ["/d", "/s", "/c", input.command]
239 : ["-lc", input.command];
225 process.platform === "win32" ? ["/d", "/s", "/c", input.command] : ["-lc", input.command];
240226
241227 return new Promise((resolve, reject) => {
242228 const child = spawn(shellPath, args, {
apps/desktop/src/preload.ts+8-16
......@@ -18,14 +18,12 @@ const nativeApi: NativeApi = {
1818 agent: {
1919 spawn: (config) => ipcRenderer.invoke(IPC_CHANNELS.agentSpawn, config),
2020 kill: (sessionId) => ipcRenderer.invoke(IPC_CHANNELS.agentKill, sessionId),
21 write: (sessionId, data) =>
22 ipcRenderer.invoke(IPC_CHANNELS.agentWrite, sessionId, data),
21 write: (sessionId, data) => ipcRenderer.invoke(IPC_CHANNELS.agentWrite, sessionId, data),
2322 onOutput: (callback) => {
2423 const listener = (_event: Electron.IpcRendererEvent, chunk: unknown) =>
2524 callback(chunk as Parameters<typeof callback>[0]);
2625 ipcRenderer.on(IPC_CHANNELS.agentOutput, listener);
27 return () =>
28 ipcRenderer.removeListener(IPC_CHANNELS.agentOutput, listener);
26 return () => ipcRenderer.removeListener(IPC_CHANNELS.agentOutput, listener);
2927 },
3028 onExit: (callback) => {
3129 const listener = (_event: Electron.IpcRendererEvent, exit: unknown) =>
......@@ -35,23 +33,17 @@ const nativeApi: NativeApi = {
3533 },
3634 },
3735 providers: {
38 startSession: (input) =>
39 ipcRenderer.invoke(IPC_CHANNELS.providerSessionStart, input),
40 sendTurn: (input) =>
41 ipcRenderer.invoke(IPC_CHANNELS.providerTurnStart, input),
42 interruptTurn: (input) =>
43 ipcRenderer.invoke(IPC_CHANNELS.providerTurnInterrupt, input),
44 respondToRequest: (input) =>
45 ipcRenderer.invoke(IPC_CHANNELS.providerRequestRespond, input),
46 stopSession: (input) =>
47 ipcRenderer.invoke(IPC_CHANNELS.providerSessionStop, input),
36 startSession: (input) => ipcRenderer.invoke(IPC_CHANNELS.providerSessionStart, input),
37 sendTurn: (input) => ipcRenderer.invoke(IPC_CHANNELS.providerTurnStart, input),
38 interruptTurn: (input) => ipcRenderer.invoke(IPC_CHANNELS.providerTurnInterrupt, input),
39 respondToRequest: (input) => ipcRenderer.invoke(IPC_CHANNELS.providerRequestRespond, input),
40 stopSession: (input) => ipcRenderer.invoke(IPC_CHANNELS.providerSessionStop, input),
4841 listSessions: () => ipcRenderer.invoke(IPC_CHANNELS.providerSessionList),
4942 onEvent: (callback) => {
5043 const listener = (_event: Electron.IpcRendererEvent, payload: unknown) =>
5144 callback(payload as Parameters<typeof callback>[0]);
5245 ipcRenderer.on(IPC_CHANNELS.providerEvent, listener);
53 return () =>
54 ipcRenderer.removeListener(IPC_CHANNELS.providerEvent, listener);
46 return () => ipcRenderer.removeListener(IPC_CHANNELS.providerEvent, listener);
5547 },
5648 },
5749 shell: {
apps/desktop/src/processManager.ts+1-4
......@@ -68,7 +68,6 @@ export class ProcessManager extends EventEmitter<ProcessManagerEvents> {
6868 }
6969
7070 private spawnPty(sessionId: string, config: AgentConfig): string {
71 // eslint-disable-next-line @typescript-eslint/no-var-requires
7271 const pty = require("node-pty") as typeof import("node-pty");
7372
7473 const ptyProcess = pty.spawn(config.command, config.args, {
......@@ -76,9 +75,7 @@ export class ProcessManager extends EventEmitter<ProcessManagerEvents> {
7675 cols: 120,
7776 rows: 30,
7877 cwd: config.cwd ?? process.cwd(),
79 env: (config.env
80 ? { ...process.env, ...config.env }
81 : process.env) as Record<string, string>,
78 env: (config.env ? { ...process.env, ...config.env } : process.env) as Record<string, string>,
8279 });
8380
8481 this.ptySessions.set(sessionId, ptyProcess);
apps/desktop/src/todoStore.ts+1-5
......@@ -82,11 +82,7 @@ export class TodoStore {
8282 }
8383
8484 private async persist(): Promise<void> {
85 await fs.writeFile(
86 this.filePath,
87 JSON.stringify(this.todos, null, 2),
88 "utf8",
89 );
85 await fs.writeFile(this.filePath, JSON.stringify(this.todos, null, 2), "utf8");
9086 }
9187
9288 private async runExclusive<T>(operation: () => Promise<T>): Promise<T> {
apps/desktop/tsconfig.json+1-1
......@@ -3,7 +3,7 @@
33 "compilerOptions": {
44 "composite": true,
55 "types": ["node", "electron"],
6 "lib": ["ES2022", "DOM"]
6 "lib": ["ES2023", "DOM"]
77 },
88 "include": ["src", "tsup.config.ts"]
99}
apps/renderer/package.json+1-2
......@@ -8,15 +8,14 @@
88 "build": "vite build",
99 "preview": "vite preview",
1010 "typecheck": "tsc --noEmit",
11 "lint": "biome check src/",
1211 "test": "vitest run --passWithNoTests"
1312 },
1413 "dependencies": {
1514 "@acme/contracts": "workspace:*",
1615 "highlight.js": "^11.11.1",
17 "react-markdown": "^10.1.0",
1816 "react": "^19.0.0",
1917 "react-dom": "^19.0.0",
18 "react-markdown": "^10.1.0",
2019 "rehype-highlight": "^7.0.2",
2120 "remark-gfm": "^4.0.1",
2221 "zod": "^3.24.1"
apps/renderer/src/components/ChatMarkdown.tsx+1-3
......@@ -18,9 +18,7 @@ export default function ChatMarkdown({ text }: ChatMarkdownProps) {
1818 <div className="chat-markdown text-sm leading-relaxed text-[#d0d0d0]">
1919 <ReactMarkdown
2020 remarkPlugins={[remarkGfm]}
21 rehypePlugins={[
22 [rehypeHighlight, { detect: true, ignoreMissing: true }],
23 ]}
21 rehypePlugins={[[rehypeHighlight, { detect: true, ignoreMissing: true }]]}
2422 components={markdownComponents}
2523 >
2624 {text}
apps/renderer/src/components/ChatView.tsx+20-50
......@@ -144,9 +144,7 @@ export default function ChatView() {
144144 const editorMenuRef = useRef<HTMLDivElement>(null);
145145
146146 const activeThread = state.threads.find((t) => t.id === state.activeThreadId);
147 const activeProject = state.projects.find(
148 (p) => p.id === activeThread?.projectId,
149 );
147 const activeProject = state.projects.find((p) => p.id === activeThread?.projectId);
150148 const selectedModel = resolveModelSlug(
151149 activeThread?.model ?? activeProject?.model ?? DEFAULT_MODEL,
152150 );
......@@ -164,7 +162,7 @@ export default function ChatView() {
164162 );
165163 const assistantCompletionByItemId = useMemo(() => {
166164 const map = new Map<string, string>();
167 const ordered = [...(activeThread?.events ?? [])].reverse();
165 const ordered = [...(activeThread?.events ?? [])].toReversed();
168166 for (const event of ordered) {
169167 if (event.method !== "item/completed") continue;
170168 if (!event.itemId) continue;
......@@ -264,18 +262,15 @@ export default function ChatView() {
264262 // Auto-scroll on new messages
265263 const messageCount = activeThread?.messages.length ?? 0;
266264 const workLogCount = workLogEntries.length;
267 // biome-ignore lint/correctness/useExhaustiveDependencies: trigger on message count change
268265 useEffect(() => {
269266 messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
270267 }, [messageCount]);
271 // biome-ignore lint/correctness/useExhaustiveDependencies: auto-scroll while active work-log events stream in
272268 useEffect(() => {
273269 if (phase !== "running") return;
274270 messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
275271 }, [phase, workLogCount]);
276272
277273 // Auto-resize textarea
278 // biome-ignore lint/correctness/useExhaustiveDependencies: trigger on prompt change
279274 useEffect(() => {
280275 const ta = textareaRef.current;
281276 if (!ta) return;
......@@ -298,10 +293,7 @@ export default function ChatView() {
298293
299294 const handleClickOutside = (event: MouseEvent) => {
300295 if (!modelMenuRef.current) return;
301 if (
302 event.target instanceof Node &&
303 !modelMenuRef.current.contains(event.target)
304 ) {
296 if (event.target instanceof Node && !modelMenuRef.current.contains(event.target)) {
305297 setIsModelMenuOpen(false);
306298 }
307299 };
......@@ -394,8 +386,7 @@ export default function ChatView() {
394386
395387 // Auto-title from first message
396388 if (activeThread.messages.length === 0) {
397 const title =
398 trimmed.length > 50 ? `${trimmed.slice(0, 50)}...` : trimmed;
389 const title = trimmed.length > 50 ? `${trimmed.slice(0, 50)}...` : trimmed;
399390 dispatch({
400391 type: "SET_THREAD_TITLE",
401392 threadId: activeThread.id,
......@@ -501,9 +492,7 @@ export default function ChatView() {
501492 <div className="drag-region h-[52px] shrink-0" />
502493 <div className="flex flex-1 items-center justify-center">
503494 <div className="text-center">
504 <p className="text-sm">
505 Select a thread or create a new one to get started.
506 </p>
495 <p className="text-sm">Select a thread or create a new one to get started.</p>
507496 </div>
508497 </div>
509498 </div>
......@@ -513,7 +502,7 @@ export default function ChatView() {
513502 return (
514503 <div className="flex flex-1 flex-col bg-[#0c0c0c]">
515504 {/* Top bar */}
516 <header className="drag-region flex items-center justify-between border-b border-white/[0.08] px-5 pt-[28px] pb-3">
505 <header className="drag-region flex items-center justify-between border-b border-white/8 px-5 pt-[28px] pb-3">
517506 <div className="flex items-center gap-3">
518507 <h2 className="text-sm font-medium text-[#e0e0e0]">
519508 {activeThread.title}
......@@ -654,9 +643,7 @@ export default function ChatView() {
654643 <div className="flex-1 overflow-y-auto px-5 py-4">
655644 {activeThread.messages.length === 0 && !isWorking ? (
656645 <div className="flex h-full items-center justify-center">
657 <p className="text-sm text-[#a0a0a0]/30">
658 Send a message to start the conversation.
659 </p>
646 <p className="text-sm text-[#a0a0a0]/30">Send a message to start the conversation.</p>
660647 </div>
661648 ) : (
662649 <div className="mx-auto max-w-3xl space-y-4">
......@@ -770,7 +757,7 @@ export default function ChatView() {
770757 {/* Input bar */}
771758 <div className="px-5 pb-4 pt-2">
772759 <form onSubmit={onSend} className="mx-auto max-w-3xl">
773 <div className="group rounded-[20px] border border-white/[0.08] bg-[#141416] transition-colors duration-200 focus-within:border-white/[0.16]">
760 <div className="group rounded-[20px] border border-white/8 bg-[#141416] transition-colors duration-200 focus-within:border-white/16">
774761 {/* Textarea area */}
775762 <div className="px-4 pt-4 pb-2">
776763 <textarea
......@@ -781,9 +768,7 @@ export default function ChatView() {
781768 onChange={(e) => setPrompt(e.target.value)}
782769 onKeyDown={onKeyDown}
783770 placeholder={
784 phase === "disconnected"
785 ? "Ask for follow-up changes"
786 : "Ask anything..."
771 phase === "disconnected" ? "Ask for follow-up changes" : "Ask anything..."
787772 }
788773 disabled={isSending || isConnecting}
789774 />
......@@ -796,12 +781,10 @@ export default function ChatView() {
796781 <div className="relative" ref={modelMenuRef}>
797782 <button
798783 type="button"
799 className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.06] hover:text-[#d0d0d0]"
784 className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/6 hover:text-[#d0d0d0]"
800785 onClick={() => setIsModelMenuOpen((open) => !open)}
801786 >
802 <span className="max-w-[180px] truncate">
803 {selectedModel}
804 </span>
787 <span className="max-w-[180px] truncate">{selectedModel}</span>
805788 <svg
806789 width="10"
807790 height="10"
......@@ -820,10 +803,8 @@ export default function ChatView() {
820803 </svg>
821804 </button>
822805 {isModelMenuOpen && (
823 <div className="absolute bottom-full left-0 z-20 mb-2 w-[320px] rounded-2xl border border-white/[0.1] bg-[#1b1b1d]/95 p-2 shadow-[0_16px_40px_rgba(0,0,0,0.55)] backdrop-blur">
824 <p className="px-2 py-1 text-[11px] text-[#a0a0a0]/70">
825 Select model
826 </p>
806 <div className="absolute bottom-full left-0 z-20 mb-2 w-[320px] rounded-2xl border border-white/10 bg-[#1b1b1d]/95 p-2 shadow-[0_16px_40px_rgba(0,0,0,0.55)] backdrop-blur">
807 <p className="px-2 py-1 text-[11px] text-[#a0a0a0]/70">Select model</p>
827808 <div className="max-h-72 overflow-y-auto">
828809 {modelOptions.map((model) => {
829810 const isSelected = model === selectedModel;
......@@ -833,8 +814,8 @@ export default function ChatView() {
833814 type="button"
834815 className={`mb-0.5 flex w-full items-center justify-between gap-2 rounded-xl px-2 py-2 text-left font-mono text-sm transition-colors duration-150 ${
835816 isSelected
836 ? "bg-white/[0.08] text-white"
837 : "text-[#d4d4d4] hover:bg-white/[0.05]"
817 ? "bg-white/8 text-white"
818 : "text-[#d4d4d4] hover:bg-white/5"
838819 }`}
839820 onClick={() => onModelSelect(model)}
840821 >
......@@ -855,17 +836,14 @@ export default function ChatView() {
855836 </div>
856837
857838 {/* Divider */}
858 <div className="mx-0.5 h-4 w-px bg-white/[0.08]" />
839 <div className="mx-0.5 h-4 w-px bg-white/8" />
859840
860841 {/* Reasoning effort */}
861842 <label
862 className="inline-flex cursor-pointer items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.06] hover:text-[#d0d0d0]"
843 className="inline-flex cursor-pointer items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/6 hover:text-[#d0d0d0]"
863844 htmlFor="reasoning-effort"
864845 >
865 <span>
866 {selectedEffort.charAt(0).toUpperCase() +
867 selectedEffort.slice(1)}
868 </span>
846 <span>{selectedEffort.charAt(0).toUpperCase() + selectedEffort.slice(1)}</span>
869847 <select
870848 id="reasoning-effort"
871849 className="absolute opacity-0 w-0 h-0"
......@@ -873,11 +851,7 @@ export default function ChatView() {
873851 onChange={(event) => setSelectedEffort(event.target.value)}
874852 >
875853 {REASONING_OPTIONS.map((effort) => (
876 <option
877 key={effort}
878 value={effort}
879 className="bg-[#1b1b1d]"
880 >
854 <option key={effort} value={effort} className="bg-[#1b1b1d]">
881855 {effort}
882856 {effort === DEFAULT_REASONING ? " (default)" : ""}
883857 </option>
......@@ -1004,11 +978,7 @@ export default function ChatView() {
1004978 className="flex h-8 w-8 items-center justify-center rounded-full bg-white/90 text-[#0c0c0c] transition-all duration-150 hover:bg-white hover:scale-105 disabled:opacity-30 disabled:hover:scale-100"
1005979 disabled={isSending || isConnecting || !prompt.trim()}
1006980 aria-label={
1007 isConnecting
1008 ? "Connecting"
1009 : isSending
1010 ? "Sending"
1011 : "Send message"
981 isConnecting ? "Connecting" : isSending ? "Sending" : "Send message"
1012982 }
1013983 >
1014984 {isConnecting || isSending ? (
apps/renderer/src/components/DiffPanel.tsx+4-12
......@@ -7,9 +7,7 @@ export default function DiffPanel() {
77 <aside className="flex h-full w-[400px] shrink-0 flex-col border-l border-white/[0.08] bg-[#141414]">
88 {/* Header */}
99 <div className="drag-region flex items-center justify-between border-b border-white/[0.08] px-4 pt-[28px] pb-3">
10 <h3 className="text-xs font-medium text-[#e0e0e0]">
11 Uncommitted changes
12 </h3>
10 <h3 className="text-xs font-medium text-[#e0e0e0]">Uncommitted changes</h3>
1311 <button
1412 type="button"
1513 className="text-[#a0a0a0]/40 transition-colors duration-150 hover:text-[#a0a0a0]/70"
......@@ -27,15 +25,11 @@ export default function DiffPanel() {
2725 <span className="rounded bg-emerald-500/20 px-1.5 py-0.5 text-[9px] font-medium text-emerald-400">
2826 M
2927 </span>
30 <span className="font-mono text-xs text-[#a0a0a0]/60">
31 src/App.tsx
32 </span>
28 <span className="font-mono text-xs text-[#a0a0a0]/60">src/App.tsx</span>
3329 </div>
3430 <div className="space-y-0.5 font-mono text-[11px]">
3531 <div className="text-red-400/60">- const oldValue = "hello";</div>
36 <div className="text-emerald-400/60">
37 + const newValue = "world";
38 </div>
32 <div className="text-emerald-400/60">+ const newValue = "world";</div>
3933 </div>
4034 </div>
4135
......@@ -44,9 +38,7 @@ export default function DiffPanel() {
4438 <span className="rounded bg-blue-500/20 px-1.5 py-0.5 text-[9px] font-medium text-blue-400">
4539 A
4640 </span>
47 <span className="font-mono text-xs text-[#a0a0a0]/60">
48 src/utils.ts
49 </span>
41 <span className="font-mono text-xs text-[#a0a0a0]/60">src/utils.ts</span>
5042 </div>
5143 </div>
5244 </div>
apps/renderer/src/components/Sidebar.tsx+14-22
......@@ -120,7 +120,7 @@ export default function Sidebar() {
120120 }, [handleNewThread, state.activeThreadId, state.projects, state.threads]);
121121
122122 return (
123 <aside className="sidebar flex h-full w-[260px] shrink-0 flex-col border-r border-white/[0.08] bg-[#141414]">
123 <aside className="sidebar flex h-full w-[260px] shrink-0 flex-col border-r border-white/8 bg-[#141414]">
124124 {/* Drag region / traffic light space */}
125125 <div className="drag-region h-[52px] shrink-0" />
126126 {/* Branding */}
......@@ -128,16 +128,14 @@ export default function Sidebar() {
128128 <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-white text-xs font-bold text-[#0c0c0c]">
129129 CT
130130 </div>
131 <span className="text-sm font-semibold tracking-tight text-[#e0e0e0]">
132 CodeThing
133 </span>
131 <span className="text-sm font-semibold tracking-tight text-[#e0e0e0]">CodeThing</span>
134132 </div>
135133
136134 {/* New thread (global) */}
137135 <div className="px-3 pb-3">
138136 <button
139137 type="button"
140 className="flex w-full items-center gap-2 rounded-lg border border-white/[0.08] bg-white/[0.04] px-3 py-2 text-xs text-[#a0a0a0]/80 transition-colors duration-150 hover:bg-white/[0.07]"
138 className="flex w-full items-center gap-2 rounded-lg border border-white/8 bg-white/4 px-3 py-2 text-xs text-[#a0a0a0]/80 transition-colors duration-150 hover:bg-white/[0.07]"
141139 onClick={() => {
142140 if (state.projects.length === 0) {
143141 setAddingProject(true);
......@@ -169,10 +167,8 @@ export default function Sidebar() {
169167 {/* Project header */}
170168 <button
171169 type="button"
172 className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 hover:bg-white/[0.05]"
173 onClick={() =>
174 dispatch({ type: "TOGGLE_PROJECT", projectId: project.id })
175 }
170 className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 hover:bg-white/5"
171 onClick={() => dispatch({ type: "TOGGLE_PROJECT", projectId: project.id })}
176172 >
177173 <span className="text-[10px] text-[#a0a0a0]/50">
178174 {project.expanded ? "â–¼" : "â–¶"}
......@@ -180,14 +176,12 @@ export default function Sidebar() {
180176 <span className="flex-1 truncate text-xs font-medium text-[#e0e0e0]/90">
181177 {project.name}
182178 </span>
183 <span className="text-[10px] text-[#a0a0a0]/40">
184 {threads.length}
185 </span>
179 <span className="text-[10px] text-[#a0a0a0]/40">{threads.length}</span>
186180 </button>
187181
188182 {/* Threads */}
189183 {project.expanded && (
190 <div className="ml-2 border-l border-white/[0.06] pl-2">
184 <div className="ml-2 border-l border-white/6 pl-2">
191185 {threads.map((thread) => {
192186 const isActive = state.activeThreadId === thread.id;
193187 const threadStatus = threadStatusLabel(
......@@ -198,9 +192,7 @@ export default function Sidebar() {
198192 key={thread.id}
199193 type="button"
200194 className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 ${
201 isActive
202 ? "bg-white/10 text-white"
203 : "text-[#a0a0a0]/70 hover:bg-white/[0.04]"
195 isActive ? "bg-white/10 text-white" : "text-[#a0a0a0]/70 hover:bg-white/4"
204196 }`}
205197 onClick={() =>
206198 dispatch({
......@@ -254,12 +246,12 @@ export default function Sidebar() {
254246
255247 {/* Add project form */}
256248 {addingProject ? (
257 <div className="border-t border-white/[0.08] p-3">
249 <div className="border-t border-white/8 p-3">
258250 <p className="mb-2 text-[10px] font-medium uppercase tracking-wider text-[#a0a0a0]/50">
259251 Add project
260252 </p>
261253 <input
262 className="mb-2 w-full rounded-md border border-white/[0.1] bg-white/[0.04] px-2 py-1.5 font-mono text-xs text-[#e0e0e0] placeholder:text-[#a0a0a0]/30 focus:border-white/30 focus:outline-none"
254 className="mb-2 w-full rounded-md border border-white/10 bg-white/4 px-2 py-1.5 font-mono text-xs text-[#e0e0e0] placeholder:text-[#a0a0a0]/30 focus:border-white/30 focus:outline-none"
263255 placeholder="/path/to/project"
264256 value={newCwd}
265257 onChange={(e) => setNewCwd(e.target.value)}
......@@ -271,7 +263,7 @@ export default function Sidebar() {
271263 {api && (
272264 <button
273265 type="button"
274 className="mb-2 flex w-full items-center justify-center rounded-md border border-white/[0.1] px-2 py-1.5 text-xs text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.04] disabled:cursor-not-allowed disabled:opacity-60"
266 className="mb-2 flex w-full items-center justify-center rounded-md border border-white/10 px-2 py-1.5 text-xs text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/4 disabled:cursor-not-allowed disabled:opacity-60"
275267 onClick={() => void handlePickFolder()}
276268 disabled={isPickingFolder}
277269 >
......@@ -288,7 +280,7 @@ export default function Sidebar() {
288280 </button>
289281 <button
290282 type="button"
291 className="flex-1 rounded-md border border-white/[0.1] px-2 py-1 text-xs text-[#a0a0a0]/60 transition-colors duration-150 hover:bg-white/[0.04]"
283 className="flex-1 rounded-md border border-white/10 px-2 py-1 text-xs text-[#a0a0a0]/60 transition-colors duration-150 hover:bg-white/4"
292284 onClick={() => setAddingProject(false)}
293285 >
294286 Cancel
......@@ -296,10 +288,10 @@ export default function Sidebar() {
296288 </div>
297289 </div>
298290 ) : (
299 <div className="border-t border-white/[0.08] p-3">
291 <div className="border-t border-white/8 p-3">
300292 <button
301293 type="button"
302 className="flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-white/[0.12] py-2 text-xs text-[#a0a0a0]/50 transition-colors duration-150 hover:border-white/[0.2] hover:text-[#a0a0a0]/70"
294 className="flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-white/12 py-2 text-xs text-[#a0a0a0]/50 transition-colors duration-150 hover:border-white/20 hover:text-[#a0a0a0]/70"
303295 onClick={() => setAddingProject(true)}
304296 >
305297 + Add project
apps/renderer/src/index.css+3-4
......@@ -5,8 +5,8 @@
55}
66
77body {
8 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
9 "Helvetica Neue", Arial, sans-serif;
8 font-family:
9 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
1010 margin: 0;
1111 padding: 0;
1212 min-height: 100vh;
......@@ -29,8 +29,7 @@ pre,
2929code,
3030textarea,
3131input {
32 font-family: "SF Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo,
33 monospace;
32 font-family: "SF Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
3433}
3534
3635/* Window drag region (frameless titlebar) */
apps/renderer/src/model-logic.test.ts+1-5
......@@ -1,9 +1,5 @@
11import { describe, expect, it } from "vitest";
2import {
3 DEFAULT_MODEL,
4 normalizeModelSlug,
5 resolveModelSlug,
6} from "./model-logic";
2import { DEFAULT_MODEL, normalizeModelSlug, resolveModelSlug } from "./model-logic";
73
84describe("normalizeModelSlug", () => {
95 it("maps 5.3 aliases to canonical codex slug", () => {
apps/renderer/src/persistenceSchema.ts+4-11
......@@ -69,10 +69,7 @@ export interface PersistedStoreSnapshot {
6969 runtimeMode: RuntimeMode;
7070}
7171
72function maybeMigrateLegacyModel(
73 model: string,
74 isLegacyPayload: boolean,
75): string {
72function maybeMigrateLegacyModel(model: string, isLegacyPayload: boolean): string {
7673 if (!isLegacyPayload) {
7774 return model;
7875 }
......@@ -86,9 +83,7 @@ function hydrateProject(
8683): Project {
8784 return {
8885 ...project,
89 model: resolveModelSlug(
90 maybeMigrateLegacyModel(project.model, isLegacyPayload),
91 ),
86 model: resolveModelSlug(maybeMigrateLegacyModel(project.model, isLegacyPayload)),
9287 };
9388}
9489
......@@ -100,9 +95,7 @@ function hydrateThread(
10095 id: thread.id,
10196 projectId: thread.projectId,
10297 title: thread.title,
103 model: resolveModelSlug(
104 maybeMigrateLegacyModel(thread.model, isLegacyPayload),
105 ),
98 model: resolveModelSlug(maybeMigrateLegacyModel(thread.model, isLegacyPayload)),
10699 session: null,
107100 messages: thread.messages.map((message) => ({
108101 ...message,
......@@ -139,7 +132,7 @@ export function hydratePersistedState(
139132 .filter((thread) => projectIds.has(thread.projectId));
140133 const hasActiveThread = Boolean(
141134 parsedState.data.activeThreadId &&
142 threads.some((thread) => thread.id === parsedState.data.activeThreadId),
135 threads.some((thread) => thread.id === parsedState.data.activeThreadId),
143136 );
144137
145138 return {
apps/renderer/src/session-logic.test.ts+2-7
......@@ -22,9 +22,7 @@ function makeEvent(overrides: Partial<ProviderEvent>): ProviderEvent {
2222 };
2323}
2424
25function makeSession(
26 overrides: Partial<ProviderSession> = {},
27): ProviderSession {
25function makeSession(overrides: Partial<ProviderSession> = {}): ProviderSession {
2826 return {
2927 sessionId: "sess-1",
3028 provider: "codex",
......@@ -99,10 +97,7 @@ describe("deriveTimelineEntries", () => {
9997
10098 const timeline = deriveTimelineEntries(messages, workEntries);
10199
102 expect(timeline.map((entry) => entry.id)).toEqual([
103 "work:w-1",
104 "message:m-1",
105 ]);
100 expect(timeline.map((entry) => entry.id)).toEqual(["work:w-1", "message:m-1"]);
106101 });
107102});
108103
apps/renderer/src/session-logic.ts+5-17
......@@ -1,8 +1,4 @@
1import type {
2 ProviderEvent,
3 ProviderKind,
4 ProviderSession,
5} from "@acme/contracts";
1import type { ProviderEvent, ProviderKind, ProviderSession } from "@acme/contracts";
62import type { ChatMessage, SessionPhase } from "./types";
73
84export const PROVIDER_OPTIONS: Array<{
......@@ -49,10 +45,7 @@ export function formatDuration(durationMs: number): string {
4945 return `${minutes}m ${seconds}s`;
5046}
5147
52export function formatElapsed(
53 startIso: string,
54 endIso: string | undefined,
55): string | null {
48export function formatElapsed(startIso: string, endIso: string | undefined): string | null {
5649 if (!endIso) return null;
5750
5851 const startedAt = Date.parse(startIso);
......@@ -304,7 +297,7 @@ export function deriveWorkLogEntries(
304297 events: ProviderEvent[],
305298 turnId: string | undefined,
306299): WorkLogEntry[] {
307 const ordered = [...events].reverse();
300 const ordered = [...events].toReversed();
308301 const entries: WorkLogEntry[] = [];
309302 const turnStartedAtIso = turnId
310303 ? ordered.find((event) => {
......@@ -312,9 +305,7 @@ export function deriveWorkLogEntries(
312305 return eventTurnId(event) === turnId;
313306 })?.createdAt
314307 : undefined;
315 const turnStartedAt = turnStartedAtIso
316 ? Date.parse(turnStartedAtIso)
317 : Number.NaN;
308 const turnStartedAt = turnStartedAtIso ? Date.parse(turnStartedAtIso) : Number.NaN;
318309
319310 for (const event of ordered) {
320311 if (turnId) {
......@@ -428,10 +419,7 @@ export function derivePhase(session: ProviderSession | null): SessionPhase {
428419 return "ready";
429420}
430421
431export function evolveSession(
432 previous: ProviderSession,
433 event: ProviderEvent,
434): ProviderSession {
422export function evolveSession(previous: ProviderSession, event: ProviderEvent): ProviderSession {
435423 const payload = asObject(event.payload);
436424
437425 if (event.method === "thread/started") {
apps/renderer/src/store.ts+2-9
......@@ -116,10 +116,7 @@ function updateThread(
116116 return threads.map((t) => (t.id === threadId ? updater(t) : t));
117117}
118118
119function findThreadBySessionId(
120 threads: Thread[],
121 sessionId: string,
122): Thread | undefined {
119function findThreadBySessionId(threads: Thread[], sessionId: string): Thread | undefined {
123120 return threads.find((t) => t.session?.sessionId === sessionId);
124121}
125122
......@@ -325,11 +322,7 @@ export function StoreProvider({ children }: { children: ReactNode }) {
325322 persistState(state);
326323 }, [state]);
327324
328 return createElement(
329 StoreContext.Provider,
330 { value: { state, dispatch } },
331 children,
332 );
325 return createElement(StoreContext.Provider, { value: { state, dispatch } }, children);
333326}
334327
335328export function useStore() {
apps/renderer/tsconfig.json+1-1
......@@ -3,7 +3,7 @@
33 "compilerOptions": {
44 "composite": true,
55 "jsx": "react-jsx",
6 "lib": ["ES2022", "DOM", "DOM.Iterable"],
6 "lib": ["ES2023", "DOM", "DOM.Iterable"],
77 "types": ["vite/client"]
88 },
99 "include": ["src", "vite.config.ts"]
biome.json deleted-29
......@@ -1,29 +0,0 @@
1{
2 "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
3 "organizeImports": {
4 "enabled": true
5 },
6 "linter": {
7 "enabled": true,
8 "rules": {
9 "recommended": true,
10 "correctness": {
11 "noUnusedImports": "warn",
12 "noUnusedVariables": "warn"
13 }
14 }
15 },
16 "formatter": {
17 "indentStyle": "space",
18 "indentWidth": 2
19 },
20 "files": {
21 "ignore": [
22 "dist",
23 "dist-electron",
24 "node_modules",
25 "bun.lock",
26 "*.tsbuildinfo"
27 ]
28 }
29}
bun.lock+42-30
......@@ -5,7 +5,8 @@
55 "": {
66 "name": "ct-round-5",
77 "devDependencies": {
8 "@biomejs/biome": "^1.9.0",
8 "oxfmt": "^0.28.0",
9 "oxlint": "^1.43.0",
910 "turbo": "^2.3.3",
1011 "typescript": "^5.7.3",
1112 "vitest": "^3.0.0",
......@@ -22,7 +23,6 @@
2223 "devDependencies": {
2324 "@electron/rebuild": "^3.7.0",
2425 "@types/node": "^22.10.2",
25 "concurrently": "^9.1.2",
2626 "electronmon": "^2.0.2",
2727 "tsup": "^8.3.5",
2828 "typescript": "^5.7.3",
......@@ -110,24 +110,6 @@
110110
111111 "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
112112
113 "@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="],
114
115 "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="],
116
117 "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="],
118
119 "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="],
120
121 "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="],
122
123 "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="],
124
125 "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="],
126
127 "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="],
128
129 "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="],
130
131113 "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="],
132114
133115 "@electron/node-gyp": ["@electron/node-gyp@github:electron/node-gyp#06b29aa", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "glob": "^8.1.0", "graceful-fs": "^4.2.6", "make-fetch-happen": "^10.2.1", "nopt": "^6.0.0", "proc-log": "^2.0.1", "semver": "^7.3.5", "tar": "^6.2.1", "which": "^2.0.2" }, "bin": "./bin/node-gyp.js" }, "electron-node-gyp-06b29aa"],
......@@ -216,6 +198,38 @@
216198
217199 "@npmcli/move-file": ["@npmcli/move-file@2.0.1", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ=="],
218200
201 "@oxfmt/darwin-arm64": ["@oxfmt/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jmUfF7cNJPw57bEK7sMIqrYRgn4LH428tSgtgLTCtjuGuu1ShREyrkeB7y8HtkXRfhBs4lVY+HMLhqElJvZ6ww=="],
202
203 "@oxfmt/darwin-x64": ["@oxfmt/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-S6vlV8S7jbjzJOSjfVg2CimUC0r7/aHDLdUm/3+/B/SU/s1jV7ivqWkMv1/8EB43d1BBwT9JQ60ZMTkBqeXSFA=="],
204
205 "@oxfmt/linux-arm64-gnu": ["@oxfmt/linux-arm64-gnu@0.28.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TfJkMZjePbLiskmxFXVAbGI/OZtD+y+fwS0wyW8O6DWG0ARTf0AipY9zGwGoOdpFuXOJceXvN4SHGLbYNDMY4Q=="],
206
207 "@oxfmt/linux-arm64-musl": ["@oxfmt/linux-arm64-musl@0.28.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-7fyQUdW203v4WWGr1T3jwTz4L7KX9y5DeATryQ6fLT6QQp9GEuct8/k0lYhd+ys42iTV/IkJF20e3YkfSOOILg=="],
208
209 "@oxfmt/linux-x64-gnu": ["@oxfmt/linux-x64-gnu@0.28.0", "", { "os": "linux", "cpu": "x64" }, "sha512-sRKqAvEonuz0qr1X1ncUZceOBJerKzkO2gZIZmosvy/JmqyffpIFL3OE2tqacFkeDhrC+dNYQpusO8zsfHo3pw=="],
210
211 "@oxfmt/linux-x64-musl": ["@oxfmt/linux-x64-musl@0.28.0", "", { "os": "linux", "cpu": "x64" }, "sha512-fW6czbXutX/tdQe8j4nSIgkUox9RXqjyxwyWXUDItpoDkoXllq17qbD7GVc0whrEhYQC6hFE1UEAcDypLJoSzw=="],
212
213 "@oxfmt/win32-arm64": ["@oxfmt/win32-arm64@0.28.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-D/HDeQBAQRjTbD9OLV6kRDcStrIfO+JsUODDCdGmhRfNX8LPCx95GpfyybpZfn3wVF8Jq/yjPXV1xLkQ+s7RcA=="],
214
215 "@oxfmt/win32-x64": ["@oxfmt/win32-x64@0.28.0", "", { "os": "win32", "cpu": "x64" }, "sha512-4+S2j4OxOIyo8dz5osm5dZuL0yVmxXvtmNdHB5xyGwAWVvyWNvf7tCaQD7w2fdSsAXQLOvK7KFQrHFe33nJUCA=="],
216
217 "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.43.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C/GhObv/pQZg34NOzB6Mk8x0wc9AKj8fXzJF8ZRKTsBPyHusC6AZ6bba0QG0TUufw1KWuD0j++oebQfWeiFXNw=="],
218
219 "@oxlint/darwin-x64": ["@oxlint/darwin-x64@1.43.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-4NjfUtEEH8ewRQ2KlZGmm6DyrvypMdHwBnQT92vD0dLScNOQzr0V9O8Ua4IWXdeCNl/XMVhAV3h4/3YEYern5A=="],
220
221 "@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@1.43.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-75tf1HvwdZ3ebk83yMbSB+moAEWK98mYqpXiaFAi6Zshie7r+Cx5PLXZFUEqkscenoZ+fcNXakHxfn94V6nf1g=="],
222
223 "@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@1.43.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-BHV4fb36T2p/7bpA9fiJ5ayt7oJbiYX10nklW5arYp4l9/9yG/FQC5J4G1evzbJ/YbipF9UH0vYBAm5xbqGrvw=="],
224
225 "@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@1.43.0", "", { "os": "linux", "cpu": "x64" }, "sha512-1l3nvnzWWse1YHibzZ4HQXdF/ibfbKZhp9IguElni3bBqEyPEyurzZ0ikWynDxKGXqZa+UNXTFuU1NRVX1RJ3g=="],
226
227 "@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@1.43.0", "", { "os": "linux", "cpu": "x64" }, "sha512-+jNYgLGRFTJxJuaSOZJBwlYo5M0TWRw0+3y5MHOL4ArrIdHyCthg6r4RbVWrsR1qUfUE1VSSHQ2bfbC99RXqMg=="],
228
229 "@oxlint/win32-arm64": ["@oxlint/win32-arm64@1.43.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-dvs1C/HCjCyGTURMagiHprsOvVTT3omDiSzi5Qw0D4QFJ1pEaNlfBhVnOUYgUfS6O7Mcmj4+G+sidRsQcWQ/kA=="],
230
231 "@oxlint/win32-x64": ["@oxlint/win32-x64@1.43.0", "", { "os": "win32", "cpu": "x64" }, "sha512-bSuItSU8mTSDsvmmLTepTdCL2FkJI6dwt9tot/k0EmiYF+ArRzmsl4lXVLssJNRV5lJEc5IViyTrh7oiwrjUqA=="],
232
219233 "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
220234
221235 "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
......@@ -470,8 +484,6 @@
470484
471485 "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
472486
473 "concurrently": ["concurrently@9.2.1", "", { "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", "shell-quote": "1.8.3", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", "concurrently": "dist/bin/concurrently.js" } }, "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng=="],
474
475487 "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
476488
477489 "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
......@@ -904,6 +916,10 @@
904916
905917 "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],
906918
919 "oxfmt": ["oxfmt@0.28.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/darwin-arm64": "0.28.0", "@oxfmt/darwin-x64": "0.28.0", "@oxfmt/linux-arm64-gnu": "0.28.0", "@oxfmt/linux-arm64-musl": "0.28.0", "@oxfmt/linux-x64-gnu": "0.28.0", "@oxfmt/linux-x64-musl": "0.28.0", "@oxfmt/win32-arm64": "0.28.0", "@oxfmt/win32-x64": "0.28.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-3+hhBqPE6Kp22KfJmnstrZbl+KdOVSEu1V0ABaFIg1rYLtrMgrupx9znnHgHLqKxAVHebjTdiCJDk30CXOt6cw=="],
920
921 "oxlint": ["oxlint@1.43.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.43.0", "@oxlint/darwin-x64": "1.43.0", "@oxlint/linux-arm64-gnu": "1.43.0", "@oxlint/linux-arm64-musl": "1.43.0", "@oxlint/linux-x64-gnu": "1.43.0", "@oxlint/linux-x64-musl": "1.43.0", "@oxlint/win32-arm64": "1.43.0", "@oxlint/win32-x64": "1.43.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.11.2" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-xiqTCsKZch+R61DPCjyqUVP2MhkQlRRYxLRBeBDi+dtQJ90MOgdcjIktvDCgXz0bgtx94EQzHEndsizZjMX2OA=="],
922
907923 "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="],
908924
909925 "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="],
......@@ -1014,8 +1030,6 @@
10141030
10151031 "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
10161032
1017 "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
1018
10191033 "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
10201034
10211035 "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
......@@ -1058,7 +1072,7 @@
10581072
10591073 "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="],
10601074
1061 "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
1075 "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
10621076
10631077 "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
10641078
......@@ -1076,7 +1090,7 @@
10761090
10771091 "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
10781092
1079 "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="],
1093 "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="],
10801094
10811095 "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
10821096
......@@ -1210,8 +1224,6 @@
12101224
12111225 "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
12121226
1213 "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
1214
12151227 "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="],
12161228
12171229 "electron/@types/node": ["@types/node@20.19.33", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw=="],
......@@ -1246,14 +1258,14 @@
12461258
12471259 "vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
12481260
1261 "vitest/tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="],
1262
12491263 "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
12501264
12511265 "@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
12521266
12531267 "@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
12541268
1255 "electronmon/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
1256
12571269 "rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
12581270
12591271 "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
package.json+13-7
......@@ -1,23 +1,29 @@
11{
22 "name": "ct-round-5",
33 "private": true,
4 "packageManager": "bun@1.1.38",
5 "workspaces": ["apps/*", "packages/*"],
4 "workspaces": [
5 "apps/*",
6 "packages/*"
7 ],
68 "scripts": {
79 "dev": "bun run build:contracts && turbo run dev --parallel",
810 "build": "turbo run build",
911 "typecheck": "turbo run typecheck",
10 "lint": "turbo run lint",
12 "lint": "oxlint --report-unused-disable-directives",
1113 "test": "turbo run test",
12 "format": "biome format --write .",
13 "check": "biome check .",
14 "fmt": "oxfmt",
1415 "smoke-test": "turbo run smoke-test",
1516 "build:contracts": "bun run --cwd packages/contracts build"
1617 },
1718 "devDependencies": {
18 "@biomejs/biome": "^1.9.0",
19 "oxfmt": "^0.28.0",
20 "oxlint": "^1.43.0",
1921 "turbo": "^2.3.3",
2022 "typescript": "^5.7.3",
2123 "vitest": "^3.0.0"
22 }
24 },
25 "engines": {
26 "bun": ">=1.3.9"
27 },
28 "packageManager": "bun@1.3.9"
2329}
packages/contracts/package.json+3-2
......@@ -2,6 +2,9 @@
22 "name": "@acme/contracts",
33 "version": "0.0.0",
44 "private": true,
5 "files": [
6 "dist"
7 ],
58 "type": "module",
69 "main": "./dist/index.cjs",
710 "module": "./dist/index.js",
......@@ -13,12 +16,10 @@
1316 "require": "./dist/index.cjs"
1417 }
1518 },
16 "files": ["dist"],
1719 "scripts": {
1820 "dev": "tsup src/index.ts --format esm,cjs --dts --watch --clean",
1921 "build": "tsup src/index.ts --format esm,cjs --dts --clean",
2022 "typecheck": "tsc --noEmit",
21 "lint": "biome check src/",
2223 "test": "vitest run"
2324 },
2425 "dependencies": {
packages/contracts/src/ipc.ts+2-6
......@@ -62,12 +62,8 @@ export interface NativeApi {
6262 onExit: (callback: (exit: AgentExit) => void) => () => void;
6363 };
6464 providers: {
65 startSession: (
66 input: ProviderSessionStartInput,
67 ) => Promise<ProviderSession>;
68 sendTurn: (
69 input: ProviderSendTurnInput,
70 ) => Promise<ProviderTurnStartResult>;
65 startSession: (input: ProviderSessionStartInput) => Promise<ProviderSession>;
66 sendTurn: (input: ProviderSendTurnInput) => Promise<ProviderTurnStartResult>;
7167 interruptTurn: (input: ProviderInterruptTurnInput) => Promise<void>;
7268 respondToRequest: (input: ProviderRespondToRequestInput) => Promise<void>;
7369 stopSession: (input: ProviderStopSessionInput) => Promise<void>;
packages/contracts/src/model.test.ts+1-6
......@@ -1,11 +1,6 @@
11import { describe, expect, it } from "vitest";
22
3import {
4 DEFAULT_MODEL,
5 MODEL_OPTIONS,
6 normalizeModelSlug,
7 resolveModelSlug,
8} from "./model";
3import { DEFAULT_MODEL, MODEL_OPTIONS, normalizeModelSlug, resolveModelSlug } from "./model";
94
105describe("normalizeModelSlug", () => {
116 it("maps known aliases to canonical slugs", () => {
packages/contracts/src/model.ts+2-8
......@@ -1,8 +1,4 @@
1export const MODEL_OPTIONS = [
2 "gpt-5.3-codex",
3 "gpt-5.2-codex",
4 "gpt-5.2",
5] as const;
1export const MODEL_OPTIONS = ["gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.2"] as const;
62
73export const DEFAULT_MODEL = "gpt-5.3-codex";
84
......@@ -11,9 +7,7 @@ export const MODEL_SLUG_ALIASES: Record<string, string> = {
117 "gpt-5.3": "gpt-5.3-codex",
128};
139
14export function normalizeModelSlug(
15 model: string | null | undefined,
16): string | null {
10export function normalizeModelSlug(model: string | null | undefined): string | null {
1711 if (typeof model !== "string") {
1812 return null;
1913 }
packages/contracts/src/provider.ts+2-6
......@@ -105,9 +105,7 @@ export const providerEventSchema = z.object({
105105});
106106
107107export type ProviderKind = z.infer<typeof providerKindSchema>;
108export type ProviderApprovalPolicy = z.infer<
109 typeof providerApprovalPolicySchema
110>;
108export type ProviderApprovalPolicy = z.infer<typeof providerApprovalPolicySchema>;
111109export type ProviderSandboxMode = z.infer<typeof providerSandboxModeSchema>;
112110export type ProviderRequestKind = z.infer<typeof providerRequestKindSchema>;
113111export type ProviderApprovalDecision = z.infer<
......@@ -115,9 +113,7 @@ export type ProviderApprovalDecision = z.infer<
115113>;
116114export type ProviderSessionStatus = z.infer<typeof providerSessionStatusSchema>;
117115export type ProviderSession = z.infer<typeof providerSessionSchema>;
118export type ProviderSessionStartInput = z.input<
119 typeof providerSessionStartInputSchema
120>;
116export type ProviderSessionStartInput = z.input<typeof providerSessionStartInputSchema>;
121117export type ProviderSendTurnInput = z.input<typeof providerSendTurnInputSchema>;
122118export type ProviderTurnStartResult = z.infer<
123119 typeof providerTurnStartResultSchema
tsconfig.base.json+1-1
......@@ -1,6 +1,6 @@
11{
22 "compilerOptions": {
3 "target": "ES2022",
3 "target": "ES2023",
44 "module": "ESNext",
55 "moduleResolution": "Bundler",
66 "strict": true,
turbo.json-4
......@@ -14,10 +14,6 @@
1414 "dependsOn": ["^typecheck"],
1515 "outputs": []
1616 },
17 "lint": {
18 "dependsOn": ["^lint"],
19 "outputs": []
20 },
2117 "test": {
2218 "dependsOn": ["^build"],
2319 "outputs": []