Skip to content

Commit 9951c5c

Browse files
committed
Merge remote-tracking branch 'origin/main' into cursor/critical-correctness-bugs-8d53
2 parents 0156a2d + c2f28f8 commit 9951c5c

118 files changed

Lines changed: 7511 additions & 2312 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.

.agents/skills/ade-web/SKILL.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
name: ade-web
3+
description: >-
4+
Launch the ADE desktop app's renderer as a standalone web app (Vite-only preview)
5+
seeded with real data from the ADE database. Works from any lane worktree without
6+
interfering with running ADE sockets or runtimes. Use when asked to start, run, or
7+
preview the ADE desktop web renderer, open the ADE web app, or view ADE UI in a browser.
8+
metadata:
9+
author: ADE
10+
version: 0.1.0
11+
---
12+
13+
# ade-web — Launch the ADE Desktop Web Renderer
14+
15+
Starts the ADE desktop renderer as a browser-accessible web app on `http://localhost:5173`,
16+
seeded with a snapshot of the real ADE database. Safe to run alongside the ADE beta or
17+
any other running ADE runtime — it does **not** touch sockets or start new runtimes.
18+
19+
## When to use
20+
21+
- User asks to run, start, preview, or open the ADE web app / desktop web renderer
22+
- User wants to visually inspect or iterate on ADE desktop UI changes in a browser
23+
- User asks to launch ADE web from a specific lane or worktree
24+
25+
## Procedure
26+
27+
### 1. Resolve the workspace root
28+
29+
The web renderer must run from the **current lane's worktree**, not the main project checkout.
30+
31+
```
32+
WORKTREE_ROOT="$(pwd)"
33+
```
34+
35+
If `pwd` is not already inside `.ade/worktrees/<lane>/`, resolve it:
36+
37+
```
38+
# If inside a worktree, pwd is already correct.
39+
# If at the project root, there is no lane context — ask the user which lane.
40+
```
41+
42+
Confirm the desktop app exists at `$WORKTREE_ROOT/apps/desktop/package.json`.
43+
44+
### 2. Kill any stale Vite on port 5173
45+
46+
```bash
47+
lsof -ti :5173 2>/dev/null | xargs kill 2>/dev/null
48+
```
49+
50+
Do **not** kill processes on any other port. Do **not** touch ADE runtime sockets
51+
(`/tmp/ade-runtime-dev.sock`, `~/.ade-beta/sock/ade.sock`, etc.).
52+
53+
### 3. Seed the database snapshot
54+
55+
Export real data from the global ADE database into the browser mock:
56+
57+
```bash
58+
cd "$WORKTREE_ROOT/apps/desktop" && node ./scripts/export-browser-mock-ade-snapshot.mjs
59+
```
60+
61+
This reads `.ade/ade.db` from the primary project root (auto-detected even from worktrees)
62+
and writes `src/renderer/browser-mock-ade-snapshot.generated.json`.
63+
64+
If this fails with "No database", the user hasn't opened the project in ADE desktop yet.
65+
The renderer will still work with built-in demo data.
66+
67+
### 4. Start the Vite dev server
68+
69+
```bash
70+
cd "$WORKTREE_ROOT/apps/desktop" && npm run dev:vite
71+
```
72+
73+
This runs `vite --port 5173 --strictPort`. The `predev:vite` hook re-exports the
74+
snapshot automatically, so step 3 is optional if you go straight here.
75+
76+
Wait for the `VITE ready` message confirming it's listening.
77+
78+
### 5. Open in the ADE browser (optional)
79+
80+
If the user wants it in ADE's built-in browser:
81+
82+
```bash
83+
ade actions run built_in_browser createTab --socket --text --arg url=http://localhost:5173/work
84+
```
85+
86+
Or navigate an existing tab:
87+
88+
```bash
89+
ade actions run built_in_browser navigate --socket --text --arg url=http://localhost:5173/work
90+
```
91+
92+
Use `--socket` to communicate with the running ADE instance. This does **not** start a
93+
new runtime or interfere with the existing socket.
94+
95+
## Important constraints
96+
97+
- **Never start a runtime or bridge.** Do not run `dev:vite:live`, `dev:browser-bridge`,
98+
or `ensureRuntime`. These may detect version mismatches and restart the user's running
99+
ADE beta/dev runtime.
100+
- **Never start or manage the ADE socket directly.** The Vite-only preview uses
101+
`browserMock.ts` to stub `window.ade` — it does not need a runtime connection.
102+
The `--socket` flag on `ade actions run` above is fine; it connects to the
103+
running desktop instance rather than managing the socket itself.
104+
- **Always run from the worktree.** All `cd` commands, file reads, and file edits must
105+
target paths under `$WORKTREE_ROOT`, never the main project checkout. When `grep` or
106+
`find` returns absolute paths rooted at the main checkout, translate them to the
107+
worktree before editing.
108+
- **Port 5173 only.** Do not change the port. The desktop app's Vite config uses
109+
`--strictPort` so it will error if the port is taken rather than silently picking another.
110+
111+
## Cleanup
112+
113+
When done, kill the Vite server:
114+
115+
```bash
116+
lsof -ti :5173 2>/dev/null | xargs kill 2>/dev/null
117+
```
118+
119+
## Troubleshooting
120+
121+
| Problem | Fix |
122+
|---------|-----|
123+
| `Port 5173 is already in use` | Kill the stale process: `lsof -ti :5173 \| xargs kill` |
124+
| `No database at ...` | Run `export-browser-mock-ade-snapshot.mjs` with `ADE_PROJECT_ROOT=/path/to/ADE` pointing at the main checkout |
125+
| `ERR_CONNECTION_REFUSED` in ADE browser | Vite died — restart with `npm run dev:vite` from the worktree |
126+
| Mock data instead of real data | Re-run the export script, then restart Vite or hard-refresh the browser |
127+
| `proxy error: /health ECONNREFUSED 127.0.0.1:18765` | Expected — this is the browser bridge port. Vite-only mode doesn't use it. Ignore. |

