fix(cloud): restore running session active trace (#811)
Co-authored-by: Codex Agent <codex@hwlab.local>
This commit is contained in:
@@ -865,6 +865,122 @@ test("workbench workspace terminal status sync preserves a newer selected conver
|
||||
}
|
||||
});
|
||||
|
||||
test("manual Code Agent session select restores running active trace", async () => {
|
||||
const accessController = createAccessController({
|
||||
env: {
|
||||
HWLAB_ACCESS_CONTROL_REQUIRED: "1",
|
||||
HWLAB_BOOTSTRAP_ADMIN_USERNAME: "admin",
|
||||
HWLAB_BOOTSTRAP_ADMIN_PASSWORD: "admin-pass"
|
||||
},
|
||||
now: () => "2026-06-01T00:00:00.000Z"
|
||||
});
|
||||
const server = createCloudApiServer({
|
||||
env: {
|
||||
HWLAB_ACCESS_CONTROL_REQUIRED: "1",
|
||||
HWLAB_BOOTSTRAP_ADMIN_USERNAME: "admin",
|
||||
HWLAB_BOOTSTRAP_ADMIN_PASSWORD: "admin-pass",
|
||||
HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: "minimax-m3"
|
||||
},
|
||||
accessController,
|
||||
now: () => "2026-06-01T00:00:00.000Z"
|
||||
});
|
||||
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||
|
||||
try {
|
||||
const { port } = server.address();
|
||||
const adminLogin = await postJson(port, "/auth/login", { username: "admin", password: "admin-pass" });
|
||||
const alice = await postJson(port, "/v1/admin/users", { username: "alice-ws-issue810", password: "alice-pass" }, adminLogin.cookie);
|
||||
assert.equal(alice.status, 201);
|
||||
const aliceLogin = await postJson(port, "/auth/login", { username: "alice-ws-issue810", password: "alice-pass" });
|
||||
const workspace = await getJson(port, "/v1/workbench/workspace?projectId=prj_device_pod_workbench", aliceLogin.cookie);
|
||||
assert.equal(workspace.status, 200);
|
||||
|
||||
await accessController.recordAgentSessionOwner({
|
||||
ownerUserId: alice.body.user.id,
|
||||
ownerRole: "user",
|
||||
sessionId: "ses_issue810_running",
|
||||
projectId: "prj_device_pod_workbench",
|
||||
agentId: "hwlab-code-agent",
|
||||
status: "running",
|
||||
conversationId: "cnv_issue810_running",
|
||||
threadId: "thread-issue-810-running",
|
||||
traceId: "trc_issue810_running",
|
||||
session: {
|
||||
source: "test-running-session",
|
||||
providerProfile: "minimax-m3",
|
||||
sessionStatus: "running",
|
||||
currentTraceId: "trc_issue810_running",
|
||||
agentRun: {
|
||||
runId: "run_issue810_running",
|
||||
commandId: "cmd_issue810_running",
|
||||
runStatus: "running",
|
||||
commandState: "running",
|
||||
backendProfile: "minimax-m3"
|
||||
},
|
||||
valuesRedacted: true,
|
||||
secretMaterialStored: false
|
||||
}
|
||||
});
|
||||
await accessController.recordAgentSessionOwner({
|
||||
ownerUserId: alice.body.user.id,
|
||||
ownerRole: "user",
|
||||
sessionId: "ses_issue810_idle",
|
||||
projectId: "prj_device_pod_workbench",
|
||||
agentId: "hwlab-code-agent",
|
||||
status: "idle",
|
||||
conversationId: "cnv_issue810_idle",
|
||||
threadId: "thread-issue-810-idle",
|
||||
traceId: null,
|
||||
session: {
|
||||
source: "test-idle-session",
|
||||
providerProfile: "minimax-m3",
|
||||
sessionStatus: "idle",
|
||||
valuesRedacted: true,
|
||||
secretMaterialStored: false
|
||||
}
|
||||
});
|
||||
|
||||
const selectedIdle = await postJson(port, "/v1/agent/sessions/ses_issue810_idle/select", {
|
||||
projectId: "prj_device_pod_workbench",
|
||||
workspaceId: workspace.body.workspace.workspaceId,
|
||||
updatedByClient: "test-suite"
|
||||
}, aliceLogin.cookie);
|
||||
assert.equal(selectedIdle.status, 200);
|
||||
assert.equal(selectedIdle.body.workspace.selectedConversationId, "cnv_issue810_idle");
|
||||
assert.equal(selectedIdle.body.workspace.selectedAgentSessionId, "ses_issue810_idle");
|
||||
assert.equal(selectedIdle.body.workspace.activeTraceId, null);
|
||||
|
||||
const selectedRunning = await postJson(port, "/v1/agent/sessions/ses_issue810_running/select", {
|
||||
projectId: "prj_device_pod_workbench",
|
||||
workspaceId: workspace.body.workspace.workspaceId,
|
||||
updatedByClient: "test-suite"
|
||||
}, aliceLogin.cookie);
|
||||
assert.equal(selectedRunning.status, 200);
|
||||
assert.equal(selectedRunning.body.status, "selected");
|
||||
assert.equal(selectedRunning.body.session.sessionId, "ses_issue810_running");
|
||||
assert.equal(selectedRunning.body.session.status, "running");
|
||||
assert.equal(selectedRunning.body.session.lastTraceId, "trc_issue810_running");
|
||||
assert.equal(selectedRunning.body.workspace.selectedConversationId, "cnv_issue810_running");
|
||||
assert.equal(selectedRunning.body.workspace.selectedAgentSessionId, "ses_issue810_running");
|
||||
assert.equal(selectedRunning.body.workspace.activeTraceId, "trc_issue810_running");
|
||||
assert.equal(selectedRunning.body.workspace.workspace.activeTraceId, "trc_issue810_running");
|
||||
assert.equal(selectedRunning.body.workspace.workspace.lastTraceId, "trc_issue810_running");
|
||||
assert.equal(selectedRunning.body.workspace.workspace.sessionStatus, "running");
|
||||
assert.equal(selectedRunning.body.workspace.workspace.providerProfile, "minimax-m3");
|
||||
|
||||
const restored = await getJson(port, "/v1/workbench/workspace?projectId=prj_device_pod_workbench", aliceLogin.cookie);
|
||||
assert.equal(restored.status, 200);
|
||||
assert.equal(restored.body.workspace.selectedConversationId, "cnv_issue810_running");
|
||||
assert.equal(restored.body.workspace.selectedAgentSessionId, "ses_issue810_running");
|
||||
assert.equal(restored.body.workspace.activeTraceId, "trc_issue810_running");
|
||||
assert.equal(restored.body.workspace.workspace.activeTraceId, "trc_issue810_running");
|
||||
assert.equal(restored.body.workspace.workspace.lastTraceId, "trc_issue810_running");
|
||||
assert.equal(restored.body.workspace.workspace.sessionStatus, "running");
|
||||
} finally {
|
||||
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
|
||||
}
|
||||
});
|
||||
|
||||
test("workbench workspace clears stale continuation after AgentRun thread resume failure", async () => {
|
||||
const staleThreadId = "019e0000-0000-7000-8000-000000000195";
|
||||
const agentRunCalls = [];
|
||||
|
||||
@@ -333,6 +333,8 @@ async function updateManualSessionWorkspace({ params = {}, options = {}, session
|
||||
? await store.getWorkspaceForUser?.({ workspaceId, ownerUserId: options.actor.id, actorRole: options.actor.role })
|
||||
: await store.getOrCreateDefaultWorkspace?.({ ownerUserId: options.actor.id, projectId });
|
||||
if (!current) return null;
|
||||
const selectedActiveTraceId = manualSessionSelectableActiveTraceId(session);
|
||||
const selectedLastTraceId = safeTraceId(session.lastTraceId) || selectedActiveTraceId || null;
|
||||
const updated = await store.updateWorkspace?.({
|
||||
workspaceId: current.id,
|
||||
ownerUserId: options.actor.id,
|
||||
@@ -343,13 +345,14 @@ async function updateManualSessionWorkspace({ params = {}, options = {}, session
|
||||
selectedConversationId: session.conversationId,
|
||||
selectedAgentSessionId: session.id,
|
||||
selectedDevicePodId: current.selectedDevicePodId,
|
||||
activeTraceId: null,
|
||||
activeTraceId: selectedActiveTraceId,
|
||||
providerProfile,
|
||||
patch: {
|
||||
...(current.workspace && typeof current.workspace === "object" ? current.workspace : {}),
|
||||
selectedConversationId: session.conversationId,
|
||||
selectedAgentSessionId: session.id,
|
||||
activeTraceId: null,
|
||||
activeTraceId: selectedActiveTraceId,
|
||||
...(selectedLastTraceId ? { lastTraceId: selectedLastTraceId } : {}),
|
||||
sessionStatus: session.status,
|
||||
threadId: safeOpaqueId(session.threadId) || null,
|
||||
providerProfile,
|
||||
@@ -368,6 +371,31 @@ async function updateManualSessionWorkspace({ params = {}, options = {}, session
|
||||
return updated ?? current;
|
||||
}
|
||||
|
||||
function manualSessionSelectableActiveTraceId(session = {}) {
|
||||
if (!manualSessionHasRunningTraceStatus(session)) return null;
|
||||
return safeTraceId(
|
||||
session.currentTraceId
|
||||
?? session.session?.currentTraceId
|
||||
?? session.session?.traceId
|
||||
?? session.session?.runnerTrace?.traceId
|
||||
?? session.session?.agentRun?.traceId
|
||||
) || safeTraceId(session.lastTraceId) || null;
|
||||
}
|
||||
|
||||
function manualSessionHasRunningTraceStatus(session = {}) {
|
||||
return [
|
||||
session.status,
|
||||
session.session?.sessionStatus,
|
||||
session.session?.lifecycleStatus,
|
||||
session.session?.runnerTrace?.sessionStatus,
|
||||
session.session?.runnerTrace?.status,
|
||||
session.session?.agentRun?.runStatus,
|
||||
session.session?.agentRun?.commandState,
|
||||
session.session?.agentRun?.terminalStatus
|
||||
].map((value) => String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"))
|
||||
.some((value) => ["running", "busy", "pending", "creating"].includes(value));
|
||||
}
|
||||
|
||||
function publicManualAgentSession(session) {
|
||||
if (!session || typeof session !== "object") return null;
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user