Skip to content

Commit 1446742

Browse files
fix: 修复历史数据回显
1 parent 38d5e16 commit 1446742

8 files changed

Lines changed: 386 additions & 17 deletions

File tree

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
- [x] 权限面板的审批逻辑没有相应到
2-
- [ ] Loading 改为 anthropic 相关的动画, 借鉴 tui 的
2+
- [x] Loading 改为 anthropic 相关的动画, 借鉴 tui 的
33
- [x] 多轮数据时, 非常多的空白和重复消息
44
- [x] 历史数据没有拉取
5+
- [x] 数据重新拉取没有把审批弹窗等显示出来

packages/remote-control-server/src/__tests__/ws-handler.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,59 @@ describe("ws-handler", () => {
374374
expect(lastMsg.response.response.updatedInput).toEqual({ cmd: "ls -la" });
375375
});
376376

377+
test("permission_response with updated_permissions", () => {
378+
const bus = getEventBus("up1");
379+
const ws = createMockWs();
380+
handleWebSocketOpen(ws, "up1");
381+
382+
const permissions = [{ type: "setMode", mode: "acceptEdits", destination: "session" }];
383+
bus.publish({
384+
id: "ep1",
385+
sessionId: "up1",
386+
type: "permission_response",
387+
payload: {
388+
approved: true,
389+
request_id: "req-ep1",
390+
updated_input: { plan: "my plan" },
391+
updated_permissions: permissions,
392+
},
393+
direction: "outbound",
394+
});
395+
396+
const sent = ws.getSentData();
397+
const lastMsg = JSON.parse(sent[sent.length - 1]);
398+
expect(lastMsg.type).toBe("control_response");
399+
expect(lastMsg.response.subtype).toBe("success");
400+
expect(lastMsg.response.response.behavior).toBe("allow");
401+
expect(lastMsg.response.response.updatedInput).toEqual({ plan: "my plan" });
402+
expect(lastMsg.response.response.updatedPermissions).toEqual(permissions);
403+
});
404+
405+
test("permission_response denied with feedback message", () => {
406+
const bus = getEventBus("dm1");
407+
const ws = createMockWs();
408+
handleWebSocketOpen(ws, "dm1");
409+
410+
bus.publish({
411+
id: "dm1",
412+
sessionId: "dm1",
413+
type: "permission_response",
414+
payload: {
415+
approved: false,
416+
request_id: "req-dm1",
417+
message: "Please add more tests",
418+
},
419+
direction: "outbound",
420+
});
421+
422+
const sent = ws.getSentData();
423+
const lastMsg = JSON.parse(sent[sent.length - 1]);
424+
expect(lastMsg.type).toBe("control_response");
425+
expect(lastMsg.response.subtype).toBe("error");
426+
expect(lastMsg.response.response.behavior).toBe("deny");
427+
expect(lastMsg.response.message).toBe("Please add more tests");
428+
});
429+
377430
test("does not forward inbound events to WS", () => {
378431
const bus = getEventBus("no_in");
379432
const ws = createMockWs();

packages/remote-control-server/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ const stripCodePrefix = (p: string) => p.replace(/^\/code/, "");
4141

4242
// Serve all static files under /code/* from web/ directory
4343
app.use("/code/*", serveStatic({ root: webDir, rewriteRequestPath: stripCodePrefix }));
44-
// /code and /code/:sessionId — SPA fallback
44+
// /code, /code/, and /code/:sessionId — SPA fallback
4545
app.get("/code", serveStatic({ root: webDir, path: "index.html" }));
46+
app.get("/code/", serveStatic({ root: webDir, path: "index.html" }));
4647
app.get("/code/:sessionId", serveStatic({ root: webDir, path: "index.html" }));
4748

4849
// v1 Environment routes

packages/remote-control-server/src/transport/ws-handler.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,26 @@ function toSDKMessage(event: SessionEvent): string {
4444
msg = { type: "control_response", response: existingResponse };
4545
} else {
4646
const updatedInput = payload?.updated_input as Record<string, unknown> | undefined;
47+
const updatedPermissions = payload?.updated_permissions as Record<string, unknown>[] | undefined;
48+
const feedbackMessage = payload?.message as string | undefined;
4749
msg = {
4850
type: "control_response",
4951
response: {
5052
subtype: approved ? "success" : "error",
5153
request_id: payload?.request_id ?? "",
5254
...(approved
53-
? { response: { behavior: "allow" as const, ...(updatedInput ? { updatedInput } : {}) } }
54-
: { error: "Permission denied by user", response: { behavior: "deny" as const } }),
55+
? {
56+
response: {
57+
behavior: "allow" as const,
58+
...(updatedInput ? { updatedInput } : {}),
59+
...(updatedPermissions ? { updatedPermissions } : {}),
60+
},
61+
}
62+
: {
63+
error: "Permission denied by user",
64+
response: { behavior: "deny" as const },
65+
...(feedbackMessage ? { message: feedbackMessage } : {}),
66+
}),
5567
},
5668
};
5769
}

packages/remote-control-server/web/app.js

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
import { getUuid, setUuid, apiBind, apiFetchSessions, apiFetchAllSessions, apiFetchEnvironments, apiFetchSession, apiFetchSessionHistory, apiSendEvent, apiSendControl, apiInterrupt, apiCreateSession } from "./api.js";
66
import { connectSSE, disconnectSSE } from "./sse.js";
7-
import { appendEvent, renderPermissionRequest, showLoading, isLoading } from "./render.js";
7+
import { appendEvent, renderPermissionRequest, showLoading, isLoading, resetReplayState, renderReplayPendingRequests } from "./render.js";
88
import { esc, formatTime, statusClass } from "./utils.js";
99

1010
// ============================================================
@@ -70,7 +70,7 @@ async function handleRoute() {
7070
} catch (err) {
7171
console.error("Failed to bind session:", err);
7272
alert("Session not found or bind failed: " + err.message);
73-
history.replaceState(null, "", "/code");
73+
history.replaceState(null, "", "/code/");
7474
}
7575
}
7676

