1import * as Schema from "effect/Schema";
2import { IsoDateTime, NonNegativeInt, ProjectId, TrimmedNonEmptyString } from "./baseSchemas.ts";
3import { ProviderInstanceId } from "./providerInstance.ts";
4
5export const AgentSessionSource = Schema.Literals(["claudeAgent", "codex", "pi"]);
6export type AgentSessionSource = typeof AgentSessionSource.Type;
7
8export const AgentSessionImportSource = Schema.Struct({
9 provider: AgentSessionSource,
10 providerInstanceId: ProviderInstanceId,
11 providerSessionId: TrimmedNonEmptyString,
12 filePath: TrimmedNonEmptyString,
13 size: NonNegativeInt,
14 mtimeMs: Schema.NullOr(Schema.Number),
15 device: Schema.Number,
16 // A decimal string keeps file IDs above 2^53 (common on NTFS) exact. Older
17 // records stored a number.
18 inode: Schema.NullOr(Schema.Union([Schema.String, Schema.Number])),
19 birthtimeMs: Schema.NullOr(Schema.Number),
20});
21export type AgentSessionImportSource = typeof AgentSessionImportSource.Type;
22
23export const AgentSessionScanInput = Schema.Struct({});
24export type AgentSessionScanInput = typeof AgentSessionScanInput.Type;
25
26export const AgentSessionProjectGit = Schema.Struct({
27 remoteKey: Schema.NullOr(Schema.String),
28 repository: Schema.NullOr(Schema.String),
29});
30export type AgentSessionProjectGit = typeof AgentSessionProjectGit.Type;
31
32export const AgentSessionProjectCandidate = Schema.Struct({
33 path: TrimmedNonEmptyString,
34 title: TrimmedNonEmptyString,
35 projectId: Schema.optional(ProjectId),
36 sources: Schema.Array(AgentSessionSource),
37 threadCount: NonNegativeInt,
38 lastActiveAt: Schema.NullOr(IsoDateTime),
39 alreadyImported: Schema.Boolean,
40 git: Schema.optionalKey(Schema.NullOr(AgentSessionProjectGit)),
41});
42export type AgentSessionProjectCandidate = typeof AgentSessionProjectCandidate.Type;
43
44export const AgentSessionScanResult = Schema.Struct({
45 candidates: Schema.Array(AgentSessionProjectCandidate),
46 scannedAt: IsoDateTime,
47 truncated: Schema.optional(Schema.Boolean),
48});
49export type AgentSessionScanResult = typeof AgentSessionScanResult.Type;
50
51export const AgentSessionImportInput = Schema.Struct({
52 projectId: ProjectId,
53 expectedWorkspaceRoot: Schema.optional(TrimmedNonEmptyString),
54});
55export type AgentSessionImportInput = typeof AgentSessionImportInput.Type;
56
57export class AgentSessionImportProjectNotFoundError extends Schema.TaggedError<AgentSessionImportProjectNotFoundError>()(
58 "AgentSessionImportProjectNotFoundError",
59 { projectId: ProjectId },
60) {
61 override get message(): string {
62 return `Project '${this.projectId}' does not exist.`;
63 }
64}
65
66export class AgentSessionImportProjectChangedError extends Schema.TaggedError<AgentSessionImportProjectChangedError>()(
67 "AgentSessionImportProjectChangedError",
68 { projectId: ProjectId },
69) {
70 override get message(): string {
71 return `Project '${this.projectId}' changed directories. Scan for projects again before importing history.`;
72 }
73}
74
75export const AgentSessionImportResult = Schema.Struct({
76 importedCount: NonNegativeInt,
77 skippedCount: NonNegativeInt,
78});
79export type AgentSessionImportResult = typeof AgentSessionImportResult.Type;
80
81export class AgentSessionScanError extends Schema.TaggedError<AgentSessionScanError>()(
82 "AgentSessionScanError",
83 {
84 operation: Schema.Literals(["read-settings", "read-projects"]),
85 cause: Schema.Defect(),
86 },
87) {
88 override get message(): string {
89 return `Failed to scan agent sessions during ${this.operation}.`;
90 }
91}