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 () => {
|
test("workbench workspace clears stale continuation after AgentRun thread resume failure", async () => {
|
||||||
const staleThreadId = "019e0000-0000-7000-8000-000000000195";
|
const staleThreadId = "019e0000-0000-7000-8000-000000000195";
|
||||||
const agentRunCalls = [];
|
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.getWorkspaceForUser?.({ workspaceId, ownerUserId: options.actor.id, actorRole: options.actor.role })
|
||||||
: await store.getOrCreateDefaultWorkspace?.({ ownerUserId: options.actor.id, projectId });
|
: await store.getOrCreateDefaultWorkspace?.({ ownerUserId: options.actor.id, projectId });
|
||||||
if (!current) return null;
|
if (!current) return null;
|
||||||
|
const selectedActiveTraceId = manualSessionSelectableActiveTraceId(session);
|
||||||
|
const selectedLastTraceId = safeTraceId(session.lastTraceId) || selectedActiveTraceId || null;
|
||||||
const updated = await store.updateWorkspace?.({
|
const updated = await store.updateWorkspace?.({
|
||||||
workspaceId: current.id,
|
workspaceId: current.id,
|
||||||
ownerUserId: options.actor.id,
|
ownerUserId: options.actor.id,
|
||||||
@@ -343,13 +345,14 @@ async function updateManualSessionWorkspace({ params = {}, options = {}, session
|
|||||||
selectedConversationId: session.conversationId,
|
selectedConversationId: session.conversationId,
|
||||||
selectedAgentSessionId: session.id,
|
selectedAgentSessionId: session.id,
|
||||||
selectedDevicePodId: current.selectedDevicePodId,
|
selectedDevicePodId: current.selectedDevicePodId,
|
||||||
activeTraceId: null,
|
activeTraceId: selectedActiveTraceId,
|
||||||
providerProfile,
|
providerProfile,
|
||||||
patch: {
|
patch: {
|
||||||
...(current.workspace && typeof current.workspace === "object" ? current.workspace : {}),
|
...(current.workspace && typeof current.workspace === "object" ? current.workspace : {}),
|
||||||
selectedConversationId: session.conversationId,
|
selectedConversationId: session.conversationId,
|
||||||
selectedAgentSessionId: session.id,
|
selectedAgentSessionId: session.id,
|
||||||
activeTraceId: null,
|
activeTraceId: selectedActiveTraceId,
|
||||||
|
...(selectedLastTraceId ? { lastTraceId: selectedLastTraceId } : {}),
|
||||||
sessionStatus: session.status,
|
sessionStatus: session.status,
|
||||||
threadId: safeOpaqueId(session.threadId) || null,
|
threadId: safeOpaqueId(session.threadId) || null,
|
||||||
providerProfile,
|
providerProfile,
|
||||||
@@ -368,6 +371,31 @@ async function updateManualSessionWorkspace({ params = {}, options = {}, session
|
|||||||
return updated ?? current;
|
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) {
|
function publicManualAgentSession(session) {
|
||||||
if (!session || typeof session !== "object") return null;
|
if (!session || typeof session !== "object") return null;
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user