Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
7165d9f
Add Cursor provider session and model selection support
juliusmarminge Mar 24, 2026
10129ed
Use server-driven ModelCapabilities for Cursor traits
juliusmarminge Mar 26, 2026
d4561ad
Switch Cursor model selection to in-session via session/set_config_op…
juliusmarminge Mar 26, 2026
97d5288
Merge remote-tracking branch 'origin/main' into t3code/greeting
juliusmarminge Mar 26, 2026
d747322
Refactor Cursor model handling in ChatView and ProviderModelPicker
juliusmarminge Mar 26, 2026
55cef69
rm probes
juliusmarminge Mar 26, 2026
86e60de
rm probe
juliusmarminge Mar 26, 2026
e7c5ac3
Probe Cursor ACP session setup in one script
juliusmarminge Mar 26, 2026
c37322e
rm unused probe
juliusmarminge Mar 26, 2026
e1f8939
Normalize provider model IDs and Cursor options
juliusmarminge Mar 27, 2026
f5e64d6
fix picker
juliusmarminge Mar 27, 2026
4d4bee2
fix spawning
juliusmarminge Mar 27, 2026
3a21c19
Add Cursor text generation and model mapping
juliusmarminge Mar 27, 2026
88696b4
fix effect lsp issues
juliusmarminge Mar 27, 2026
ea47805
use constructors
juliusmarminge Mar 27, 2026
97ed04d
kewl
juliusmarminge Mar 27, 2026
0575c11
move claude model id lookup
juliusmarminge Mar 27, 2026
aa5b8d2
rm unused test
juliusmarminge Mar 27, 2026
26ef535
kewl
juliusmarminge Mar 27, 2026
c029bac
nit
juliusmarminge Mar 27, 2026
f7b2b07
kewl
juliusmarminge Mar 27, 2026
accc67d
effect-acp
juliusmarminge Mar 27, 2026
84f9533
improve sdk and use it
juliusmarminge Mar 27, 2026
22594d6
nit
juliusmarminge Mar 27, 2026
0c218d2
noExternal
juliusmarminge Mar 27, 2026
f76321f
Log ACP requests and preserve turn-start failures
juliusmarminge Mar 27, 2026
1787185
tidy up effect-acp
juliusmarminge Mar 27, 2026
ba6604b
error on fmt
juliusmarminge Mar 27, 2026
1f7a48a
small nit
juliusmarminge Mar 27, 2026
9476dd2
Handle cancelled Cursor ACP turns and simplify model ids
juliusmarminge Mar 27, 2026
058822b
Extract ACP runtime event helpers and preserve model slugs
juliusmarminge Mar 27, 2026
146e102
Constrain ACP runtime event source types
juliusmarminge Mar 27, 2026
e9c611f
Merge origin/main into t3code/greeting
juliusmarminge Mar 28, 2026
f2614d0
Settle pending user input on session stop
juliusmarminge Mar 28, 2026
fe988ec
Log outgoing ACP notifications before sending
juliusmarminge Mar 28, 2026
c4e9e40
Remove child-process export from effect-acp package
juliusmarminge Mar 28, 2026
65621de
Patch ACP protocol to use request envelopes
juliusmarminge Mar 28, 2026
cad3cbe
Propagate ACP child exits through protocol termination
juliusmarminge Mar 28, 2026
ff8577a
servicify
juliusmarminge Mar 28, 2026
7192f24
Merge origin/main into t3code/greeting
juliusmarminge Mar 28, 2026
81d1239
Align Effect catalog with merged main
juliusmarminge Mar 28, 2026
b994a72
fix ubild
juliusmarminge Mar 28, 2026
5cb6d05
agent/client separation
juliusmarminge Mar 28, 2026
e44eea6
fix
juliusmarminge Mar 28, 2026
399be25
kewl
juliusmarminge Mar 28, 2026
ece6436
fixes
juliusmarminge Mar 29, 2026
981aeba
Merge origin/main into t3code/greeting
juliusmarminge Mar 29, 2026
8e6fc4c
Merge origin/main into t3code/greeting
juliusmarminge Mar 29, 2026
68e06f6
Merge origin/main into t3code/greeting
juliusmarminge Mar 30, 2026
f1ac1ff
Merge remote-tracking branch 'origin/main' into t3code/greeting
juliusmarminge Mar 30, 2026
085dccc
Merge remote-tracking branch 'origin/main' into t3code/greeting
juliusmarminge Apr 1, 2026
e8d6a12
Merge origin/main into t3code/greeting
juliusmarminge Apr 3, 2026
f0d4415
Add ACP model picker support for Cursor
juliusmarminge Apr 9, 2026
1dea9cc
Gate Cursor parameterized model picker on preview channel
juliusmarminge Apr 9, 2026
6cbf692
Support JSON Cursor about output
juliusmarminge Apr 9, 2026
d9b8123
Merge remote-tracking branch 'origin/main' into t3code/greeting
Apr 9, 2026
01bfe63
Merge remote-tracking branch 'origin/main' into t3code/greeting
Apr 10, 2026
2a23bd2
Remove obsolete Effect patches
Apr 10, 2026
50d2528
Update Cursor provider test expectations
Apr 10, 2026
543e496
Preserve explicit Cursor option resets
juliusmarminge Apr 10, 2026
7ee8909
Merge remote-tracking branch 'origin/main' into t3code/greeting
Apr 11, 2026
0c1793a
Refine provider discovery and Cursor ACP model handling
juliusmarminge Apr 11, 2026
9d2409e
Merge origin/main into t3code/greeting
juliusmarminge Apr 13, 2026
dc7e19c
Preserve cursor model when traits change
juliusmarminge Apr 13, 2026
c689f21
Add Cursor model cache and max-effort support
juliusmarminge Apr 13, 2026
191b24c
Merge origin/main into t3code/greeting
juliusmarminge Apr 13, 2026
218cd8b
Speed up Cursor provider discovery
juliusmarminge Apr 14, 2026
e9bf9dc
Keep Cursor capability discovery off the refresh path
juliusmarminge Apr 14, 2026
6c36109
Fix desktop backend readiness probe
juliusmarminge Apr 14, 2026
e94d19c
Merge origin/main into t3code/greeting
juliusmarminge Apr 14, 2026
c3f926c
Close cursor ACP runtime after generation
juliusmarminge Apr 14, 2026
e78ff43
Harden ACP session handling and provider refreshes
juliusmarminge Apr 15, 2026
a7ab294
Merge origin/main into t3code/greeting
juliusmarminge Apr 16, 2026
ce947ec
Fix validation fallout after main merge
juliusmarminge Apr 16, 2026
eb4f923
Harden Cursor ACP probe cleanup and warning handling
juliusmarminge Apr 16, 2026
d9657e6
Scope ACP runtime to session lifecycle
juliusmarminge Apr 16, 2026
a1a8b0d
Serialize same-thread Cursor session startup
juliusmarminge Apr 16, 2026
ff0e88a
Start provider probes on startup
juliusmarminge Apr 16, 2026
188f902
Gate Cursor provider behind runtime flag
juliusmarminge Apr 17, 2026
8e18135
Always enable Cursor flag
juliusmarminge Apr 17, 2026
75cff58
fix: remove hardcoded true from isCursorEnabled feature flag
cursoragent Apr 17, 2026
a95008e
Merge origin/main into t3code/greeting
juliusmarminge Apr 17, 2026
5ca5e9c
fix: remove hardcoded true from isCursorEnabled feature flag
cursor[bot] Apr 17, 2026
fda21fb
Merge origin/main into t3code/greeting
juliusmarminge Apr 17, 2026
c9207c7
Remove cursor feature-flag gating
juliusmarminge Apr 17, 2026
489c2c9
Move Claude API model ID resolution into provider
juliusmarminge Apr 17, 2026
32df4e7
Remove duplicate model and layer providers
juliusmarminge Apr 17, 2026
c0436c6
Refactor provider model picker option handling
juliusmarminge Apr 17, 2026
d4b40db
fixx
juliusmarminge Apr 17, 2026
849e3dc
fix option logic
juliusmarminge Apr 17, 2026
ed2a46f
Skip redundant ACP session config writes
juliusmarminge Apr 17, 2026
10c1faf
Expose ACP event stream via getter
juliusmarminge Apr 17, 2026
47bd4be
Fallback to current model option in Cursor probes
juliusmarminge Apr 17, 2026
2395967
Use runtime event accessor consistently
juliusmarminge Apr 17, 2026
de2c424
Handle provider request errors by detail text
juliusmarminge Apr 17, 2026
7555ae6
kewl
juliusmarminge Apr 17, 2026
f91a965
me
juliusmarminge Apr 17, 2026
1753bc6
retry capability probe
juliusmarminge Apr 17, 2026
0c46385
Default Cursor off and show Early Access badge in settings
juliusmarminge Apr 17, 2026
b2f3a10
Honor explicit turn model when session model switching is unsupported
juliusmarminge Apr 17, 2026
aa696b5
Remove test for model override on unsupported session restart
juliusmarminge Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 315 additions & 0 deletions apps/server/scripts/acp-mock-agent.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
#!/usr/bin/env node
/**
* Minimal NDJSON JSON-RPC "agent" for ACP client tests.
* Reads stdin lines; writes responses/notifications to stdout.
*/
import * as readline from "node:readline";
import { appendFileSync } from "node:fs";

