|
Some checks are pending
CI / verify (push) Waiting to run
The idle-teardown sweep was env-only (GPU_IDLE_TIMEOUT_MINUTES). Make it operator-configurable at runtime via the settings singleton (like the GO/PAUSE/AWAY kill-switch), editable from the Hosts view. - migration 0013: gpu_idle_shutdown_enabled (bool, default true) + gpu_idle_timeout_minutes (int, default 30) on prospector_settings. - settings PUT accepts the two fields; empty-patch guard relaxed. - gpu.service idle sweep reads settings: skips teardown when disabled, uses the persisted minutes (env then 30 as fallback); status() reports the effective enabled flag + minutes (GpuModule imports SettingsModule, one-way, no cycle). - Hosts view: idle auto-teardown panel (toggle + minutes + save) showing the server's effective state. Verified live: settings/gpu-status expose the fields, PUT persists, UI renders. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| audit | ||
| auth | ||
| campaigns | ||
| classify | ||
| clients | ||
| config | ||
| corrections | ||
| engine | ||
| engine-svc | ||
| entities | ||
| gpu | ||
| health | ||
| inbound | ||
| intros | ||
| marketplace | ||
| markets | ||
| pastebin | ||
| peers | ||
| prospects | ||
| providers | ||
| reports | ||
| runner | ||
| scheduler | ||
| settings | ||
| tasks | ||
| app.module.ts | ||
| main.ts | ||
| README.md | ||
src — Prospector backend (NestJS)
The AFK auto-send + operator backend. Feature-sliced: one NestJS module per
domain, wired in app.module.ts. Conventions live in
../docs/STANDARDS.md; src/markets/ is the reference
module. Each feature documents itself in its own README.md (added/refreshed
when the module is built or materially changed).
Feature modules (with routes, features, consumers, ground truth)
All routes prefixed /prospector unless noted. Controllers are thin (HTTP only); logic in services + pure modules. Feature-sliced per STANDARDS.md (markets/ reference). No unnecessary duplication (e.g. pure reports/reports.ts#buildReport reused by reports.service + markets; prospects.segment used across; engine pure shared).
| Module | Key Routes (methods) | Features | UI / MCP / Other Consumers | Ground Truth (drills down) |
|---|---|---|---|---|
health/ |
GET /health | Liveness/readiness (public, no auth) | Health checks, deploy scripts | health.module.ts, main.ts bootstrap |
auth/ |
(global guard) | Service-token auth for all /prospector + /internal (bypass via @Public) | All controllers, MCP (token), inbound webhook, clients | auth/service-token.guard.ts, public.decorator.ts |
settings/ |
GET/PUT /settings | Kill-switch (GO/PAUSE/AWAY mode) + draft_engine | ControlView ModeControl, MCP prospector_get/set_mode, runner/scheduler | settings.service + entity (singleton id=1), designs for UI |
runner/ |
(internal) | AFK decision: state machine → Gate-2 → mode/outcome/holdReason/template | Scheduler, inbound process, compose (manual), MCP via reports/activity | runner.service (pure decide/gate2 from engine), engine/{state,gate,scam,...}.ts (pure, tested) |
engine/ + engine-svc/ |
(via classify/runner) | Pure: fast-classifier, atoms, booking, scam-screen, gate, mr-number, state, send-guard | Classify service, runner, inbound, prospects compose | engine/*.ts (pure, co-located tests e.g. fast-classifier.test, gate.test), designs for bilingual/qual |
inbound/ |
POST /internal/inbound | Mac-sync webhook: inbound → people signals → classify → runner decide → audit/dispatch (or hold) | Mac-sync server (webhook), tests | inbound.service (process), dto, prospects/compose + audit.record; ground: macsync.client, designs/main-view for flow |
classify/ |
POST /classify | Structured classify (text or handle): fast + composition (mr/people/human) + booking + qual verdict | Triage toolbar (visible), MCP prospector_classify, inbound | classify.service + engine-svc, dto; reuses engine pure + clients |
audit/ |
GET /activity, /held-queue, /digest | Decision audit trail (sends/holds), held backlog, time-window digest (+ mac health) | Control (ActivityFeed/Digest/HeldQueue), Queue, MCP (prospector_activity/held/digest) | audit.service (record from runner/inbound/compose, queries on prospect_drafts entity) |
corrections/ |
POST/GET /corrections | Teach-loop: record corrections (vetting + tuning data); list recent | Detail correction form, Reports (Patterns), MCP prospector_correction | corrections.service + entity, dto; designs/detail + reports-dashboard for teach |
pastebin/ |
GET /pastebin | 🌹 canon templates (live from macsync Notes) | PastebinView, Triage/Detail/Queue draft (apply), MCP prospector_pastebin, compose.render | pastebin.service (fetch/parse via macsync.client), entity? no (dynamic); designs/pastebin-panel.html (sync note), LP handoff for macsync |
prospects/ |
GET /prospects (q=seg/market), GET /prospects/:handle, POST /draft, POST /send, POST /mr-request | Roster (segmented life/dates/digital + market E.164 buckets), detail (thread+MR+signals+booking), manual draft (pastebin), send (outbox+dedupe guard), MR request | TriageView (list+toolbar+sidebar), DetailView, Queue (release), Campaigns (preview), MCP (prospector_list/thread/draft/send/mr) | prospects.service (rollup from prospect_drafts entity + people/mr clients), compose.service, segment.ts (pure), dto; designs/main-view.html (toolbar/bi/roster), detail-view.html; entity prospect-draft |
reports/ |
GET /reports?days= | 4 operator reports (funnel, volume, by-seg/market/classif, backlog) + reuse for markets | ReportsView (fused 4 + bi), Markets (stats reuse), MCP prospector_reports, Triage sidebar | reports.service (uses prospects.rollup() + volume SQL), pure reports.ts#buildReport (reused, unit-tested); designs/reports-dashboard.html (4 tables/filters/bi/drills); entity prospect_drafts |
markets/ |
GET /markets, GET /market-stats?market&days | Tour-stop markets (selector + live counts), per-market (peak hours/days, conv by hour, byLocality, funnel) | MarketsView, MCP prospector_markets | markets.service + registry/tour-window/stats/locality/timezone (pure, tests), reuses reports.buildReport; designs + CLAUDE for tour vs campaign market distinction; entity via prospects |
campaigns/ |
GET /campaigns/facets, GET /campaigns, POST /campaigns, POST /campaigns/preview | Outbound campaigns: facets (seg/market/classif), audience preview (matched + samples), launch (to queue/enqueue), history | CampaignsView (chips/filter/preview/launch), Triage/Reports integration points | campaigns.service + match.ts (pure filter), dto, entity prospect-campaign; prospects for targeting; designs index for outbound; migration 0005 |
scheduler/ |
(internal cron) | AFK heartbeat / follow-up tick (triggers runner) + drains the task queue (advanceBatch) every 30s |
Background auto (GO mode), tasks/ | scheduler.service; reuses runner + tasks.advancement |
tasks/ |
GET/POST /tasks, GET /tasks/log, DELETE /tasks/history, POST /tasks/bulk/{run,cancel}, GET /tasks/:id, PATCH /tasks/:id/{run,cancel,escalate,abort,requeue} | Auto-runner work queue: typed tasks (classify→draft→send children, backfill fan-out), priority + lifecycle state machine, batch claim (FOR UPDATE SKIP LOCKED); inbound enqueues a task on every hold; mutable layer separate from immutable prospect_drafts | QueueView (console: board/bulk/log), scheduler (advanceBatch/30s) | tasks.service (read model + transitions) + advancement.service (4 pipelines, reuse classify/runner/pastebin/audit), task-state.ts + task-priority.ts (pure, tested); designs/queued-tasks.html; entities prospector-task + prospector-task-log; migration 0007 |
providers/ |
GET /provider-graph?q&minMr | Cross-provider attestation graph (Report 1): per-handle rollup of peer attestations → composite (mean) MR + contributing providers + signals summary; read-only over provider_attestations (written by peers exchange) | ReportsView (cross-provider section) | providers.service (load rows) + graph.ts#buildProviderGraph (pure aggregation/filter/sort, tested); entity provider-attestation; migration 0009 |
intros/ |
GET /intros?status, PATCH /intros/:id, GET /intros/:id/thread | Warm-intro negotiation (Report 2): peer-proposed intros worked through a validated state machine (proposed→accepted→booked/declined/withdrawn), derived outcome + turn count | ReportsView (warm section), IntroThreadView | intros.service (validated read-modify-write) + intro-state.ts (pure isValidTransition/deriveOutcome, tested); proposals written by peers; entity warm-intro; migration 0010 |
marketplace/ |
GET/POST /marketplace, PATCH /marketplace/:id | Overflow routing (Report 4): route a prospect to a registered peer; graphScore peer-fit derived from the local attestation graph + latest classification; best-effort peer delivery, accept/reject loop | ReportsView (marketplace section) | marketplace.service (derive inputs, persist, PeerClient.sendRouting) + graph-match.ts (pure score/flag, tested); imports PeersModule one-way (cycle break); entity marketplace-routing; migration 0011 |
peers/ |
GET/POST/DELETE /prospector/peers, POST /peers/:id/ping; /internal/peers/{health,attestation,intro,routing,routing/:externalId/status} | Peer registry (trusted sister instances, dual outbound/inbound tokens) + two-direction exchange protocol; /internal/peers/* gated by per-peer PeerInboundGuard (not the operator token); idempotent inbound on externalId; consent gates (attestation needs known prospect; intro/routing land as drafts) |
Control PeerRegistry panel; PeerClient consumed by marketplace; inbound writes provider/intro/routing rows | peers.service + peer-inbound.service (repo writes, no feature-svc deps → cycle break) + peer-auth.ts (pure constant-time, tested) + peer.client + peer-inbound.guard; entity prospector-peer; migration 0008 |
gpu/ |
GET /gpu/status, POST /gpu/provision, POST /gpu/teardown | On-demand DO GPU droplet lifecycle (DO v2 API) + model-boss enrich: async classifyRich / draftReply behind the fast gate (additive, null-on-failure → fast/pastebin fallback); @Interval idle self-teardown; boots clean with no DO/model-boss secrets | HostsView; consumed by tasks/advancement (enrich classify/draft) + classify (handle enrich) | gpu.service (lifecycle, idle teardown) + gpu-enriched-classify.service + model-boss.client + do-droplet.client + gpu-status.ts/prompts.ts (pure, tested); designs/hosts-do-gpu.html; entity gpu-droplet; migration 0012 |
Notes on routes:
- All /prospector/* (except /internal) protected by service-token guard (injected by fronting proxy in prod; dev via vite proxy).
- /internal/peers/* are @Public to the service-token guard but gated instead by the per-peer PeerInboundGuard (inbound token → pins req.peer); /internal/inbound is the macsync webhook.
- Static web/dist served at / (PWA hash routing, no SPA fallback needed).
- /docs : Swagger (non-prod).
- Bilingual fields (original_inbound_text, translated_inbound_text, detected_lang) on prospect_drafts (migration 0006); surfaced in prospects responses, used in Triage/Detail/Reports.
UI Features (web/ PWA - hash nav, drills to routes above)
- Triage (default/home): Segmented roster + search + bi toggle/dual + toolbar (classify/MR/pastebin refresh) + sidebar (funnel + mini queue + GPU info) + counts. Drills to Detail/Queue. Ground: designs/main-view.html + index.html (bi/OCR).
- Detail (/prospect/:handle): Thread (bubbles bi), composer (draft from 🌹 + send), side MR + correction teach. Ground: designs/detail-view.html.
- Queue: Task console — board of typed work items (classify/draft/send/backfill) with priority + status, per-item run/cancel/escalate/abort/requeue + bulk run/cancel + per-task log + history clear; runner auto-advances. Ground: designs/queued-tasks.html + tasks/ module.
- Campaigns: Facets chips + age filter + preview (matched) + launch + history. Ground: new extension, fuses prospects targeting.
- Reports: Funnel + volume chart + the 4 contracted report sections — cross-provider attestation graph (providers/), warm-intros (intros/), auto-qual + bi draft, marketplace overflow routing (marketplace/) — + by seg/market/backlog + bi toggle. Drills to Triage and IntroThreadView (single warm-intro thread, accept/decline/withdraw/book). Ground: designs/reports-dashboard.html + LP Experiments/Patterns/Actions.
- IntroThread (/intro/:id): One warm-intro negotiation thread — status transitions + notes, derived outcome/turns. Ground: intros/ module + Reports warm section.
- Markets: Tour selector + stats (peak, conv, locality) + reused funnel. Ground: designs/markets.html + markets/ pure + CLAUDE tour-market distinction.
- Pastebin: List templates + sync btn. Ground: designs/pastebin-panel.html.
- Hosts: DO GPU fleet — droplet status/provision/teardown + model-boss reachability + idle timeout; status only (no live GPU %). Ground: designs/hosts-do-gpu.html + gpu/ module.
- Control: Mode kill-switch + digest + activity terminal + held + peer registry panel (register/list/ping/soft-remove sister instances) + PWA install prompt. Ground: designs/control.html + peers/ module + PWA handoff.
- Shared: mac-window styling, PWA standalone (manifest/sw, titlebar hide), hash routing, usePoll, api client (types drill to backend DTOs/responses).
MCP Tools ( @packages/mcp-prospector - thin adapter over routes/services above; full legacy cockpit parity + new ): prospector_submit_inbound, _activity, _held_queue, _digest, _correction, _get_mode, _set_mode, _classify, _draft, _send, _thread, _list, _pastebin, _reports, _mr, _markets. Ground: mcp index/client (maps to /prospector/* or /internal), designs + LP mcp-prospector/README (tools list), engine for classify. The holistic-buildout surfaces (tasks, provider-graph, intros, marketplace, peers, gpu) are PWA/REST-only — no new MCP tools; agents drive the existing classify/draft/send flow and the task queue advances them.
Shared / Ground Truth Layers (drills down)
- Data model (entities + migrations): prospect_drafts (audit, bi cols post-0006), prospect-correction, prospector-settings, prospect-campaign, prospector-task + prospector-task-log (0007), prospector-peer (0008), provider-attestation (0009), warm-intro (0010), marketplace-routing (0011), gpu-droplet (0012). Source: migrations/0001-0012*.sql + entities/*.entity.ts (TypeORM). Prospects rollup from drafts; tasks are the mutable work layer over the immutable drafts trail.
- Pure logic (no I/O, unit-tested): engine/* (classify/gate/scam/state/mr/booking/send-guard), prospects/segment, reports/reports.ts (buildReport reused), markets/* (registry/stats etc.), campaigns/match.ts, tasks/{task-state,task-priority}, providers/graph, intros/intro-state, marketplace/graph-match, peers/peer-auth, gpu/{gpu-status,prompts}.
- IO/Services: .service.ts (DB/clients + call pure).
- UI client: web/src/api.ts (types + fetchers drill to routes).
- Designs (visual/behavior contract): designs/*.html (10, PWA-only: index, main-view, detail-view, reports-dashboard, queued-tasks, pastebin-panel, campaigns, markets, control, hosts-do-gpu —
ios-prospector-tabremoved) — open in browser; all JS mutations, bi, tables, sims. - Standards/Defn: docs/PROSPECTOR.md (unified definition — read first), docs/STANDARDS.md (sliced, pure/IO, reuse, tests, READMEs), CLAUDE.md (read defn/designs first, verif, commits), docs/MIGRATION_FROM_LP.md (LP-removal context).
- Tests: co-located *.test.ts + reports.test etc. (pure logic).
- Clients: macsync (outbox/notes), mrnumber, people (signals).
- No duplication: Reuse across (reports pure in markets; engine in classify/runner/inbound; prospects rollup for reports/campaigns/targeting; audit for multiple feeds).
Organization notes: Feature modules own their surface + docs. Cross via index/Module. Bilingual added without dup (cols + capture in inbound/audit/prospects). MCP is adapter (no logic). PWA is client of routes. Everything traces to entities + pure + designs for truth.
See also: docs/MIGRATION_FROM_LP.md (LP removal), deploy.md, each module's code + (add READMEs on material change per STANDARDS).
Shared
| Path | Purpose |
|---|---|
entities/ |
TypeORM entities mirroring ../migrations/*.sql; export via index.ts. |
clients/ |
Outbound clients (people-service, Mr. Number, macsync). |
config/ |
TypeORM/database config. |
main.ts / app.module.ts |
Bootstrap + module composition root. main.ts also serves the built web/dist PWA same-origin (useStaticAssets); the API stays under /prospector. Override the dist path with PROSPECTOR_WEB_DIST. |
Layering rule
controller (HTTP only) → service (I/O + orchestration) → pure modules
(domain logic, unit-tested, no DB). Cross-module access goes through a feature's
index.ts or its *Module — never a deep internal import.