Skip to content

Commit b22021c

Browse files
committed
Integrate harness runtime and CoahCode branding
- route chat through the harness-backed runtime - add Home project flow, scheduled tasks, and project icons - update desktop/app branding and docs
1 parent b15ec11 commit b22021c

97 files changed

Lines changed: 10742 additions & 3720 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

HANDOFF.md

Lines changed: 485 additions & 288 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# T3 Code
1+
# CoahCode
22

3-
T3 Code is a minimal web GUI for coding agents (currently Codex and Claude, more coming soon).
3+
CoahCode is a fork of [T3 Code](https://github.com/pingdotgg/t3code) with a Cursor-style harness layer, MCP support, LSP integration, scheduled runs, steering, and related agent UX work on top of the original app.
44

55
## Installation
66

apps/desktop/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@
2626
"typescript": "catalog:",
2727
"vitest": "catalog:"
2828
},
29-
"productName": "T3 Code (Alpha)"
29+
"productName": "CoahCode (Alpha)"
3030
}

apps/desktop/scripts/electron-launcher.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// This file mostly exists because we want dev mode to say "T3 Code (Dev)" instead of "electron"
1+
// This file mostly exists because we want dev mode to say "CoahCode (Dev)" instead of "electron"
22

33
import { spawnSync } from "node:child_process";
44
import {
@@ -17,7 +17,7 @@ import { dirname, join, resolve } from "node:path";
1717
import { fileURLToPath } from "node:url";
1818

1919
const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL);
20-
const APP_DISPLAY_NAME = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)";
20+
const APP_DISPLAY_NAME = isDevelopment ? "CoahCode (Dev)" : "CoahCode (Alpha)";
2121
const APP_BUNDLE_ID = isDevelopment ? "com.t3tools.t3code.dev" : "com.t3tools.t3code";
2222
const LAUNCHER_VERSION = 1;
2323