@@ -170,14 +170,15 @@ async function renderSessionDetail(id) {
170170
badge.className = `status-badge status-${statusClass(session.status)}`;
171171
} catch (err) {
172172
alert("Failed to load session: " + err.message);
173-
navigate("/code");
173+
navigate("/code/");
174174
return;
175175
}
176176
document.getElementById("event-stream").innerHTML = "";
177177
document.getElementById("permission-area").innerHTML = "";
178178
document.getElementById("permission-area").classList.add("hidden");
179179

180180
// Load historical events before connecting to live stream
181+
resetReplayState();
181182
let lastSeqNum = 0;
182183
try {
183184
const { events } = await apiFetchSessionHistory(id);
@@ -190,6 +191,8 @@ async function renderSessionDetail(id) {
190191
} catch (err) {
191192
console.warn("Failed to load session history:", err);
192193
}
194+
// Re-render any still-unresolved permission prompts from history
195+
renderReplayPendingRequests();
193196

194197
connectSSE(id, appendEvent, lastSeqNum);
195198
}
@@ -365,12 +368,83 @@ window._submitAnswers = async function (requestId, btn) {
365368
};
366369

367370
function removePermissionPrompt(btn) {
368-
const prompt = btn.closest(".permission-prompt, .ask-panel");
371+
const prompt = btn.closest(".permission-prompt, .ask-panel, .plan-panel");
369372
if (prompt) prompt.remove();
370373
const area = document.getElementById("permission-area");
371374
if (area && area.children.length === 0) area.classList.add("hidden");
372375
}
373376

