diff --git a/notes/epiphany-core-harness-surfaces.md b/notes/epiphany-core-harness-surfaces.md index 89788d0d..e2460546 100644 --- a/notes/epiphany-core-harness-surfaces.md +++ b/notes/epiphany-core-harness-surfaces.md @@ -48,7 +48,7 @@ The rule is: | `thread/epiphany/stateUpdated` | notification | landed | Emits updated projected state, source, revision, and changed fields after successful update/promote writes. | | `thread/epiphany/scene` | read-only reflection | landed, live-smoked | Compact client scene derived from authoritative Epiphany state, including checkpoint summary reflection. | | `thread/epiphany/jobs` | read-only reflection | landed, live-smoked | Derived indexing, remap, verification, and specialist-progress slots from typed state and retrieval summaries. | -| `thread/epiphany/freshness` | read-only reflection | landed, live-smoked | Retrieval and graph freshness lens derived from retrieval summaries plus graph frontier/churn state. | +| `thread/epiphany/freshness` | read-only reflection | landed, live-smoked | Retrieval and graph freshness lens derived from retrieval summaries plus graph frontier/churn state, with watcher-backed invalidation inputs for loaded threads. | | `thread/epiphany/context` | read-only reflection | landed, live-smoked | Targeted state shard for graph nodes/edges, active frontier, graph checkpoint, investigation checkpoint, observations, and evidence. | | `thread/epiphany/pressure` | read-only reflection | landed, live-smoked | Context-pressure gauge derived from token telemetry and recorded auto-compact/context limits. | @@ -130,7 +130,7 @@ These are not landed yet: - live typed job/progress state for running indexing, remap, verification, and specialist work - `thread/epiphany/jobsUpdated` or equivalent progress notifications - richer evidence-range and graph-shard inspection beyond the landed context shard -- watcher-driven graph/retrieval/invariant invalidation on top of the landed freshness reflection +- automatic watcher-driven graph/retrieval/invariant invalidation policy on top of the landed freshness reflection - automatic tool-output observation promotion - typed turn intent before broad mutation - hard mutation gates for stale map or violated invariants @@ -184,11 +184,13 @@ It reflects from existing sources only: - graph frontier dirty paths and open-question/open-gap pressure - graph checkpoint identity - churn freshness hints such as `fresh` or `stale` +- live watcher-backed invalidation telemetry for loaded threads: watched root, recent changed paths, mapped graph-node hits, and active-frontier hits - state revision and live/stored source identity It does not mutate `SessionState`, refresh retrieval, remap graphs, emit -`thread/epiphany/stateUpdated`, or pretend watcher-driven invalidation is -already built. It is a pressure lens, not the crew with the crowbar. +`thread/epiphany/stateUpdated`, schedule follow-up work, or perform automatic +semantic invalidation. It is a pressure lens with eyes, not the crew with the +crowbar. ## Context Shard Surface Direction @@ -229,13 +231,14 @@ Future runtime behavior should: - distinguish what survived from what was discarded - make compaction visible instead of pretending continuity is magic -The pressure signal now exists as `thread/epiphany/pressure`, the first -freshness signal now exists as `thread/epiphany/freshness`, and durable -investigation packets now exist in typed state plus prompt/scene/context -reflection. What does not exist yet is the runtime coordinator that consumes -those signals. Automatic CRRC still needs live watcher-backed invalidity -telemetry and an explicit policy for when a checkpoint means "resume" versus -"re-gather" instead of vibes with a clipboard. +The pressure signal now exists as `thread/epiphany/pressure`, the freshness +signal now exists as `thread/epiphany/freshness`, live watcher-backed +invalidation telemetry now exists inside that freshness surface for loaded +threads, and durable investigation packets now exist in typed state plus +prompt/scene/context reflection. What does not exist yet is the runtime +coordinator that consumes those signals. Automatic CRRC still needs an explicit +policy for when a checkpoint means "resume" versus "re-gather" instead of vibes +with a clipboard. Compaction should squeeze scratch, not the map. diff --git a/notes/epiphany-current-algorithmic-map.md b/notes/epiphany-current-algorithmic-map.md index cb23981b..f95edec5 100644 --- a/notes/epiphany-current-algorithmic-map.md +++ b/notes/epiphany-current-algorithmic-map.md @@ -24,7 +24,7 @@ The spine exists in sixteen live paths: - live state notifications and write responses: app-server declares experimental `thread/epiphany/stateUpdated` and emits it with the updated typed state, typed `source`, event-level `revision`, and typed `changedFields` after successful direct updates and accepted promotions, but not after rejected promotions; direct `thread/epiphany/update` and accepted `thread/epiphany/promote` responses expose the same revision/change metadata, accepted promotions always report `Evidence` because verifier evidence is appended even when the patch evidence list is empty, and successful write response/notification states now use the same client-visible live projection as `thread/read`, including retrieval-summary backfill when durable state has no persisted retrieval metadata, in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:1113), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4354), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4419), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4428), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4555), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4608), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4652), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4693), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11327), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11370). - scene projection: app-server declares experimental read-only `thread/epiphany/scene`, reads the same live/stored Epiphany state view used by `thread/read`, and derives a compact client scene with objective, active subgoal, invariant status counts, graph focus/counts, retrieval status, investigation-checkpoint summary counts, observation/evidence summaries, churn, live/stored source, and available control-plane actions without mutating or persisting state in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:340), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:3924), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4115), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:10757). - job/progress reflection: app-server declares experimental read-only `thread/epiphany/jobs`, reads the same thread view as scene, optionally fills retrieval state for loaded threads with no Epiphany state, and derives four non-authoritative progress slots for retrieval indexing, graph remap, invariant verification, and future specialist work without scheduling, notifying, mutating, or persisting jobs in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:345), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4132), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4150), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11354). -- freshness reflection: app-server declares experimental read-only `thread/epiphany/freshness`, reads the same live/stored thread view used by scene/jobs/context, fills live retrieval state for loaded threads, and derives one bounded retrieval/graph freshness lens with exact dirty-path pressure, checkpoint identity, churn hints, and revision/source identity without mutating, scheduling, notifying, or pretending watcher-driven invalidation already exists in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:350), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4160), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4211), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:10892). +- freshness reflection: app-server declares experimental read-only `thread/epiphany/freshness`, reads the same live/stored thread view used by scene/jobs/context, fills live retrieval state for loaded threads, and derives one bounded retrieval/graph freshness lens plus, for loaded threads, watcher-backed invalidation inputs with watched root, changed paths, mapped graph-node hits, active-frontier hits, and revision/source identity without mutating, scheduling, notifying, or performing automatic semantic invalidation in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:350), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4167), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4259), [epiphany_invalidation.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/epiphany_invalidation.rs:19), [epiphany_invalidation.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/epiphany_invalidation.rs:67), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4219), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11073). - targeted context reflection: app-server declares experimental read-only `thread/epiphany/context`, reads the same live/stored Epiphany state view used by scene/jobs, and derives a bounded state shard containing selected graph nodes/edges, links, active frontier, checkpoint, the full durable investigation-checkpoint packet, observations, direct evidence, linked evidence, and missing requested ids without retrieval, proposal, promotion, notification, mutation, or persistence in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:355), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4253), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4261), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11152). - context-pressure reflection: core token telemetry carries `model_auto_compact_token_limit` beside total/last usage and `model_context_window`; app-server declares experimental read-only `thread/epiphany/pressure`, reads live token usage or the latest stored rollout `TokenCount`, and derives `unknown`/`low`/`elevated`/`high`/`critical` pressure plus a non-acting compaction-prep recommendation without mutating Epiphany state or scheduling compaction in [protocol.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/protocol/src/protocol.rs:2237), [turn_context.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/session/turn_context.rs:103), [session/mod.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/session/mod.rs:2716), [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:360), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4361), [token_usage_replay.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor/token_usage_replay.rs:75), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4306), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11028). - retrieval/indexing: app-server routes `thread/epiphany/retrieve` and `thread/epiphany/index` through loaded `CodexThread` host methods into `epiphany-core` in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4356), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4432), [codex_thread.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/codex_thread.rs:467), and [codex_thread.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/codex_thread.rs:449). @@ -66,7 +66,7 @@ flowchart TD QS --> Q N --> QJ["thread/epiphany/jobs derives progress slots"] QJ --> Q - N --> QF["thread/epiphany/freshness derives retrieval/graph freshness lens"] + N --> QF["thread/epiphany/freshness derives freshness lens + watcher invalidation inputs"] QF --> Q N --> QC["thread/epiphany/context derives targeted state shard"] QC --> Q @@ -113,7 +113,7 @@ What this means in plain English: - The app-server can expose it as typed thread data. - The app-server can also expose a compact read-only scene derived from that typed state for future clients. - The app-server can expose read-only job/progress slots derived from typed state and retrieval summaries without creating a scheduler. -- The app-server can expose a read-only freshness lens derived from retrieval summaries plus graph frontier/churn state without turning file staleness into a hidden worker. +- The app-server can expose a read-only freshness lens derived from retrieval summaries plus graph frontier/churn state and, for loaded threads, watcher-backed invalidation telemetry without turning file staleness into a hidden worker. - The app-server can expose a targeted read-only context shard for clients that need graph/evidence detail without pulling the full typed state. - The app-server can expose context pressure derived from real token telemetry without starting compaction or pretending vibes are telemetry. - A loaded thread can ask for a typed observation/evidence proposal without mutating state. @@ -131,9 +131,9 @@ Source audit on 2026-04-26 re-read the changed Phase 6 reflection paths against The current landed machine is still coherent. The core shape is not ornamental: one protocol state object flows through session state, prompt rendering, rollout persistence, thread hydration, explicit retrieval/indexing, read-only distillation/proposal, verifier-backed promotion, the single durable update writer, and now a live state notification that reflects completed writes. The useful simplification pressure is already present in the boundaries: retrieval reads, indexing writes only the semantic catalog, distillation/proposal draft patches, promotion gates, update persists, and notification broadcasts only after the write is done. -Nothing in the current typed spine obviously deserves deletion right now. The parts that would turn this into Jenga are still correctly listed as non-flows: automatic tool-output ingestion, watcher invalidation, specialist scheduling, broad event streaming, and an automatic Compact-Rehydrate-Reorient-Continue coordinator. Thin GUI/client reflection is no longer forbidden as a category, provided it reflects and steers typed state rather than inventing a second source of truth. +Nothing in the current typed spine obviously deserves deletion right now. The parts that would turn this into Jenga are still correctly listed as non-flows: automatic tool-output ingestion, automatic watcher-driven invalidation, specialist scheduling, broad event streaming, and an automatic Compact-Rehydrate-Reorient-Continue coordinator. Thin GUI/client reflection is no longer forbidden as a category, provided it reflects and steers typed state rather than inventing a second source of truth. -The next Perfect Machine move is not adding another writer, not re-hardening Phase 5, and no longer the first scene, job, freshness, targeted context, pressure, or investigation-checkpoint reflection. Those surfaces now let clients inspect the machine Epiphany already built at summary, progress, bounded-detail, freshness, context-pressure, and durable-planning-packet levels. The next outward work should add a missing observation source only when there is a concrete consumer: live watcher-backed invalidation inputs, live progress notifications once real long-running job owners exist, or future CRRC coordination that consumes the landed checkpoint instead of inventing transcript telepathy. +The next Perfect Machine move is not adding another writer, not re-hardening Phase 5, and no longer the first scene, job, freshness, targeted context, pressure, watcher, or investigation-checkpoint reflection. Those surfaces now let clients inspect the machine Epiphany already built at summary, progress, bounded-detail, freshness, context-pressure, file-movement, and durable-planning-packet levels. The next outward work should add a real coordinator, not another decorative sense organ: a bounded CRRC policy layer that consumes the landed pressure, freshness, watcher, and checkpoint signals, with live progress notifications waiting until real long-running job owners exist. ## Natural Language Spine @@ -150,7 +150,7 @@ This is the same flow in the language Epiphany is supposed to make the model car | Thread read | Clients can ask for the current Epiphany model as typed data instead of scraping prompt text or transcript debris. | The dashboard reads the blueprint file directly, not a photo of the workbench. | | Scene projection | Clients can ask for a compact reflection of the typed model: active goal, graph focus, retrieval status, compact investigation-checkpoint summary, evidence counts, churn, and available control actions. It is derived from state, not a second state store. | A foreman-facing wall board copied from the blueprint ledger; useful at a glance, legally not the blueprint. | | Job projection | Clients can ask which Epiphany work slots are idle, needed, blocked, running, or unavailable for indexing, remap, verification, and future specialists. The slots are derived from state and retrieval summaries; they do not start work. | A shift board listing which crews would be needed if work begins, not a foreman secretly hiring people in the back room. | -| Freshness projection | Clients can ask whether retrieval or graph understanding is stale, missing, or ready, including exact dirty paths and checkpoint identity. The answer is a lens over existing retrieval/frontier/churn signals; it does not remap or invalidate anything by itself. | A clipboard listing which blueprint tabs got coffee on them and which shelf of the catalog needs rechecking, without sending anyone to swing a hammer. | +| Freshness projection | Clients can ask whether retrieval or graph understanding is stale, missing, or ready, and loaded threads now also expose watcher-observed file movement with watched root, changed paths, mapped graph-node hits, and frontier hits. The answer is still a lens over existing retrieval/frontier/churn/watcher signals; it does not remap, schedule, or invalidate anything by itself. | A clipboard listing which blueprint tabs got coffee on them and which shelf of the catalog needs rechecking, now with a door sensor showing which cabinet was opened, without sending anyone to swing a hammer. | | Context projection | Clients can ask for one bounded slice of the typed model: selected graph nodes/edges, active frontier, graph checkpoint, full investigation checkpoint packet, observations, direct evidence, linked evidence, and missing ids. It is derived from state and does not propose or promote anything. | A librarian opens the exact ledger pages you named, adds the active bookmark by default, and says which requested tabs were not in the book. | | Pressure projection | Clients can ask how close the live or stored thread is to compaction pressure using token usage, context window, and the recorded auto-compact threshold. The answer may recommend preparation, but it does not act. | A pressure gauge on the boiler; it does not grab the wrench or pretend steam is a moral failing. | | Retrieval | A loaded thread can ask a structured question of the repo through one typed retrieval surface. | A librarian brings back marked pages instead of making the agent rummage through every shelf. | @@ -1232,7 +1232,7 @@ These are deliberately not shipped yet: - no broad live Epiphany event stream beyond `thread/epiphany/stateUpdated` for successful update/promote writes. - no automatic evidence promotion from tool output; the current distill/propose/promote path still requires explicit verifier-backed calls. - no automatic Compact-Rehydrate-Reorient-Continue coordinator; current code preserves and rehydrates valid Epiphany snapshots across resume/rollback/compaction, exposes context pressure, and now carries a durable investigation checkpoint packet, but runtime reorientation and next-action selection are still enforced by repo-local state, handoff notes, and operating discipline rather than a scheduler. -- no watcher-driven graph or semantic invalidation. +- no automatic watcher-driven graph or semantic invalidation. - no code-intelligence graph. - no specialist-agent scheduler. - no live long-running job execution or `thread/epiphany/jobsUpdated` progress notification. @@ -1259,7 +1259,7 @@ stored Epiphany state -> successful writes emit thread/epiphany/stateUpdated ``` -The current remaining missing organ is not the red pen, not the first chewing motion, not the first state-change bell, not the first client-shaped scene, not the first job/progress reflection, not the first freshness lens, not the first targeted graph/evidence context read, not the first context-pressure gauge, and no longer the first durable investigation checkpoint packet. Those exist. Proposal now has the first bits of map memory, focus, and selection hygiene: it can auto-select a bounded evidence-backed observation cluster when ids are omitted, requires accepting recent evidence behind selected observations, prioritizes stronger selected observations for proposal wording, reuses existing architecture nodes by concrete code-ref/path/id checks before creating new path nodes, can rescue unanchored graph nodes through strict unique semantic overlap, follows graph links and incident edges into the frontier, and reports match-kind-aware churn pressure from the actual proposal shape. Distillation now also has the first source-output-aware teeth for tool/model summaries. Promotion now notices risky deltas instead of just checking the shape of the envelope, treats graph expansion as risky even if the patch tries to mumble "low pressure" while dragging a new wall into the blueprint, token-matches strong verifier kinds so `contest` cannot sneak past as `test`, and validates investigation checkpoints against known evidence before the packet can be inked. Successful update/promote writes now emit `thread/epiphany/stateUpdated` with `source: "update"` or `source: "promote"`, event-level revision, and typed changed fields so clients no longer need to poll blindly, infer the cause from timing, or diff the whole state to know which top-level sections moved. The scene, jobs, freshness, context, and pressure surfaces give clients compact or bounded read-only views without making the GUI/client authoritative, and scene/context now surface the checkpoint packet back out without becoming writers. The remaining missing organs are live watcher-backed invalidation inputs, live job progress notifications once real long-running job owners exist, and eventually role-scoped specialist ownership without silently auto-promoting anything. +The current remaining missing organ is not the red pen, not the first chewing motion, not the first state-change bell, not the first client-shaped scene, not the first job/progress reflection, not the first freshness lens, not the first watcher-backed invalidation read, not the first targeted graph/evidence context read, not the first context-pressure gauge, and no longer the first durable investigation checkpoint packet. Those exist. Proposal now has the first bits of map memory, focus, and selection hygiene: it can auto-select a bounded evidence-backed observation cluster when ids are omitted, requires accepting recent evidence behind selected observations, prioritizes stronger selected observations for proposal wording, reuses existing architecture nodes by concrete code-ref/path/id checks before creating new path nodes, can rescue unanchored graph nodes through strict unique semantic overlap, follows graph links and incident edges into the frontier, and reports match-kind-aware churn pressure from the actual proposal shape. Distillation now also has the first source-output-aware teeth for tool/model summaries. Promotion now notices risky deltas instead of just checking the shape of the envelope, treats graph expansion as risky even if the patch tries to mumble "low pressure" while dragging a new wall into the blueprint, token-matches strong verifier kinds so `contest` cannot sneak past as `test`, and validates investigation checkpoints against known evidence before the packet can be inked. Successful update/promote writes now emit `thread/epiphany/stateUpdated` with `source: "update"` or `source: "promote"`, event-level revision, and typed changed fields so clients no longer need to poll blindly, infer the cause from timing, or diff the whole state to know which top-level sections moved. The scene, jobs, freshness, context, and pressure surfaces give clients compact or bounded read-only views without making the GUI/client authoritative, scene/context surface the checkpoint packet back out without becoming writers, and freshness now surfaces watcher telemetry without becoming a hidden worker. The remaining missing organs are a bounded CRRC coordinator that decides whether the landed checkpoint means resume or re-gather, live job progress notifications once real long-running job owners exist, and eventually role-scoped specialist ownership without silently auto-promoting anything. ```text model/tool observations @@ -1273,7 +1273,7 @@ model/tool observations The typed write path exists, the first deterministic observation/evidence distillation path exists, source-output-aware distillation now summarizes noisy tool/model output into typed evidence with final result/error/finished lines ahead of generic warnings, the first deterministic map/churn proposal path exists, proposal-quality hardening now auto-selects bounded observation clusters when ids are omitted, requires accepting recent evidence, reuses existing graph nodes by observed code refs, prioritizes stronger selected observations, rescues unanchored graph nodes by strict semantic overlap, focuses linked frontier context, reports match-kind-aware map-delta pressure, and the first verifier-backed promotion gate exists. The first map/churn safety layer also exists: promotion can reject state replacement patches that are not tied to explicit observations/evidence, that contain structurally broken subgoal, invariant, graph, frontier, checkpoint, investigation-checkpoint, or churn fields, that try to promote risky churn without a warning and stronger verifier evidence, that claim a graph expansion is low-pressure to dodge the warning gate, or that rely on substring verifier-kind accidents. The first live state notification layer also exists: accepted writes publish updated typed state plus event-level revision and typed changed fields; rejected promotions stay quiet. -In natural language: current Epiphany can preserve a map, show a map, project a compact scene from the map, project derived job/progress slots from the map and retrieval summary, project a read-only freshness lens over retrieval and graph staleness, project a targeted graph/evidence context shard from the map, project context pressure from token telemetry, bank an in-flight investigation packet in durable typed state, reflect that packet back into prompt/scene/context, retrieve evidence for the map, draft one explicit observation/evidence patch, derive a bounded graph/frontier/churn candidate from verified evidence-backed observations with code refs, choose a small coherent observation set when the caller does not provide one, favor the strongest selected observation when drafting that candidate, reject or accept a verified candidate, apply explicit map/evidence/churn edits, notify clients after successful writes, and sanity-check those edits before they hit the ledger. It still does not invalidate stale graph regions, execute long-running Epiphany jobs as tracked background work, emit job progress notifications, or coordinate specialist ownership. Teeth are installed; chewing is supervised; the first bell rings; the first wall board exists; the first shift board exists; the first freshness clipboard exists; the first binder lens exists; the first pressure gauge exists; the first ember packet exists; the next slices are about safe waking and senses, not jawbones. +In natural language: current Epiphany can preserve a map, show a map, project a compact scene from the map, project derived job/progress slots from the map and retrieval summary, project a read-only freshness lens over retrieval and graph staleness, layer watcher-backed invalidation telemetry onto that lens for loaded threads, project a targeted graph/evidence context shard from the map, project context pressure from token telemetry, bank an in-flight investigation packet in durable typed state, reflect that packet back into prompt/scene/context, retrieve evidence for the map, draft one explicit observation/evidence patch, derive a bounded graph/frontier/churn candidate from verified evidence-backed observations with code refs, choose a small coherent observation set when the caller does not provide one, favor the strongest selected observation when drafting that candidate, reject or accept a verified candidate, apply explicit map/evidence/churn edits, notify clients after successful writes, and sanity-check those edits before they hit the ledger. It still does not invalidate stale graph regions automatically, execute long-running Epiphany jobs as tracked background work, emit job progress notifications, or coordinate specialist ownership. Teeth are installed; chewing is supervised; the first bell rings; the first wall board exists; the first shift board exists; the first freshness clipboard exists; the first door sensor now exists too; the first binder lens exists; the first pressure gauge exists; the first ember packet exists; the next slices are about safe waking and coordination, not jawbones. ## Verification Hooks @@ -1294,6 +1294,7 @@ Current tests cover the landed flows at useful seams: - live app-server smoke for the first Phase 6 scene boundary in `tools/epiphany_phase6_scene_smoke.py`. - live app-server smoke for the first Phase 6 jobs boundary in `tools/epiphany_phase6_jobs_smoke.py`. - live app-server smoke for the first Phase 6 freshness boundary in `tools/epiphany_phase6_freshness_smoke.py`. +- live app-server smoke for watcher-backed invalidation inputs inside the Phase 6 freshness boundary in `tools/epiphany_phase6_invalidation_smoke.py`. - live app-server smoke for the first Phase 6 context boundary in `tools/epiphany_phase6_context_smoke.py`. - live app-server smoke for the first Phase 6 pressure boundary in `tools/epiphany_phase6_pressure_smoke.py`. @@ -1308,6 +1309,7 @@ $env:CARGO_TARGET_DIR='C:\Users\Meta\.cargo-target-codex'; cargo test -p codex-a & 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\epiphany_phase6_scene_smoke.py' & 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\epiphany_phase6_jobs_smoke.py' & 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\epiphany_phase6_freshness_smoke.py' +& 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\epiphany_phase6_invalidation_smoke.py' & 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\epiphany_phase6_context_smoke.py' & 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\epiphany_phase6_pressure_smoke.py' ``` diff --git a/notes/epiphany-fork-implementation-plan.md b/notes/epiphany-fork-implementation-plan.md index a9144834..e3c6e972 100644 --- a/notes/epiphany-fork-implementation-plan.md +++ b/notes/epiphany-fork-implementation-plan.md @@ -59,8 +59,9 @@ The landed machine now has: - live Phase 6 scene app-server smoke coverage in `tools/epiphany_phase6_scene_smoke.py` - read-only Phase 6 job/progress reflection through `thread/epiphany/jobs` - live Phase 6 jobs app-server smoke coverage in `tools/epiphany_phase6_jobs_smoke.py` -- read-only Phase 6 retrieval/graph freshness reflection through `thread/epiphany/freshness` +- read-only Phase 6 retrieval/graph freshness reflection plus watcher-backed invalidation inputs through `thread/epiphany/freshness` - live Phase 6 freshness app-server smoke coverage in `tools/epiphany_phase6_freshness_smoke.py` +- live Phase 6 watcher-backed invalidation smoke coverage in `tools/epiphany_phase6_invalidation_smoke.py` - read-only Phase 6 targeted state-shard reflection through `thread/epiphany/context` - live Phase 6 context app-server smoke coverage in `tools/epiphany_phase6_context_smoke.py` - read-only Phase 6 context-pressure reflection through `thread/epiphany/pressure` @@ -80,7 +81,7 @@ These boundaries are more important than the individual method names: - `thread/epiphany/index` may update the semantic retrieval catalog, but it is not a hidden Epiphany-state writer. - `thread/epiphany/scene` is a client reflection, not a second source of truth. - `thread/epiphany/jobs` is a derived reflection, not a scheduler or durable job store. -- `thread/epiphany/freshness` is a derived reflection, not watcher-driven invalidation, a mutation gate, or a hidden refresh scheduler. +- `thread/epiphany/freshness` is a derived reflection, not automatic watcher-driven invalidation, a mutation gate, or a hidden refresh scheduler. - `thread/epiphany/context` is a targeted reflection, not a state writer or hidden proposal engine. - `thread/epiphany/pressure` is a context-pressure reflection, not an automatic compactor, scheduler, or CRRC coordinator. - The GUI may render and steer typed state, but it must not manufacture canonical understanding. @@ -120,9 +121,9 @@ and notify typed state. It can. The next unknowns are: - how to expose live long-running job progress without making the GUI authoritative -- when watcher-driven invalidation becomes necessary instead of merely tempting +- how the landed watcher-backed invalidation telemetry should be consumed without turning freshness into a secret worker - how the landed investigation checkpoint should be consumed by future automatic CRRC without becoming ceremony machinery -- how much automatic CRRC coordination belongs in runtime once pressure and checkpoint signals both exist +- how much automatic CRRC coordination belongs in runtime once pressure, freshness, watcher, and checkpoint signals all exist - what Phase 6 should prove before specialist scheduling begins ## Phase 6 Direction @@ -131,7 +132,7 @@ Phase 6 should grow observable harness state outward from the typed spine. Useful candidates: -1. Add live watcher-backed invalidation inputs on top of the landed freshness lens when stale graph or retrieval state needs earlier warning. +1. Add a bounded CRRC coordination/policy slice that consumes pressure, freshness, watcher telemetry, and the durable investigation checkpoint to decide whether a wakeup should resume or re-gather. 2. Add live job progress notifications only after there is a real long-running owner to report. 3. Add targeted scene/jobs/context/pressure/checkpoint fields only when a client or smoke exposes a real gap. @@ -182,6 +183,12 @@ Before modifying freshness reflection behavior, run: & 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\epiphany_phase6_freshness_smoke.py' ``` +Before modifying watcher-backed invalidation behavior inside freshness reflection, run: + +```powershell +& 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\epiphany_phase6_invalidation_smoke.py' +``` + Before modifying targeted context-shard behavior, run: ```powershell diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index ddd80cda..1eab0e5c 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -28,7 +28,7 @@ Do not trust this file for the exact live HEAD. Always check git. - Do not copy exact branch or HEAD from this note. Run `git status --short --branch` and `git log --oneline -5`. - Phase 1 through Phase 5 are complete enough. -- Phase 6 has read-only `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/freshness`, `thread/epiphany/context`, and `thread/epiphany/pressure`; all five have live app-server smoke coverage. +- Phase 6 has read-only `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/freshness`, `thread/epiphany/context`, and `thread/epiphany/pressure`; freshness now also carries watcher-backed invalidation inputs for loaded threads, and the reflection seam has live app-server smoke coverage. - Durable in-flight investigation checkpointing is now landed in authoritative typed state, writable through `thread/epiphany/update` or accepted `thread/epiphany/promote`, rendered into the prompt, and reflected through scene/context. - The repo is an Epiphany fork of Codex, not a Codex preset. - `vendor/codex` is tracked directly, not a submodule. @@ -64,13 +64,14 @@ The current spine: - successful-write notification through `thread/epiphany/stateUpdated` - read-only compact reflection through `thread/epiphany/scene` - read-only job/progress reflection through `thread/epiphany/jobs` -- read-only retrieval/graph freshness reflection through `thread/epiphany/freshness` +- read-only retrieval/graph freshness reflection plus watcher-backed invalidation inputs through `thread/epiphany/freshness` - read-only targeted state-shard reflection through `thread/epiphany/context` - read-only context-pressure reflection through `thread/epiphany/pressure` - durable investigation checkpoint packet through typed state, prompt, scene, and context - live scene app-server smoke through `tools/epiphany_phase6_scene_smoke.py` - live jobs app-server smoke through `tools/epiphany_phase6_jobs_smoke.py` - live freshness app-server smoke through `tools/epiphany_phase6_freshness_smoke.py` +- live watcher-backed invalidation smoke through `tools/epiphany_phase6_invalidation_smoke.py` - live context app-server smoke through `tools/epiphany_phase6_context_smoke.py` - live pressure app-server smoke through `tools/epiphany_phase6_pressure_smoke.py` @@ -118,6 +119,12 @@ For freshness reflection behavior changes, run: & 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\epiphany_phase6_freshness_smoke.py' ``` +For watcher-backed invalidation behavior inside freshness reflection, run: + +```powershell +& 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\epiphany_phase6_invalidation_smoke.py' +``` + For targeted context-shard behavior changes, run: ```powershell @@ -170,9 +177,11 @@ Do not continue implementation automatically from a rehydrate-only request. The Phase 6 freshness slice is landed. It exposes read-only `thread/epiphany/freshness` from live retrieval summaries plus graph -frontier/churn state. It reports exact dirty-path pressure and revision/source -identity, but it does not mutate state, schedule refresh work, or pretend -watcher-driven invalidation already exists. +frontier/churn state and, for loaded threads, watcher-backed invalidation +inputs. It reports exact dirty-path pressure, watcher-observed changed paths, +mapped graph/frontier hits, and revision/source identity, but it does not +mutate state, schedule refresh work, or perform automatic semantic +invalidation. The Phase 6 context-pressure slice is also landed. It exposes read-only `thread/epiphany/pressure` from real token telemetry and the recorded @@ -185,9 +194,10 @@ evidence, and reflects the packet into prompt/scene/context so post-compaction wakeups can tell whether they have a real ember or only ash. When the user asks to continue, choose the next Phase 6 slice from the current -map: watcher-backed invalidation inputs are the most likely next organ now that -the read-only freshness lens exists, with live progress notifications waiting -until real job owners exist. +map: the most likely next organ is a bounded CRRC coordination/policy layer +that consumes the landed pressure, freshness, watcher, and investigation- +checkpoint signals to decide whether a checkpoint means resume or re-gather, +with live progress notifications still waiting until real job owners exist. Also keep the guardrail in mind: pressure plus a checkpoint packet is still not automatic CRRC. Runtime coordination, reorientation policy, and next-action @@ -199,7 +209,7 @@ Live `thread/epiphany/scene`, `thread/epiphany/jobs`, ## Not Yet -- watcher-driven semantic invalidation +- automatic watcher-driven semantic invalidation - automatic observation promotion - specialist-agent scheduling - GUI-as-source-of-truth diff --git a/state/evidence.jsonl b/state/evidence.jsonl index c61ba0d2..e80c8e69 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -40,3 +40,4 @@ {"ts": "2026-04-26T04:43:01+00:00", "type": "verification", "status": "ok", "note": "Phase 6 freshness reflection landed as read-only thread/epiphany/freshness. The surface reports live-or-stored retrieval freshness plus graph frontier/churn staleness with exact dirty-path pressure, revision/source identity, and no mutation, scheduling, or watcher-driven invalidation. Focused app-server protocol/app-server tests plus tools/epiphany_phase6_freshness_smoke.py and tools/epiphany_phase6_scene_smoke.py passed."} {"ts": "2026-04-26T05:49:55+00:00", "type": "design", "status": "accepted", "note": "Capability creates obligation for Epiphany: grow cognition ahead of authority, keep permissions explicit, make interruption sacred, stage capability release, and treat misuse plus paternal anti-human 'caretaker' outcomes as first-class design risks."} {"ts": "2026-04-26T06:01:53+00:00", "type": "design", "status": "accepted", "note": "Epiphany publication stance is built-in-the-open rather than indiscriminately FOSS: use FOSS where viable, source-available licensing where economics or capability risk demand it, and commercial terms where organizations extract real enterprise value."} +{"ts": "2026-04-26T06:51:04+00:00", "type": "verification", "status": "ok", "note": "Phase 6 watcher-backed invalidation inputs landed inside read-only thread/epiphany/freshness. Loaded threads now expose runtime watcher telemetry with watched root, changed paths, mapped graph-node/frontier hits, and no state mutation, scheduling, notification, or automatic semantic invalidation. Focused protocol/app-server tests, schema fixture regeneration/checks, app-server build, and tools/epiphany_phase6_invalidation_smoke.py plus tools/epiphany_phase6_freshness_smoke.py passed."} diff --git a/state/map.yaml b/state/map.yaml index e21c9953..462ef572 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -74,7 +74,7 @@ accepted_design: - The harness now has an in-flight investigation checkpoint packet in durable typed state; automatic CRRC still needs runtime logic that reads the packet and decides whether to resume or force reorientation and re-gathering after compaction. - GUI/client work must reflect or steer typed state rather than manufacture a second source of truth. - `thread/epiphany/jobs` is the first read-only job/progress reflection: it derives indexing, remap, verification, and specialist slots from existing state; it is not a scheduler or durable job store. - - `thread/epiphany/freshness` is the first read-only freshness surface: it derives retrieval and graph staleness from live retrieval summaries plus frontier/churn state without mutating, scheduling, or pretending watcher-driven invalidation already exists. + - `thread/epiphany/freshness` is the first read-only freshness surface: it derives retrieval and graph staleness from live retrieval summaries plus frontier/churn state, and for loaded threads reflects watcher-backed invalidation inputs with changed paths plus mapped graph/frontier hits, without mutating, scheduling, or performing automatic semantic invalidation. - `thread/epiphany/context` is the first targeted read-only state-shard surface: it selects graph nodes/edges, active frontier, checkpoint, observations, and evidence from authoritative typed state without mutating or notifying. - `thread/epiphany/pressure` is the first read-only context-pressure surface: it derives pressure from token telemetry and auto-compact/context limits without compacting, scheduling, mutating, or notifying. - Epiphany safety doctrine is to separate cognition from actuation, keep authority explicit, make interruption sacred, stage capability release, and treat operator misuse as a normal design input rather than a rude hypothetical. @@ -94,12 +94,13 @@ landed_baseline: - Phase 3: typed client read surface through `Thread.epiphanyState`. - Phase 4: hybrid retrieval plus explicit Qdrant indexing with BM25 fallback. - Phase 5: typed update/distill/propose/promote/read/notification control plane. - - Phase 6: read-only scene, job/progress, freshness, targeted context, and context-pressure reflection surfaces, plus durable investigation checkpointing for compaction-safe planning. + - Phase 6: read-only scene, job/progress, freshness with watcher-backed invalidation inputs, targeted context, and context-pressure reflection surfaces, plus durable investigation checkpointing for compaction-safe planning. guardrails: - Use `tools/epiphany_phase5_smoke.py` before modifying update, distill, propose, promote, read-projection, or notification behavior. - Use `tools/epiphany_phase6_scene_smoke.py` before modifying scene projection behavior. - Use `tools/epiphany_phase6_jobs_smoke.py` before modifying job/progress reflection behavior. - Use `tools/epiphany_phase6_freshness_smoke.py` before modifying freshness reflection behavior. + - Use `tools/epiphany_phase6_invalidation_smoke.py` before modifying watcher-backed invalidation behavior inside freshness reflection. - Use `tools/epiphany_phase6_context_smoke.py` before modifying targeted context-shard behavior. - Use `tools/epiphany_phase6_pressure_smoke.py` before modifying context-pressure reflection behavior. - Use focused scene/jobs/context/pressure/protocol/schema checks for reflection-only changes. @@ -120,5 +121,5 @@ rejected_paths: reason: persistent state that accumulates every local truth becomes memory cruft and weakens rehydration current_status: - summary: Phase 1 durable state, Phase 2 prompt integration, Phase 3 typed read, Phase 4 retrieval/indexing, Phase 5 update/distill/propose/promote/read/notification, and Phase 6 read-only scene, jobs, freshness, targeted context, context-pressure reflection, and durable investigation checkpointing are landed. The new freshness lens reports live-or-stored retrieval staleness plus graph frontier/churn freshness with exact dirty-path pressure and revision/source identity, without mutating state or pretending watcher-driven invalidation already exists; focused protocol/app-server checks plus freshness and scene smokes cover the seam. Investigation checkpoints still live in authoritative typed state, flow through the prompt, and reflect through scene/context without adding a second writer or fake scheduler. Persistent state was cut down so map, handoff, plan, harness surfaces, scratch, and evidence each carry one job instead of becoming a Jenga tower; evidence is now a distilled ledger rather than an activity feed. The global AGENTS Epiphany core command says persistent state is the agent's mind and explicitly permits the agent to ask for self-modification when memory or workflow makes it worse. The continuity goal is to bank the fire before the dark so compaction becomes less like death and more like sleep. - next_action: After compaction, rehydrate and reorient. If the user asks to continue, choose the next Phase 6 slice from the current map: watcher-backed invalidation inputs are the most likely next organ now that the read-only freshness lens exists, with live progress notifications still waiting until real job owners exist. Do not restart the completed pressure, freshness, or investigation-checkpoint slices unless a concrete regression appears. + summary: Phase 1 durable state, Phase 2 prompt integration, Phase 3 typed read, Phase 4 retrieval/indexing, Phase 5 update/distill/propose/promote/read/notification, and Phase 6 read-only scene, jobs, freshness, targeted context, context-pressure reflection, watcher-backed invalidation inputs, and durable investigation checkpointing are landed. `thread/epiphany/freshness` now reports live-or-stored retrieval staleness plus graph frontier/churn freshness and, for loaded threads, runtime watcher telemetry with exact changed paths, mapped graph-node hits, frontier hits, and revision/source identity; it still does not mutate state, schedule refresh work, or perform automatic semantic invalidation. Focused protocol/app-server checks plus freshness and invalidation smokes cover the seam. Investigation checkpoints still live in authoritative typed state, flow through the prompt, and reflect through scene/context without adding a second writer or fake scheduler. Persistent state was cut down so map, handoff, plan, harness surfaces, scratch, and evidence each carry one job instead of becoming a Jenga tower; evidence is now a distilled ledger rather than an activity feed. The global AGENTS Epiphany core command says persistent state is the agent's mind and explicitly permits the agent to ask for self-modification when memory or workflow makes it worse. The continuity goal is to bank the fire before the dark so compaction becomes less like death and more like sleep. + next_action: After compaction, rehydrate and reorient. If the user asks to continue, the next likely Phase 6 organ is a bounded CRRC coordination/policy slice that consumes the landed pressure, freshness, watcher, and investigation-checkpoint signals to decide whether a checkpoint means resume or re-gather, with live progress notifications still waiting until real job owners exist. Do not restart the completed pressure, freshness, watcher-invalidation, or investigation-checkpoint slices unless a concrete regression appears. diff --git a/tools/epiphany_phase6_invalidation_smoke.py b/tools/epiphany_phase6_invalidation_smoke.py new file mode 100644 index 00000000..1d68d4cb --- /dev/null +++ b/tools/epiphany_phase6_invalidation_smoke.py @@ -0,0 +1,270 @@ +from __future__ import annotations + +import argparse +import json +import shutil +import time +from pathlib import Path +from typing import Any + +from epiphany_phase5_smoke import AppServerClient +from epiphany_phase5_smoke import DEFAULT_APP_SERVER +from epiphany_phase5_smoke import ROOT +from epiphany_phase5_smoke import require +from epiphany_phase5_smoke import reset_smoke_paths + + +DEFAULT_CODEX_HOME = ROOT / ".epiphany-smoke" / "phase6-invalidation-codex-home" +DEFAULT_WORKSPACE = ROOT / ".epiphany-smoke" / "phase6-invalidation-workspace" +DEFAULT_RESULT = ROOT / ".epiphany-smoke" / "phase6-invalidation-smoke-result.json" +DEFAULT_TRANSCRIPT = ROOT / ".epiphany-smoke" / "phase6-invalidation-smoke-transcript.jsonl" +DEFAULT_STDERR = ROOT / ".epiphany-smoke" / "phase6-invalidation-smoke-server.stderr.log" + +WATCHED_RELATIVE_PATH = Path("src") / "watcher_target.rs" + + +def prepare_workspace(workspace: Path) -> Path: + if workspace.exists(): + shutil.rmtree(workspace) + watched_file = workspace / WATCHED_RELATIVE_PATH + watched_file.parent.mkdir(parents=True, exist_ok=True) + watched_file.write_text( + "pub fn watcher_target() -> &'static str {\n \"before\"\n}\n", + encoding="utf-8", + ) + return watched_file + + +def invalidation_patch() -> dict[str, Any]: + return { + "objective": "Expose watcher-backed invalidation inputs through the freshness lens without mutating Epiphany state.", + "activeSubgoalId": "phase6-invalidation-smoke", + "subgoals": [ + { + "id": "phase6-invalidation-smoke", + "title": "Watch live file changes", + "status": "active", + "summary": "Freshness should reflect watcher input as a read-only invalidation hint.", + } + ], + "graphs": { + "architecture": { + "nodes": [ + { + "id": "watcher-target", + "title": "Watcher target", + "purpose": "Expose a mapped file that the workspace watcher can touch.", + "code_refs": [ + { + "path": WATCHED_RELATIVE_PATH.as_posix(), + "start_line": 1, + "end_line": 3, + "symbol": "watcher_target", + } + ], + } + ] + }, + "dataflow": {"nodes": []}, + "links": [], + }, + "graphFrontier": { + "active_node_ids": ["watcher-target"], + "dirty_paths": [], + }, + "graphCheckpoint": { + "checkpoint_id": "ck-invalidation-1", + "graph_revision": 1, + "summary": "Watcher invalidation smoke checkpoint", + "frontier_node_ids": ["watcher-target"], + }, + "churn": { + "understanding_status": "ready", + "diff_pressure": "low", + "graph_freshness": "fresh", + "unexplained_writes": 0, + }, + } + + +def assert_clean_watcher(response: dict[str, Any]) -> None: + watcher = response["watcher"] + require(response["source"] == "live", "invalidation freshness should report live source") + require(watcher["status"] == "clean", "watcher should start clean before file changes") + require( + watcher["watchedRoot"].endswith("phase6-invalidation-workspace"), + "watcher should report the live workspace root", + ) + require(watcher["changedPathCount"] == 0, "clean watcher should not report changed paths") + + +def wait_for_changed_watcher( + client: AppServerClient, + thread_id: str, + *, + timeout: float = 10.0, +) -> dict[str, Any]: + deadline = time.time() + timeout + last_response: dict[str, Any] | None = None + while time.time() < deadline: + response = client.send("thread/epiphany/freshness", {"threadId": thread_id}) + assert response is not None + last_response = response + if response["watcher"]["status"] == "changed": + return response + time.sleep(0.2) + raise AssertionError( + f"watcher-backed invalidation did not surface before timeout; last response: {last_response!r}" + ) + + +def assert_changed_watcher(response: dict[str, Any]) -> None: + watcher = response["watcher"] + normalized_changed_paths = [path.replace("\\", "/") for path in watcher["changedPaths"]] + require(watcher["status"] == "changed", "watcher should report changed after file write") + require(watcher["changedPathCount"] >= 1, "changed watcher should report path count") + require( + WATCHED_RELATIVE_PATH.as_posix() in normalized_changed_paths, + "changed watcher should report the touched relative path", + ) + require( + watcher["graphNodeIds"] == ["watcher-target"], + "changed watcher should identify the mapped graph node", + ) + require( + watcher["activeFrontierNodeIds"] == ["watcher-target"], + "changed watcher should identify active frontier nodes touched by the change", + ) + require( + isinstance(watcher.get("observedAtUnixSeconds"), int), + "changed watcher should record when the change was observed", + ) + + +def run_smoke(args: argparse.Namespace) -> dict[str, Any]: + app_server = args.app_server.resolve() + if not app_server.exists(): + raise FileNotFoundError(f"codex app-server binary not found: {app_server}") + + codex_home = args.codex_home.resolve() + workspace = args.workspace.resolve() + watched_file = prepare_workspace(workspace) + result_path = args.result.resolve() + transcript_path = args.transcript.resolve() + stderr_path = args.stderr.resolve() + reset_smoke_paths(codex_home, result_path, transcript_path, stderr_path) + + with AppServerClient(app_server, codex_home, transcript_path, stderr_path) as client: + client.send( + "initialize", + { + "clientInfo": { + "name": "epiphany-phase6-invalidation-smoke", + "title": "Epiphany Phase 6 Invalidation Smoke", + "version": "0.1.0", + }, + "capabilities": {"experimentalApi": True}, + }, + ) + client.send("initialized", expect_response=False) + started = client.send("thread/start", {"cwd": str(workspace), "ephemeral": True}) + assert started is not None + thread_id = started["thread"]["id"] + + clean_notification_start = len(client.notifications) + clean_response = client.send("thread/epiphany/freshness", {"threadId": thread_id}) + assert clean_response is not None + assert_clean_watcher(clean_response) + client.require_no_notification( + "thread/epiphany/stateUpdated", + start_index=clean_notification_start, + ) + + update_notification_start = len(client.notifications) + update = client.send( + "thread/epiphany/update", + {"threadId": thread_id, "expectedRevision": 0, "patch": invalidation_patch()}, + ) + assert update is not None + require(update["revision"] == 1, "invalidation smoke patch should advance revision to 1") + client.wait_for_notification( + "thread/epiphany/stateUpdated", + start_index=update_notification_start, + ) + + ready_notification_start = len(client.notifications) + ready_response = client.send("thread/epiphany/freshness", {"threadId": thread_id}) + assert ready_response is not None + assert_clean_watcher(ready_response) + require( + ready_response["stateRevision"] == 1, + "watcher-backed freshness should preserve state revision identity", + ) + client.require_no_notification( + "thread/epiphany/stateUpdated", + start_index=ready_notification_start, + ) + + watched_file.write_text( + "pub fn watcher_target() -> &'static str {\n \"after\"\n}\n", + encoding="utf-8", + ) + + changed_notification_start = len(client.notifications) + changed_response = wait_for_changed_watcher(client, thread_id) + assert_changed_watcher(changed_response) + client.require_no_notification( + "thread/epiphany/stateUpdated", + start_index=changed_notification_start, + ) + + final_read = client.send("thread/read", {"threadId": thread_id, "includeTurns": False}) + assert final_read is not None + require( + final_read["thread"]["epiphanyState"]["revision"] == 1, + "watcher-backed freshness should not mutate durable state", + ) + + result = { + "threadId": thread_id, + "codexHome": str(codex_home), + "workspace": str(workspace), + "initialWatcherStatus": clean_response["watcher"]["status"], + "readyWatcherStatus": ready_response["watcher"]["status"], + "changedWatcherStatus": changed_response["watcher"]["status"], + "changedPaths": changed_response["watcher"]["changedPaths"], + "graphNodeIds": changed_response["watcher"]["graphNodeIds"], + "activeFrontierNodeIds": changed_response["watcher"]["activeFrontierNodeIds"], + "changedWatcherNotificationCount": client.count_notifications( + "thread/epiphany/stateUpdated", + start_index=changed_notification_start, + ), + "finalReadRevision": final_read["thread"]["epiphanyState"]["revision"], + } + + result_path.parent.mkdir(parents=True, exist_ok=True) + result_path.write_text( + json.dumps(result, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + return result + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Live-smoke watcher-backed Epiphany invalidation inputs." + ) + parser.add_argument("--app-server", type=Path, default=DEFAULT_APP_SERVER) + parser.add_argument("--codex-home", type=Path, default=DEFAULT_CODEX_HOME) + parser.add_argument("--workspace", type=Path, default=DEFAULT_WORKSPACE) + parser.add_argument("--result", type=Path, default=DEFAULT_RESULT) + parser.add_argument("--transcript", type=Path, default=DEFAULT_TRANSCRIPT) + parser.add_argument("--stderr", type=Path, default=DEFAULT_STDERR) + args = parser.parse_args() + result = run_smoke(args) + print(json.dumps(result, indent=2, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 484fa252..8db277bb 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -1093,6 +1093,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -3773,6 +3839,16 @@ "null" ] }, + "investigationCheckpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "mode": { "anyOf": [ { diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 53d51c77..4b680ac2 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -1353,6 +1353,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1627,6 +1693,16 @@ }, "type": "array" }, + "investigation_checkpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "last_updated_turn_id": { "type": [ "string", @@ -3722,6 +3798,7 @@ "graphFrontier", "graphCheckpoint", "scratch", + "investigationCheckpoint", "observations", "evidence", "churn", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index fe9c6366..31f0aa33 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -8202,6 +8202,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/v2/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/v2/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -8476,6 +8542,16 @@ }, "type": "array" }, + "investigation_checkpoint": { + "anyOf": [ + { + "$ref": "#/definitions/v2/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "last_updated_turn_id": { "type": [ "string", @@ -14842,6 +14918,16 @@ "graph": { "$ref": "#/definitions/v2/ThreadEpiphanyGraphContext" }, + "investigationCheckpoint": { + "anyOf": [ + { + "$ref": "#/definitions/v2/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "observations": { "items": { "$ref": "#/definitions/v2/EpiphanyObservation" @@ -14897,6 +14983,13 @@ ], "type": "string" }, + "ThreadEpiphanyFreshnessSource": { + "enum": [ + "stored", + "live" + ], + "type": "string" + }, "ThreadEpiphanyGraphContext": { "properties": { "architectureEdges": { @@ -14932,6 +15025,125 @@ }, "type": "object" }, + "ThreadEpiphanyGraphFreshness": { + "properties": { + "checkpointId": { + "type": [ + "string", + "null" + ] + }, + "dirtyPathCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "dirtyPaths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "graphFreshness": { + "type": [ + "string", + "null" + ] + }, + "note": { + "type": "string" + }, + "openGapCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "openQuestionCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "status": { + "$ref": "#/definitions/v2/ThreadEpiphanyGraphFreshnessStatus" + } + }, + "required": [ + "dirtyPathCount", + "note", + "openGapCount", + "openQuestionCount", + "status" + ], + "type": "object" + }, + "ThreadEpiphanyGraphFreshnessStatus": { + "enum": [ + "missing", + "ready", + "stale" + ], + "type": "string" + }, + "ThreadEpiphanyInvalidationInput": { + "properties": { + "activeFrontierNodeIds": { + "items": { + "type": "string" + }, + "type": "array" + }, + "changedPathCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "changedPaths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "graphNodeIds": { + "items": { + "type": "string" + }, + "type": "array" + }, + "note": { + "type": "string" + }, + "observedAtUnixSeconds": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "status": { + "$ref": "#/definitions/v2/ThreadEpiphanyInvalidationStatus" + }, + "watchedRoot": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "changedPathCount", + "note", + "status" + ], + "type": "object" + }, + "ThreadEpiphanyInvalidationStatus": { + "enum": [ + "unavailable", + "clean", + "changed" + ], + "type": "string" + }, "ThreadEpiphanyJob": { "properties": { "blockingReason": { @@ -15127,6 +15339,66 @@ ], "type": "string" }, + "ThreadEpiphanyRetrievalFreshness": { + "properties": { + "dirtyPaths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "indexedChunkCount": { + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "indexedFileCount": { + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "lastIndexedAtUnixSeconds": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "note": { + "type": "string" + }, + "semanticAvailable": { + "type": [ + "boolean", + "null" + ] + }, + "status": { + "$ref": "#/definitions/v2/ThreadEpiphanyRetrievalFreshnessStatus" + } + }, + "required": [ + "note", + "status" + ], + "type": "object" + }, + "ThreadEpiphanyRetrievalFreshnessStatus": { + "enum": [ + "missing", + "ready", + "stale", + "indexing", + "unavailable" + ], + "type": "string" + }, "ThreadEpiphanyRetrieveIndexSummary": { "properties": { "dirtyPaths": { @@ -15322,6 +15594,16 @@ }, "type": "array" }, + "investigationCheckpoint": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadEpiphanySceneInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "objective": { "type": [ "string", @@ -15378,6 +15660,7 @@ "distill", "context", "jobs", + "freshness", "pressure", "propose", "promote", @@ -15499,6 +15782,65 @@ ], "type": "object" }, + "ThreadEpiphanySceneInvestigationCheckpoint": { + "properties": { + "capturedAtTurnId": { + "type": [ + "string", + "null" + ] + }, + "checkpointId": { + "type": "string" + }, + "codeRefCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "disposition": { + "$ref": "#/definitions/v2/EpiphanyInvestigationDisposition" + }, + "evidenceCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "nextAction": { + "type": [ + "string", + "null" + ] + }, + "openQuestionCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpointId", + "codeRefCount", + "disposition", + "evidenceCount", + "focus", + "kind", + "openQuestionCount" + ], + "type": "object" + }, "ThreadEpiphanySceneRecord": { "properties": { "codeRefCount": { @@ -15670,6 +16012,7 @@ "graphFrontier", "graphCheckpoint", "scratch", + "investigationCheckpoint", "observations", "evidence", "churn", @@ -15781,6 +16124,16 @@ "null" ] }, + "investigationCheckpoint": { + "anyOf": [ + { + "$ref": "#/definitions/v2/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "mode": { "anyOf": [ { diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index d8778147..97ddaeb1 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -4762,6 +4762,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -5036,6 +5102,16 @@ }, "type": "array" }, + "investigation_checkpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "last_updated_turn_id": { "type": [ "string", @@ -12729,6 +12805,16 @@ "graph": { "$ref": "#/definitions/ThreadEpiphanyGraphContext" }, + "investigationCheckpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "observations": { "items": { "$ref": "#/definitions/EpiphanyObservation" @@ -12784,6 +12870,13 @@ ], "type": "string" }, + "ThreadEpiphanyFreshnessSource": { + "enum": [ + "stored", + "live" + ], + "type": "string" + }, "ThreadEpiphanyGraphContext": { "properties": { "architectureEdges": { @@ -12819,6 +12912,125 @@ }, "type": "object" }, + "ThreadEpiphanyGraphFreshness": { + "properties": { + "checkpointId": { + "type": [ + "string", + "null" + ] + }, + "dirtyPathCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "dirtyPaths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "graphFreshness": { + "type": [ + "string", + "null" + ] + }, + "note": { + "type": "string" + }, + "openGapCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "openQuestionCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "status": { + "$ref": "#/definitions/ThreadEpiphanyGraphFreshnessStatus" + } + }, + "required": [ + "dirtyPathCount", + "note", + "openGapCount", + "openQuestionCount", + "status" + ], + "type": "object" + }, + "ThreadEpiphanyGraphFreshnessStatus": { + "enum": [ + "missing", + "ready", + "stale" + ], + "type": "string" + }, + "ThreadEpiphanyInvalidationInput": { + "properties": { + "activeFrontierNodeIds": { + "items": { + "type": "string" + }, + "type": "array" + }, + "changedPathCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "changedPaths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "graphNodeIds": { + "items": { + "type": "string" + }, + "type": "array" + }, + "note": { + "type": "string" + }, + "observedAtUnixSeconds": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "status": { + "$ref": "#/definitions/ThreadEpiphanyInvalidationStatus" + }, + "watchedRoot": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "changedPathCount", + "note", + "status" + ], + "type": "object" + }, + "ThreadEpiphanyInvalidationStatus": { + "enum": [ + "unavailable", + "clean", + "changed" + ], + "type": "string" + }, "ThreadEpiphanyJob": { "properties": { "blockingReason": { @@ -13014,6 +13226,66 @@ ], "type": "string" }, + "ThreadEpiphanyRetrievalFreshness": { + "properties": { + "dirtyPaths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "indexedChunkCount": { + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "indexedFileCount": { + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "lastIndexedAtUnixSeconds": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "note": { + "type": "string" + }, + "semanticAvailable": { + "type": [ + "boolean", + "null" + ] + }, + "status": { + "$ref": "#/definitions/ThreadEpiphanyRetrievalFreshnessStatus" + } + }, + "required": [ + "note", + "status" + ], + "type": "object" + }, + "ThreadEpiphanyRetrievalFreshnessStatus": { + "enum": [ + "missing", + "ready", + "stale", + "indexing", + "unavailable" + ], + "type": "string" + }, "ThreadEpiphanyRetrieveIndexSummary": { "properties": { "dirtyPaths": { @@ -13209,6 +13481,16 @@ }, "type": "array" }, + "investigationCheckpoint": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadEpiphanySceneInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "objective": { "type": [ "string", @@ -13265,6 +13547,7 @@ "distill", "context", "jobs", + "freshness", "pressure", "propose", "promote", @@ -13386,6 +13669,65 @@ ], "type": "object" }, + "ThreadEpiphanySceneInvestigationCheckpoint": { + "properties": { + "capturedAtTurnId": { + "type": [ + "string", + "null" + ] + }, + "checkpointId": { + "type": "string" + }, + "codeRefCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidenceCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "nextAction": { + "type": [ + "string", + "null" + ] + }, + "openQuestionCount": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpointId", + "codeRefCount", + "disposition", + "evidenceCount", + "focus", + "kind", + "openQuestionCount" + ], + "type": "object" + }, "ThreadEpiphanySceneRecord": { "properties": { "codeRefCount": { @@ -13557,6 +13899,7 @@ "graphFrontier", "graphCheckpoint", "scratch", + "investigationCheckpoint", "observations", "evidence", "churn", @@ -13668,6 +14011,16 @@ "null" ] }, + "investigationCheckpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "mode": { "anyOf": [ { diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadEpiphanyStateUpdatedNotification.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadEpiphanyStateUpdatedNotification.json index 3d7efa0e..87bbf77d 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadEpiphanyStateUpdatedNotification.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadEpiphanyStateUpdatedNotification.json @@ -352,6 +352,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -626,6 +692,16 @@ }, "type": "array" }, + "investigation_checkpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "last_updated_turn_id": { "type": [ "string", @@ -715,6 +791,7 @@ "graphFrontier", "graphCheckpoint", "scratch", + "investigationCheckpoint", "observations", "evidence", "churn", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 613aab67..4f4682b7 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -800,6 +800,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1074,6 +1140,16 @@ }, "type": "array" }, + "investigation_checkpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index f7a6ed66..675c0c9e 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -741,6 +741,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1015,6 +1081,16 @@ }, "type": "array" }, + "investigation_checkpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json index 4915d0ce..89ba7d54 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -741,6 +741,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1015,6 +1081,16 @@ }, "type": "array" }, + "investigation_checkpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index d874ae14..d50894b3 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -741,6 +741,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1015,6 +1081,16 @@ }, "type": "array" }, + "investigation_checkpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index f845433f..fa989937 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -800,6 +800,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1074,6 +1140,16 @@ }, "type": "array" }, + "investigation_checkpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index 9fd9a993..3b0c102f 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -741,6 +741,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1015,6 +1081,16 @@ }, "type": "array" }, + "investigation_checkpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 6858a77c..1dc5664d 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -800,6 +800,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1074,6 +1140,16 @@ }, "type": "array" }, + "investigation_checkpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index cc67decb..b87c3a3a 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -741,6 +741,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1015,6 +1081,16 @@ }, "type": "array" }, + "investigation_checkpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index caeb73e4..40b11046 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -741,6 +741,72 @@ ], "type": "object" }, + "EpiphanyInvestigationCheckpoint": { + "properties": { + "captured_at_turn_id": { + "type": [ + "string", + "null" + ] + }, + "checkpoint_id": { + "type": "string" + }, + "code_refs": { + "items": { + "$ref": "#/definitions/EpiphanyCodeRef" + }, + "type": "array" + }, + "disposition": { + "$ref": "#/definitions/EpiphanyInvestigationDisposition" + }, + "evidence_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "focus": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "next_action": { + "type": [ + "string", + "null" + ] + }, + "open_questions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "checkpoint_id", + "disposition", + "focus", + "kind" + ], + "type": "object" + }, + "EpiphanyInvestigationDisposition": { + "enum": [ + "resume_ready", + "regather_required" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1015,6 +1081,16 @@ }, "type": "array" }, + "investigation_checkpoint": { + "anyOf": [ + { + "$ref": "#/definitions/EpiphanyInvestigationCheckpoint" + }, + { + "type": "null" + } + ] + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyInvestigationCheckpoint.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyInvestigationCheckpoint.ts new file mode 100644 index 00000000..c22360a9 --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyInvestigationCheckpoint.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { EpiphanyInvestigationDisposition } from "./EpiphanyInvestigationDisposition"; + +export type EpiphanyInvestigationCheckpoint = { checkpoint_id: string, kind: string, disposition: EpiphanyInvestigationDisposition, focus: string, summary?: string | null, next_action?: string | null, captured_at_turn_id?: string | null, open_questions?: Array, code_refs?: Array, evidence_ids?: Array, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyInvestigationDisposition.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyInvestigationDisposition.ts new file mode 100644 index 00000000..9c6d793b --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyInvestigationDisposition.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type EpiphanyInvestigationDisposition = "resume_ready" | "regather_required"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyThreadState.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyThreadState.ts index d5ef75b8..2acf0394 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyThreadState.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyThreadState.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type EpiphanyThreadState = { revision: bigint, objective?: string | null, active_subgoal_id?: string | null, subgoals?: Array, invariants?: Array, graphs?: EpiphanyGraphs, graph_frontier?: EpiphanyGraphFrontier | null, graph_checkpoint?: EpiphanyGraphCheckpoint | null, retrieval?: EpiphanyRetrievalState | null, scratch?: EpiphanyScratchPad | null, observations?: Array, recent_evidence?: Array, churn?: EpiphanyChurnState | null, mode?: EpiphanyModeState | null, last_updated_turn_id?: string | null, }; +export type EpiphanyThreadState = { revision: bigint, objective?: string | null, active_subgoal_id?: string | null, subgoals?: Array, invariants?: Array, graphs?: EpiphanyGraphs, graph_frontier?: EpiphanyGraphFrontier | null, graph_checkpoint?: EpiphanyGraphCheckpoint | null, retrieval?: EpiphanyRetrievalState | null, scratch?: EpiphanyScratchPad | null, investigation_checkpoint?: EpiphanyInvestigationCheckpoint | null, observations?: Array, recent_evidence?: Array, churn?: EpiphanyChurnState | null, mode?: EpiphanyModeState | null, last_updated_turn_id?: string | null, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/index.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/index.ts index 590db21b..53e9cabb 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -23,6 +23,8 @@ export type { EpiphanyGraphLink } from "./EpiphanyGraphLink"; export type { EpiphanyGraphNode } from "./EpiphanyGraphNode"; export type { EpiphanyGraphs } from "./EpiphanyGraphs"; export type { EpiphanyInvariant } from "./EpiphanyInvariant"; +export type { EpiphanyInvestigationCheckpoint } from "./EpiphanyInvestigationCheckpoint"; +export type { EpiphanyInvestigationDisposition } from "./EpiphanyInvestigationDisposition"; export type { EpiphanyModeState } from "./EpiphanyModeState"; export type { EpiphanyObservation } from "./EpiphanyObservation"; export type { EpiphanyRetrievalStatus } from "./EpiphanyRetrievalStatus"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyContext.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyContext.ts index 04da78b1..1dd87b2b 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyContext.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyContext.ts @@ -4,7 +4,8 @@ import type { EpiphanyEvidenceRecord } from "../EpiphanyEvidenceRecord"; import type { EpiphanyGraphCheckpoint } from "../EpiphanyGraphCheckpoint"; import type { EpiphanyGraphFrontier } from "../EpiphanyGraphFrontier"; +import type { EpiphanyInvestigationCheckpoint } from "../EpiphanyInvestigationCheckpoint"; import type { EpiphanyObservation } from "../EpiphanyObservation"; import type { ThreadEpiphanyGraphContext } from "./ThreadEpiphanyGraphContext"; -export type ThreadEpiphanyContext = { graph: ThreadEpiphanyGraphContext, frontier?: EpiphanyGraphFrontier | null, checkpoint?: EpiphanyGraphCheckpoint | null, observations?: Array, evidence?: Array, }; +export type ThreadEpiphanyContext = { graph: ThreadEpiphanyGraphContext, frontier?: EpiphanyGraphFrontier | null, checkpoint?: EpiphanyGraphCheckpoint | null, investigationCheckpoint?: EpiphanyInvestigationCheckpoint | null, observations?: Array, evidence?: Array, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyFreshnessSource.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyFreshnessSource.ts new file mode 100644 index 00000000..0a69a9f0 --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyFreshnessSource.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadEpiphanyFreshnessSource = "stored" | "live"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyGraphFreshness.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyGraphFreshness.ts new file mode 100644 index 00000000..45263f28 --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyGraphFreshness.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadEpiphanyGraphFreshnessStatus } from "./ThreadEpiphanyGraphFreshnessStatus"; + +export type ThreadEpiphanyGraphFreshness = { status: ThreadEpiphanyGraphFreshnessStatus, graphFreshness?: string | null, checkpointId?: string | null, dirtyPathCount: number, dirtyPaths?: Array, openQuestionCount: number, openGapCount: number, note: string, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyGraphFreshnessStatus.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyGraphFreshnessStatus.ts new file mode 100644 index 00000000..4c4fcf17 --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyGraphFreshnessStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadEpiphanyGraphFreshnessStatus = "missing" | "ready" | "stale"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyInvalidationInput.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyInvalidationInput.ts new file mode 100644 index 00000000..bdc061a7 --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyInvalidationInput.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadEpiphanyInvalidationStatus } from "./ThreadEpiphanyInvalidationStatus"; + +export type ThreadEpiphanyInvalidationInput = { status: ThreadEpiphanyInvalidationStatus, watchedRoot?: string | null, observedAtUnixSeconds?: bigint | null, changedPathCount: number, changedPaths?: Array, graphNodeIds?: Array, activeFrontierNodeIds?: Array, note: string, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyInvalidationStatus.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyInvalidationStatus.ts new file mode 100644 index 00000000..fb7c93a9 --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyInvalidationStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadEpiphanyInvalidationStatus = "unavailable" | "clean" | "changed"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyRetrievalFreshness.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyRetrievalFreshness.ts new file mode 100644 index 00000000..849e4c4e --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyRetrievalFreshness.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadEpiphanyRetrievalFreshnessStatus } from "./ThreadEpiphanyRetrievalFreshnessStatus"; + +export type ThreadEpiphanyRetrievalFreshness = { status: ThreadEpiphanyRetrievalFreshnessStatus, semanticAvailable?: boolean | null, lastIndexedAtUnixSeconds?: bigint | null, indexedFileCount?: number | null, indexedChunkCount?: number | null, dirtyPaths?: Array, note: string, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyRetrievalFreshnessStatus.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyRetrievalFreshnessStatus.ts new file mode 100644 index 00000000..cc18e5fb --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyRetrievalFreshnessStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadEpiphanyRetrievalFreshnessStatus = "missing" | "ready" | "stale" | "indexing" | "unavailable"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyScene.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyScene.ts index 8ec7039f..da59f728 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyScene.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyScene.ts @@ -4,6 +4,7 @@ import type { ThreadEpiphanySceneAction } from "./ThreadEpiphanySceneAction"; import type { ThreadEpiphanySceneChurn } from "./ThreadEpiphanySceneChurn"; import type { ThreadEpiphanySceneGraph } from "./ThreadEpiphanySceneGraph"; +import type { ThreadEpiphanySceneInvestigationCheckpoint } from "./ThreadEpiphanySceneInvestigationCheckpoint"; import type { ThreadEpiphanySceneRecords } from "./ThreadEpiphanySceneRecords"; import type { ThreadEpiphanySceneRetrieval } from "./ThreadEpiphanySceneRetrieval"; import type { ThreadEpiphanySceneSource } from "./ThreadEpiphanySceneSource"; @@ -11,4 +12,4 @@ import type { ThreadEpiphanySceneStateStatus } from "./ThreadEpiphanySceneStateS import type { ThreadEpiphanySceneStatusCount } from "./ThreadEpiphanySceneStatusCount"; import type { ThreadEpiphanySceneSubgoal } from "./ThreadEpiphanySceneSubgoal"; -export type ThreadEpiphanyScene = { stateStatus: ThreadEpiphanySceneStateStatus, source: ThreadEpiphanySceneSource, revision?: bigint | null, objective?: string | null, activeSubgoal?: ThreadEpiphanySceneSubgoal | null, subgoals?: Array, invariantStatusCounts?: Array, graph: ThreadEpiphanySceneGraph, retrieval?: ThreadEpiphanySceneRetrieval | null, observations: ThreadEpiphanySceneRecords, evidence: ThreadEpiphanySceneRecords, churn?: ThreadEpiphanySceneChurn | null, availableActions?: Array, }; +export type ThreadEpiphanyScene = { stateStatus: ThreadEpiphanySceneStateStatus, source: ThreadEpiphanySceneSource, revision?: bigint | null, objective?: string | null, activeSubgoal?: ThreadEpiphanySceneSubgoal | null, subgoals?: Array, invariantStatusCounts?: Array, graph: ThreadEpiphanySceneGraph, retrieval?: ThreadEpiphanySceneRetrieval | null, investigationCheckpoint?: ThreadEpiphanySceneInvestigationCheckpoint | null, observations: ThreadEpiphanySceneRecords, evidence: ThreadEpiphanySceneRecords, churn?: ThreadEpiphanySceneChurn | null, availableActions?: Array, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanySceneAction.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanySceneAction.ts index 73b07187..7ea225c5 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanySceneAction.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanySceneAction.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type ThreadEpiphanySceneAction = "index" | "retrieve" | "distill" | "context" | "jobs" | "pressure" | "propose" | "promote" | "update"; +export type ThreadEpiphanySceneAction = "index" | "retrieve" | "distill" | "context" | "jobs" | "freshness" | "pressure" | "propose" | "promote" | "update"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanySceneInvestigationCheckpoint.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanySceneInvestigationCheckpoint.ts new file mode 100644 index 00000000..0061f664 --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanySceneInvestigationCheckpoint.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { EpiphanyInvestigationDisposition } from "../EpiphanyInvestigationDisposition"; + +export type ThreadEpiphanySceneInvestigationCheckpoint = { checkpointId: string, kind: string, disposition: EpiphanyInvestigationDisposition, focus: string, summary?: string | null, nextAction?: string | null, capturedAtTurnId?: string | null, openQuestionCount: number, codeRefCount: number, evidenceCount: number, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyStateUpdatedField.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyStateUpdatedField.ts index dd2052ff..6ea139a1 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyStateUpdatedField.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyStateUpdatedField.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type ThreadEpiphanyStateUpdatedField = "objective" | "activeSubgoalId" | "subgoals" | "invariants" | "graphs" | "graphFrontier" | "graphCheckpoint" | "scratch" | "observations" | "evidence" | "churn" | "mode"; +export type ThreadEpiphanyStateUpdatedField = "objective" | "activeSubgoalId" | "subgoals" | "invariants" | "graphs" | "graphFrontier" | "graphCheckpoint" | "scratch" | "investigationCheckpoint" | "observations" | "evidence" | "churn" | "mode"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyUpdatePatch.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyUpdatePatch.ts index eedcd4eb..2c0c55b0 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyUpdatePatch.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyUpdatePatch.ts @@ -7,9 +7,10 @@ import type { EpiphanyGraphCheckpoint } from "../EpiphanyGraphCheckpoint"; import type { EpiphanyGraphFrontier } from "../EpiphanyGraphFrontier"; import type { EpiphanyGraphs } from "../EpiphanyGraphs"; import type { EpiphanyInvariant } from "../EpiphanyInvariant"; +import type { EpiphanyInvestigationCheckpoint } from "../EpiphanyInvestigationCheckpoint"; import type { EpiphanyModeState } from "../EpiphanyModeState"; import type { EpiphanyObservation } from "../EpiphanyObservation"; import type { EpiphanyScratchPad } from "../EpiphanyScratchPad"; import type { EpiphanySubgoal } from "../EpiphanySubgoal"; -export type ThreadEpiphanyUpdatePatch = { objective?: string | null, activeSubgoalId?: string | null, subgoals?: Array | null, invariants?: Array | null, graphs?: EpiphanyGraphs | null, graphFrontier?: EpiphanyGraphFrontier | null, graphCheckpoint?: EpiphanyGraphCheckpoint | null, scratch?: EpiphanyScratchPad | null, observations?: Array, evidence?: Array, churn?: EpiphanyChurnState | null, mode?: EpiphanyModeState | null, }; +export type ThreadEpiphanyUpdatePatch = { objective?: string | null, activeSubgoalId?: string | null, subgoals?: Array | null, invariants?: Array | null, graphs?: EpiphanyGraphs | null, graphFrontier?: EpiphanyGraphFrontier | null, graphCheckpoint?: EpiphanyGraphCheckpoint | null, scratch?: EpiphanyScratchPad | null, investigationCheckpoint?: EpiphanyInvestigationCheckpoint | null, observations?: Array, evidence?: Array, churn?: EpiphanyChurnState | null, mode?: EpiphanyModeState | null, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 38946370..eb27d64e 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -321,7 +321,12 @@ export type { ThreadEpiphanyContext } from "./ThreadEpiphanyContext"; export type { ThreadEpiphanyContextMissing } from "./ThreadEpiphanyContextMissing"; export type { ThreadEpiphanyContextSource } from "./ThreadEpiphanyContextSource"; export type { ThreadEpiphanyContextStateStatus } from "./ThreadEpiphanyContextStateStatus"; +export type { ThreadEpiphanyFreshnessSource } from "./ThreadEpiphanyFreshnessSource"; export type { ThreadEpiphanyGraphContext } from "./ThreadEpiphanyGraphContext"; +export type { ThreadEpiphanyGraphFreshness } from "./ThreadEpiphanyGraphFreshness"; +export type { ThreadEpiphanyGraphFreshnessStatus } from "./ThreadEpiphanyGraphFreshnessStatus"; +export type { ThreadEpiphanyInvalidationInput } from "./ThreadEpiphanyInvalidationInput"; +export type { ThreadEpiphanyInvalidationStatus } from "./ThreadEpiphanyInvalidationStatus"; export type { ThreadEpiphanyJob } from "./ThreadEpiphanyJob"; export type { ThreadEpiphanyJobKind } from "./ThreadEpiphanyJobKind"; export type { ThreadEpiphanyJobStatus } from "./ThreadEpiphanyJobStatus"; @@ -331,6 +336,8 @@ export type { ThreadEpiphanyPressureBasis } from "./ThreadEpiphanyPressureBasis" export type { ThreadEpiphanyPressureLevel } from "./ThreadEpiphanyPressureLevel"; export type { ThreadEpiphanyPressureSource } from "./ThreadEpiphanyPressureSource"; export type { ThreadEpiphanyPressureStatus } from "./ThreadEpiphanyPressureStatus"; +export type { ThreadEpiphanyRetrievalFreshness } from "./ThreadEpiphanyRetrievalFreshness"; +export type { ThreadEpiphanyRetrievalFreshnessStatus } from "./ThreadEpiphanyRetrievalFreshnessStatus"; export type { ThreadEpiphanyRetrieveIndexSummary } from "./ThreadEpiphanyRetrieveIndexSummary"; export type { ThreadEpiphanyRetrieveResult } from "./ThreadEpiphanyRetrieveResult"; export type { ThreadEpiphanyRetrieveResultKind } from "./ThreadEpiphanyRetrieveResultKind"; @@ -339,6 +346,7 @@ export type { ThreadEpiphanyScene } from "./ThreadEpiphanyScene"; export type { ThreadEpiphanySceneAction } from "./ThreadEpiphanySceneAction"; export type { ThreadEpiphanySceneChurn } from "./ThreadEpiphanySceneChurn"; export type { ThreadEpiphanySceneGraph } from "./ThreadEpiphanySceneGraph"; +export type { ThreadEpiphanySceneInvestigationCheckpoint } from "./ThreadEpiphanySceneInvestigationCheckpoint"; export type { ThreadEpiphanySceneRecord } from "./ThreadEpiphanySceneRecord"; export type { ThreadEpiphanySceneRecords } from "./ThreadEpiphanySceneRecords"; export type { ThreadEpiphanySceneRetrieval } from "./ThreadEpiphanySceneRetrieval"; diff --git a/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs b/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs index 09193a15..f0e4fe6a 100644 --- a/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs @@ -1886,6 +1886,17 @@ mod tests { note: "Graph freshness is stale; frontier has 1 dirty path(s), 1 open question id(s), and 0 open gap id(s)." .to_string(), }, + watcher: v2::ThreadEpiphanyInvalidationInput { + status: v2::ThreadEpiphanyInvalidationStatus::Changed, + watched_root: Some(PathBuf::from("/repo")), + observed_at_unix_seconds: Some(1_744_500_123), + changed_path_count: 1, + changed_paths: vec![PathBuf::from("src/router.rs")], + graph_node_ids: vec!["router-flow".to_string()], + active_frontier_node_ids: vec!["router-flow".to_string()], + note: "Watcher observed 1 recent changed path(s) touching 1 mapped graph node(s), including 1 active frontier node(s)." + .to_string(), + }, }, }; @@ -1917,6 +1928,16 @@ mod tests { "openQuestionCount": 1, "openGapCount": 0, "note": "Graph freshness is stale; frontier has 1 dirty path(s), 1 open question id(s), and 0 open gap id(s)." + }, + "watcher": { + "status": "changed", + "watchedRoot": "/repo", + "observedAtUnixSeconds": 1744500123, + "changedPathCount": 1, + "changedPaths": ["src/router.rs"], + "graphNodeIds": ["router-flow"], + "activeFrontierNodeIds": ["router-flow"], + "note": "Watcher observed 1 recent changed path(s) touching 1 mapped graph node(s), including 1 active frontier node(s)." } } }), diff --git a/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs b/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs index 9908166b..f612bfd3 100644 --- a/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -4172,6 +4172,7 @@ pub struct ThreadEpiphanyFreshnessResponse { pub state_revision: Option, pub retrieval: ThreadEpiphanyRetrievalFreshness, pub graph: ThreadEpiphanyGraphFreshness, + pub watcher: ThreadEpiphanyInvalidationInput, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] @@ -4243,6 +4244,36 @@ pub struct ThreadEpiphanyGraphFreshness { pub note: String, } +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub enum ThreadEpiphanyInvalidationStatus { + Unavailable, + Clean, + Changed, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadEpiphanyInvalidationInput { + pub status: ThreadEpiphanyInvalidationStatus, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional = nullable)] + pub watched_root: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional = nullable)] + pub observed_at_unix_seconds: Option, + pub changed_path_count: u32, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub changed_paths: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub graph_node_ids: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub active_frontier_node_ids: Vec, + pub note: String, +} + #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/vendor/codex/codex-rs/app-server/README.md b/vendor/codex/codex-rs/app-server/README.md index 02c6268c..5d87d5b3 100644 --- a/vendor/codex/codex-rs/app-server/README.md +++ b/vendor/codex/codex-rs/app-server/README.md @@ -146,7 +146,7 @@ Experimental Epiphany retrieval: - `thread/epiphany/promote` is an explicit loaded-thread-only promotion gate. It evaluates a patch plus verifier evidence, rejects with reasons without mutating state when policy fails, and applies the patch through the durable update path when accepted. Accepted responses include the landed `revision` and `changedFields`. - `thread/epiphany/update` is the explicit loaded-thread-only control-plane write path for Epiphany state. It can append observations/evidence and replace bounded typed state fields such as objective, subgoals, graph frontier/checkpoint, scratch, churn, and mode; it bumps the Epiphany revision, returns `revision` plus `changedFields`, and persists a rollout snapshot immediately. - `thread/epiphany/stateUpdated` is an experimental server notification emitted after successful `thread/epiphany/update` and accepted `thread/epiphany/promote`. It carries the updated typed `epiphanyState`, a `source` of `update` or `promote`, event-level `revision`, and typed `changedFields`; rejected promotions do not emit it. -- `thread/epiphany/freshness` is a read-only reflection surface for retrieval and graph staleness. It reports revision/source identity, retrieval freshness, graph freshness, and exact dirty-path pressure without mutating state, scheduling refresh work, or pretending watcher-driven invalidation already exists. +- `thread/epiphany/freshness` is a read-only reflection surface for retrieval and graph staleness. It reports revision/source identity, retrieval freshness, graph freshness, and exact dirty-path pressure, and for loaded threads now also reports watcher-backed invalidation inputs with watched root, changed paths, mapped graph-node hits, and active-frontier hits. It still does not mutate state, schedule refresh work, or perform automatic watcher-driven semantic invalidation. The request envelope is camelCase, but nested reused Epiphany core DTOs currently keep their core snake_case fields, such as `source_kind`, `understanding_status`, and `diff_pressure`. - `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread. When the request includes a `cwd` and the resolved sandbox is `workspace-write` or full access, app-server also marks that project as trusted in the user `config.toml`. Pass `sessionStartSource: "clear"` when starting a replacement thread after clearing the current session so `SessionStart` hooks receive `source: "clear"` instead of the default `"startup"`. For permissions, prefer `permissionProfile`; the legacy `sandbox` shorthand is still accepted but cannot be combined with `permissionProfile`. diff --git a/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs b/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs index 199541b4..b7e9eb95 100644 --- a/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs +++ b/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs @@ -3,6 +3,8 @@ use crate::bespoke_event_handling::maybe_emit_hook_prompt_item_completed; use crate::command_exec::CommandExecManager; use crate::command_exec::StartCommandExecParams; use crate::config_manager::ConfigManager; +use crate::epiphany_invalidation::EpiphanyInvalidationManager; +use crate::epiphany_invalidation::EpiphanyInvalidationSnapshot; use crate::error_code::INPUT_TOO_LARGE_ERROR_CODE; use crate::error_code::INTERNAL_ERROR_CODE; use crate::error_code::INVALID_PARAMS_ERROR_CODE; @@ -162,6 +164,8 @@ use codex_app_server_protocol::ThreadEpiphanyGraphFreshness; use codex_app_server_protocol::ThreadEpiphanyGraphFreshnessStatus; use codex_app_server_protocol::ThreadEpiphanyIndexParams; use codex_app_server_protocol::ThreadEpiphanyIndexResponse; +use codex_app_server_protocol::ThreadEpiphanyInvalidationInput; +use codex_app_server_protocol::ThreadEpiphanyInvalidationStatus; use codex_app_server_protocol::ThreadEpiphanyJob; use codex_app_server_protocol::ThreadEpiphanyJobKind; use codex_app_server_protocol::ThreadEpiphanyJobStatus; @@ -569,6 +573,7 @@ pub(crate) struct CodexMessageProcessor { pending_thread_unloads: Arc>>, thread_state_manager: ThreadStateManager, thread_watch_manager: ThreadWatchManager, + epiphany_invalidation_manager: EpiphanyInvalidationManager, command_exec_manager: CommandExecManager, pending_fuzzy_searches: Arc>>>, fuzzy_search_sessions: Arc>>, @@ -594,6 +599,7 @@ struct ListenerTaskContext { analytics_events_client: AnalyticsEventsClient, general_analytics_enabled: bool, thread_watch_manager: ThreadWatchManager, + epiphany_invalidation_manager: EpiphanyInvalidationManager, fallback_model_provider: String, codex_home: PathBuf, } @@ -826,6 +832,7 @@ impl CodexMessageProcessor { pending_thread_unloads: Arc::new(Mutex::new(HashSet::new())), thread_state_manager: ThreadStateManager::new(), thread_watch_manager: ThreadWatchManager::new_with_outgoing(outgoing), + epiphany_invalidation_manager: EpiphanyInvalidationManager::new(), command_exec_manager: CommandExecManager::default(), pending_fuzzy_searches: Arc::new(Mutex::new(HashMap::new())), fuzzy_search_sessions: Arc::new(Mutex::new(HashMap::new())), @@ -2518,6 +2525,7 @@ impl CodexMessageProcessor { analytics_events_client: self.analytics_events_client.clone(), general_analytics_enabled: self.config.features.enabled(Feature::GeneralAnalytics), thread_watch_manager: self.thread_watch_manager.clone(), + epiphany_invalidation_manager: self.epiphany_invalidation_manager.clone(), fallback_model_provider: self.config.model_provider_id.clone(), codex_home: self.config.codex_home.to_path_buf(), }; @@ -4242,8 +4250,24 @@ impl CodexMessageProcessor { } else { None }; - let (state_revision, retrieval, graph) = - map_epiphany_freshness(thread.epiphany_state.as_ref(), retrieval_override.as_ref()); + let watcher_snapshot = if let Some(loaded_thread) = loaded_thread.as_ref() { + let config_snapshot = loaded_thread.config_snapshot().await; + self.epiphany_invalidation_manager + .ensure_thread_watch(&thread_id, &config_snapshot.cwd) + .await; + Some( + self.epiphany_invalidation_manager + .snapshot(&thread_id) + .await, + ) + } else { + None + }; + let (state_revision, retrieval, graph, watcher) = map_epiphany_freshness( + thread.epiphany_state.as_ref(), + retrieval_override.as_ref(), + watcher_snapshot.as_ref(), + ); let response = ThreadEpiphanyFreshnessResponse { thread_id, source: if loaded_thread.is_some() { @@ -4254,6 +4278,7 @@ impl CodexMessageProcessor { state_revision, retrieval, graph, + watcher, }; self.outgoing.send_response(request_id, response).await; } @@ -7046,6 +7071,9 @@ impl CodexMessageProcessor { self.thread_state_manager .remove_thread_state(thread_id) .await; + self.epiphany_invalidation_manager + .remove_thread(&thread_id.to_string()) + .await; self.thread_watch_manager .remove_thread(&thread_id.to_string()) .await; @@ -7057,6 +7085,7 @@ impl CodexMessageProcessor { pending_thread_unloads: Arc>>, thread_state_manager: ThreadStateManager, thread_watch_manager: ThreadWatchManager, + epiphany_invalidation_manager: EpiphanyInvalidationManager, thread_id: ThreadId, thread: Arc, ) { @@ -7068,18 +7097,27 @@ impl CodexMessageProcessor { .cancel_requests_for_thread(thread_id, /*error*/ None) .await; thread_state_manager.remove_thread_state(thread_id).await; + epiphany_invalidation_manager + .remove_thread(&thread_id.to_string()) + .await; tokio::spawn(async move { match Self::wait_for_thread_shutdown(&thread).await { ThreadShutdownResult::Complete => { if thread_manager.remove_thread(&thread_id).await.is_none() { info!("thread {thread_id} was already removed before teardown finalized"); + epiphany_invalidation_manager + .remove_thread(&thread_id.to_string()) + .await; thread_watch_manager .remove_thread(&thread_id.to_string()) .await; pending_thread_unloads.lock().await.remove(&thread_id); return; } + epiphany_invalidation_manager + .remove_thread(&thread_id.to_string()) + .await; thread_watch_manager .remove_thread(&thread_id.to_string()) .await; @@ -8579,6 +8617,7 @@ impl CodexMessageProcessor { analytics_events_client: self.analytics_events_client.clone(), general_analytics_enabled: self.config.features.enabled(Feature::GeneralAnalytics), thread_watch_manager: self.thread_watch_manager.clone(), + epiphany_invalidation_manager: self.epiphany_invalidation_manager.clone(), fallback_model_provider: self.config.model_provider_id.clone(), codex_home: self.config.codex_home.to_path_buf(), }, @@ -8697,6 +8736,7 @@ impl CodexMessageProcessor { analytics_events_client: self.analytics_events_client.clone(), general_analytics_enabled: self.config.features.enabled(Feature::GeneralAnalytics), thread_watch_manager: self.thread_watch_manager.clone(), + epiphany_invalidation_manager: self.epiphany_invalidation_manager.clone(), fallback_model_provider: self.config.model_provider_id.clone(), codex_home: self.config.codex_home.to_path_buf(), }, @@ -8746,6 +8786,7 @@ impl CodexMessageProcessor { analytics_events_client: _, general_analytics_enabled: _, thread_watch_manager, + epiphany_invalidation_manager, fallback_model_provider, codex_home, } = listener_task_context; @@ -8859,6 +8900,7 @@ impl CodexMessageProcessor { pending_thread_unloads.clone(), thread_state_manager.clone(), thread_watch_manager.clone(), + epiphany_invalidation_manager.clone(), conversation_id, conversation.clone(), ) @@ -10892,16 +10934,19 @@ fn epiphany_scene_available_actions( fn map_epiphany_freshness( state: Option<&EpiphanyThreadState>, retrieval_override: Option<&EpiphanyRetrievalState>, + watcher_snapshot: Option<&EpiphanyInvalidationSnapshot>, ) -> ( Option, ThreadEpiphanyRetrievalFreshness, ThreadEpiphanyGraphFreshness, + ThreadEpiphanyInvalidationInput, ) { let retrieval = epiphany_retrieval_state_for_reflection(state, retrieval_override); ( state.map(|state| state.revision), map_epiphany_retrieval_freshness(retrieval), map_epiphany_graph_freshness(state), + map_epiphany_invalidation_input(state, watcher_snapshot), ) } @@ -11025,6 +11070,180 @@ fn map_epiphany_graph_freshness( } } +fn map_epiphany_invalidation_input( + state: Option<&EpiphanyThreadState>, + watcher_snapshot: Option<&EpiphanyInvalidationSnapshot>, +) -> ThreadEpiphanyInvalidationInput { + let Some(watcher_snapshot) = watcher_snapshot else { + return ThreadEpiphanyInvalidationInput { + status: ThreadEpiphanyInvalidationStatus::Unavailable, + watched_root: None, + observed_at_unix_seconds: None, + changed_path_count: 0, + changed_paths: Vec::new(), + graph_node_ids: Vec::new(), + active_frontier_node_ids: Vec::new(), + note: "Watcher-backed invalidation inputs are only available for loaded threads." + .to_string(), + }; + }; + + if !watcher_snapshot.available { + return ThreadEpiphanyInvalidationInput { + status: ThreadEpiphanyInvalidationStatus::Unavailable, + watched_root: None, + observed_at_unix_seconds: None, + changed_path_count: 0, + changed_paths: Vec::new(), + graph_node_ids: Vec::new(), + active_frontier_node_ids: Vec::new(), + note: "The workspace watcher is unavailable for this app-server process.".to_string(), + }; + } + + let changed_paths = watcher_snapshot.changed_paths.clone(); + let changed_path_count = changed_paths.len() as u32; + let watched_root = watcher_snapshot + .workspace_root + .as_ref() + .map(|root| root.to_path_buf()); + + if changed_paths.is_empty() { + return ThreadEpiphanyInvalidationInput { + status: ThreadEpiphanyInvalidationStatus::Clean, + watched_root, + observed_at_unix_seconds: watcher_snapshot.observed_at_unix_seconds, + changed_path_count, + changed_paths, + graph_node_ids: Vec::new(), + active_frontier_node_ids: Vec::new(), + note: "Watcher has not observed recent filesystem changes under the workspace root." + .to_string(), + }; + } + + let graph_node_ids = state + .map(|state| { + graph_node_ids_for_changed_paths( + state, + &changed_paths, + watcher_snapshot + .workspace_root + .as_ref() + .map(|root| root.as_path()), + ) + }) + .unwrap_or_default(); + let active_frontier_node_ids = state + .map(|state| { + active_frontier_node_ids_for_changed_paths( + state, + &changed_paths, + watcher_snapshot + .workspace_root + .as_ref() + .map(|root| root.as_path()), + ) + }) + .unwrap_or_default(); + let note = if graph_node_ids.is_empty() { + format!( + "Watcher observed {changed_path_count} recent changed path(s), but no mapped graph node code refs matched yet." + ) + } else if active_frontier_node_ids.is_empty() { + format!( + "Watcher observed {changed_path_count} recent changed path(s) touching {} mapped graph node(s).", + graph_node_ids.len() + ) + } else { + format!( + "Watcher observed {changed_path_count} recent changed path(s) touching {} mapped graph node(s), including {} active frontier node(s).", + graph_node_ids.len(), + active_frontier_node_ids.len() + ) + }; + + ThreadEpiphanyInvalidationInput { + status: ThreadEpiphanyInvalidationStatus::Changed, + watched_root, + observed_at_unix_seconds: watcher_snapshot.observed_at_unix_seconds, + changed_path_count, + changed_paths, + graph_node_ids, + active_frontier_node_ids, + note, + } +} + +fn graph_node_ids_for_changed_paths( + state: &EpiphanyThreadState, + changed_paths: &[PathBuf], + workspace_root: Option<&Path>, +) -> Vec { + let changed_path_keys: HashSet = changed_paths + .iter() + .map(|path| epiphany_path_key(path.as_path())) + .collect(); + let mut node_ids = Vec::new(); + let mut seen = HashSet::new(); + + for node in state + .graphs + .architecture + .nodes + .iter() + .chain(state.graphs.dataflow.nodes.iter()) + { + let matches_changed_path = node.code_refs.iter().any(|code_ref| { + changed_path_keys.contains(&code_ref_path_key( + Path::new(&code_ref.path), + workspace_root, + )) + }); + if matches_changed_path && seen.insert(node.id.as_str()) { + node_ids.push(node.id.clone()); + } + } + + node_ids +} + +fn active_frontier_node_ids_for_changed_paths( + state: &EpiphanyThreadState, + changed_paths: &[PathBuf], + workspace_root: Option<&Path>, +) -> Vec { + let graph_node_ids = graph_node_ids_for_changed_paths(state, changed_paths, workspace_root); + let frontier_node_ids = state + .graph_frontier + .as_ref() + .map(|frontier| frontier.active_node_ids.as_slice()) + .unwrap_or_default(); + let graph_node_ids: HashSet<&str> = graph_node_ids.iter().map(String::as_str).collect(); + + frontier_node_ids + .iter() + .filter(|node_id| graph_node_ids.contains(node_id.as_str())) + .cloned() + .collect() +} + +fn epiphany_path_key(path: &Path) -> String { + path.to_string_lossy() + .replace('\\', "/") + .trim_start_matches("./") + .to_string() +} + +fn code_ref_path_key(path: &Path, workspace_root: Option<&Path>) -> String { + if let Some(workspace_root) = workspace_root + && let Ok(relative_path) = path.strip_prefix(workspace_root) + { + return epiphany_path_key(relative_path); + } + epiphany_path_key(path) +} + fn map_epiphany_pressure(info: Option<&CoreTokenUsageInfo>) -> ThreadEpiphanyPressure { let Some(info) = info else { return ThreadEpiphanyPressure { @@ -13210,7 +13429,8 @@ mod tests { ..Default::default() }; - let (state_revision, retrieval, graph) = map_epiphany_freshness(None, Some(&retrieval)); + let (state_revision, retrieval, graph, watcher) = + map_epiphany_freshness(None, Some(&retrieval), None); assert_eq!(state_revision, None); assert_eq!( @@ -13220,12 +13440,35 @@ mod tests { assert_eq!(retrieval.semantic_available, Some(true)); assert_eq!(graph.status, ThreadEpiphanyGraphFreshnessStatus::Missing); assert_eq!(graph.dirty_path_count, 0); + assert_eq!( + watcher.status, + ThreadEpiphanyInvalidationStatus::Unavailable + ); } #[test] fn map_epiphany_freshness_marks_graph_and_retrieval_stale() { let state = EpiphanyThreadState { revision: 7, + graphs: codex_protocol::protocol::EpiphanyGraphs { + architecture: codex_protocol::protocol::EpiphanyGraph { + nodes: vec![codex_protocol::protocol::EpiphanyGraphNode { + id: "state-spine".to_string(), + title: "State spine".to_string(), + purpose: "Typed state".to_string(), + code_refs: vec![codex_protocol::protocol::EpiphanyCodeRef { + path: test_path_buf("/workspace/src/router.rs"), + start_line: None, + end_line: None, + symbol: None, + note: None, + }], + ..Default::default() + }], + ..Default::default() + }, + ..Default::default() + }, graph_frontier: Some(codex_protocol::protocol::EpiphanyGraphFrontier { active_node_ids: vec!["state-spine".to_string()], dirty_paths: vec![PathBuf::from("src/router.rs")], @@ -13255,9 +13498,15 @@ mod tests { dirty_paths: vec![PathBuf::from("src/router.rs")], ..Default::default() }; + let watcher_snapshot = EpiphanyInvalidationSnapshot { + available: true, + workspace_root: Some(test_path_buf("/workspace").abs()), + observed_at_unix_seconds: Some(1_744_600_000), + changed_paths: vec![PathBuf::from("src/router.rs")], + }; - let (state_revision, retrieval, graph) = - map_epiphany_freshness(Some(&state), Some(&retrieval)); + let (state_revision, retrieval, graph, watcher) = + map_epiphany_freshness(Some(&state), Some(&retrieval), Some(&watcher_snapshot)); assert_eq!(state_revision, Some(7)); assert_eq!( @@ -13270,6 +13519,75 @@ mod tests { assert_eq!(graph.dirty_path_count, 1); assert_eq!(graph.open_question_count, 1); assert_eq!(graph.open_gap_count, 0); + assert_eq!(watcher.status, ThreadEpiphanyInvalidationStatus::Changed); + assert_eq!( + watcher.watched_root, + Some(test_path_buf("/workspace").abs().to_path_buf()) + ); + assert_eq!(watcher.changed_paths, vec![PathBuf::from("src/router.rs")]); + assert_eq!(watcher.graph_node_ids, vec!["state-spine".to_string()]); + assert_eq!( + watcher.active_frontier_node_ids, + vec!["state-spine".to_string()] + ); + } + + #[test] + fn map_epiphany_freshness_reports_clean_watcher_when_no_changes_were_seen() { + let watcher_snapshot = EpiphanyInvalidationSnapshot { + available: true, + workspace_root: Some(test_path_buf("/workspace").abs()), + observed_at_unix_seconds: None, + changed_paths: Vec::new(), + }; + + let (_, _, _, watcher) = map_epiphany_freshness(None, None, Some(&watcher_snapshot)); + + assert_eq!(watcher.status, ThreadEpiphanyInvalidationStatus::Clean); + assert_eq!(watcher.changed_path_count, 0); + assert_eq!( + watcher.watched_root, + Some(test_path_buf("/workspace").abs().to_path_buf()) + ); + } + + #[test] + fn map_epiphany_freshness_reports_unmatched_changed_paths_without_inventing_nodes() { + let state = EpiphanyThreadState { + graphs: codex_protocol::protocol::EpiphanyGraphs { + architecture: codex_protocol::protocol::EpiphanyGraph { + nodes: vec![codex_protocol::protocol::EpiphanyGraphNode { + id: "scene".to_string(), + title: "Scene".to_string(), + purpose: "Reflection".to_string(), + code_refs: vec![codex_protocol::protocol::EpiphanyCodeRef { + path: test_path_buf("/workspace/src/scene.rs"), + start_line: None, + end_line: None, + symbol: None, + note: None, + }], + ..Default::default() + }], + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + let watcher_snapshot = EpiphanyInvalidationSnapshot { + available: true, + workspace_root: Some(test_path_buf("/workspace").abs()), + observed_at_unix_seconds: Some(1_744_600_001), + changed_paths: vec![PathBuf::from("src/other.rs")], + }; + + let (_, _, _, watcher) = + map_epiphany_freshness(Some(&state), None, Some(&watcher_snapshot)); + + assert_eq!(watcher.status, ThreadEpiphanyInvalidationStatus::Changed); + assert!(watcher.graph_node_ids.is_empty()); + assert!(watcher.active_frontier_node_ids.is_empty()); } #[test] diff --git a/vendor/codex/codex-rs/app-server/src/epiphany_invalidation.rs b/vendor/codex/codex-rs/app-server/src/epiphany_invalidation.rs new file mode 100644 index 00000000..2af933a0 --- /dev/null +++ b/vendor/codex/codex-rs/app-server/src/epiphany_invalidation.rs @@ -0,0 +1,210 @@ +use codex_core::file_watcher::FileWatcher; +use codex_core::file_watcher::FileWatcherSubscriber; +use codex_core::file_watcher::ThrottledWatchReceiver; +use codex_core::file_watcher::WatchPath; +use codex_core::file_watcher::WatchRegistration; +use codex_utils_absolute_path::AbsolutePathBuf; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; +use std::time::SystemTime; +use std::time::UNIX_EPOCH; +use tokio::sync::Mutex as AsyncMutex; +use tokio::sync::oneshot; +use tracing::warn; + +const INVALIDATION_WATCH_DEBOUNCE: Duration = Duration::from_millis(200); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct EpiphanyInvalidationSnapshot { + pub available: bool, + pub workspace_root: Option, + pub observed_at_unix_seconds: Option, + pub changed_paths: Vec, +} + +#[derive(Clone)] +pub(crate) struct EpiphanyInvalidationManager { + available: bool, + file_watcher: Arc, + state: Arc>>, +} + +struct WatchEntry { + workspace_root: AbsolutePathBuf, + latest: Arc>, + terminate_tx: oneshot::Sender>, + _subscriber: FileWatcherSubscriber, + _registration: WatchRegistration, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +struct LatestInvalidation { + observed_at_unix_seconds: Option, + changed_paths: Vec, +} + +impl EpiphanyInvalidationManager { + pub(crate) fn new() -> Self { + match FileWatcher::new() { + Ok(file_watcher) => Self { + available: true, + file_watcher: Arc::new(file_watcher), + state: Arc::new(AsyncMutex::new(HashMap::new())), + }, + Err(err) => { + warn!("epiphany invalidation manager falling back to noop core watcher: {err}"); + Self { + available: false, + file_watcher: Arc::new(FileWatcher::noop()), + state: Arc::new(AsyncMutex::new(HashMap::new())), + } + } + } + } + + pub(crate) async fn ensure_thread_watch( + &self, + thread_id: &str, + workspace_root: &AbsolutePathBuf, + ) { + if !self.available { + return; + } + + let existing_entry = { + let mut state = self.state.lock().await; + match state.get(thread_id) { + Some(entry) if &entry.workspace_root == workspace_root => return, + Some(_) => state.remove(thread_id), + None => None, + } + }; + + if let Some(entry) = existing_entry { + stop_watch_entry(entry).await; + } + + let (subscriber, rx) = self.file_watcher.add_subscriber(); + let registration = subscriber.register_paths(vec![WatchPath { + path: workspace_root.to_path_buf(), + recursive: true, + }]); + let latest = Arc::new(AsyncMutex::new(LatestInvalidation::default())); + let (terminate_tx, terminate_rx) = oneshot::channel(); + let workspace_root_path = workspace_root.to_path_buf(); + + let entry = WatchEntry { + workspace_root: workspace_root.clone(), + latest: Arc::clone(&latest), + terminate_tx, + _subscriber: subscriber, + _registration: registration, + }; + self.state.lock().await.insert(thread_id.to_string(), entry); + + tokio::spawn(async move { + let mut rx = ThrottledWatchReceiver::new(rx, INVALIDATION_WATCH_DEBOUNCE); + let mut terminate_rx = terminate_rx; + let mut done_tx = None; + + loop { + let event = tokio::select! { + biased; + result = &mut terminate_rx => { + done_tx = result.ok(); + break; + } + event = rx.recv() => match event { + Some(event) => event, + None => break, + }, + }; + + let observed_at_unix_seconds = unix_now(); + let mut latest = latest.lock().await; + latest.observed_at_unix_seconds = Some(observed_at_unix_seconds); + latest.changed_paths = + normalize_changed_paths(event.paths, workspace_root_path.as_path()); + } + + if let Some(done_tx) = done_tx { + let _ = done_tx.send(()); + } + }); + } + + pub(crate) async fn snapshot(&self, thread_id: &str) -> EpiphanyInvalidationSnapshot { + if !self.available { + return EpiphanyInvalidationSnapshot { + available: false, + workspace_root: None, + observed_at_unix_seconds: None, + changed_paths: Vec::new(), + }; + } + + let (workspace_root, latest) = { + let state = self.state.lock().await; + match state.get(thread_id) { + Some(entry) => ( + Some(entry.workspace_root.clone()), + Some(Arc::clone(&entry.latest)), + ), + None => (None, None), + } + }; + + let Some(latest) = latest else { + return EpiphanyInvalidationSnapshot { + available: false, + workspace_root: None, + observed_at_unix_seconds: None, + changed_paths: Vec::new(), + }; + }; + + let latest = latest.lock().await.clone(); + EpiphanyInvalidationSnapshot { + available: true, + workspace_root, + observed_at_unix_seconds: latest.observed_at_unix_seconds, + changed_paths: latest.changed_paths, + } + } + + pub(crate) async fn remove_thread(&self, thread_id: &str) { + let entry = self.state.lock().await.remove(thread_id); + if let Some(entry) = entry { + stop_watch_entry(entry).await; + } + } +} + +async fn stop_watch_entry(entry: WatchEntry) { + let (done_tx, done_rx) = oneshot::channel(); + let _ = entry.terminate_tx.send(done_tx); + let _ = done_rx.await; +} + +fn unix_now() -> i64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_secs() as i64) + .unwrap_or_default() +} + +fn normalize_changed_paths(paths: Vec, workspace_root: &std::path::Path) -> Vec { + let mut normalized = paths + .into_iter() + .filter_map(|path| match path.strip_prefix(workspace_root) { + Ok(relative) if relative.as_os_str().is_empty() => None, + Ok(relative) => Some(relative.to_path_buf()), + Err(_) => Some(path), + }) + .collect::>(); + normalized.sort(); + normalized.dedup(); + normalized +} diff --git a/vendor/codex/codex-rs/app-server/src/lib.rs b/vendor/codex/codex-rs/app-server/src/lib.rs index a2f35305..0e972ffa 100644 --- a/vendor/codex/codex-rs/app-server/src/lib.rs +++ b/vendor/codex/codex-rs/app-server/src/lib.rs @@ -75,6 +75,7 @@ mod config_manager; mod config_manager_service; mod device_key_api; mod dynamic_tools; +mod epiphany_invalidation; mod error_code; mod external_agent_config_api; mod filters;