AGENTS.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ Desktop release:
8282
- Validation commands are documented in the "Validation" section above.
8383
- The desktop test suite is large; CI shards it. For local iteration, run a single file or one CI-style shard rather than the full suite.
8484

85+
### Working in ADE lanes (worktrees)
86+
87+
- When an agent session runs inside an ADE lane, its working directory is the lane's worktree (e.g. `/path/to/ADE/.ade/worktrees/<lane-slug>/`). **All file reads, edits, and writes MUST target paths under that worktree, never under the main project-root checkout.**
88+
- `grep`, `find`, and Explore agents may return absolute paths rooted at the main checkout. Before editing, translate those paths to the worktree: replace the project root prefix with the worktree root. For example, `/Users/admin/Projects/ADE/apps/desktop/src/foo.ts` becomes `<worktree>/apps/desktop/src/foo.ts`.
89+
- Use relative paths from your working directory whenever possible — they resolve to the worktree automatically.
90+
- If `ADE_REPO_ROOT` is set in the environment, use it as the canonical base for all file operations.
91+
- When launching dev servers (Vite, Electron, etc.) for a lane, run them from the worktree, not the main checkout: `cd <worktree>/apps/desktop && npm run dev:vite`.
92+
93+
### Running the ADE desktop web renderer (Vite-only preview)
94+
95+
- The desktop renderer can run standalone in a browser without Electron via `npm run dev:vite` in `apps/desktop`. This starts Vite on port 5173 with a browser mock for `window.ade`.
96+
- To seed the mock with real data from the ADE database, run `npm run export:browser-mock-ade` in `apps/desktop` first, or let the `predev:vite` hook do it automatically. The export script reads `.ade/ade.db` from the primary project root and writes a snapshot to `src/renderer/browser-mock-ade-snapshot.generated.json`.
97+
- This works from any lane worktree: `cd <worktree>/apps/desktop && npm run dev:vite`. The export script detects worktree paths and resolves the `.ade/ade.db` location from the parent project root.
98+
- For live data (connected to the ADE runtime socket instead of mock data), use `npm run dev:vite:live`. This starts both Vite and a browser-runtime bridge. Note: this calls `ensureRuntime` which may restart a stale dev runtime — avoid if the ADE beta or another runtime is already running on the target socket.
99+
- Open `http://localhost:5173/work` in a browser or ADE's built-in browser to view the Work tab.
100+
85101
### Inspecting the local Electron desktop app with Codex Computer Use on macOS
86102