const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
const requestLogPath = process.env.T3_ACP_REQUEST_LOG_PATH;
const emitToolCalls = process.env.T3_ACP_EMIT_TOOL_CALLS === "1";
const sessionId = "mock-session-1";
let currentModeId = "ask";
let currentModelId = "default";
let nextRequestId = 1;

function configOptions() {
return [
{
id: "model",
name: "Model",
category: "model",
type: "select",
currentValue: currentModelId,
options: [
{ value: "default", name: "Auto" },
{ value: "composer-2", name: "Composer 2" },
{ value: "composer-2[fast=true]", name: "Composer 2 Fast" },
{ value: "gpt-5.3-codex[reasoning=medium,fast=false]", name: "Codex 5.3" },
],
},
];
}

const availableModes = [
{
id: "ask",
name: "Ask",
description: "Request permission before making any changes",
},
{
id: "architect",
name: "Architect",
description: "Design and plan software systems without implementation",
},
{
id: "code",
name: "Code",
description: "Write and modify code with full tool access",
},
];
const pendingPermissionRequests = new Map();

function send(obj) {
process.stdout.write(`${JSON.stringify(obj)}\n`);
}

function modeState() {
return {
currentModeId,
availableModes,
};
}

function sendSessionUpdate(update, session = sessionId) {
send({
jsonrpc: "2.0",
method: "session/update",
params: {
sessionId: session,
update,
},
});
}

