Merge pull request #368 from pikasTech/feat/d601-code-agent-session-registry-317

feat: expose Code Agent session facts
This commit is contained in:
Lyon
2026-05-24 00:07:08 +08:00
committed by GitHub
8 changed files with 92 additions and 1 deletions
+2
View File
@@ -790,6 +790,7 @@ async function callReadOnlyRunner({ intent, conversationId, sessionId, traceId,
workspace: resolvedWorkspace,
sandbox: READONLY_RUNNER_SANDBOX,
runnerKind: READONLY_RUNNER_KIND,
sessionMode: READONLY_SESSION_MODE,
capabilityLevel: READONLY_SESSION_CAPABILITY_LEVEL,
implementationType: READONLY_IMPLEMENTATION_TYPE,
traceId,
@@ -1131,6 +1132,7 @@ async function callM3IoSkillRunner({ intent, conversationId, sessionId, traceId,
workspace: resolvedWorkspace,
sandbox: M3_IO_SKILL_SANDBOX,
runnerKind: M3_IO_SKILL_RUNNER_KIND,
sessionMode: M3_IO_SKILL_SESSION_MODE,
capabilityLevel: sessionCapabilityLevel,
implementationType: M3_IO_SKILL_IMPLEMENTATION_TYPE,
traceId,
@@ -75,6 +75,7 @@ export function createCodeAgentSessionRegistry(options = {}) {
workspace: params.workspace ?? null,
sandbox: params.sandbox ?? "read-only",
runnerKind: params.runnerKind ?? "hwlab-readonly-runner",
sessionMode: params.sessionMode ?? params.implementationType ?? "controlled-readonly-session-registry",
capabilityLevel: params.capabilityLevel ?? "read-only-session-tools",
implementationType: params.implementationType ?? "controlled-readonly-session-registry",
createdAt: timestamp,
@@ -96,6 +97,7 @@ export function createCodeAgentSessionRegistry(options = {}) {
session.workspace = params.workspace ?? session.workspace;
session.sandbox = params.sandbox ?? session.sandbox;
session.runnerKind = params.runnerKind ?? session.runnerKind;
session.sessionMode = params.sessionMode ?? session.sessionMode;
session.capabilityLevel = params.capabilityLevel ?? session.capabilityLevel;
session.implementationType = params.implementationType ?? session.implementationType;
session.status = "busy";
@@ -173,6 +175,11 @@ export function createCodeAgentSessionRegistry(options = {}) {
maxSessions,
idleTimeoutMs,
statuses: [...CODE_AGENT_SESSION_STATUSES],
sessionMode: "controlled-readonly-session-registry",
recentSessions: [...sessions.values()]
.sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)))
.slice(0, 10)
.map((session) => publicSession(session)),
longLivedCodexStdio: false,
durable: false,
secretMaterialStored: false,
@@ -245,6 +252,7 @@ function publicSession(session, { conversationId = null, reused = null } = {}) {
workspace: session.workspace,
sandbox: session.sandbox,
runnerKind: session.runnerKind,
sessionMode: session.sessionMode,
capabilityLevel: session.capabilityLevel,
implementationType: session.implementationType,
createdAt: session.createdAt,
@@ -32,10 +32,21 @@ test("code agent session registry creates, reuses, and expires sessions without
assert.equal(first.reused, false);
assert.equal(first.session.sessionId, "ses_test-registry");
assert.equal(first.session.status, "busy");
assert.equal(first.session.workspace, "/workspace/hwlab");
assert.equal(first.session.sandbox, "read-only");
assert.equal(first.session.runnerKind, "hwlab-readonly-runner");
assert.equal(first.session.sessionMode, "controlled-readonly-session-registry");
assert.equal(first.session.capabilityLevel, "read-only-session-tools");
assert.equal(first.session.idleTimeoutMs, 1000);
assert.equal(first.session.lastTraceId, "trc_registry_1");
assert.equal(first.session.secretMaterialStored, false);
assert.equal(first.session.valuesRedacted, true);
const describedCreating = registry.describe();
assert.equal(describedCreating.sessionMode, "controlled-readonly-session-registry");
assert.equal(describedCreating.recentSessions[0].sessionId, "ses_test-registry");
assert.equal(describedCreating.recentSessions[0].sessionMode, "controlled-readonly-session-registry");
assert.equal(describedCreating.recentSessions[0].workspace, "/workspace/hwlab");
assert.equal(describedCreating.recentSessions[0].secretMaterialStored, false);
registry.release(first.session.sessionId, {
conversationId: "cnv_registry",
traceId: "trc_registry_1",
@@ -132,6 +143,7 @@ test("read-only runner returns session registry evidence and blocked long-lived
assert.equal(first.sessionId, "ses_chat_registry");
assert.equal(first.session.sessionId, "ses_chat_registry");
assert.equal(first.session.status, "idle");
assert.equal(first.session.sessionMode, "controlled-readonly-session-registry");
assert.equal(first.session.workspace, process.cwd());
assert.equal(first.session.lastTraceId, "trc_chat_registry_pwd");
assert.equal(first.capabilityLevel, "read-only-session-tools");
@@ -387,6 +399,7 @@ test("Code Agent M3 DO write uses Skill CLI to call only HWLAB API /v1/m3/io", a
assert.equal(payload.provider, "hwlab-skill-cli");
assert.equal(payload.backend, "hwlab-cloud-api/hwlab-agent-runtime-skill-cli");
assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.ready);
assert.equal(payload.session.sessionMode, "controlled-m3-io-skill-cli");
assert.equal(payload.toolCalls.length, 1);
assert.equal(payload.toolCalls[0].name, "hwlab-agent-runtime.m3-io");
assert.equal(payload.toolCalls[0].status, "completed");
@@ -1038,6 +1051,7 @@ test("Codex stdio runner startup failure marks session failed and blocks long-li
assert.equal(payload.error.code, "codex_stdio_failed");
assert.equal(payload.session.sessionId, "ses_stdio_start_failed");
assert.equal(payload.session.status, "failed");
assert.equal(payload.session.sessionMode, "codex-mcp-stdio-long-lived");
assert.equal(payload.session.statusReason, "codex_stdio_failed");
assert.equal(payload.longLivedSessionGate.status, "blocked");
assert.equal(payload.longLivedSessionGate.pass, false);
+2
View File
@@ -450,6 +450,7 @@ export function createCodexStdioSessionManager(options = {}) {
workspace: params.workspace,
sandbox: params.sandbox,
runnerKind: CODEX_STDIO_RUNNER_KIND,
sessionMode: CODEX_STDIO_SESSION_MODE,
capabilityLevel: CODEX_STDIO_CAPABILITY_LEVEL,
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
createdAt: timestamp,
@@ -759,6 +760,7 @@ function publicSession(session, { conversationId = null, reused = null } = {}) {
workspace: session.workspace,
sandbox: session.sandbox,
runnerKind: session.runnerKind,
sessionMode: session.sessionMode,
capabilityLevel: session.capabilityLevel,
implementationType: session.implementationType,
createdAt: session.createdAt,
+7
View File
@@ -2023,6 +2023,13 @@ function codeAgentSummaryRows(summary) {
codeAgentSummaryRow("provider/mode/backend", summary.providerModeBackend, "source"),
codeAgentSummaryRow("capabilityLevel", summary.capabilityLevel, summary.tone),
codeAgentSummaryRow("session", summary.sessionSummary, sessionSummaryTone(summary)),
codeAgentSummaryRow("sessionId/status", summary.sessionIdStatus, sessionSummaryTone(summary)),
codeAgentSummaryRow("workspace", summary.workspace, "source"),
codeAgentSummaryRow("sandbox", summary.sandbox, "source"),
codeAgentSummaryRow("runner", summary.runnerKind, summary.kind === "long-lived-session" ? "ok" : "source"),
codeAgentSummaryRow("toolCalls", summary.toolCalls, summary.toolCalls === "none" ? "source" : summary.tone),
codeAgentSummaryRow("skills", summary.skills, summary.skills === "none" ? "source" : summary.tone),
codeAgentSummaryRow("runnerTrace", summary.runnerTrace, "source"),
codeAgentSummaryRow("readiness blockers", summary.blockersLabel, summary.readinessBlockers.length > 0 ? "blocked" : "source"),
codeAgentSummaryRow("last traceId", summary.lastTraceId, "source"),
codeAgentSummaryRow("当前部署 revision", summary.deploymentRevision, "source")
+28
View File
@@ -1,3 +1,9 @@
import {
runnerTraceSummary as compactRunnerTraceSummary,
skillsSummary as compactSkillsSummary,
toolCallsSummary as compactToolCallsSummary
} from "./code-agent-facts.mjs";
const GREEN_TONES = new Set(["ok", "available", "ready", "dev-live", "live", "pass"]);
const SECRET_MARKER = /\b(?:OPENAI_API_KEY|DATABASE_URL|secret|secretref|token|password|passwd|credential|private key|kubeconfig|密钥|密码|凭证)\b/iu;
const NON_LIVE_VALUES = new Set(["", "n/a", "none", "null", "undefined", "unknown", "not_observed", "not-observed"]);
@@ -15,7 +21,14 @@ export function classifyCodeAgentStatusSummary({ availability = null, latestMess
const codeAgentStatus = safeField(firstString(payload.status, payload.readinessStatus, observedAvailability?.status));
const capabilityLevel = safeField(firstString(payload.capabilityLevel, payload.runner?.capabilityLevel, observedAvailability?.capabilityLevel));
const sessionMode = safeField(firstString(payload.sessionMode, payload.session?.sessionMode, payload.runnerTrace?.sessionMode, payload.runner?.sessionMode));
const sessionId = safeField(firstString(payload.session?.sessionId, payload.sessionId, payload.runner?.sessionId, payload.sessionReuse?.sessionId));
const sessionStatus = safeField(firstString(payload.session?.status, payload.sessionStatus, payload.runnerTrace?.sessionStatus, payload.sessionReuse?.status));
const workspace = safeField(firstString(payload.workspace, payload.session?.workspace, payload.runner?.workspace, payload.sessionReuse?.workspace));
const sandbox = safeField(firstString(payload.sandbox, payload.session?.sandbox, payload.runner?.sandbox));
const runnerKind = safeField(firstString(payload.runner?.kind, payload.session?.runnerKind, payload.runnerTrace?.runnerKind));
const toolCalls = safeField(compactToolCallsSummary(payload.toolCalls));
const skills = safeField(compactSkillsSummary(payload.skills));
const runnerTrace = safeField(compactRunnerTraceSummary(payload.runnerTrace));
const lastTraceId = safeTraceId(firstString(
latestMessage?.traceId,
payload.traceId,
@@ -37,8 +50,16 @@ export function classifyCodeAgentStatusSummary({ availability = null, latestMess
codeAgentStatus,
capabilityLevel,
sessionMode,
sessionId,
sessionStatus,
sessionSummary: compactJoin([sessionMode, sessionStatus], " / "),
sessionIdStatus: compactJoin([sessionId, sessionStatus], " / "),
workspace,
sandbox,
runnerKind,
toolCalls,
skills,
runnerTrace,
readinessBlockers: blockers,
blockersLabel: blockers.length > 0 ? blockers.join(", ") : "无",
lastTraceId: lastTraceId || "未观测",
@@ -50,7 +71,14 @@ export function classifyCodeAgentStatusSummary({ availability = null, latestMess
codeAgentStatus,
capabilityLevel,
sessionMode,
sessionId,
sessionStatus,
workspace,
sandbox,
runnerKind,
toolCalls,
skills,
runnerTrace,
readinessBlockers: blockers,
lastTraceId: lastTraceId || null,
deploymentRevision
+24 -1
View File
@@ -61,14 +61,29 @@ test("maps degraded readonly session tools to warning, not green", () => {
backend: "hwlab-cloud-api/controlled-readonly-session-registry",
capabilityLevel: "read-only-session-tools",
sessionMode: "controlled-readonly-session-registry",
workspace: "/workspace/hwlab",
sandbox: "read-only",
session: {
sessionId: "ses_readonly",
status: "idle",
lastTraceId: "trc_readonly"
lastTraceId: "trc_readonly",
workspace: "/workspace/hwlab",
sandbox: "read-only",
runnerKind: "hwlab-readonly-runner",
sessionMode: "controlled-readonly-session-registry"
},
runner: {
kind: "hwlab-readonly-runner",
writeCapable: false
},
toolCalls: [{ name: "pwd", status: "completed" }],
skills: { status: "not_requested", count: 0 },
runnerTrace: {
traceId: "trc_readonly",
runnerKind: "hwlab-readonly-runner",
sessionMode: "controlled-readonly-session-registry",
turn: 1
},
blockers: [
{ code: "stdio_protocol_not_wired" }
]
@@ -78,6 +93,14 @@ test("maps degraded readonly session tools to warning, not green", () => {
assert.equal(summary.kind, "read-only-session-tools");
assert.equal(summary.label, "只读会话工具");
assert.equal(summary.tone, "warn");
assert.equal(summary.sessionId, "ses_readonly");
assert.equal(summary.sessionIdStatus, "ses_readonly / idle");
assert.equal(summary.workspace, "/workspace/hwlab");
assert.equal(summary.sandbox, "read-only");
assert.equal(summary.runnerKind, "hwlab-readonly-runner");
assert.equal(summary.toolCalls, "1/1 pwd(completed)");
assert.equal(summary.skills, "not_requested:count=0");
assert.match(summary.runnerTrace, /runnerKind=hwlab-readonly-runner/u);
assert.equal(summary.readinessBlockers.includes("stdio_protocol_not_wired"), true);
assert.equal(summary.lastTraceId, "trc_readonly");
});
+7
View File
@@ -701,6 +701,13 @@ assert.match(app, /recordField\("implementation", message\.implementationType\)/
assert.match(app, /recordField\("limitations", limitationsSummary\(message\.runnerLimitations\)\)/);
assert.match(app, /recordField\("codexStdio", codexStdioFeasibilitySummary\(message\.codexStdioFeasibility\)\)/);
assert.match(app, /recordField\("longLivedGate", longLivedSessionGateSummary\(message\.longLivedSessionGate\)\)/);
assert.match(app, /codeAgentSummaryRow\("sessionId\/status", summary\.sessionIdStatus/);
assert.match(app, /codeAgentSummaryRow\("workspace", summary\.workspace/);
assert.match(app, /codeAgentSummaryRow\("sandbox", summary\.sandbox/);
assert.match(app, /codeAgentSummaryRow\("runner", summary\.runnerKind/);
assert.match(app, /codeAgentSummaryRow\("toolCalls", summary\.toolCalls/);
assert.match(app, /codeAgentSummaryRow\("skills", summary\.skills/);
assert.match(app, /codeAgentSummaryRow\("runnerTrace", summary\.runnerTrace/);
assert.match(app, /function sessionSummary/);
assert.match(app, /function longLivedSessionGateSummary/);
assert.match(app, /read-only-session-tools/);