Update files

This commit is contained in:
Mohamed Boudra
2026-01-23 14:18:06 +07:00
parent 912a694221
commit 938d57c8ae
7 changed files with 271 additions and 5 deletions

View File

@@ -202,4 +202,170 @@ describe("AgentManager", () => {
const refreshed = manager.getAgent(snapshot.id);
expect(refreshed?.runtimeInfo?.model).toBe("gpt-5.2-codex");
});
test("listAgents excludes internal agents", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
const registryPath = join(workdir, "agents.json");
const registry = new AgentRegistry(registryPath, logger);
let agentCounter = 0;
const manager = new AgentManager({
clients: {
codex: new TestAgentClient(),
},
registry,
logger,
idFactory: () => `agent-${agentCounter++}`,
});
// Create a normal agent
await manager.createAgent({
provider: "codex",
cwd: workdir,
title: "Normal Agent",
});
// Create an internal agent
await manager.createAgent({
provider: "codex",
cwd: workdir,
title: "Internal Agent",
internal: true,
});
const agents = manager.listAgents();
expect(agents).toHaveLength(1);
expect(agents[0]?.config.title).toBe("Normal Agent");
});
test("getAgent returns internal agents by ID", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
const registryPath = join(workdir, "agents.json");
const registry = new AgentRegistry(registryPath, logger);
const manager = new AgentManager({
clients: {
codex: new TestAgentClient(),
},
registry,
logger,
idFactory: () => "internal-agent",
});
await manager.createAgent({
provider: "codex",
cwd: workdir,
title: "Internal Agent",
internal: true,
});
const agent = manager.getAgent("internal-agent");
expect(agent).not.toBeNull();
expect(agent?.internal).toBe(true);
});
test("subscribe does not emit state events for internal agents to global subscribers", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
const registryPath = join(workdir, "agents.json");
const registry = new AgentRegistry(registryPath, logger);
let agentCounter = 0;
const manager = new AgentManager({
clients: {
codex: new TestAgentClient(),
},
registry,
logger,
idFactory: () => `agent-${agentCounter++}`,
});
const receivedEvents: string[] = [];
manager.subscribe((event) => {
if (event.type === "agent_state") {
receivedEvents.push(event.agent.id);
}
});
// Create a normal agent - should emit
await manager.createAgent({
provider: "codex",
cwd: workdir,
title: "Normal Agent",
});
// Create an internal agent - should NOT emit to global subscriber
await manager.createAgent({
provider: "codex",
cwd: workdir,
title: "Internal Agent",
internal: true,
});
// Should only have events from the normal agent
expect(receivedEvents.filter((id) => id === "agent-0").length).toBeGreaterThan(0);
expect(receivedEvents.filter((id) => id === "agent-1").length).toBe(0);
});
test("subscribe emits state events for internal agents when subscribed by agentId", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
const registryPath = join(workdir, "agents.json");
const registry = new AgentRegistry(registryPath, logger);
const manager = new AgentManager({
clients: {
codex: new TestAgentClient(),
},
registry,
logger,
idFactory: () => "internal-agent",
});
const receivedEvents: string[] = [];
// Subscribe specifically to the internal agent
manager.subscribe(
(event) => {
if (event.type === "agent_state") {
receivedEvents.push(event.agent.id);
}
},
{ agentId: "internal-agent", replayState: false }
);
await manager.createAgent({
provider: "codex",
cwd: workdir,
title: "Internal Agent",
internal: true,
});
// Should receive events when subscribed by specific agentId
expect(receivedEvents.filter((id) => id === "internal-agent").length).toBeGreaterThan(0);
});
test("onAgentAttention is not called for internal agents", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
const registryPath = join(workdir, "agents.json");
const registry = new AgentRegistry(registryPath, logger);
const attentionCalls: string[] = [];
const manager = new AgentManager({
clients: {
codex: new TestAgentClient(),
},
registry,
logger,
idFactory: () => "internal-agent",
onAgentAttention: ({ agentId }) => {
attentionCalls.push(agentId);
},
});
const agent = await manager.createAgent({
provider: "codex",
cwd: workdir,
title: "Internal Agent",
internal: true,
});
// Run and complete the agent (which normally triggers attention)
await manager.runAgent(agent.id, "hello");
// Should NOT have triggered attention callback for internal agent
expect(attentionCalls).toHaveLength(0);
});
});

View File

