Prioritize ready actions and scheduled agent prompts (#1316)

* Sort sessions by last activity

* Prioritize shipping actions in git controls

* Fix scheduled new agent titles
This commit is contained in:
Mohamed Boudra
2026-06-03 16:16:05 +00:00
committed by GitHub
parent bf81496a4f
commit 71ce96b913
2 changed files with 218 additions and 4 deletions
@@ -216,6 +216,212 @@ describe("ScheduleService", () => {
); );
}); });
test("titles scheduled new agents from the schedule prompt", async () => {
const manager = new AgentManager({
logger: createTestLogger(),
clients: createTestAgentClients(),
registry: agentStorage,
});
const service = new ScheduleService({
paseoHome: tempDir,
logger: createTestLogger(),
agentManager: manager,
agentStorage,
providerSnapshotManager: NO_UNATTENDED_SCHEDULE_POLICY,
now: () => now,
});
const created = await service.create({
prompt: "Audit flaky checkout flow\n\nReport only blockers.",
cadence: { type: "every", everyMs: 60_000 },
target: {
type: "new-agent",
config: {
provider: "claude",
cwd: tempDir,
approvalPolicy: "never",
},
},
maxRuns: 1,
});
now = new Date("2026-01-01T00:01:00.000Z");
await service.tick();
const inspected = await service.inspect(created.id);
const agentId = inspected.runs[0]?.agentId;
expect(agentId).toMatch(/^[0-9a-f-]{36}$/);
const storedAgent = await agentStorage.get(agentId!);
expect(storedAgent?.title).toBe("Audit flaky checkout flow");
});
test("shows scheduled new-agent prompts as normal user turns", async () => {
class PromptEchoScheduleSession implements AgentSession {
readonly provider = "claude";
readonly capabilities = SCHEDULE_TEST_CAPABILITIES;
readonly id = "scheduled-prompt-echo-session";
private turnCount = 0;
private readonly subscribers = new Set<(event: AgentStreamEvent) => void>();
async run(_prompt: AgentPromptInput, _options?: AgentRunOptions): Promise<AgentRunResult> {
return {
sessionId: this.id,
finalText: "done",
timeline: [{ type: "assistant_message", text: "done" }],
};
}
async startTurn(prompt: AgentPromptInput): Promise<{ turnId: string }> {
const turnId = `turn-${++this.turnCount}`;
const textPrompt = typeof prompt === "string" ? prompt : JSON.stringify(prompt);
setImmediate(() => {
this.emit({ type: "turn_started", provider: this.provider, turnId });
this.emit({
type: "timeline",
provider: this.provider,
turnId,
item: { type: "user_message", text: textPrompt },
});
this.emit({
type: "timeline",
provider: this.provider,
turnId,
item: { type: "assistant_message", text: "done" },
});
this.emit({
type: "turn_completed",
provider: this.provider,
turnId,
usage: { inputTokens: 1, outputTokens: 1 },
});
});
return { turnId };
}
subscribe(callback: (event: AgentStreamEvent) => void): () => void {
this.subscribers.add(callback);
return () => {
this.subscribers.delete(callback);
};
}
async *streamHistory(): AsyncGenerator<AgentStreamEvent> {}
async getRuntimeInfo() {
return {
provider: this.provider,
sessionId: this.id,
model: null,
modeId: null,
};
}
async getAvailableModes(): Promise<AgentMode[]> {
return [];
}
async getCurrentMode(): Promise<string | null> {
return null;
}
async setMode(_modeId: string): Promise<void> {}
getPendingPermissions(): AgentPermissionRequest[] {
return [];
}
async respondToPermission(
_requestId: string,
_response: AgentPermissionResponse,
): Promise<void> {}
describePersistence(): AgentPersistenceHandle {
return {
provider: this.provider,
sessionId: this.id,
};
}
async interrupt(): Promise<void> {}
async close(): Promise<void> {}
private emit(event: AgentStreamEvent): void {
for (const subscriber of this.subscribers) {
subscriber(event);
}
}
}
class PromptEchoScheduleClient implements AgentClient {
readonly provider = "claude";
readonly capabilities = SCHEDULE_TEST_CAPABILITIES;
async createSession(_config: AgentSessionConfig): Promise<AgentSession> {
return new PromptEchoScheduleSession();
}
async resumeSession(_handle: AgentPersistenceHandle): Promise<AgentSession> {
return new PromptEchoScheduleSession();
}
async listModels(_options: ListModelsOptions): Promise<AgentModelDefinition[]> {
return [];
}
async isAvailable(): Promise<boolean> {
return true;
}
}
const manager = new AgentManager({
logger: createTestLogger(),
clients: { claude: new PromptEchoScheduleClient() },
registry: agentStorage,
});
const service = new ScheduleService({
paseoHome: tempDir,
logger: createTestLogger(),
agentManager: manager,
agentStorage,
providerSnapshotManager: NO_UNATTENDED_SCHEDULE_POLICY,
now: () => now,
});
const observedUserMessages: string[] = [];
const unsubscribe = manager.subscribe((event) => {
if (event.type !== "agent_stream" || event.event.type !== "timeline") {
return;
}
if (event.event.item.type === "user_message") {
observedUserMessages.push(event.event.item.text);
}
});
const created = await service.create({
prompt: "Audit nightly run",
cadence: { type: "every", everyMs: 60_000 },
target: {
type: "new-agent",
config: {
provider: "claude",
cwd: tempDir,
approvalPolicy: "never",
},
},
maxRuns: 1,
});
now = new Date("2026-01-01T00:01:00.000Z");
try {
await service.tick();
} finally {
unsubscribe();
}
expect(observedUserMessages).toEqual(["Audit nightly run"]);
expect((await service.inspect(created.id)).runs[0]?.status).toBe("succeeded");
});
test("archives new-agent schedule sessions after the run finishes", async () => { test("archives new-agent schedule sessions after the run finishes", async () => {
class CountingScheduleSession implements AgentSession { class CountingScheduleSession implements AgentSession {
readonly provider = "claude"; readonly provider = "claude";
+12 -4
View File
@@ -7,6 +7,7 @@ import type { AgentSessionConfig } from "../agent/agent-sdk-types.js";
import { curateAgentActivity } from "../agent/activity-curator.js"; import { curateAgentActivity } from "../agent/activity-curator.js";
import { ensureAgentLoaded } from "../agent/agent-loading.js"; import { ensureAgentLoaded } from "../agent/agent-loading.js";
import { formatSystemNotificationPrompt } from "../agent/agent-prompt.js"; import { formatSystemNotificationPrompt } from "../agent/agent-prompt.js";
import { resolveCreateAgentTitles } from "../agent/create-agent-title.js";
import { ScheduleStore } from "./store.js"; import { ScheduleStore } from "./store.js";
import { computeNextRunAt, validateScheduleCadence } from "./cron.js"; import { computeNextRunAt, validateScheduleCadence } from "./cron.js";
import type { import type {
@@ -528,9 +529,8 @@ export class ScheduleService {
schedule: StoredSchedule, schedule: StoredSchedule,
runId: string, runId: string,
): Promise<ScheduleExecutionResult> { ): Promise<ScheduleExecutionResult> {
const wrappedPrompt = formatSystemNotificationPrompt(buildScheduleFireBody(schedule, runId));
if (schedule.target.type === "agent") { if (schedule.target.type === "agent") {
const wrappedPrompt = formatSystemNotificationPrompt(buildScheduleFireBody(schedule, runId));
const record = await this.agentStorage.get(schedule.target.agentId); const record = await this.agentStorage.get(schedule.target.agentId);
if (record?.archivedAt) { if (record?.archivedAt) {
throw new Error(`Agent ${schedule.target.agentId} is archived`); throw new Error(`Agent ${schedule.target.agentId} is archived`);
@@ -583,14 +583,22 @@ export class ScheduleService {
systemPrompt: targetConfig.systemPrompt, systemPrompt: targetConfig.systemPrompt,
mcpServers: targetConfig.mcpServers as AgentSessionConfig["mcpServers"], mcpServers: targetConfig.mcpServers as AgentSessionConfig["mcpServers"],
}; };
const { provisionalTitle } = resolveCreateAgentTitles({
configTitle: config.title,
initialPrompt: schedule.prompt,
});
const labels = { const labels = {
"paseo.schedule-id": schedule.id, "paseo.schedule-id": schedule.id,
"paseo.schedule-run": runId, "paseo.schedule-run": runId,
}; };
const agent = await this.agentManager.createAgent(config, undefined, { labels }); const agent = await this.agentManager.createAgent(config, undefined, {
labels,
initialPrompt: schedule.prompt,
initialTitle: provisionalTitle,
});
let result; let result;
try { try {
result = await this.agentManager.runAgent(agent.id, wrappedPrompt); result = await this.agentManager.runAgent(agent.id, schedule.prompt);
} catch (error) { } catch (error) {
try { try {
await this.agentManager.archiveAgent(agent.id); await this.agentManager.archiveAgent(agent.id);