* public-gh/master: (55 commits) fix(issue-documents): address greptile review Update packages/shared/src/validators/issue.ts feat(ui): add issue document copy and download actions fix(ui): unify new issue upload action feat(ui): stage issue files before create feat(ui): handle issue document edit conflicts fix(ui): refresh issue documents from live events feat(ui): deep link issue documents fix(ui): streamline issue document chrome fix(ui): collapse empty document and attachment states fix(ui): simplify document card body layout fix(issues): address document review comments feat(issues): add issue documents and inline editing docs: add agent evals framework plan fix(cli): quote env values with special characters Fix worktree seed source selection fix: address greptile follow-up docs: add paperclip skill tightening plan fix: isolate codex home in worktrees Add worktree UI branding ... # Conflicts: # packages/db/src/migrations/meta/0028_snapshot.json # packages/db/src/migrations/meta/_journal.json # packages/shared/src/index.ts # server/src/routes/issues.ts # ui/src/api/issues.ts # ui/src/components/NewIssueDialog.tsx # ui/src/pages/IssueDetail.tsx
191 lines
5.6 KiB
TypeScript
191 lines
5.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { resolveDefaultAgentWorkspaceDir } from "../home-paths.js";
|
|
import {
|
|
prioritizeProjectWorkspaceCandidatesForRun,
|
|
resolveRuntimeSessionParamsForWorkspace,
|
|
shouldResetTaskSessionForWake,
|
|
type ResolvedWorkspaceForRun,
|
|
} from "../services/heartbeat.ts";
|
|
|
|
function buildResolvedWorkspace(overrides: Partial<ResolvedWorkspaceForRun> = {}): ResolvedWorkspaceForRun {
|
|
return {
|
|
cwd: "/tmp/project",
|
|
source: "project_primary",
|
|
projectId: "project-1",
|
|
workspaceId: "workspace-1",
|
|
repoUrl: null,
|
|
repoRef: null,
|
|
workspaceHints: [],
|
|
warnings: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("resolveRuntimeSessionParamsForWorkspace", () => {
|
|
it("migrates fallback workspace sessions to project workspace when project cwd becomes available", () => {
|
|
const agentId = "agent-123";
|
|
const fallbackCwd = resolveDefaultAgentWorkspaceDir(agentId);
|
|
|
|
const result = resolveRuntimeSessionParamsForWorkspace({
|
|
agentId,
|
|
previousSessionParams: {
|
|
sessionId: "session-1",
|
|
cwd: fallbackCwd,
|
|
workspaceId: "workspace-1",
|
|
},
|
|
resolvedWorkspace: buildResolvedWorkspace({ cwd: "/tmp/new-project-cwd" }),
|
|
});
|
|
|
|
expect(result.sessionParams).toMatchObject({
|
|
sessionId: "session-1",
|
|
cwd: "/tmp/new-project-cwd",
|
|
workspaceId: "workspace-1",
|
|
});
|
|
expect(result.warning).toContain("Attempting to resume session");
|
|
});
|
|
|
|
it("does not migrate when previous session cwd is not the fallback workspace", () => {
|
|
const result = resolveRuntimeSessionParamsForWorkspace({
|
|
agentId: "agent-123",
|
|
previousSessionParams: {
|
|
sessionId: "session-1",
|
|
cwd: "/tmp/some-other-cwd",
|
|
workspaceId: "workspace-1",
|
|
},
|
|
resolvedWorkspace: buildResolvedWorkspace({ cwd: "/tmp/new-project-cwd" }),
|
|
});
|
|
|
|
expect(result.sessionParams).toEqual({
|
|
sessionId: "session-1",
|
|
cwd: "/tmp/some-other-cwd",
|
|
workspaceId: "workspace-1",
|
|
});
|
|
expect(result.warning).toBeNull();
|
|
});
|
|
|
|
it("does not migrate when resolved workspace id differs from previous session workspace id", () => {
|
|
const agentId = "agent-123";
|
|
const fallbackCwd = resolveDefaultAgentWorkspaceDir(agentId);
|
|
|
|
const result = resolveRuntimeSessionParamsForWorkspace({
|
|
agentId,
|
|
previousSessionParams: {
|
|
sessionId: "session-1",
|
|
cwd: fallbackCwd,
|
|
workspaceId: "workspace-1",
|
|
},
|
|
resolvedWorkspace: buildResolvedWorkspace({
|
|
cwd: "/tmp/new-project-cwd",
|
|
workspaceId: "workspace-2",
|
|
}),
|
|
});
|
|
|
|
expect(result.sessionParams).toEqual({
|
|
sessionId: "session-1",
|
|
cwd: fallbackCwd,
|
|
workspaceId: "workspace-1",
|
|
});
|
|
expect(result.warning).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("shouldResetTaskSessionForWake", () => {
|
|
it("resets session context on assignment wake", () => {
|
|
expect(shouldResetTaskSessionForWake({ wakeReason: "issue_assigned" })).toBe(true);
|
|
});
|
|
|
|
it("preserves session context on timer heartbeats", () => {
|
|
expect(shouldResetTaskSessionForWake({ wakeSource: "timer" })).toBe(false);
|
|
});
|
|
|
|
it("preserves session context on manual on-demand invokes by default", () => {
|
|
expect(
|
|
shouldResetTaskSessionForWake({
|
|
wakeSource: "on_demand",
|
|
wakeTriggerDetail: "manual",
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("resets session context when a fresh session is explicitly requested", () => {
|
|
expect(
|
|
shouldResetTaskSessionForWake({
|
|
wakeSource: "on_demand",
|
|
wakeTriggerDetail: "manual",
|
|
forceFreshSession: true,
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("does not reset session context on mention wake comment", () => {
|
|
expect(
|
|
shouldResetTaskSessionForWake({
|
|
wakeReason: "issue_comment_mentioned",
|
|
wakeCommentId: "comment-1",
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("does not reset session context when commentId is present", () => {
|
|
expect(
|
|
shouldResetTaskSessionForWake({
|
|
wakeReason: "issue_commented",
|
|
commentId: "comment-2",
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("does not reset for comment wakes", () => {
|
|
expect(shouldResetTaskSessionForWake({ wakeReason: "issue_commented" })).toBe(false);
|
|
});
|
|
|
|
it("does not reset when wake reason is missing", () => {
|
|
expect(shouldResetTaskSessionForWake({})).toBe(false);
|
|
});
|
|
|
|
it("does not reset session context on callback on-demand invokes", () => {
|
|
expect(
|
|
shouldResetTaskSessionForWake({
|
|
wakeSource: "on_demand",
|
|
wakeTriggerDetail: "callback",
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("prioritizeProjectWorkspaceCandidatesForRun", () => {
|
|
it("moves the explicitly selected workspace to the front", () => {
|
|
const rows = [
|
|
{ id: "workspace-1", cwd: "/tmp/one" },
|
|
{ id: "workspace-2", cwd: "/tmp/two" },
|
|
{ id: "workspace-3", cwd: "/tmp/three" },
|
|
];
|
|
|
|
expect(
|
|
prioritizeProjectWorkspaceCandidatesForRun(rows, "workspace-2").map((row) => row.id),
|
|
).toEqual(["workspace-2", "workspace-1", "workspace-3"]);
|
|
});
|
|
|
|
it("keeps the original order when no preferred workspace is selected", () => {
|
|
const rows = [
|
|
{ id: "workspace-1" },
|
|
{ id: "workspace-2" },
|
|
];
|
|
|
|
expect(
|
|
prioritizeProjectWorkspaceCandidatesForRun(rows, null).map((row) => row.id),
|
|
).toEqual(["workspace-1", "workspace-2"]);
|
|
});
|
|
|
|
it("keeps the original order when the selected workspace is missing", () => {
|
|
const rows = [
|
|
{ id: "workspace-1" },
|
|
{ id: "workspace-2" },
|
|
];
|
|
|
|
expect(
|
|
prioritizeProjectWorkspaceCandidatesForRun(rows, "workspace-9").map((row) => row.id),
|
|
).toEqual(["workspace-1", "workspace-2"]);
|
|
});
|
|
});
|