fix(gateway): source agent provenance from roster owner (#125032)

This commit is contained in:
Peter Steinberger
2026-08-16 21:58:33 -07:00
committed by GitHub
parent 559ee3a209
commit c8aed9b1ef
3 changed files with 37 additions and 24 deletions
+3 -20
View File
@@ -93,7 +93,6 @@ import {
readAgentDeletionJournal,
type AgentDeletionJournalCleanupPath,
} from "../../state/agent-deletion-journal.js";
import { listAgentProvenance } from "../../state/agent-provenance.js";
import { assertNoOpenClawAgentDatabaseLeases } from "../../state/openclaw-agent-db-lease.js";
import { unregisterOpenClawAgentDatabase } from "../../state/openclaw-agent-db-registry.js";
import {
@@ -899,27 +898,11 @@ export const agentsHandlers: GatewayRequestHandlers = {
const cfg = context.getRuntimeConfig();
const modelCatalog = await readPreparedServerMethodModelCatalog(context);
const result = listAgentsForGateway(cfg, modelCatalog, {
includeSystem: hasGatewayClientCap(client?.connect.caps, GATEWAY_CLIENT_CAPS.AGENT_KIND),
});
const provenanceById = new Map(
listAgentProvenance().map((record) => [record.agentId, record] as const),
);
respond(
true,
{
...result,
agents: result.agents.map((agent) => {
const provenance = provenanceById.get(agent.id);
return provenance
? Object.assign({}, agent, {
createdVia: provenance.createdVia,
creatorAgentId: provenance.creatorAgentId,
createdAt: provenance.createdAtMs,
})
: agent;
}),
},
listAgentsForGateway(cfg, modelCatalog, {
includeSystem: hasGatewayClientCap(client?.connect.caps, GATEWAY_CLIENT_CAPS.AGENT_KIND),
}),
undefined,
);
},
@@ -40,6 +40,7 @@ import {
runExclusiveSessionLifecycleMutation,
} from "../sessions/session-lifecycle-admission.js";
import { buildPersistedUserTurnMessage } from "../sessions/user-turn-transcript.js";
import { recordAgentProvenance } from "../state/agent-provenance.js";
import { openOpenClawAgentDatabase } from "../state/openclaw-agent-db.js";
import { captureEnv, setTestEnvValue } from "../test-utils/env.js";
import { withOpenClawTestState } from "../test-utils/openclaw-test-state.js";
@@ -1123,7 +1124,7 @@ describe("gateway server chat", () => {
});
});
test("chat.startup returns chat history with the initial agents list", async () => {
test("chat.startup returns chat history with the initial agents list and provenance", async () => {
await withGatewayChatHarness(async ({ ws, createSessionDir }) => {
await writeGatewayConfig({
agents: {
@@ -1135,7 +1136,7 @@ describe("gateway server chat", () => {
"openai/gpt-main": {},
},
},
entries: { main: { default: true } },
entries: { main: { default: true }, research: {} },
},
models: {
providers: {
@@ -1146,6 +1147,11 @@ describe("gateway server chat", () => {
},
},
});
recordAgentProvenance(
"research",
{ createdVia: "agent", creatorAgentId: "main" },
{ nowMs: 42 },
);
await connectOk(ws);
await createSessionDir();
const updatedAt = Date.now();
@@ -1160,7 +1166,12 @@ describe("gateway server chat", () => {
const startup = await rpcReq<{
agentsList?: {
agents?: Array<{ id?: string }>;
agents?: Array<{
id?: string;
createdVia?: string;
creatorAgentId?: string | null;
createdAt?: number;
}>;
defaultId?: string | null;
mainKey?: string | null;
};
@@ -1176,6 +1187,13 @@ describe("gateway server chat", () => {
expect(startup.payload?.agentsList?.defaultId).toBe("main");
expect(startup.payload?.agentsList?.mainKey).toBe("main");
expect(startup.payload?.agentsList?.agents?.map((agent) => agent.id)).toContain("main");
expect(
startup.payload?.agentsList?.agents?.find((agent) => agent.id === "research"),
).toMatchObject({
createdVia: "agent",
creatorAgentId: "main",
createdAt: 42,
});
expect(startup.payload?.sessionInfo).toMatchObject({
key: "agent:main:main",
sessionId: "sess-main",
+13 -1
View File
@@ -30,6 +30,7 @@ import { canonicalSessionKeyMigrationRequiredError } from "../config/sessions/se
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizeAgentId, parseAgentSessionKey } from "../routing/session-key.js";
import { isAcpSessionKey } from "../sessions/session-key-utils.js";
import { listAgentProvenance } from "../state/agent-provenance.js";
import { listGatewayAgentsBasic } from "./agent-list.js";
import type { GatewayAgentOwnership } from "./agent-list.js";
import { tryResolveSessionCompatibilityOwnerAgentId } from "./session-request-agent.js";
@@ -337,6 +338,9 @@ export function listAgentsForGateway(
const roster = options?.includeSystem
? basic.agents
: basic.agents.filter((entry) => entry.kind !== "system");
const provenanceById = new Map(
listAgentProvenance().map((record) => [record.agentId, record] as const),
);
const agents = roster.map((entry) => {
const { id } = entry;
const meta = configuredById.get(id);
@@ -366,7 +370,7 @@ export function listAgentsForGateway(
// Must mirror the sessions.create worktree preflight: subdirectory workspaces inside a
// repo are worktree-capable, so the UI toggle and the create path cannot diverge.
const workspaceGit = insideGitCheckout(workspace);
return Object.assign(
const agent = Object.assign(
{
id,
...(options?.includeSystem ? { kind: entry.kind } : {}),
@@ -382,6 +386,14 @@ export function listAgentsForGateway(
},
{ model },
);
const provenance = provenanceById.get(id);
return provenance
? Object.assign(agent, {
createdVia: provenance.createdVia,
creatorAgentId: provenance.creatorAgentId,
createdAt: provenance.createdAtMs,
})
: agent;
});
return {
defaultId: basic.defaultId,