apps/desktop/src/clientPersistence.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const clientSettings: ClientSettings = {
5252
confirmThreadArchive: true,
5353
confirmThreadDelete: false,
5454
diffWordWrap: true,
55+
followUpBehavior: "steer",
5556
sidebarProjectSortOrder: "manual",
5657
sidebarThreadSortOrder: "created_at",
5758
timestampFormat: "24-hour",

apps/desktop/src/main.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ const SAVED_ENVIRONMENT_REGISTRY_PATH = Path.join(STATE_DIR, "saved-environments
100100
const DESKTOP_SCHEME = "t3";
101101
const ROOT_DIR = Path.resolve(__dirname, "../../..");
102102
const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL);
103-
const APP_DISPLAY_NAME = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)";
103+
const APP_DISPLAY_NAME = isDevelopment ? "CoahCode (Dev)" : "CoahCode (Alpha)";
104104
const APP_USER_MODEL_ID = isDevelopment ? "com.t3tools.t3code.dev" : "com.t3tools.t3code";
105105
const LINUX_DESKTOP_ENTRY_NAME = isDevelopment ? "t3code-dev.desktop" : "t3code.desktop";
106106
const LINUX_WM_CLASS = isDevelopment ? "t3code-dev" : "t3code";
@@ -669,7 +669,7 @@ function handleFatalStartupError(stage: string, error: unknown): void {
669669
console.error(`[desktop] fatal startup error (${stage})`, error);
670670
if (!isQuitting) {
671671
isQuitting = true;
672-
dialog.showErrorBox("T3 Code failed to start", `Stage: ${stage}\n${message}${detail}`);
672+
dialog.showErrorBox("CoahCode failed to start", `Stage: ${stage}\n${message}${detail}`);
673673
}
674674
stopBackend();
675675
restoreStdIoCapture?.();
@@ -774,7 +774,7 @@ async function checkForUpdatesFromMenu(): Promise<void> {
774774
void dialog.showMessageBox({
775775
type: "info",
776776
title: "You're up to date!",
777-
message: `T3 Code ${updateState.currentVersion} is currently the newest version available.`,
777+
message: `CoahCode ${updateState.currentVersion} is currently the newest version available.`,
778778
buttons: ["OK"],
779779
});
780780
} else if (updateState.status === "error") {

apps/server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@effect/sql-sqlite-bun": "catalog:",
3030
"@pierre/diffs": "^1.1.0-beta.16",
3131
"effect": "catalog:",
32+
"glob": "^11.0.3",
3233
"node-pty": "^1.1.0",
3334
"open": "^10.1.0"
3435
},

apps/server/src/git/Layers/RoutingTextGeneration.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,9 @@
1010
* @module RoutingTextGeneration
1111
*/
1212
import { Effect, Layer, Context } from "effect";
13+
import type { ProviderKind } from "@t3tools/contracts";
1314

14-
import {
15-
TextGeneration,
16-
type TextGenerationProvider,
17-
type TextGenerationShape,
18-
} from "../Services/TextGeneration.ts";
15+
import { TextGeneration, type TextGenerationShape } from "../Services/TextGeneration.ts";
1916
import { CodexTextGenerationLive } from "./CodexTextGeneration.ts";
2017
import { ClaudeTextGenerationLive } from "./ClaudeTextGeneration.ts";
2118

@@ -39,7 +36,7 @@ const makeRoutingTextGeneration = Effect.gen(function* () {
3936
const codex = yield* CodexTextGen;
4037
const claude = yield* ClaudeTextGen;
4138

42-
const route = (provider?: TextGenerationProvider): TextGenerationShape =>
39+
const route = (provider?: ProviderKind): TextGenerationShape =>
4340
provider === "claudeAgent" ? claude : codex;
4441

4542
return {

apps/server/src/harness/engine/checkpoint.ts

Lines changed: 137 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -10,140 +10,147 @@ import { homedir } from "os";
1010
const CHECKPOINT_BASE = join(homedir(), ".coahcode", "checkpoints");
1111

1212
export interface Checkpoint {
13-
readonly id: string;
14-
readonly timestamp: number;
15-
readonly description: string;
16-
readonly workspacePath: string;
17-
readonly hash: string;
13+
readonly id: string;
14+
readonly timestamp: number;
15+
readonly description: string;
16+
readonly workspacePath: string;
17+
readonly hash: string;
1818
}
1919

2020
async function runGit(args: string[], cwd: string): Promise<string> {
21-
return new Promise((resolve, reject) => {
22-
const proc = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
23-
let stdout = "";
24-
let stderr = "";
25-
proc.stdout.on("data", (d) => { stdout += d.toString(); });
26-
proc.stderr.on("data", (d) => { stderr += d.toString(); });
27-
proc.on("close", (code) => {
28-
if (code === 0) resolve(stdout.trim());
29-
else reject(new Error(`git ${args.join(" ")} failed: ${stderr}`));
30-
});
31-
});
21+
return new Promise((resolve, reject) => {
22+
const proc = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
23+
let stdout = "";
24+
let stderr = "";
25+
proc.stdout.on("data", (d) => {
26+
stdout += d.toString();
27+
});
28+
proc.stderr.on("data", (d) => {
29+
stderr += d.toString();
30+
});
31+
proc.on("close", (code) => {
32+
if (code === 0) resolve(stdout.trim());
33+
else reject(new Error(`git ${args.join(" ")} failed: ${stderr}`));
34+
});
35+
});
3236
}
3337

3438
export class CheckpointManager {
35-
private readonly checkpointDir: string;
36-
private initialized = false;
37-
38-
constructor(private readonly workspacePath: string) {
39-
// Hash workspace path for unique checkpoint dir
40-
const pathHash = Buffer.from(workspacePath).toString("base64url").slice(0, 20);
41-
this.checkpointDir = join(CHECKPOINT_BASE, pathHash);
42-
}
43-
44-
private async ensureInit(): Promise<void> {
45-
if (this.initialized) return;
46-
47-
await fs.mkdir(this.checkpointDir, { recursive: true });
48-
49-
// Check if already a git repo
50-
try {
51-
await runGit(["rev-parse", "--git-dir"], this.checkpointDir);
52-
} catch {
53-
// Initialize shadow git repo
54-
await runGit(["init"], this.checkpointDir);
55-
await runGit(["config", "user.name", "CoahCode Checkpoints"], this.checkpointDir);
56-
await runGit(["config", "user.email", "checkpoints@coahcode.local"], this.checkpointDir);
57-
}
58-
59-
this.initialized = true;
60-
}
61-
62-
async createCheckpoint(description: string, files: readonly string[]): Promise<Checkpoint> {
63-
await this.ensureInit();
64-
65-
// Copy files to checkpoint dir
66-
for (const file of files) {
67-
try {
68-
const content = await fs.readFile(file, "utf-8");
69-
const relative = file.startsWith(this.workspacePath)
70-
? file.slice(this.workspacePath.length + 1)
71-
: file;
72-
73-
const destPath = join(this.checkpointDir, relative);
74-
const destDir = destPath.substring(0, destPath.lastIndexOf("/"));
75-
await fs.mkdir(destDir, { recursive: true });
76-
await fs.writeFile(destPath, content, "utf-8");
77-
} catch {
78-
// File might not exist yet (new file creation)
79-
}
80-
}
81-
82-
// Commit
83-
try {
84-
await runGit(["add", "-A"], this.checkpointDir);
85-
await runGit(["commit", "-m", description, "--allow-empty"], this.checkpointDir);
86-
} catch {
87-
// Nothing to commit
88-
}
89-
90-
const hash = await runGit(["rev-parse", "HEAD"], this.checkpointDir).catch(() => "unknown");
91-
92-
return {
93-
id: `cp_${Date.now()}`,
94-
timestamp: Date.now(),
95-
description,
96-
workspacePath: this.workspacePath,
97-
hash,
98-
};
99-
}
100-
101-
async listCheckpoints(limit = 20): Promise<readonly Checkpoint[]> {
102-
await this.ensureInit();
103-
104-
try {
105-
const log = await runGit(
106-
["log", `--max-count=${limit}`, "--format=%H|%at|%s"],
107-
this.checkpointDir,
108-
);
109-
110-
return log.split("\n").filter(Boolean).map((line) => {
111-
const [hash = "", timestamp = "0", ...descParts] = line.split("|");
112-
return {
113-
id: `cp_${timestamp}`,
114-
timestamp: parseInt(timestamp, 10) * 1000,
115-
description: descParts.join("|"),
116-
workspacePath: this.workspacePath,
117-
hash,
118-
};
119-
});
120-
} catch {
121-
return [];
122-
}
123-
}
124-
125-
async rollback(hash: string): Promise<{ restoredFiles: readonly string[] }> {
126-
await this.ensureInit();
127-
128-
// Get list of files at that commit
129-
const files = await runGit(["ls-tree", "-r", "--name-only", hash], this.checkpointDir);
130-
const fileList = files.split("\n").filter(Boolean);
131-
132-
const restoredFiles: string[] = [];
133-
134-
for (const relative of fileList) {
135-
try {
136-
const content = await runGit(["show", `${hash}:${relative}`], this.checkpointDir);
137-
const destPath = join(this.workspacePath, relative);
138-
const destDir = destPath.substring(0, destPath.lastIndexOf("/"));
139-
await fs.mkdir(destDir, { recursive: true });
140-
await fs.writeFile(destPath, content, "utf-8");
141-
restoredFiles.push(destPath);
142-
} catch {
143-
// File might have issues
144-
}
145-
}
146-
147-
return { restoredFiles };
148-
}
39+
private readonly checkpointDir: string;
40+
private initialized = false;
41+
42+
constructor(private readonly workspacePath: string) {
43+
// Hash workspace path for unique checkpoint dir
44+
const pathHash = Buffer.from(workspacePath).toString("base64url").slice(0, 20);
45+
this.checkpointDir = join(CHECKPOINT_BASE, pathHash);
46+
}
47+
48+
private async ensureInit(): Promise<void> {
49+
if (this.initialized) return;
50+
51+
await fs.mkdir(this.checkpointDir, { recursive: true });
52+
53+
// Check if already a git repo
54+
try {
55+
await runGit(["rev-parse", "--git-dir"], this.checkpointDir);
56+
} catch {
57+
// Initialize shadow git repo
58+
await runGit(["init"], this.checkpointDir);
59+
await runGit(["config", "user.name", "CoahCode Checkpoints"], this.checkpointDir);
60+
await runGit(["config", "user.email", "checkpoints@coahcode.local"], this.checkpointDir);
61+
}
62+
63+
this.initialized = true;
64+
}
65+
66+
async createCheckpoint(description: string, files: readonly string[]): Promise<Checkpoint> {
67+
await this.ensureInit();
68+
69+
// Copy files to checkpoint dir
70+
for (const file of files) {
71+
try {
72+
const content = await fs.readFile(file, "utf-8");
73+
const relative = file.startsWith(this.workspacePath)
74+
? file.slice(this.workspacePath.length + 1)
75+
: file;
76+
77+
const destPath = join(this.checkpointDir, relative);
78+
const destDir = destPath.substring(0, destPath.lastIndexOf("/"));
79+
await fs.mkdir(destDir, { recursive: true });
80+
await fs.writeFile(destPath, content, "utf-8");
81+
} catch {
82+
// File might not exist yet (new file creation)
83+
}
84+
}
85+
86+
// Commit
87+
try {
88+
await runGit(["add", "-A"], this.checkpointDir);
89+
await runGit(["commit", "-m", description, "--allow-empty"], this.checkpointDir);
90+
} catch {
91+
// Nothing to commit
92+
}
93+
94+
const hash = await runGit(["rev-parse", "HEAD"], this.checkpointDir).catch(() => "unknown");
95+
96+
return {
97+
id: `cp_${Date.now()}`,
98+
timestamp: Date.now(),
99+
description,
100+
workspacePath: this.workspacePath,
101+
hash,
102+
};
103+
}
104+
105+
async listCheckpoints(limit = 20): Promise<readonly Checkpoint[]> {
106+
await this.ensureInit();
107+
108+
try {
109+
const log = await runGit(
110+
["log", `--max-count=${limit}`, "--format=%H|%at|%s"],
111+
this.checkpointDir,
112+
);
113+
114+
return log
115+
.split("\n")
116+
.filter(Boolean)
117+
.map((line) => {
118+
const [hash = "", timestamp = "0", ...descParts] = line.split("|");
119+
return {
120+
id: `cp_${timestamp}`,
121+
timestamp: parseInt(timestamp, 10) * 1000,
122+
description: descParts.join("|"),
123+
workspacePath: this.workspacePath,
124+
hash,
125+
};
126+
});
127+
} catch {
128+
return [];
129+
}
130+
}
131+
132+
async rollback(hash: string): Promise<{ restoredFiles: readonly string[] }> {
133+
await this.ensureInit();
134+
135+
// Get list of files at that commit
136+
const files = await runGit(["ls-tree", "-r", "--name-only", hash], this.checkpointDir);
137+
const fileList = files.split("\n").filter(Boolean);
138+
139+
const restoredFiles: string[] = [];
140+
141+
for (const relative of fileList) {
142+
try {
143+
const content = await runGit(["show", `${hash}:${relative}`], this.checkpointDir);
144+
const destPath = join(this.workspacePath, relative);
145+
const destDir = destPath.substring(0, destPath.lastIndexOf("/"));
146+
await fs.mkdir(destDir, { recursive: true });
147+
await fs.writeFile(destPath, content, "utf-8");
148+
restoredFiles.push(destPath);
149+
} catch {
150+
// File might have issues
151+
}
152+
}
153+
154+
return { restoredFiles };
155+
}
149156
}

0 commit comments

Comments
 (0)