mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Update files
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1877,6 +1877,7 @@ export class Session {
|
||||
cwd: agent.cwd,
|
||||
title,
|
||||
parentAgentId: agent.id,
|
||||
internal: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user