Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 0 additions & 5 deletions apps/ade-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,16 +420,11 @@ ade chat message session-id --kind auto --text "status/context"
ade chat steer session-id --text "active-turn context"
ade chat note "running e2e shard 2/4" # update the caller's Work sidebar status; add --session <id> to target explicitly
ade chat ask "Which account should I use?" # escalate a blocking question; add --session <id> to target explicitly
ade chat settle --outcome "opened PR #841, CI green" # settle only when runtime lifecycle checks pass; exits 1 with exact blockers otherwise
ade chat unsettle # return the caller to the active lifecycle; add --session <id> to target explicitly
ade session show session-id --text # settle/snooze state, and why a snoozed row came back
ade session snooze session-id --for 1h # 30m|1h|4h|1d|1.5h; a bare number means minutes; relative durations cap at 30d
ade session snooze session-id --until 2026-07-26T18:00:00Z # explicit ISO-8601 deadline (must be in the future)
ade session snooze session-id --until-asked # open-ended, matching the desktop/iOS "Until I'm asked" preset: only a hand-raise brings it back
ade session wake session-id --reason manual # timer|needs_you|error|turn_complete|manual
ade session settle session-id --outcome "CI green" # same as `ade chat settle`, but works for CLI/terminal sessions too
ade session settle session-id --keep-active # pin active instead; the only way to hold a clean-exit row out of the quiet tier
ade session unsettle session-id
ade session clear-woke session-id # drop the "woke early" marker after visiting the row
ade session actions --text # raw session service actions
ade chat schedules session-id --pause # pause this agent session's durable wakeups/cron/loops (omit flag to inspect, --resume to re-arm)
Expand Down
40 changes: 19 additions & 21 deletions apps/ade-cli/src/adeRpcServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1555,19 +1555,6 @@ describe("adeRpcServer", () => {
"Waiting for release choice",
),
},
{
action: "settleSelfSession",
args: { outcome: "Shipped" },
assert: () => expect(runtime.sessionService.settleSession).toHaveBeenCalledWith(
"chat-1",
{ outcome: "Shipped", source: "agent_explicit" },
),
},
{
action: "unsettleSelfSession",
args: {},
assert: () => expect(runtime.sessionService.unsettleSession).toHaveBeenCalledWith("chat-1"),
},
];
for (const lifecycle of lifecycleCalls) {
const result = await callTool(handler, "run_ade_action", {
Expand All @@ -1590,12 +1577,23 @@ describe("adeRpcServer", () => {
"Cross-session write",
);

const manualSettleDenied = await callTool(handler, "run_ade_action", {
domain: "session",
action: "settleSession",
args: { sessionId: "chat-1", outcome: "Bypass blockers" },
});
expect(manualSettleDenied.isError).toBe(true);
// Settlement is user- and PR-merge-driven only (2026-07). A session-bound
// caller gets no settle writer at all: the caller-scoped `*SelfSession`
// pair was deleted, and every survivor is CTO-only while binding caps the
// caller below cto.
for (const settleAttempt of [
{ action: "settleSession", args: { sessionId: "chat-1", outcome: "Bypass blockers" } },
{ action: "unsettleSession", args: { sessionId: "chat-1" } },
{ action: "settleSelfSession", args: { sessionId: "chat-1", outcome: "Shipped" } },
{ action: "unsettleSelfSession", args: { sessionId: "chat-1" } },
{ action: "settleSessions", args: { sessionIds: ["chat-1"] } },
{ action: "setSettleOverride", args: { sessionId: "chat-1", override: "settled" } },
]) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const denialResult = await callTool(handler, "run_ade_action", settleAttempt);
expect(denialResult.isError).toBe(true);
}
expect(runtime.sessionService.settleSession).not.toHaveBeenCalled();
expect(runtime.sessionService.unsettleSession).not.toHaveBeenCalled();
});
});

