fix: preserve workbench launch context in session facts
This commit is contained in:
@@ -14,6 +14,7 @@ test("workbench launch records OTel stage for Project Management link write 404"
|
||||
});
|
||||
const response = jsonResponse();
|
||||
const fetchCalls = [];
|
||||
const factWrites = [];
|
||||
globalThis.fetch = async (url, init) => {
|
||||
fetchCalls.push({ url: String(url), method: init?.method, body: init?.body });
|
||||
if (String(url).includes("/v1/project-management/mdtodo/launch-context")) {
|
||||
@@ -51,8 +52,9 @@ test("workbench launch records OTel stage for Project Management link write 404"
|
||||
}
|
||||
},
|
||||
runtimeStore: {
|
||||
async writeWorkbenchFacts() {
|
||||
return { ok: true };
|
||||
async writeWorkbenchFacts(params, requestMeta) {
|
||||
factWrites.push({ params, requestMeta });
|
||||
return { ok: true, facts: params.facts };
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -83,6 +85,13 @@ test("workbench launch records OTel stage for Project Management link write 404"
|
||||
assert.equal(context.otelAttributes["workbench.launch.context_fingerprint"], "ctx_launch_otel");
|
||||
assert.match(context.otelAttributes["workbench.launch.task_ref_hash"], /^[0-9a-f]{24}$/u);
|
||||
assert.equal(context.otelAttributes["workbench.launch.values_redacted"], true);
|
||||
assert.equal(factWrites.length, 1);
|
||||
const sessionJson = factWrites[0].params.facts.sessions[0].sessionJson;
|
||||
assert.equal(sessionJson.launchContext.sourceId, "hwlab-v03-mdtodo");
|
||||
assert.equal(sessionJson.launchContext.hwpodId, "constart-71freq-c");
|
||||
assert.equal(sessionJson.launchContext.contextFingerprint, "ctx_launch_otel");
|
||||
assert.equal(sessionJson.launchContext.executionContext.hwpodWorkspaceArgs, "--hwpod-id constart-71freq-c --workspace-path 'F:\\Work\\ConStart'");
|
||||
assert.equal(sessionJson.launchContext.valuesRedacted, true);
|
||||
});
|
||||
|
||||
function jsonRequest(body) {
|
||||
|
||||
@@ -7,6 +7,106 @@ import { test } from "bun:test";
|
||||
import { createCloudRuntimeStore } from "../db/runtime-store.ts";
|
||||
import { writeWorkbenchProjectionEvent, writeWorkbenchProjectionSession } from "./workbench-projection-writer.ts";
|
||||
|
||||
test("workbench projection writer preserves Workbench launch context on Code Agent session facts", async () => {
|
||||
const factWrites = [];
|
||||
const accessWrites = [];
|
||||
const launchContext = {
|
||||
source: "project-management",
|
||||
projectId: "project_constart_71freq",
|
||||
taskRef: "mdtodo:constart-71freq-mdtodo:file_c5abae4e69c60370:R1",
|
||||
sourceId: "constart-71freq-mdtodo",
|
||||
sourceKind: "hwpod-workspace",
|
||||
fileRef: "file_c5abae4e69c60370",
|
||||
taskId: "R1",
|
||||
hwpodId: "constart-71freq-c",
|
||||
nodeId: "node-d601-f103-v2",
|
||||
mdtodoRootRef: "docs/MDTODO",
|
||||
workspaceRootHash: "workspace-hash",
|
||||
hwpodWorkspaceArgs: "--hwpod-id constart-71freq-c --workspace-path 'F:\\Work\\ConStart'",
|
||||
contextFingerprint: "ctx_projection_launch",
|
||||
executionContext: {
|
||||
sourceId: "constart-71freq-mdtodo",
|
||||
sourceKind: "hwpod-workspace",
|
||||
projectId: "project_constart_71freq",
|
||||
taskRef: "mdtodo:constart-71freq-mdtodo:file_c5abae4e69c60370:R1",
|
||||
fileRef: "file_c5abae4e69c60370",
|
||||
taskId: "R1",
|
||||
hwpodId: "constart-71freq-c",
|
||||
nodeId: "node-d601-f103-v2",
|
||||
mdtodoRootRef: "docs/MDTODO",
|
||||
workspaceRootHash: "workspace-hash",
|
||||
hwpodWorkspaceArgs: "--hwpod-id constart-71freq-c --workspace-path 'F:\\Work\\ConStart'",
|
||||
contextFingerprint: "ctx_projection_launch",
|
||||
valuesRedacted: true
|
||||
},
|
||||
valuesRedacted: true
|
||||
};
|
||||
const runtimeStore = {
|
||||
async writeWorkbenchFacts(params, requestMeta) {
|
||||
factWrites.push({ params, requestMeta });
|
||||
return { written: true, facts: params.facts };
|
||||
}
|
||||
};
|
||||
const accessController = {
|
||||
async recordAgentSessionOwner(input) {
|
||||
accessWrites.push(input);
|
||||
return {
|
||||
id: input.sessionId,
|
||||
projectId: input.projectId,
|
||||
ownerUserId: input.ownerUserId,
|
||||
conversationId: input.conversationId,
|
||||
threadId: input.threadId,
|
||||
lastTraceId: input.traceId,
|
||||
status: input.status,
|
||||
session: input.session,
|
||||
updatedAt: "2026-06-28T11:15:00.000Z"
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
await writeWorkbenchProjectionSession({
|
||||
accessController,
|
||||
runtimeStore,
|
||||
traceId: "trc_projection_launch_context",
|
||||
ownerUserId: "usr_projection_launch",
|
||||
ownerRole: "admin",
|
||||
sessionId: "ses_projection_launch_context",
|
||||
projectId: "project_constart_71freq",
|
||||
conversationId: "cnv_projection_launch_context",
|
||||
threadId: "thread-projection-launch-context",
|
||||
status: "running",
|
||||
payload: {
|
||||
traceId: "trc_projection_launch_context",
|
||||
status: "running",
|
||||
updatedAt: "2026-06-28T11:15:00.000Z"
|
||||
},
|
||||
params: {
|
||||
projectId: "project_constart_71freq",
|
||||
taskRef: "mdtodo:constart-71freq-mdtodo:file_c5abae4e69c60370:R1",
|
||||
message: "run this MDTODO task",
|
||||
launchContext
|
||||
},
|
||||
session: {
|
||||
sessionStatus: "running",
|
||||
messages: [
|
||||
{ messageId: "msg_projection_launch_user", role: "user", text: "run this MDTODO task", status: "sent", turnId: "trc_projection_launch_context", traceId: "trc_projection_launch_context" }
|
||||
],
|
||||
valuesRedacted: true,
|
||||
secretMaterialStored: false
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(accessWrites.length, 1);
|
||||
assert.equal(factWrites.length, 1);
|
||||
const sessionJson = factWrites[0].params.facts.sessions[0].sessionJson;
|
||||
assert.equal(sessionJson.launchContext.sourceId, "constart-71freq-mdtodo");
|
||||
assert.equal(sessionJson.launchContext.hwpodId, "constart-71freq-c");
|
||||
assert.equal(sessionJson.launchContext.contextFingerprint, "ctx_projection_launch");
|
||||
assert.equal(sessionJson.launchContext.executionContext.hwpodWorkspaceArgs, "--hwpod-id constart-71freq-c --workspace-path 'F:\\Work\\ConStart'");
|
||||
assert.equal(sessionJson.launchContext.valuesRedacted, true);
|
||||
assert.equal(sessionJson.secretMaterialStored, false);
|
||||
});
|
||||
|
||||
test("workbench projection writer commits terminal owner evidence as sealed durable facts", async () => {
|
||||
const factWrites = [];
|
||||
const accessWrites = [];
|
||||
|
||||
@@ -68,6 +68,7 @@ export async function writeWorkbenchSessionAdmissionFact({ runtimeStore = null,
|
||||
const createdAt = timestampValue(session.startedAt ?? session.createdAt ?? session.session?.createdAt ?? now);
|
||||
const updatedAt = timestampValue(session.updatedAt ?? createdAt);
|
||||
const normalizedStatus = normalizeWorkbenchStatus(session.status ?? session.session?.sessionStatus ?? status) || "idle";
|
||||
const sessionJson = workbenchSessionJsonWithLaunchContext(plainObjectValue(session.session) ?? {}, { payload: session });
|
||||
const fact = {
|
||||
sessionId,
|
||||
ownerUserId: textValue(session.ownerUserId ?? ownerUserId) || null,
|
||||
@@ -84,10 +85,10 @@ export async function writeWorkbenchSessionAdmissionFact({ runtimeStore = null,
|
||||
terminal: false,
|
||||
sealed: false,
|
||||
sessionJson: {
|
||||
...(session.session && typeof session.session === "object" ? session.session : {}),
|
||||
...sessionJson,
|
||||
sessionStatus: normalizedStatus,
|
||||
source: textValue(session.session?.source) || "manual-session-create",
|
||||
providerProfile: textValue(session.session?.providerProfile) || null,
|
||||
source: textValue(sessionJson.source ?? session.session?.source) || "manual-session-create",
|
||||
providerProfile: textValue(sessionJson.providerProfile ?? session.session?.providerProfile) || null,
|
||||
valuesRedacted: true,
|
||||
secretMaterialStored: false
|
||||
},
|
||||
@@ -549,6 +550,7 @@ function buildWorkbenchProjectionFacts({ traceId = null, ownerUserId = null, own
|
||||
timestamp,
|
||||
timing
|
||||
});
|
||||
const sessionJson = workbenchSessionJsonWithLaunchContext(session, { payload, params });
|
||||
return {
|
||||
sessions: resolvedSessionId ? [{
|
||||
sessionId: resolvedSessionId,
|
||||
@@ -564,7 +566,7 @@ function buildWorkbenchProjectionFacts({ traceId = null, ownerUserId = null, own
|
||||
sourceEventId: safeId,
|
||||
terminal,
|
||||
sealed: terminal,
|
||||
sessionJson: session,
|
||||
sessionJson,
|
||||
timing,
|
||||
startedAt: timing.startedAt,
|
||||
lastEventAt: timing.lastEventAt,
|
||||
@@ -624,6 +626,44 @@ function buildWorkbenchProjectionFacts({ traceId = null, ownerUserId = null, own
|
||||
};
|
||||
}
|
||||
|
||||
function workbenchSessionJsonWithLaunchContext(session = {}, { payload = null, params = null } = {}) {
|
||||
const base = plainObjectValue(session) ?? {};
|
||||
const launchContext = firstPlainObjectValue(
|
||||
base.launchContext,
|
||||
base.sessionJson?.launchContext,
|
||||
base.session?.launchContext,
|
||||
payload?.launchContext,
|
||||
payload?.sessionJson?.launchContext,
|
||||
payload?.session?.launchContext,
|
||||
params?.launchContext,
|
||||
params?.sessionJson?.launchContext,
|
||||
params?.session?.launchContext
|
||||
);
|
||||
if (!launchContext) return base;
|
||||
const next = {
|
||||
...base,
|
||||
launchContext: {
|
||||
...launchContext,
|
||||
valuesRedacted: true
|
||||
},
|
||||
valuesRedacted: true
|
||||
};
|
||||
if (!Object.prototype.hasOwnProperty.call(next, "secretMaterialStored")) next.secretMaterialStored = false;
|
||||
return next;
|
||||
}
|
||||
|
||||
function firstPlainObjectValue(...values) {
|
||||
for (const value of values) {
|
||||
const object = plainObjectValue(value);
|
||||
if (object) return object;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function plainObjectValue(value) {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
||||
}
|
||||
|
||||
function workbenchProjectionFactSequence({ projection = {}, previousCheckpoint = null, agentRun = null } = {}) {
|
||||
const projectionSeq = nonNegativeInteger(projection?.lastProjectedSeq);
|
||||
const previousSeq = nonNegativeInteger(previousCheckpoint?.projectedSeq ?? previousCheckpoint?.lastProjectedSeq);
|
||||
|
||||
Reference in New Issue
Block a user