Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e606cf3
fix(goals): persist goal cards and restore the hook on daemon resume
wenshao Jul 9, 2026
1ac5fc8
feat(web-shell): add a workspace Goals page
wenshao Jul 9, 2026
a679a47
Merge branch 'main' into feat/web-shell-goals-page
yiliang114 Jul 9, 2026
9269d8b
fix(web-shell): stop the Goals poll from overlapping itself
wenshao Jul 9, 2026
c4baeef
fix(goals): address review — clear-keyword condition, silent failures…
wenshao Jul 9, 2026
6ea0e7c
fix(goals): cap restored conditions, keep goal-creation errors on screen
wenshao Jul 9, 2026
ce9411e
fix(goals): surface restore/record failures, report unprobed sessions
wenshao Jul 9, 2026
3eae9d5
test(goals): update the /goals integration test for droppedCount
wenshao Jul 9, 2026
0ec45a7
fix(goals): refuse to replay an oversized goal card
wenshao Jul 9, 2026
a4ae93a
Merge remote-tracking branch 'origin/main' into feat/web-shell-goals-…
wenshao Jul 9, 2026
91b2331
fix(goals): keep the terminal observer alive across ACP resume
wenshao Jul 10, 2026
7065786
merge: resolve conflict in packages/cli/src/ui/types.ts
qwen-code-dev-bot Jul 10, 2026
a6d2ac4
Merge branch 'main' into feat/web-shell-goals-page
wenshao Jul 10, 2026
8ee5eb4
fix(goals): drop the condition length cap on restore and in the web s…
wenshao Jul 10, 2026
d3f231c
Merge branch 'main' into feat/web-shell-goals-page
wenshao Jul 10, 2026
af8c327
merge: resolve conflicts with origin/main for PR #6561
qwen-code-dev-bot Jul 10, 2026
bee3295
fix(web-shell): reuse the empty session a failed goal attempt leaves …
wenshao Jul 11, 2026
f20e31c
fix(goals): forget the stranded goal session on leaving the Goals page
wenshao Jul 11, 2026
4781ed0
fix(goals): identify a goal run by its condition, not just its card k…
wenshao Jul 11, 2026
261f363
fix(web-shell): announce Goals dialog errors and give its buttons a f…
wenshao Jul 11, 2026
ef00294
fix(cli): stop a broken stderr from abandoning a transcript replay
wenshao Jul 12, 2026
7796aa3
merge: resolve conflicts with origin/main for PR #6561
wenshao Jul 12, 2026
0dd0530
merge: sync origin/main into feat/web-shell-goals-page
wenshao Jul 15, 2026
49eb827
Merge remote-tracking branch 'origin/main' into feat/web-shell-goals-…
wenshao Jul 15, 2026
e540e29
Merge remote-tracking branch 'origin/main' into feat/web-shell-goals-…
wenshao Jul 17, 2026
00f3b5c
fix(web-shell): keep the Goals page mounted across createNewSession
wenshao Jul 17, 2026
2501d57
fix(web-shell): stop the visuals spec asserting a badge #7035 removed
wenshao Jul 17, 2026
5cd6914
Merge remote-tracking branch 'origin/main' into feat/web-shell-goals-…
wenshao Jul 18, 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
1 change: 1 addition & 0 deletions .qwen/skills/autofix/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ owns the model-driven decisions, code changes, and pre-commit verification.
Keep `failure.md` and `handoff.md` English-only WITHOUT a details block:
handoff comments embed a byte-truncated excerpt of them, and a severed
`<details>` tag would swallow the rest of the comment when rendered.

- Never ask the user a question in this headless workflow. If blocked, write
`<workdir>/failure.md` with what you learned and stop.

Expand Down
40 changes: 40 additions & 0 deletions integration-tests/cli/qwen-serve-routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,46 @@ describe('qwen serve — cancel + list', () => {
});
});