Expand Down Expand Up @@ -2236,7 +2234,7 @@ describe("adeRpcServer", () => {
expect(finalArg).toContain("clean up processes you start");
expect(finalArg).toContain("ade chat note");
expect(finalArg).toContain("ade chat ask");
expect(finalArg).toContain("ade chat settle");
expect(finalArg).toContain("You cannot settle or unsettle a session");
expect(finalArg.endsWith("Implement API wiring")).toBe(true);
expect(response.structuredContent.startupCommand).toContain("claude");
expect(response.structuredContent.startupCommand).toContain("--model");
Expand Down Expand Up @@ -2268,7 +2266,7 @@ describe("adeRpcServer", () => {
const finalArg = createCall.args?.at(-1) ?? "";
expect(finalArg).toContain("ade chat note");
expect(finalArg).toContain("ade chat ask");
expect(finalArg).toContain("ade chat settle");
expect(finalArg).toContain("You cannot settle or unsettle a session");
expect(createCall.args).not.toContain("--full-auto");
expect(createCall.startupCommand).toContain("--sandbox workspace-write --ask-for-approval on-request");
expect(createCall.startupCommand).not.toContain("--full-auto");
Expand Down
8 changes: 6 additions & 2 deletions apps/ade-cli/src/adeRpcServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2675,8 +2675,12 @@ const SCOPED_CHAT_ACTIONS = new Set([
"setScheduledWorkPaused",
"requestSessionAttention",
"setSessionStatusNote",
"settleSelfSession",
"unsettleSelfSession",
// `settleSelfSession` / `unsettleSelfSession` used to be scoped here so a
// bound agent could only settle its OWN row. Both actions were removed in
// 2026-07: "is this work finished" is a subjective judgment agents are
// unreliable at, so the surviving settle writers (`session.settleSession`,
// `session.unsettleSession`, the bulk pair, `session.setSettleOverride`) are
// all CTO-only and refuse a session-bound agent outright — no scoping needed.
"interrupt",
"interruptWithQueueMode",
"restoreCancelledQueue",
Expand Down
74 changes: 10 additions & 64 deletions apps/ade-cli/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3060,16 +3060,6 @@ describe("ADE CLI", () => {
action: "setSessionStatusNote",
args: { note: "running e2e shard 2/4" },
},
{
command: ["settle", "--outcome", "opened PR #841, CI green"],
action: "settleSelfSession",
args: { outcome: "opened PR #841, CI green" },
},
{
command: ["unsettle"],
action: "unsettleSelfSession",
args: {},
},
];

for (const testCase of cases) {
Expand All @@ -3083,41 +3073,6 @@ describe("ADE CLI", () => {
},
});
}
const settlePlan = expectExecutePlan(buildCliPlan([
"chat",
"settle",
"--outcome",
"done",
]));
expect(settlePlan.formatter).toBe("session-settlement");
expect(settlePlan.exitCodeFromResult?.({ ok: false, blockers: [] })).toBe(1);
expect(settlePlan.exitCodeFromResult?.({ ok: true })).toBe(0);
expect(() => buildCliPlan(["chat", "settle"]))
.toThrow(/outcome is required/i);
expect(() => buildCliPlan(["chat", "settle", "--outcome", ""]))
.toThrow(/outcome is required/i);

const blockedText = formatOutput({
ok: false,
sessionId: "session-x",
blockers: [
{
code: "pending_input",
message: "Resolve the pending input before settling.",
},
{
code: "scheduled_work_active",
message: "Cancel or complete scheduled work before settling.",
},
],
}, { text: true } as any, settlePlan.formatter);
expect(blockedText).toContain("Session session-x was not settled.");
expect(blockedText).toContain(
"- pending_input: Resolve the pending input before settling.",
);
expect(blockedText).toContain(
"- scheduled_work_active: Cancel or complete scheduled work before settling.",
);

const clearNote = expectExecutePlan(buildCliPlan(["chat", "note", ""]));
expect(clearNote.steps[0]?.params).toMatchObject({
Expand Down Expand Up @@ -3171,19 +3126,16 @@ describe("ADE CLI", () => {
if (help.kind === "help") {
expect(help.text).toContain("ade chat note");
expect(help.text).toContain("ade chat ask");
expect(help.text).toContain("ade chat settle");
expect(help.text).toContain("ade chat unsettle");
expect(help.text).toContain("runtime lifecycle checks");
expect(help.text).toContain("prints exact blockers");
// Settling is user-/PR-merge-driven only; the help must say so rather
// than advertise a command that no longer exists.
expect(help.text).toContain("'chat settle' / 'chat unsettle' were removed");
expect(help.text).toContain("--session <id>");
}
});

it.each([
["ask", ["q"], "requestSessionAttention", { message: "q" }],
["note", ["working"], "setSessionStatusNote", { note: "working" }],
["settle", ["--outcome", "done"], "settleSelfSession", { outcome: "done" }],
["unsettle", [], "unsettleSelfSession", {}],
])(
"passes --session through for chat %s",
(subcommand, commandArgs, action, expectedArgs) => {
Expand Down Expand Up @@ -3319,15 +3271,6 @@ describe("ADE CLI", () => {
sessionId: "session-x",
reason: "needs_you",
}],
[["settle", "session-x", "--outcome", "done"], "settleSelfSession", {
sessionId: "session-x",
outcome: "done",
}],
[["settle", "session-x", "--keep-active"], "setSettleOverride", {
sessionId: "session-x",
override: "active",
}],
[["unsettle", "session-x"], "unsettleSelfSession", { sessionId: "session-x" }],
[["clear-woke", "session-x"], "clearWokeMarker", { sessionId: "session-x" }],
[["show", "session-x"], "get", { sessionId: "session-x" }],
])("plans ade session %s", (commandArgs, action, expectedArgs) => {
Expand All @@ -3351,9 +3294,9 @@ describe("ADE CLI", () => {
const previous = process.env.ADE_CHAT_SESSION_ID;
process.env.ADE_CHAT_SESSION_ID = "session-env";
try {
const envPlan = expectExecutePlan(buildCliPlan(["session", "unsettle"]));
const envPlan = expectExecutePlan(buildCliPlan(["session", "clear-woke"]));
expect(envPlan.steps[0]?.params).toMatchObject({
arguments: { action: "unsettleSelfSession", args: { sessionId: "session-env" } },
arguments: { action: "clearWokeMarker", args: { sessionId: "session-env" } },
});
} finally {
if (previous === undefined) delete process.env.ADE_CHAT_SESSION_ID;
Expand Down Expand Up @@ -3382,11 +3325,14 @@ describe("ADE CLI", () => {
expect(help.text).toContain("ade session snooze <id> --for 1h");
expect(help.text).toContain("ade session wake <id>");
expect(help.text).toContain("--until-asked");
expect(help.text).toContain("--keep-active");
// Settle is gone from this family and the help says why.
expect(help.text).not.toContain("ade session settle");
expect(help.text).toContain("'settle' and 'unsettle' were removed");
}
const top = buildCliPlan([]);
if (top.kind === "help") {
expect(top.text).toContain("ade session snooze | wake | settle | unsettle");
expect(top.text).toContain("ade session show | snooze | wake | clear-woke");
expect(top.text).not.toContain("ade session snooze | wake | settle | unsettle");
}
});
});
Expand Down
Loading