Merge pull request #368 from pikasTech/feat/d601-code-agent-session-registry-317
feat: expose Code Agent session facts
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -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/);
|
||||
|
||||
Reference in New Issue
Block a user