describe('qwen serve — GET /goals', () => {
const getGoals = async () => {
const res = await fetch(`${base}/goals`, {
headers: { Authorization: `Bearer ${TOKEN}` },
});
return { status: res.status, body: await res.json() };
};

it('returns an empty, versioned list when no session has a goal', async () => {
const { status, body } = await getGoals();
expect(status).toBe(200);
expect(body).toEqual({ v: 1, goals: [], droppedCount: 0 });
});

it('probes each live session over the bridge without reporting a goal', async () => {
// The real round trip: serve -> bridge -> `sessionGoalGet` ext method in
// the `qwen --acp` child -> back. A live session with no `/goal` must come
// back as "no goal" rather than an error or a phantom entry.
const session = await client.createOrAttachSession({
workspaceCwd: REPO_ROOT,
sessionScope: 'thread',
});
try {
const { status, body } = await getGoals();
expect(status).toBe(200);
// `droppedCount: 0` is the load-bearing half: it proves the ext-method
// probe actually reached the child. A dropped probe would also yield an
// empty `goals`, so that alone cannot tell success from a silent failure.
expect(body).toEqual({ v: 1, goals: [], droppedCount: 0 });
} finally {
await client.closeSession(session.sessionId);
}
});

it('requires the bearer token', async () => {
const res = await fetch(`${base}/goals`);
expect(res.status).toBe(401);
});
});

describe('qwen serve — DELETE /session/:id', () => {
it('204 on explicit close', async () => {
const session = await client.createOrAttachSession({
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions packages/acp-bridge/src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ import type {
BridgeRestoreSessionRequest,
BridgeSessionState,
BridgeRestoredSession,
BridgeSessionGoal,
BridgeSessionSummary,
BridgePendingInteraction,
BridgeClientRequestContext,
Expand Down Expand Up @@ -6146,6 +6147,13 @@ export function createAcpSessionBridge(opts: BridgeOptions): AcpSessionBridge {
);
},

async getSessionGoal(sessionId) {
return requestSessionStatus<BridgeSessionGoal>(
sessionId,
SERVE_CONTROL_EXT_METHODS.sessionGoalGet,
);
},

async continueSession(sessionId, context) {
// Validate the originator up-front, mirroring POST /session/:id/prompt, so
// an unknown client id (or a session that vanished) surfaces as an error
Expand Down
25 changes: 25 additions & 0 deletions packages/acp-bridge/src/bridgeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,24 @@ export interface BridgeSessionSummary {
color?: SessionGroupPresetColor | null;
}

/**
* A session's live `/goal` state, as reported by the `qwen --acp` child.
*
* Only the active goal crosses the bridge. The child also caches the most
* recent goal that ended on its own, but nothing on this side reads it, so it
* is not part of the wire shape — add it back alongside the first consumer.
*/
export interface BridgeSessionGoal {
active: {
condition: string;
/** Judge turns completed so far; 0 before the first stop-hook evaluation. */
iterations: number;
setAt: number;
/** The judge's verdict on the most recent turn, when it has run. */
lastReason?: string;
} | null;
}

export interface SessionMetadataUpdate {
displayName?: string;
}
Expand Down Expand Up @@ -971,6 +989,13 @@ export interface AcpSessionBridge {
sessionId: string,
): Promise<{ cleared: boolean; condition?: string }>;

/**
* Read a live session's goal state. Throws `SessionNotFoundError` when the
* session is not resident — goals live in the child's memory, so a
* non-resident session has no goal to report.
*/
getSessionGoal(sessionId: string): Promise<BridgeSessionGoal>;

/**
* Resume a live session's unfinished previous turn — an interrupted prompt
* (model never answered) or a turn left with dangling tool calls — without
Expand Down
7 changes: 7 additions & 0 deletions packages/acp-bridge/src/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ export const SERVE_CONTROL_EXT_METHODS = {
// Runtime MCP server mutation ext-methods
sessionTaskCancel: 'qwen/control/session/task/cancel',
sessionGoalClear: 'qwen/control/session/goal/clear',
/**
* Read a live session's `/goal` state. The active goal lives only in the
* child's in-memory store, so this is the sole authoritative source for the
* condition, its running turn count and the judge's last verdict. Params:
* `{ sessionId }`; result: `{ active: ActiveGoalView | null }`.
*/
sessionGoalGet: 'qwen/control/session/goal/get',
workspaceMcpRuntimeAdd: 'qwen/control/workspace/mcp/runtime-add',
workspaceMcpRuntimeRemove: 'qwen/control/workspace/mcp/runtime-remove',
workspaceReload: 'qwen/control/workspace/reload',
Expand Down
Loading
Loading