Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions apps/ade-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ ade init
ade lanes list --text
ade lanes create "fix-checkout-flow" --parent main
ade lanes create "lin-123" --linear-issue-json '{"id":"...","identifier":"LIN-123","title":"...","projectId":"...","projectSlug":"...","teamId":"...","teamKey":"...","stateId":"...","stateName":"Todo","stateType":"unstarted","priority":2,"priorityLabel":"high","labels":[],"assigneeId":null,"assigneeName":null,"createdAt":"...","updatedAt":"..."}'
ade lanes reparent lane-child --parent lane-parent --stack-base-branch main
ade --role cto linear quick-view --text
ade --role cto linear search-issues --query "auth" --state-type started,unstarted --first 50
ade git commit --lane lane-id
Expand All @@ -239,6 +240,7 @@ ade diff patch --lane lane-id --path src/file.ts --text
ade prs create --lane lane-id --base main --title "Fix checkout flow"
ade prs create --lane lane-id --base main --close-linear-issue-on-merge
ade prs list-open --text
ade prs github-snapshot --include-external-closed
ade prs path-to-merge --pr pr-id --model gpt-5.5 --max-rounds 3 --no-auto-merge
ade prs path-to-merge --pr pr-id --model gpt-5.5 --conflict-strategy auto --force-finalize conditional
ade prs pipeline pr-id save --conflict-strategy rebase --no-early-merge-on-green
Expand Down
1 change: 1 addition & 0 deletions apps/ade-cli/src/adeRpcServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ function createRuntime() {
void data;
return true;
}),
readTranscriptTail: vi.fn(async () => ""),
enrichSessions: vi.fn((sessions: unknown[]) => sessions),
},
testService: {
Expand Down
12 changes: 8 additions & 4 deletions apps/ade-cli/src/adeRpcServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3592,9 +3592,11 @@ async function waitForSessionCompletion(args: {
while (Date.now() <= deadline) {
const session = runtime.sessionService.get(sessionId);
if (session && session.status !== "running") {
const logTail = runtime.sessionService.readTranscriptTail(session.transcriptPath, maxLogBytes, {
const logTail = await runtime.ptyService.readTranscriptTail({
sessionId,
maxBytes: maxLogBytes,
raw: true,
alignToLineBoundary: true
alignToLineBoundary: true,
});
return {
session,
Expand All @@ -3610,9 +3612,11 @@ async function waitForSessionCompletion(args: {
session,
timedOut: true,
logTail: session
? runtime.sessionService.readTranscriptTail(session.transcriptPath, maxLogBytes, {
? await runtime.ptyService.readTranscriptTail({
sessionId,
maxBytes: maxLogBytes,
raw: true,
alignToLineBoundary: true
alignToLineBoundary: true,
})
: ""
};
Expand Down
78 changes: 78 additions & 0 deletions apps/ade-cli/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1502,6 +1502,74 @@ describe("ADE CLI", () => {
});
});

it("forwards lane reparent stack base branch override to the runtime action", () => {
const reparent = buildCliPlan([
"lanes",
"reparent",
"lane-child",
"--parent",
"lane-parent",
"--stack-base-branch",
"develop",
]);
expect(reparent.kind).toBe("execute");
if (reparent.kind !== "execute") return;
expect(reparent.steps[0]?.params).toEqual({
name: "run_ade_action",
arguments: {
domain: "lane",
action: "reparent",
args: {
laneId: "lane-child",
newParentLaneId: "lane-parent",
stackBaseBranchRef: "develop",
},
},
});

const reparentDefault = buildCliPlan([
"lanes",
"reparent",
"lane-child",
"--parent",
"lane-parent",
]);
expect(reparentDefault.kind).toBe("execute");
if (reparentDefault.kind !== "execute") return;
expect(reparentDefault.steps[0]?.params).toEqual({
name: "run_ade_action",
arguments: {
domain: "lane",
action: "reparent",
args: {
laneId: "lane-child",
newParentLaneId: "lane-parent",
},
},
});
});

it("forwards PR GitHub snapshot full-history flag to the runtime action", () => {
const snapshot = buildCliPlan([
"prs",
"github-snapshot",
"--include-external-closed",
]);
expect(snapshot.kind).toBe("execute");
if (snapshot.kind !== "execute") return;
expect(snapshot.steps[0]?.params).toEqual({
name: "run_ade_action",
arguments: {
domain: "pr",
action: "getGithubSnapshot",
args: {
force: false,
includeExternalClosed: true,
},
},
});
});

it("maps discoverable git status, sync, and conflict helpers to existing actions", () => {
const fullStatus = buildCliPlan([
"git",
Expand Down Expand Up @@ -1854,6 +1922,16 @@ describe("ADE CLI", () => {
// Regression: --text as output flag must not swallow --help.
const lanesHelp = buildCliPlan(["lanes", "list", "--text", "--help"]);
expect(lanesHelp.kind).toBe("help");

const reparentHelp = buildCliPlan([
"lanes",
"reparent",
"lane-child",
"--stack-base-branch",
"develop",
"--help",
]);
expect(reparentHelp.kind).toBe("help");
});

it("maps PR create Linear close flag to the typed RPC tool", () => {
Expand Down
48 changes: 35 additions & 13 deletions apps/ade-cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,9 @@ const HELP_BY_COMMAND: Record<string, string> = {
$ ade lanes archive <lane> Archive a lane in ADE
$ ade lanes unarchive <lane> Restore an archived lane
$ ade lanes attach --path <worktree> --name <n> Attach an external worktree
$ ade lanes reparent <lane> --parent <parent> Move lane onto a new parent (runs git rebase)
$ ade lanes reparent <lane> --parent <parent> --stack-base-branch <branch>
Reparent and stack onto a specific branch (e.g. origin/main)
$ ade lanes actions --text List callable lane service methods
`,
git: `${ADE_BANNER}
Expand Down Expand Up @@ -947,6 +950,8 @@ const HELP_BY_COMMAND: Record<string, string> = {
$ ade prs link --lane <lane> --url <pr-url> Map an existing GitHub PR to a lane
$ ade prs checks <pr> --text Show check status
$ ade prs comments <pr> --text Show unresolved review work
$ ade prs github-snapshot --include-external-closed
Include closed external PR history in the GitHub snapshot
$ ade prs inventory <pr> Refresh ADE issue inventory
$ ade prs path-to-merge <pr> --model <model> --max-rounds 3 --no-auto-merge
$ ade prs path-to-merge <pr> --model <model> --conflict-strategy auto --force-finalize conditional
Expand Down Expand Up @@ -2478,6 +2483,23 @@ function buildLanePlan(args: string[]): CliPlan {
readLaneId(args) ?? firstPositional(args),
"laneId",
);
const reparentArgs: JsonObject = {
laneId,
newParentLaneId:
readValue(args, [
"--parent",
"--parent-lane",
"--parent-lane-id",
]) ?? firstPositional(args),
};
const stackBaseBranchRef = readValue(args, [
"--stack-base-branch",
"--stack-base",
"--base-branch-ref",
]);
if (stackBaseBranchRef != null) {
reparentArgs.stackBaseBranchRef = stackBaseBranchRef;
}
return {
kind: "execute",
label: "lane reparent",
Expand All @@ -2486,15 +2508,7 @@ function buildLanePlan(args: string[]): CliPlan {
"result",
"lane",
"reparent",
collectGenericObjectArgs(args, {
laneId,
newParentLaneId:
readValue(args, [
"--parent",
"--parent-lane",
"--parent-lane-id",
]) ?? firstPositional(args),
}),
collectGenericObjectArgs(args, reparentArgs),
),
],
};
Expand Down Expand Up @@ -3628,7 +3642,13 @@ function buildPrPlan(args: string[]): CliPlan {
label: "PR mobile snapshot",
steps: [actionArgsListStep("result", "pr", "getMobileSnapshot", [])],
};
if (sub === "github-snapshot")
if (sub === "github-snapshot") {
const snapshotArgs: JsonObject = {
force: readFlag(args, ["--force"]),
};
if (readFlag(args, ["--include-external-closed", "--include-closed-external"])) {
snapshotArgs.includeExternalClosed = true;
}
return {
kind: "execute",
label: "PR GitHub snapshot",
Expand All @@ -3637,12 +3657,11 @@ function buildPrPlan(args: string[]): CliPlan {
"result",
"pr",
"getGithubSnapshot",
collectGenericObjectArgs(args, {
force: readFlag(args, ["--force"]),
}),
collectGenericObjectArgs(args, snapshotArgs),
),
],
};
}
if (sub === "conflicts") {
const mode = firstPositional(args) ?? "list";
if (mode === "list")
Expand Down Expand Up @@ -8312,6 +8331,7 @@ const VALUE_CARRIER_FLAGS: ReadonlySet<string> = new Set([
"--backend",
"--base",
"--base-branch",
"--base-branch-ref",
"--base-ref",
"--body",
"--branch",
Expand Down Expand Up @@ -8449,6 +8469,8 @@ const VALUE_CARRIER_FLAGS: ReadonlySet<string> = new Set([
"--source",
"--source-lane",
"--stack",
"--stack-base",
"--stack-base-branch",
"--stack-id",
"--scheme",
"--start-point",
Expand Down
1 change: 1 addition & 0 deletions apps/ade-cli/src/services/sync/syncHostService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ function createHostArgs(projectRoot: string, projects: SyncMobileProjectSummary[
},
ptyService: {
create: vi.fn(),
readTranscriptTail: vi.fn(async () => ""),
enrichSessions: (rows: unknown[]) => rows,
},
computerUseArtifactBrokerService: {
Expand Down
11 changes: 6 additions & 5 deletions apps/ade-cli/src/services/sync/syncHostService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2830,11 +2830,12 @@ export function createSyncHostService(args: SyncHostServiceArgs) {
peer.subscribedSessionIds.add(sessionId);
const session = args.sessionService.get(sessionId);
const transcript = session
? await args.sessionService.readTranscriptTail(
session.transcriptPath,
Math.max(1_024, Math.min(2_000_000, Math.floor(payload?.maxBytes ?? DEFAULT_TERMINAL_SNAPSHOT_BYTES))),
{ raw: true, alignToLineBoundary: true },
)
? await args.ptyService.readTranscriptTail({
sessionId,
maxBytes: Math.max(1_024, Math.min(2_000_000, Math.floor(payload?.maxBytes ?? DEFAULT_TERMINAL_SNAPSHOT_BYTES))),
raw: true,
alignToLineBoundary: true,
})
: "";
const snapshot: SyncTerminalSnapshotPayload = {
sessionId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,9 +404,11 @@ function parseRenameLaneArgs(value: Record<string, unknown>): RenameLaneArgs {
}

function parseReparentLaneArgs(value: Record<string, unknown>): ReparentLaneArgs {
const stackBaseBranchRef = asTrimmedString(value.stackBaseBranchRef);
return {
laneId: requireString(value.laneId, "lanes.reparent requires laneId."),
newParentLaneId: requireString(value.newParentLaneId, "lanes.reparent requires newParentLaneId."),
...(stackBaseBranchRef ? { stackBaseBranchRef } : {}),
};
}

Expand Down Expand Up @@ -2333,7 +2335,10 @@ export function createSyncRemoteCommandService(args: SyncRemoteCommandServiceArg
register("prs.getComments", { viewerAllowed: true }, async (payload) => args.prService.getComments(requirePrId(payload, "prs.getComments")));
register("prs.getFiles", { viewerAllowed: true }, async (payload) => args.prService.getFiles(requirePrId(payload, "prs.getFiles")));
register("prs.getGitHubSnapshot", { viewerAllowed: true }, async (payload) =>
args.prService.getGithubSnapshot({ force: payload.force === true }));
args.prService.getGithubSnapshot({
force: payload.force === true,
includeExternalClosed: payload.includeExternalClosed === true,
}));
register("prs.getReviewThreads", { viewerAllowed: true }, async (payload) => args.prService.getReviewThreads(requirePrId(payload, "prs.getReviewThreads")));
register("prs.getActionRuns", { viewerAllowed: true }, async (payload) => args.prService.getActionRuns(requirePrId(payload, "prs.getActionRuns")));
register("prs.getActivity", { viewerAllowed: true }, async (payload) => args.prService.getActivity(requirePrId(payload, "prs.getActivity")));
Expand Down
79 changes: 79 additions & 0 deletions apps/ade-cli/src/tuiClient/__tests__/appInput.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,85 @@ describe("subagentSnapshotsFromEvents", () => {
summary: "done",
});
});

it("adopts the resolved task id when a runtime placeholder has the same parent tool id", () => {
const snapshots = subagentSnapshotsFromEvents([
{
sessionId: "s1",
timestamp: "2026-01-01T12:00:00.000Z",
sequence: 1,
event: {
type: "subagent_started",
taskId: "spawn-1",
parentToolUseId: "spawn-1",
description: "Parallel agent",
},
},
{
sessionId: "s1",
timestamp: "2026-01-01T12:00:01.000Z",
sequence: 2,
event: {
type: "subagent_progress",
taskId: "thread-1",
parentToolUseId: "spawn-1",
summary: "working",
},
},
]);

expect(snapshots).toHaveLength(1);
expect(snapshots[0]).toMatchObject({
id: "thread-1",
name: "Parallel agent",
parentToolUseId: "spawn-1",
status: "running",
summary: "working",
});
});

it("stops foreground subagents when their parent turn has ended", () => {
const snapshots = subagentSnapshotsFromEvents([
{
sessionId: "s1",
timestamp: "2026-01-01T12:00:00.000Z",
sequence: 1,
event: {
type: "subagent_started",
taskId: "agent-1",
description: "Foreground agent",
turnId: "turn-1",
},
},
{
sessionId: "s1",
timestamp: "2026-01-01T12:00:01.000Z",
sequence: 2,
event: {
type: "subagent_started",
taskId: "agent-bg",
description: "Background agent",
background: true,
turnId: "turn-1",
},
},
{
sessionId: "s1",
timestamp: "2026-01-01T12:00:02.000Z",
sequence: 3,
event: { type: "done", turnId: "turn-1", status: "completed" },
},
]);

expect(snapshots.find((snapshot) => snapshot.id === "agent-1")).toMatchObject({
status: "stopped",
summary: "Parent turn ended before ADE received a final subagent status",
});
expect(snapshots.find((snapshot) => snapshot.id === "agent-bg")).toMatchObject({
status: "running",
background: true,
});
});
});

describe("clampChatScrollOffsetRows", () => {
Expand Down
Loading
Loading