87103
- To inspect ADE desktop parity locally with Codex Computer Use, launch the dev app from the worktree with `npm run dev` in `apps/desktop`.

apps/ade-cli/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ ade lanes create "lin-123" --linear-issue-json '{"id":"...","identifier":"LIN-12
234234
ade lanes reparent lane-child --parent lane-parent --stack-base-branch main
235235
ade --role cto linear quick-view --text
236236
ade --role cto linear search-issues --query "auth" --state-type started,unstarted --first 50
237+
ade --role cto linear issue-comments --issue-id <linear-issue-uuid>
237238
ade git commit --lane lane-id
238239
ade git push --lane lane-id
239240
ade git pull --lane lane-id --rebase

apps/ade-cli/src/adeRpcServer.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,9 @@ function createRuntime() {
654654
issues: [{ id: "issue-1", identifier: "ADE-123", title: "Test", _query: query }],
655655
pageInfo: { hasNextPage: false, endCursor: null },
656656
})),
657+
fetchIssueComments: vi.fn(async (issueId: string) => [
658+
{ id: "comment-1", body: "First comment", createdAt: "2026-03-17T19:00:00.000Z", userName: "arul", userDisplayName: "Arul" },
659+
]),
657660
} as any,
658661
linearSyncService: {
659662
getDashboard: vi.fn(() => ({ enabled: true, running: false, ingressMode: "webhook-first", reconciliationIntervalSec: 60, lastPollAt: null, lastSuccessAt: null, lastError: null, queue: { queued: 1, blocked: 0, failed: 0 }, workflowRuns: { active: 1, waiting: 0 }, recentIssues: [] })),
@@ -1584,6 +1587,21 @@ describe("adeRpcServer", () => {
15841587
);
15851588
});
15861589