rl.on("line", (line) => {
const trimmed = line.trim();
if (!trimmed) return;
let msg;
try {
msg = JSON.parse(trimmed);
} catch {
return;
}
if (!msg || typeof msg !== "object") return;
if (requestLogPath) {
appendFileSync(requestLogPath, `${JSON.stringify(msg)}\n`, "utf8");
}

const id = msg.id;
const method = msg.method;

if (method === undefined && id !== undefined && pendingPermissionRequests.has(id)) {
const pending = pendingPermissionRequests.get(id);
pendingPermissionRequests.delete(id);
sendSessionUpdate(
{
sessionUpdate: "tool_call_update",
toolCallId: pending.toolCallId,
title: "Terminal",
kind: "execute",
status: "completed",
rawOutput: {
exitCode: 0,
stdout: '{ "name": "t3" }',
stderr: "",
},
},
pending.sessionId,
);
sendSessionUpdate(
{
sessionUpdate: "agent_message_chunk",
content: { type: "text", text: "hello from mock" },
},
pending.sessionId,
);
send({
jsonrpc: "2.0",
id: pending.promptRequestId,
result: { stopReason: "end_turn" },
});
return;
}

if (method === "initialize" && id !== undefined) {
send({
jsonrpc: "2.0",
id,
result: {
protocolVersion: 1,
agentCapabilities: { loadSession: true },
},
});
return;
}

if (method === "authenticate" && id !== undefined) {
send({ jsonrpc: "2.0", id, result: { authenticated: true } });
return;
}

if (method === "session/new" && id !== undefined) {
send({
jsonrpc: "2.0",
id,
result: {
sessionId,
modes: modeState(),
configOptions: configOptions(),
},
});
return;
}

if (method === "session/load" && id !== undefined) {
const requestedSessionId = msg.params?.sessionId ?? sessionId;
sendSessionUpdate(
{
sessionUpdate: "user_message_chunk",
content: { type: "text", text: "replay" },
},
requestedSessionId,
);
send({
jsonrpc: "2.0",
id,
result: {
modes: modeState(),
configOptions: configOptions(),
},
});
return;
}

if (method === "session/set_config_option" && id !== undefined) {
const configId = msg.params?.configId;
const value = msg.params?.value;
if (configId === "model" && typeof value === "string") {
currentModelId = value;
}
send({
jsonrpc: "2.0",
id,
result: { configOptions: configOptions() },
});
return;
}

if (method === "session/prompt" && id !== undefined) {
const requestedSessionId = msg.params?.sessionId ?? sessionId;
if (emitToolCalls) {
const toolCallId = "tool-call-1";
const permissionRequestId = nextRequestId++;
sendSessionUpdate(
{
sessionUpdate: "tool_call",
toolCallId,
title: "Terminal",
kind: "execute",
status: "pending",
rawInput: {
command: ["cat", "server/package.json"],
},
},
requestedSessionId,
);
sendSessionUpdate(
{
sessionUpdate: "tool_call_update",
toolCallId,
status: "in_progress",
},
requestedSessionId,
);
pendingPermissionRequests.set(permissionRequestId, {
promptRequestId: id,
sessionId: requestedSessionId,
toolCallId,
});
send({
jsonrpc: "2.0",
id: permissionRequestId,
method: "session/request_permission",
params: {
sessionId: requestedSessionId,
toolCall: {
toolCallId,
title: "`cat server/package.json`",
kind: "execute",
status: "pending",
content: [
{
type: "content",
content: {
type: "text",
text: "Not in allowlist: cat server/package.json",
},
},
],
},
options: [
{ optionId: "allow-once", name: "Allow once", kind: "allow_once" },
{ optionId: "allow-always", name: "Allow always", kind: "allow_always" },
{ optionId: "reject-once", name: "Reject", kind: "reject_once" },
],
},
});
return;
}
sendSessionUpdate(
{
sessionUpdate: "plan",
explanation: `Mock plan while in ${currentModeId}`,
entries: [
{
content: "Inspect mock ACP state",
priority: "high",
status: "completed",
},
{
content: "Implement the requested change",
priority: "high",
status: "in_progress",
},
],
},
requestedSessionId,
);
sendSessionUpdate(
{
sessionUpdate: "agent_message_chunk",
content: { type: "text", text: "hello from mock" },
},
requestedSessionId,
);
send({
jsonrpc: "2.0",
id,
result: { stopReason: "end_turn" },
});
return;
}

if ((method === "session/set_mode" || method === "session/mode/set") && id !== undefined) {
const nextModeId =
typeof msg.params?.modeId === "string"
? msg.params.modeId
: typeof msg.params?.mode === "string"
? msg.params.mode
: undefined;
if (typeof nextModeId === "string" && nextModeId.trim()) {
currentModeId = nextModeId.trim();
sendSessionUpdate({
sessionUpdate: "current_mode_update",
currentModeId,
});
}
send({ jsonrpc: "2.0", id, result: null });
return;
}

if (method === "session/cancel" && id !== undefined) {
send({ jsonrpc: "2.0", id, result: null });
return;
}

if (id !== undefined) {
send({
jsonrpc: "2.0",
id,
error: { code: -32601, message: `Unhandled method: ${String(method)}` },
});
}
});
4 changes: 2 additions & 2 deletions apps/server/src/git/Layers/ClaudeTextGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { Effect, Layer, Option, Schema, Stream } from "effect";
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";

