feat(post-application): automatically pull from email (#145)

* feat(post-application): add schema and shared types for provider ingestion (#136)

* test(orchestrator): ensure full localStorage shape in vitest setup

* feat(post-application): add provider registry and dispatcher framework (#137) (#146)

* Implement Gmail provider credential persistence (#147)

* Add unified post-application provider action API (#148)

* Implement Gmail ingestion sync with 95/60 relevance policy

* Implement Gmail ingestion sync with 95/60 relevance policy (#149)

* feat(post-application): add job mapping engine with llm rerank fallback

* feat(post-application): add inbox review APIs with transactional approve/deny (#151)

* feat(post-application): add tracking inbox UI with provider controls (#152)

* oauth implementation

* UI changes

* see past runs in more detail

* occurred at comes from email

* state mismatch

* better UI representation

* comments

* comments

* comments

* comments

* documentation

* explainer

* set things manually

* scrolling

* any found email can be pending

* searchable download

* Email-to-Job Matching Decision Tree

* email viewer list improvement

* simplification initial commit

* exclude discovered jobs

* show only resady

* dropdown

* mermaid

* syntax

* targets is the same as logging that is done manually

* event label

* duplicate avoidance

* clean up html

* token saving

* print

* send idx not uuid

* remove logging

* formatting

* better documentation

* documentation

* comments

* process all

* comments
This commit is contained in:
Shaheer Sarfaraz
2026-02-12 19:48:25 +00:00
committed by GitHub
parent 05f1c62de2
commit 687fd5e91f
46 changed files with 7652 additions and 89 deletions
+209
View File
@@ -364,6 +364,215 @@ export type ApiResponse<T> =
meta: ApiMeta;
};
export const POST_APPLICATION_PROVIDERS = ["gmail", "imap"] as const;
export type PostApplicationProvider =
(typeof POST_APPLICATION_PROVIDERS)[number];
export const POST_APPLICATION_PROVIDER_ACTIONS = [
"connect",
"status",
"sync",
"disconnect",
] as const;
export type PostApplicationProviderAction =
(typeof POST_APPLICATION_PROVIDER_ACTIONS)[number];
export const POST_APPLICATION_INTEGRATION_STATUSES = [
"disconnected",
"connected",
"error",
] as const;
export type PostApplicationIntegrationStatus =
(typeof POST_APPLICATION_INTEGRATION_STATUSES)[number];
export const POST_APPLICATION_SYNC_RUN_STATUSES = [
"running",
"completed",
"failed",
"cancelled",
] as const;
export type PostApplicationSyncRunStatus =
(typeof POST_APPLICATION_SYNC_RUN_STATUSES)[number];
export const POST_APPLICATION_RELEVANCE_DECISIONS = [
"relevant",
"not_relevant",
"needs_llm",
] as const;
export type PostApplicationRelevanceDecision =
(typeof POST_APPLICATION_RELEVANCE_DECISIONS)[number];
export const POST_APPLICATION_MESSAGE_TYPES = [
"interview",
"rejection",
"offer",
"update",
"other",
] as const;
export type PostApplicationMessageType =
(typeof POST_APPLICATION_MESSAGE_TYPES)[number];
export const POST_APPLICATION_ROUTER_STAGE_TARGETS = [
"no_change",
"applied",
"recruiter_screen",
"assessment",
"hiring_manager_screen",
"technical_interview",
"onsite",
"offer",
"rejected",
"withdrawn",
"closed",
] as const;
export type PostApplicationRouterStageTarget =
(typeof POST_APPLICATION_ROUTER_STAGE_TARGETS)[number];
export const POST_APPLICATION_PROCESSING_STATUSES = [
"auto_linked",
"pending_user",
"manual_linked",
"ignored",
] as const;
export type PostApplicationProcessingStatus =
(typeof POST_APPLICATION_PROCESSING_STATUSES)[number];
export interface PostApplicationIntegration {
id: string;
provider: PostApplicationProvider;
accountKey: string;
displayName: string | null;
status: PostApplicationIntegrationStatus;
credentials: Record<string, unknown> | null;
lastConnectedAt: number | null;
lastSyncedAt: number | null;
lastError: string | null;
createdAt: string;
updatedAt: string;
}
export interface PostApplicationSyncRun {
id: string;
provider: PostApplicationProvider;
accountKey: string;
integrationId: string | null;
status: PostApplicationSyncRunStatus;
startedAt: number;
completedAt: number | null;
messagesDiscovered: number;
messagesRelevant: number;
messagesClassified: number;
messagesMatched: number;
messagesApproved: number;
messagesDenied: number;
messagesErrored: number;
errorCode: string | null;
errorMessage: string | null;
createdAt: string;
updatedAt: string;
}
export interface PostApplicationMessage {
id: string;
provider: PostApplicationProvider;
accountKey: string;
integrationId: string | null;
syncRunId: string | null;
externalMessageId: string;
externalThreadId: string | null;
fromAddress: string;
fromDomain: string | null;
senderName: string | null;
subject: string;
receivedAt: number;
snippet: string;
classificationLabel: string | null;
classificationConfidence: number | null;
classificationPayload: Record<string, unknown> | null;
relevanceLlmScore: number | null;
relevanceDecision: PostApplicationRelevanceDecision;
matchedJobId: string | null;
matchConfidence: number | null;
stageTarget: PostApplicationRouterStageTarget | null;
messageType: PostApplicationMessageType;
stageEventPayload: Record<string, unknown> | null;
processingStatus: PostApplicationProcessingStatus;
decidedAt: number | null;
decidedBy: string | null;
errorCode: string | null;
errorMessage: string | null;
createdAt: string;
updatedAt: string;
}
export interface PostApplicationProviderActionConnectRequest {
accountKey?: string;
payload?: Record<string, unknown>;
}
export interface PostApplicationProviderActionSyncRequest {
accountKey?: string;
maxMessages?: number;
searchDays?: number;
}
export interface PostApplicationProviderStatus {
provider: PostApplicationProvider;
accountKey: string;
connected: boolean;
integration: PostApplicationIntegration | null;
}
export interface PostApplicationProviderActionResponse {
provider: PostApplicationProvider;
action: PostApplicationProviderAction;
accountKey: string;
status: PostApplicationProviderStatus;
message?: string;
}
export interface PostApplicationInboxItem {
message: PostApplicationMessage;
matchedJob?: {
id: string;
title: string;
employer: string;
} | null;
}
export type BulkPostApplicationAction = "approve" | "deny";
export interface BulkPostApplicationActionRequest {
action: BulkPostApplicationAction;
provider: PostApplicationProvider;
accountKey: string;
}
export type BulkPostApplicationActionResult =
| {
messageId: string;
ok: true;
message: PostApplicationMessage;
stageEventId?: string | null;
}
| {
messageId: string;
ok: false;
error: {
code: string;
message: string;
};
};
export interface BulkPostApplicationActionResponse {
action: BulkPostApplicationAction;
requested: number;
succeeded: number;
failed: number;
skipped: number;
results: BulkPostApplicationActionResult[];
}
export interface JobsListResponse<TJob = Job> {
jobs: TJob[];
total: number;