377+
// ============================================================
378+
// ExitPlanMode interactions
379+
// ============================================================
380+
381+
window._selectPlanOption = function (btn, value) {
382+
const panel = btn.closest(".plan-panel");
383+
if (!panel) return;
384+
385+
// Deselect all siblings
386+
panel.querySelectorAll(".plan-option").forEach((o) => o.classList.remove("selected"));
387+
btn.classList.add("selected");
388+
panel._selectedValue = value;
389+
390+
// Show/hide feedback textarea
391+
const feedbackArea = panel.querySelector(".plan-feedback-area");
392+
if (feedbackArea) {
393+
feedbackArea.classList.toggle("visible", value === "no");
394+
}
395+
};
396+
397+
window._submitPlanResponse = async function (requestId, btn) {
398+
const panel = btn.closest(".plan-panel");
399+
if (!panel) return;
400+
401+
const selectedValue = panel._selectedValue;
402+
if (!selectedValue) {
403+
alert("Please select an option first.");
404+
return;
405+
}
406+
407+
btn.disabled = true;
408+
409+
try {
410+
if (selectedValue === "no") {
411+
// Rejection with optional feedback
412+
const feedbackInput = panel.querySelector(".plan-feedback-input");
413+
const feedback = feedbackInput ? feedbackInput.value.trim() : "";
414+
await apiSendControl(currentSessionId, {
415+
type: "permission_response",
416+
approved: false,
417+
request_id: requestId,
418+
...(feedback ? { message: feedback } : {}),
419+
});
420+
removePermissionPrompt(btn);
421+
} else {
422+
// Approval with permission mode
423+
const modeMap = {
424+
"yes-accept-edits": "acceptEdits",
425+
"yes-default": "default",
426+
};
427+
const mode = modeMap[selectedValue] || "default";
428+
const planContent = panel._planContent || "";
429+
430+
await apiSendControl(currentSessionId, {
431+
type: "permission_response",
432+
approved: true,
433+
request_id: requestId,
434+
...(planContent ? { updated_input: { plan: planContent } } : {}),
435+
updated_permissions: [
436+
{ type: "setMode", mode, destination: "session" },
437+
],
438+
});
439+
removePermissionPrompt(btn);
440+
showLoading();
441+
}
442+
} catch (err) {
443+
alert("Failed to submit: " + err.message);
444+
btn.disabled = false;
445+
}
446+
};
447+
374448
// ============================================================
375449
// New Session Dialog
376450
// ============================================================
@@ -486,7 +560,7 @@ function setupIdentityPanel() {
486560
if (importedUuid) {
487561
setUuid(importedUuid);
488562
panel.classList.add("hidden");
489-
navigate("/code");
563+
navigate("/code/");
490564
renderDashboard();
491565
return;
492566
}
@@ -495,7 +569,7 @@ function setupIdentityPanel() {
495569
if (code.data.length >= 32) {
496570
setUuid(code.data);
497571
panel.classList.add("hidden");
498-
navigate("/code");
572+
navigate("/code/");
499573
renderDashboard();
500574
return;
501575
}

packages/remote-control-server/web/index.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@
77
<link rel="preconnect" href="https://fonts.googleapis.com" />
88
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
99
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,500;12..96,600;12..96,700&family=Figtree:wght@300;400;500;600;700&family=Fira+Code:wght@400;500&display=swap" />
10-
<link rel="stylesheet" href="/code/style.css" />
10+
<link rel="stylesheet" href="./style.css" />
1111
</head>
1212
<body>
1313
<!-- Nav Bar -->
1414
<nav id="navbar">
1515
<div class="nav-inner">
16-
<a href="/code" class="nav-logo">
16+
<a href="/code/" class="nav-logo">
1717
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
1818
<path d="M10 1L12.2 7.8L19 10L12.2 12.2L10 19L7.8 12.2L1 10L7.8 7.8L10 1Z" fill="#D97757"/>
1919
</svg>
2020
Remote Control
2121
</a>
2222
<div class="nav-links">
23-
<a href="/code" class="nav-link" id="nav-dashboard">Dashboard</a>
23+
<a href="/code/" class="nav-link" id="nav-dashboard">Dashboard</a>
2424
<button id="nav-identity" class="nav-link btn-text" title="Identity & QR">
2525
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="vertical-align:-2px;margin-right:4px;">
2626
<path d="M6 8C7.66 8 9 6.66 9 5C9 3.34 7.66 2 6 2C4.34 2 3 3.34 3 5C3 6.66 4.34 8 6 8ZM6 10C3.99 10 0 11.01 0 13V14H12V13C12 11.01 8.01 10 6 10ZM13 8V5H11V8H8V10H11V13H13V10H16V8H13Z" fill="currentColor"/>
@@ -75,7 +75,7 @@ <h3>New Session</h3>
7575
<div class="session-container">
7676
<!-- Header -->
7777
<div class="session-header">
78-
<a href="/code" class="back-link">&larr; Dashboard</a>
78+
<a href="/code/" class="back-link">&larr; Dashboard</a>
7979
<div class="session-meta">
8080
<h2 id="session-title" class="session-detail-title">Session</h2>
8181
<div class="session-meta-row">
@@ -140,6 +140,6 @@ <h3>Identity</h3>
140140
<!-- QR Libraries -->
141141
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
142142
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.js"></script>
143-
<script type="module" src="/code/app.js"></script>
143+
<script type="module" src="./app.js"></script>
144144
</body>
145145
</html>

0 commit comments

Comments
 (0)