import { ClaudeModelSelection } from "@t3tools/contracts";
import { resolveApiModelId } from "@t3tools/shared/model";
import { sanitizeBranchFragment, sanitizeFeatureBranchName } from "@t3tools/shared/git";

import { TextGenerationError } from "../Errors.ts";
Expand All @@ -27,6 +26,7 @@ import {
sanitizePrTitle,
toJsonSchemaObject,
} from "../Utils.ts";
import { resolveClaudeApiModelId } from "../../provider/Layers/ClaudeModelId.ts";
import { normalizeClaudeModelOptions } from "../../provider/Layers/ClaudeProvider.ts";
import { ServerSettingsService } from "../../serverSettings.ts";

Expand Down Expand Up @@ -104,7 +104,7 @@ const makeClaudeTextGeneration = Effect.gen(function* () {
"--json-schema",
jsonSchemaStr,
"--model",
resolveApiModelId(modelSelection),
resolveClaudeApiModelId(modelSelection),
...(normalizedOptions?.effort ? ["--effort", normalizedOptions.effort] : []),
...(Object.keys(settings).length > 0 ? ["--settings", JSON.stringify(settings)] : []),
"--dangerously-skip-permissions",
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/git/Services/TextGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { ChatAttachment, ModelSelection } from "@t3tools/contracts";
import type { TextGenerationError } from "../Errors.ts";

/** Providers that support git text generation (commit messages, PR content, branch names). */
export type TextGenerationProvider = "codex" | "claudeAgent";
export type TextGenerationProvider = "codex" | "claudeAgent" | "cursor";

export interface CommitMessageGenerationInput {
cwd: string;
Expand Down
Loading
Loading