@@ -101,6 +101,10 @@ type ManagedAgentBase = {
lastError?: string;
attention: AttentionState;
parentAgentId?: string;
/**
* Internal agents are hidden from listings and don't trigger notifications.
*/
internal?: boolean;
};
type ManagedAgentWithSession = ManagedAgentBase & {
@@ -230,7 +234,11 @@ export class AgentManager {
});
}
} else {
// For global subscribers, skip internal agents during replay
for (const agent of this.agents.values()) {
if (agent.internal) {
continue;
}
callback({
type: "agent_state",
agent: { ...agent },
@@ -245,9 +253,11 @@ export class AgentManager {
}
listAgents(): ManagedAgent[] {
return Array.from(this.agents.values()).map((agent) => ({
...agent,
}));
return Array.from(this.agents.values())
.filter((agent) => !agent.internal)
.map((agent) => ({
...agent,
}));
}
async listPersistedAgents(
@@ -811,6 +821,7 @@ export class AgentManager {
lastUserMessageAt: null,
attention: { requiresAttention: false },
parentAgentId: config.parentAgentId,
internal: config.internal ?? false,
} as ActiveManagedAgent;
this.agents.set(agentId, managed);
@@ -831,7 +842,7 @@ export class AgentManager {
private async persistSnapshot(
agent: ManagedAgent,
options?: { title?: string | null }
options?: { title?: string | null; internal?: boolean }
): Promise<void> {
if (!this.registry) {
return;
@@ -977,6 +988,11 @@ export class AgentManager {
// Track the new status
this.previousStatuses.set(agent.id, currentStatus);
// Skip attention tracking for internal agents
if (agent.internal) {
return;
}
// Skip if already requires attention
if (agent.attention.requiresAttention) {
return;
@@ -1054,6 +1070,18 @@ export class AgentManager {
) {
continue;
}
// Skip internal agents for global subscribers (those without a specific agentId)
if (!subscriber.agentId) {
if (event.type === "agent_state" && event.agent.internal) {
continue;
}
if (event.type === "agent_stream") {
const agent = this.agents.get(event.agentId);
if (agent?.internal) {
continue;
}
}
}
subscriber.callback(event);
}
}

View File

@@ -21,6 +21,7 @@ export type { ManagedAgent };
type ProjectionOptions = {
title?: string | null;
createdAt?: string;
internal?: boolean;
};
export function toStoredAgentRecord(
@@ -56,6 +57,7 @@ export function toStoredAgentRecord(
? agent.attention.attentionTimestamp.toISOString()
: null,
parentAgentId: agent.parentAgentId ?? null,
internal: options?.internal,
} satisfies StoredAgentRecord;
}

View File

@@ -249,4 +249,64 @@ describe("AgentRegistry", () => {
const sanitized = readFileSync(filePath, "utf8");
expect(sanitized.includes("GARBAGE-TRAILING")).toBe(false);
});
test("list returns all agents including internal ones", async () => {
// Create a normal agent
await registry.applySnapshot(
createManagedAgent({
id: "normal-agent",
cwd: "/tmp/project",
})
);
// Create an internal agent
await registry.applySnapshot(
createManagedAgent({
id: "internal-agent",
cwd: "/tmp/project",
config: { internal: true },
}),
{ internal: true }
);
// Registry should return all agents - filtering is done at the manager level
const records = await registry.list();
expect(records).toHaveLength(2);
});
test("get returns internal agents by ID", async () => {
await registry.applySnapshot(
createManagedAgent({
id: "internal-agent",
cwd: "/tmp/project",
config: { internal: true },
}),
{ internal: true }
);
const record = await registry.get("internal-agent");
expect(record).not.toBeNull();
expect(record?.internal).toBe(true);
});
test("internal flag is persisted and reloaded", async () => {
await registry.applySnapshot(
createManagedAgent({
id: "internal-agent",
cwd: "/tmp/project",
config: { internal: true },
}),
{ internal: true }
);
// Reload the registry from disk
const reloaded = new AgentRegistry(filePath, logger);
const record = await reloaded.get("internal-agent");
expect(record?.internal).toBe(true);
// Registry returns all agents - filtering happens at manager level
const records = await reloaded.list();
expect(records).toHaveLength(1);
expect(records[0]?.internal).toBe(true);
});
});

View File

@@ -54,6 +54,7 @@ const STORED_AGENT_SCHEMA = z.object({
attentionReason: z.enum(["finished", "error", "permission"]).nullable().optional(),
attentionTimestamp: z.string().nullable().optional(),
parentAgentId: z.string().nullable().optional(),
internal: z.boolean().optional(),
});
export type SerializableAgentConfig = Pick<
@@ -129,15 +130,18 @@ export class AgentRegistry {
async applySnapshot(
agent: ManagedAgent,
options?: { title?: string | null }
options?: { title?: string | null; internal?: boolean }
): Promise<void> {
await this.load();
const existing = this.cache.get(agent.id);
const hasTitleOverride =
options !== undefined && Object.prototype.hasOwnProperty.call(options, "title");
const hasInternalOverride =
options !== undefined && Object.prototype.hasOwnProperty.call(options, "internal");
const record = toStoredAgentRecord(agent, {
title: hasTitleOverride ? options?.title ?? null : existing?.title ?? null,
createdAt: existing?.createdAt,
internal: hasInternalOverride ? options?.internal : (agent.internal ?? existing?.internal),
});
this.cache.set(agent.id, record);
await this.flush();

View File

@@ -208,6 +208,11 @@ export type AgentSessionConfig = {
};
mcpServers?: AgentMetadata;
parentAgentId?: string;
/**
* Internal agents are hidden from listings and don't trigger notifications.
* They are used for ephemeral system tasks like commit/PR generation.
*/
internal?: boolean;
};
export interface AgentSession {

View File

@@ -1877,6 +1877,7 @@ export class Session {
cwd: agent.cwd,
title,
parentAgentId: agent.id,
internal: true,
};
}