1590+
it("fetches Linear issue comments via getLinearIssueComments", async () => {
1591+
const { runtime } = createRuntime();
1592+
const handler = createAdeRpcRequestHandler({ runtime, serverVersion: "test" });
1593+
1594+
await initialize(handler, { callerId: "cto-1", role: "cto" });
1595+
const result = await callTool(handler, "getLinearIssueComments", {
1596+
issueId: "issue-1",
1597+
});
1598+
1599+
expect((runtime.linearIssueTracker as any).fetchIssueComments).toHaveBeenCalledWith("issue-1");
1600+
expect(result.structuredContent).toEqual([
1601+
expect.objectContaining({ id: "comment-1", body: "First comment" }),
1602+
]);
1603+
});
1604+
15871605
it("forwards employeeOverride and laneId when resuming a Linear sync queue item", async () => {
15881606
const { runtime } = createRuntime();
15891607
const handler = createAdeRpcRequestHandler({ runtime, serverVersion: "test" });

apps/ade-cli/src/adeRpcServer.ts

Lines changed: 41 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,6 +1533,18 @@ const CTO_LINEAR_SYNC_TOOL_SPECS: ToolSpec[] = [
15331533
}
15341534
}
15351535
},
1536+
{
1537+
name: "getLinearIssueComments",
1538+
description: "Fetch comments on a Linear issue by its ID.",
1539+
inputSchema: {
1540+
type: "object",
1541+
required: ["issueId"],
1542+
additionalProperties: false,
1543+
properties: {
1544+
issueId: { type: "string", minLength: 1 }
1545+
}
1546+
}
1547+
},
15361548
{
15371549
name: "getLinearSyncDashboard",
15381550
description: "Read the ADE Linear sync dashboard.",
@@ -1692,6 +1704,7 @@ const READ_ONLY_TOOLS = new Set([
16921704
"getLinearQuickView",
16931705
"getLinearIssuePickerData",
16941706
"searchLinearIssues",
1707+
"getLinearIssueComments",
16951708
"listLinearWorkflows",
16961709
"getLinearRunStatus",
16971710
"getLinearSyncDashboard",
@@ -3477,6 +3490,12 @@ async function runTool(args: {
34773490
});
34783491
}
34793492

3493+
if (name === "getLinearIssueComments") {
3494+
const issueId = assertNonEmptyString(toolArgs.issueId, "issueId");
3495+
const tracker = requireLinearIssueTracker(runtime);
3496+
return await tracker.fetchIssueComments(issueId);
3497+
}
3498+
34803499
if (name === "getLinearSyncDashboard") {
34813500
return requireLinearSyncService(runtime).getDashboard();
34823501
}
@@ -5202,55 +5221,6 @@ export function createAdeRpcRequestHandler(args: {
52025221
},
52035222
};
52045223

5205-
const auditActionCall = async (
5206-
actionName: string,
5207-
actionArgs: Record<string, unknown>,
5208-
runner: () => Promise<unknown>
5209-
): Promise<unknown> => {
5210-
const startedAt = Date.now();
5211-
const laneId = resolveRequestedOrSessionLaneId(runtime, session, actionArgs);
5212-
const operation = runtime.operationService.start({
5213-
laneId,
5214-
kind: "ade_action_call",
5215-
metadata: {
5216-
action: actionName,
5217-
callerId: session.identity.callerId,
5218-
role: session.identity.role,
5219-
chatSessionId: session.identity.chatSessionId,
5220-
runId: session.identity.runId,
5221-
stepId: session.identity.stepId,
5222-
attemptId: session.identity.attemptId,
5223-
ownerId: session.identity.ownerId,
5224-
args: sanitizeForAudit(actionArgs)
5225-
}
5226-
});
5227-
5228-
try {
5229-
const result = await runner();
5230-
runtime.operationService.finish({
5231-
operationId: operation.operationId,
5232-
status: "succeeded",
5233-
metadataPatch: {
5234-
resultStatus: "success",
5235-
durationMs: Date.now() - startedAt,
5236-
result: sanitizeForAudit(result)
5237-
}
5238-
});
5239-
return result;
5240-
} catch (error) {
5241-
runtime.operationService.finish({
5242-
operationId: operation.operationId,
5243-
status: "failed",
5244-
metadataPatch: {
5245-
resultStatus: "failed",
5246-
durationMs: Date.now() - startedAt,
5247-
errorMessage: error instanceof Error ? error.message : String(error)
5248-
}
5249-
});
5250-
throw error;
5251-
}
5252-
};
5253-
52545224
const listActions = async (): Promise<Record<string, unknown>> => {
52555225
const actionSpecs = await listToolSpecsForSession(runtime, session);
52565226
return {
@@ -5263,18 +5233,16 @@ export function createAdeRpcRequestHandler(args: {
52635233
};
52645234

52655235
const callAction = async (actionName: string, actionArgs: Record<string, unknown>): Promise<unknown> => {
5266-
return await auditActionCall(actionName, actionArgs, async () => {
5267-
if (
5268-
READ_ONLY_TOOLS.has(actionName) ||
5269-
MUTATION_TOOLS.has(actionName) ||
5270-
actionName === "spawn_agent" ||
5271-
actionName === "ask_user"
5272-
) {
5273-
return await runTool({ runtime, session, name: actionName, toolArgs: actionArgs });
5274-
}
5236+
if (
5237+
READ_ONLY_TOOLS.has(actionName) ||
5238+
MUTATION_TOOLS.has(actionName) ||
5239+
actionName === "spawn_agent" ||
5240+
actionName === "ask_user"
5241+
) {
5242+
return await runTool({ runtime, session, name: actionName, toolArgs: actionArgs });
5243+
}
52755244

5276-
throw new JsonRpcError(JsonRpcErrorCode.methodNotFound, `Unsupported ADE action: ${actionName}`);
5277-
});
5245+
throw new JsonRpcError(JsonRpcErrorCode.methodNotFound, `Unsupported ADE action: ${actionName}`);
52785246
};
52795247

52805248
const handler = (async (request: JsonRpcRequest): Promise<unknown | null> => {
@@ -5405,48 +5373,35 @@ export function createAdeRpcRequestHandler(args: {
54055373
if (isCtoOnlyAdeAction("pty", ptyAction) && !callerHasRoleAtLeast(session.identity.role, "cto")) {
54065374
throw new JsonRpcError(JsonRpcErrorCode.methodNotFound, `Unsupported PTY method: ${method}`);
54075375
}
5408-
const runPtyAction = async (runner: () => Promise<unknown> | unknown): Promise<unknown> =>
5409-
auditActionCall(method, ptyArgs, async () => runner());
54105376
if (method === "pty.create") {
54115377
ensurePtyCreateAuthorized(runtime, session, method, ptyArgs);
5412-
return await runPtyAction(async () => {
5413-
const result = await runtime.ptyService.create(ptyArgs as Parameters<typeof runtime.ptyService.create>[0]);
5414-
return {
5415-
...result,
5416-
session: runtime.sessionService.get(result.sessionId),
5417-
};
5418-
});
5378+
const result = await runtime.ptyService.create(ptyArgs as Parameters<typeof runtime.ptyService.create>[0]);
5379+
return {
5380+
...result,
5381+
session: runtime.sessionService.get(result.sessionId),
5382+
};
54195383
}
54205384
if (method === "pty.sendToSession") {
54215385
ensurePtyTargetAuthorized(runtime, session, method, ptyArgs);
5422-
return await runPtyAction(() =>
5423-
runtime.ptyService.sendToSession(ptyArgs as Parameters<typeof runtime.ptyService.sendToSession>[0]));
5386+
return await runtime.ptyService.sendToSession(ptyArgs as Parameters<typeof runtime.ptyService.sendToSession>[0]);
54245387
}
54255388
if (method === "pty.write") {
54265389
ensurePtyTargetAuthorized(runtime, session, method, ptyArgs);
5427-
return await runPtyAction(() => {
5428-
runtime.ptyService.write(ptyArgs as Parameters<typeof runtime.ptyService.write>[0]);
5429-
return null;
5430-
});
5390+
runtime.ptyService.write(ptyArgs as Parameters<typeof runtime.ptyService.write>[0]);
5391+
return null;
54315392
}
54325393
if (method === "pty.resize") {
54335394
ensurePtyTargetAuthorized(runtime, session, method, ptyArgs);
5434-
return await runPtyAction(() => {
5435-
runtime.ptyService.resize(ptyArgs as Parameters<typeof runtime.ptyService.resize>[0]);
5436-
return null;
5437-
});
5395+
runtime.ptyService.resize(ptyArgs as Parameters<typeof runtime.ptyService.resize>[0]);
5396+
return null;
54385397
}
54395398
if (method === "pty.dispose") {
54405399
ensurePtyTargetAuthorized(runtime, session, method, ptyArgs);
5441-
return await runPtyAction(() => {
5442-
runtime.ptyService.dispose(ptyArgs as Parameters<typeof runtime.ptyService.dispose>[0]);
5443-
return null;
5444-
});
5400+
runtime.ptyService.dispose(ptyArgs as Parameters<typeof runtime.ptyService.dispose>[0]);
5401+
return null;
54455402
}
54465403
if (method === "pty.list") {
5447-
return await runPtyAction(() => ({
5448-
sessions: listAuthorizedPtySessions(runtime, session, method, ptyArgs),
5449-
}));
5404+
return { sessions: listAuthorizedPtySessions(runtime, session, method, ptyArgs) };
54505405
}
54515406
throw new JsonRpcError(JsonRpcErrorCode.methodNotFound, `Unsupported PTY method: ${method}`);
54525407
}

apps/ade-cli/src/bootstrap.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,6 @@ export async function createAdeRuntime(args: {
977977
logger,
978978
pollIntervalMs: 120_000,
979979
onUpdate: (snapshot) => pushEvent("runtime", { type: "usage", snapshot }),
980-
onThresholdEvent: (event) => pushEvent("runtime", { type: "usage_threshold", event }),
981980
});
982981
const budgetCapService = createBudgetCapService({
983982
db,

0 commit comments

Comments
 (0)